A personal reading library that lives on your own machine.
OneShelf keeps manga, comics, books and papers in one place: it finds them at the sources you choose, downloads what you ask for, remembers where you stopped reading, and tells you when something new arrives. There is no account, no cloud, and nothing phones home. Your library is a directory on your own disk, and you can pick it up and move it whenever you like.
git clone https://github.com/IPurplel/OneShelf.git
cd OneShelf
./install.shThen open http://127.0.0.1:8420.
The first run builds the image, which takes a few minutes. After that, starting takes seconds.
OneShelf includes its eight official source adapters automatically on first run — there is nothing to build, upload or approve. You can disable or remove any of them from Sources, and a source you remove stays removed: restarting, updating or recreating the container never brings it back.
./update.sh # update to the latest version, keeping your library
./uninstall.sh # stop OneShelf, keeping your libraryStatus. Application, browser, and deployment-script checks are recorded in the latest verification report. Sources are covered by offline package fixtures; live availability is not established by that run. A full container deployment on a real machine remains unverified: the Fedora host gate is still open, so treat the deployment as unproven until it closes.
- A container runtime. Either Podman or Docker. OneShelf will not install one for you, and will
say exactly what to do if neither is there.
- Fedora / RHEL:
sudo dnf install podman podman-compose - Debian / Ubuntu:
sudo apt install podman podman-compose - Or Docker Engine with the Compose plugin.
- Fedora / RHEL:
- About 3 GB of disk for the image, plus whatever your library grows to.
- Linux, Bash, Git, coreutils and curl or wget. No database or separate web server to configure.
Podman on Fedora is a first-class path; Docker Engine on Linux is also supported. The real container
gate is pending. If both are present, the scripts prefer Podman. To choose Docker, run
ONESHELF_RUNTIME=docker ./install.sh (use the same prefix for update and uninstall).
The Linux deployment uses host networking so the application sees the real client IP; it binds only
to loopback by default. It shares the host network namespace. Docker Desktop is not a verified path.
Everything persistent is kept outside the container, in five directories:
| Directory | What is in it |
|---|---|
deploy/volumes/data |
the library database, the search index, staging and diagnostics |
deploy/volumes/content |
your downloaded and imported files |
deploy/volumes/plugins |
the source adapters you have installed |
deploy/volumes/keys |
the key for your saved source logins — back this up separately |
deploy/volumes/backups |
OneShelf's own .osbackup archives |
They survive restarts, rebuilds, updates, and removing the container entirely. To keep your library
somewhere else — a bigger disk, a different mount — set the matching ONESHELF_*_PATH in .env to an
absolute path dedicated to OneShelf. The installer may change ownership of those five directories
and label them for SELinux; do not select shared directories. Paths may not overlap or contain
symlinks. A nonempty custom directory must already contain the .oneshelf-managed marker from
a previous install; otherwise the installer stops before relabelling or changing ownership. When
migrating existing OneShelf-only storage manually, review its contents, back it up, and create that
empty marker yourself to explicitly designate it for OneShelf. Changing a path does not move existing content: copy it while stopped, then change .env
and run ./install.sh again. The existing copy is yours to retain.
Configuration is a literal KEY=value file at the repository root. Quotes and inline comments are
supported; multiline values, variable interpolation and escapes are rejected by the scripts. Exported
ONESHELF_* deployment values override .env consistently for Compose and readiness probes.
OneShelf takes a Library Backup of its own every seven days into deploy/volumes/backups: your
works, shelf, reading progress, follows and settings, without the files themselves. A Full Backup
adds the content of works you choose, and is manual. Keep those archives on a different disk from the
library if you can — OneShelf warns you when they are on the same one.
Two things are deliberately not in any backup: the session key in keys/, and your saved source
logins. Copy the keys/ directory somewhere safe yourself, the way you would a password.
./update.shIt pulls the latest version, rebuilds, and restarts. Your library, your settings and your .env are
left alone, and database migrations run the same way they do on any normal start.
Automatic Git updates require main and origin/main, a clean tracked tree, no unfinished Git
operation, and no local commits ahead of or diverged from the remote. A checkout without an upstream,
or an archive without Git metadata, rebuilds locally with an explicit notice. Untracked files are
preserved; Git refuses an update if they would be overwritten.
Back up before updating. Migrations run on startup and are forward-only: a failed health check does not mean the database is unchanged. Do not simply check out an older commit against a newer database. Preserve the volumes and follow upgrade/recovery instructions, using a compatible backup or pre-migration snapshot if a downgrade is necessary.
./uninstall.shstops and removes the containers and keeps every byte of your library. Run ./install.sh again
whenever you want it back.
If you genuinely want the data gone as well:
./uninstall.sh --delete-dataIt lists exactly which directories it will delete, and only proceeds if you type the confirmation
sentence in full. Automatic deletion is limited to the five default deploy/volumes/ directories.
Custom paths, symlinked paths and overlapping paths are refused; review and remove custom storage
manually. A failure to remove a directory is reported as failure, never as success.
By default OneShelf listens on this machine only. To use it from a phone or tablet on your own
network, edit .env:
ONESHELF_BIND=0.0.0.0
ONESHELF_TRUSTED_NETWORKS=192.168.1.0/24 # the subnet you actually trust
ONESHELF_ALLOWED_HOSTS=localhost,my-machine.localthen ./install.sh again. Trust is decided by the real network connection, never by a header a
client can set. Anything arriving from outside those networks has to authenticate with a passkey,
which you set up in Settings → Remote Access. If a reverse proxy runs on this same host, configure
its address in ONESHELF_TRUSTED_PROXIES before exposing it publicly; otherwise the application
sees its trusted loopback connection instead of the remote visitor. See access configuration.
The scripts are a convenience, not a requirement. The Compose file is deploy/compose.yaml and is
ordinary. After the first ./install.sh has prepared configuration, directories and ownership,
use these commands from the repository root:
# Podman
podman compose --env-file .env -f deploy/compose.yaml build
podman compose --env-file .env -f deploy/compose.yaml up -d
podman compose --env-file .env -f deploy/compose.yaml logs -f
podman compose --env-file .env -f deploy/compose.yaml down # keeps volumes
# Docker
docker compose --env-file .env -f deploy/compose.yaml build
docker compose --env-file .env -f deploy/compose.yaml up -d
docker compose --env-file .env -f deploy/compose.yaml logs -f
docker compose --env-file .env -f deploy/compose.yaml downStandalone podman-compose and docker-compose providers implementing the current Compose
specification are detected too. Legacy Docker Compose v1 is not a supported target.
Configuration lives in .env at the repository root, not in deploy/. Always pass --env-file .env
when invoking Compose manually. Relative bind paths are resolved relative to deploy/. Every value is documented there. The container runs as an unprivileged user (uid 10001)
and exposes a healthcheck on /api/ready.
Pre-release images stored plugins and backups inside the data directory, despite exposing separate
mounts. The scripts stop before new mounts could hide those files. If that check fails, inspect the
runtime error first. For an actual legacy install: stop it with ./uninstall.sh, back up all five
host directories, and copy the contents of <data-path>/plugins and <data-path>/backups into the
configured plugin and backup host directories only when those destinations are empty. Use
podman unshare for rootless-owned files. Never merge or overwrite populated destinations. Rename
the original subdirectories to plugins.pre-release and backups.pre-release to retain them, then
rerun ./install.sh. These are file-location changes; database migrations still run only on startup.
If unsure which copies are authoritative, keep both and stop before moving anything.
podmanordockeris installed but nothing happens. Its service may not be running:systemctl --user start podman.socket, orsudo systemctl start docker.- OneShelf cannot write to its directories.
install.shcorrects ownership of its own directories through the runtime. If you moved them somewhere the container user cannot reach, pointONESHELF_*_PATHat a directory you own. - Something else is on port 8420. Change
ONESHELF_PORTin.envand run./install.shagain. - Look at the log.
podman compose --env-file .env -f deploy/compose.yaml logs --tail=50
It does not bypass paywalls, DRM, CAPTCHAs or anti-bot systems; it does not solve challenges or
pretend to be a browser it is not. Where a source's robots.txt disallows something, OneShelf simply
does not do it, and the missing capability is recorded rather than worked around. There is no
telemetry of any kind, no account, and no cloud service behind it.
Eight sources ship and are verified against the live sites: MangaDex, WEBTOON, Tapas, 3asq (Al-Aasheq), Safahat (Hindawi), Project Gutenberg, arXiv and Standard Ebooks. They are installed for you on first run from a release snapshot included in OneShelf — no network needed — and marked Official · Bundled on the Sources screen, where you can see exactly what each one is allowed to reach, and disable or remove it.
Each is a declarative package — data, not code — installed through the same validation and packaged tests as any other. When an update to one asks for more than it had before, it waits for your review rather than being approved for you. What each source can and cannot do is recorded in docs/c9/source-capability-ledger.md.
The Sources screen also shows the Source Registry: adapters published from
OneShelf-Adapters, where adapters are developed and contributed. Each entry carries its own trust
level — Official, Verified Community or Community — and installs only after the same review and packaged tests.
Removed one of the included sources? It is listed there as Not installed. New sources and adapter updates
arrive through the Registry without waiting for a new OneShelf release; nothing installs, updates or gains a
permission without your review, a disabled source stays disabled, and an older version is never offered over
a newer one. If the Registry cannot be reached, your installed sources simply keep working. Official and
Verified Community entries are trusted because this is OneShelf's own first-party Registry, whose tiers the
project's repository controls; a Registry you point OneShelf at yourself gets Community trust unless its
packages carry a signature from a key you trust. Set ONESHELF_REGISTRY_URL= in .env to switch it off.
Want to add a source? See OneShelf-Adapters' CONTRIBUTING.md.
| docs/operations.md | deployment, storage, backup, restore, upgrade |
| docs/api.md | the HTTP API |
| docs/plugins.md | how source packages work, and how to write one |
| docs/development.md | running from source, tests, environment variables |
No licence has been chosen yet. Until one is added, the default applies: the code is published for you
to read, and no rights to use, modify or redistribute it are granted. If you intend others to use
OneShelf, add a LICENSE file — that choice is yours to make, not one this repository should assume.