State Files¶
No database, and no Docker named volumes. JSON files in
./config/state, written with write-to-temp plus atomic rename,
mutated under an exclusive flock. jq works on all of it,
from the host, with no container in the way.
Files¶
| File | Contents |
|---|---|
subscribers.json |
The roster, including pending and banned entries. |
pending.json |
Requests held for approval. |
room.json |
The live room. Absent means nothing is up. |
runtime.json |
Pause switch, close request. |
.lock |
The advisory lock both binaries take. |
Everything lives in this directory¶
Every persistent thing the deployment has is a file here.
A named volume is invisible to tar, easy to forget, and turns
"move to another machine" into an archaeology exercise.
| Directory | Holds | Replaceable? |
|---|---|---|
state/ |
Roster, quotas, live room, pause switch | No. Irreplaceable. |
signal-cli/ |
Signal linked-device identity | No, without re-registering |
tor-party-line/ |
Party line checkout + Tor data | Yes (git clone) |
.cache/, target/ |
Cargo registry and build artifacts | Yes (regenerated on build) |
state/ and signal-cli/ are in .gitignore: they travel
with the directory, not with the repository.
Container UIDs¶
The containers run as HOST_UID:HOST_GID from .env, so every
file they write belongs to you rather than root. Without those
set, the containers run as root and you need sudo to read
your own roster. See .env.example.
The full image does not need .env for this. Its entrypoint runs
as root, because Tor and I2P need it, but it reads your uid from
the owner of the mounted ./config, hands that directory back to
you before starting anything, and exports the uid so that every
state file the daemon writes is chowned to you as it is written.
The daemon itself still runs as root, because the Tor, I2P and
Reticulum relays need it; root is the cost of those three room
types, and the handover is what stops it costing you ownership.
Pass HOST_UID and HOST_GID only when ./config is not owned by
you, such as a named volume left behind by an earlier root-run
container, because then there is no owner to read the uid from.
Moving to another machine¶
docker compose down
tar czf party-line-pager.tar.gz \
--exclude=./.cache --exclude=./target \
--exclude=./.git .
# copy to new host, unpack, then:
printf 'HOST_UID=%s\nHOST_GID=%s\nDOCKER_GID=%s\n' \
"$(id -u)" "$(id -g)" \
"$(getent group docker | cut -d: -f3)"
# update .env with the output above
docker compose up -d
docker compose exec party-line-pagerd party-line-pagerctl who
The two excludes are build caches. The .env UIDs are the only
per-machine value: a stale one shows up as permission errors on
state/.
Nothing else is left behind except rebuildable Docker images.
Identity model¶
A subscriber is an address on a service. There is no person
model and no account linking: telegram:123 and
email:party-line-pager@example.org are two unrelated subscribers even if
the same human owns both. They get two quotas. Policing that is
the admin's job. There is no way to verify a human identity
across networks, and pretending otherwise would be theatre.