lodge
The lodge extension adds lodge, the tailnet web shell for Frostyard’s apps, to a Frostyard bootc image. It serves Notes, Mail, Read, and Porter from one address on your tailnet. Its own page is lodge. A server that runs the suite’s services runs Floe. Every image carries the extension’s definition, switched off. The extension itself is not in the image: Updex turns it on and downloads it.
Before you start
Section titled “Before you start”You need:
- A Frostyard bootc image built on Base.
- Root access to enable, update, and remove the extension.
- Network access to
https://repository.frostyard.org/ext/lodge/. - Free space in
/varfor the image.
lodge has no published test results on Frostyard images yet. Try it on a machine you can spare first.
Enable lodge
Section titled “Enable lodge”Make the configuration before you enable lodge. With Updex v2.1.3 and later, the enable starts lodge at once. Make the configuration directory:
sudo install -d -m 0755 /etc/lodgePut a tagged, pre-approved Tailscale key as TS_AUTHKEY in
/etc/lodge/lodge.env, owned by root, mode 0600, before the first start.
Without one, sudo lodgectl status shows each node’s login URL. Remove
TS_AUTHKEY from the file after the first start.
See what Updex found, then enable the feature and download it now:
updex features list --component=lodgesudo updex features enable lodge --nowlodge appears as a component, with its feature disabled. The enable command
downloads the image, checks its checksum and signature, links it, and refreshes
the merge. With Updex v2.1.3 and later, the refresh also creates the users,
groups, and directories that the extension declares, before systemd reloads.
Upholds= then starts the unit, with no reboot. updex --version shows your
version.
The lab has not yet proven this for lodge: no lab run has enabled it live yet.
If the unit fails, follow the start-limit-hit steps below, or reboot.
If updex features list does not show the feature, the image is older than the
extension. Update the image first.
With an Updex older than v2.1.3, the unit cannot start after the enable: its
user and groups do not exist yet. Reboot. At boot the image merges the
extensions, runs systemd-sysusers and systemd-tmpfiles --create, reloads
systemd, and Upholds= starts the unit. To start it without a reboot:
sudo systemd-sysuserssudo systemd-tmpfiles --createsudo systemctl reset-failed lodge.servicesudo systemctl start lodge.serviceCheck the merge and the unit:
systemd-sysext statussystemctl status lodge.servicesudo lodgectl statussystemd-sysext status lists lodge, systemctl status shows the unit active,
and lodgectl status shows lodge’s nodes, apps, sessions, providers, and agent
socket.
If the unit shows failed with start-limit-hit, systemd stopped starting it.
Run sudo systemctl reset-failed lodge.service, then
sudo systemctl start lodge.service, or reboot. Then run the checks again.
Allow people and devices:
sudo lodgectl allow login <login>sudo lodgectl allow node <node>To add agents and inference providers, copy
/usr/share/lodge/config.example.json to /etc/lodge/config.json, edit it,
then run sudo lodgectl reload.
If enable --now exits with a refresh error (refresh_error in JSON), the
download worked, but the activation did not. If the output says
activation did not finish, the extension is merged, but its users and groups
can be missing: reboot. Otherwise the merge state is unknown: reboot. A
sudo systemd-sysext refresh by hand does not create the extension’s users
and groups. Then run the checks above again.
What runs
Section titled “What runs”lodge.service runs on the host, and it is the only unit. There is no socket
unit.
The drop-in /usr/lib/systemd/system/multi-user.target.d/10-lodge.conf has
Upholds= for the unit, and the preset
/usr/lib/systemd/system-preset/40-lodge.preset enables it. It starts at boot.
If it stops, even with exit status 0, systemd starts it again. To stop it for
longer, remove the extension or mask the unit.
extension-release.lodge sets EXTENSION_RELOAD_MANAGER=1, so a merge or
refresh reloads systemd’s unit files.
lodge.service runs /usr/bin/lodge as the user lodge, with its state in
/var/lib/lodge.
The sysusers file /usr/lib/sysusers.d/lodge.conf creates the user and group
lodge, and the groups lodge-apps and lodge-agents, with lodge in both.
The tmpfiles file /usr/lib/tmpfiles.d/lodge.conf creates /run/lodge, owned
by lodge, and /run/lodge/apps, owned by root, and /var/lib/lodge, owned
by lodge, mode 0700. lodge.service has no RuntimeDirectory=, so
stopping lodge does not remove the apps’ sockets.
Each app extension’s sysusers file declares the user lodge too, with the same
line, so that the app’s daemon finds the user without lodge’s extension.
The configuration belongs to you, and the extension ships no file in /etc:
/etc/lodge/config.json and /etc/lodge/lodge.env. lodge starts without them.
A missing config.json means no agents and no providers.
The web shell is /usr/share/lodge/shell/, and the commands are
/usr/bin/lodge and /usr/bin/lodgectl. The extension ships no app: each app
extension adds its own under /usr/share/lodge/apps/.
Update, roll back, and remove
Section titled “Update, roll back, and remove”Back up lodge’s state before you update. lodge migrates its database when it starts, and an older lodge refuses a newer database. A rollback does not undo a migration. A restore of the state also keeps the names of lodge’s tailnet nodes.
Stop lodge for the backup. Systemd starts an upheld unit again, so mask it while you copy. While lodge is stopped, no browser and no agent reaches any app. The archive holds secrets, so keep it root-only:
sudo systemctl mask --now lodge.servicesudo sh -c 'umask 077; tar -czpf /root/lodge-state-$(date -u +%Y%m%dT%H%MZ).tar.gz /var/lib/lodge'sudo systemctl unmask lodge.servicesudo systemctl start lodge.servicetar exits 0, and systemctl status lodge.service shows the unit active
again.
Check for an update, then install it:
updex features check --component=lodgesudo updex features update --component=lodgeBy default, the update merges the new files at once, and systemd reloads its unit files. Updex v2.1.3 and later also create new users, groups, and directories at once. An older Updex leaves them for the next boot. The running lodge keeps its old binary until it restarts. A reboot applies everything.
The new files wait for the next boot when you pass --no-refresh, when the
automatic update timer installs the update, or when the output says
refresh skipped: Updex skips the refresh when it cannot refresh safely. Then
reboot. See Update.
With an Updex older than v2.1.3, run these commands first. updex --version
shows your version.
sudo systemd-sysuserssudo systemd-tmpfiles --createsudo systemctl daemon-reloadTo run the new version now, restart lodge:
sudo systemctl restart lodge.serviceCheck the version that runs:
journalctl -u lodge.service -n 20sudo lodgectl statusThe journal shows lodge starting with the new version and commit.
lodgectl status shows the state of each app.
Roll back to the previous version:
sudo updex features rollback lodgeThe rollback takes effect at the next boot. To apply it now, add --now, then
restart lodge as above. See Roll back.
When the older version runs, read the journal:
journalctl -u lodge.service -n 50If it shows the database is at version N, newer than this lodge's M, the
newer lodge migrated the database. Restore the state from your backup. Replace
ARCHIVE with the path of the archive:
sudo systemctl mask --now lodge.servicesudo rm -rf /var/lib/lodgesudo tar -xzpf ARCHIVE -C / var/lib/lodgesudo systemctl unmask lodge.servicesudo systemctl start lodge.serviceThe restore loses the tokens, the allow-list changes, and the provider keys and
sign-ins made after the backup. If lodge refreshed a codex sign-in after the
backup, the restored one is refused: sudo lodgectl status shows the provider
unavailable, signed out. To sign in again, run the
lodgectl provider login NAME --client-id ID command that the status gives on
a computer with a browser, without sudo. Copy the login file it writes to
lodge’s host, run sudo lodgectl provider import NAME < FILE, then delete the
file on both computers.
Remove lodge:
sudo updex features disable lodge --now --forceUpdex unmerges all extensions on the host, deletes the image, and merges the
rest again. Reboot when the command finishes. Without --force, Updex refuses
while the image is merged.
What ships
Section titled “What ships”| Item | What you get |
|---|---|
| Package | frostyard-lodge, the .deb attached to the app’s GitHub release, installed into the image with dpkg. The build checks the download against a pinned SHA-256, and checks that its dependencies are met before it installs. Its GitHub build-provenance attestation is checked when the pin is set and in every CI build, before the image is built |
| Feature | Lodge (tailnet web shell for Frostyard's first-party apps) |
| Commands | /usr/bin/lodge and /usr/bin/lodgectl |
| Service | lodge.service, which runs /usr/bin/lodge as the user lodge |
| State | /var/lib/lodge, mode 0700 |
| User and group | lodge, groups lodge-apps and lodge-agents, with lodge in both, from /usr/lib/sysusers.d/lodge.conf |
| Runtime directories | /usr/lib/tmpfiles.d/lodge.conf: /run/lodge, owned by lodge, and /run/lodge/apps, owned by root |
| Web shell | /usr/share/lodge/shell/ |
| Example configuration | /usr/share/lodge/config.example.json |
| Activation | /usr/lib/systemd/system/multi-user.target.d/10-lodge.conf with Upholds=, and /usr/lib/systemd/system-preset/40-lodge.preset |
| Version | The image file name carries the frostyard-lodge package version, which is the app’s release version |
| Image file | lodge_<version>_13_x86-64.raw.zst, where 13 is the OS version ID; .raw.xz, .raw.gz, and .raw also work |
| Download | From https://repository.frostyard.org/ext/lodge/, signature required (Verify=true), into /var/lib/extensions.d/ |
Each build checks the commands, the unit, the sysusers and tmpfiles files, the web shell’s index.html, the example configuration, and the preset, activation drop-in and extension-release files. A build that is missing one fails. Each build also checks that every upheld unit has no Condition*= line, or is a oneshot that stays active.
See also
Section titled “See also”- System extension catalog
- Manage system extensions
- lodge
- Frostnotes notesd
- Frostmail maild
- frostread readd
- Porter porterd
- lodge packaging README: the first start, the allow-lists, and agents.
Sources
Section titled “Sources”Reviewed 2026-10-29. snosi commit
e423112908540aa3a8045fa9d8022a3742e3cd17;
core commit
cd2720e0c55d08f3ba8f28a3b55dd5796a5c425a;
lodge v0.1.0 commit
a43d3be1de45704e0514d51da4c4b32d575f7c6c;
Updex v2.1.3 commit
2661e987faac986f2594d1d9bbbadf1bf3053f5b.
mkosi.images/lodge/mkosi.confmkosi.images/lodge/mkosi.postinst.chrootmkosi.images/lodge/required-paths.txtmkosi.images/lodge/mkosi.extra/usr/lib/systemd/system-preset/40-lodge.presetmkosi.images/lodge/mkosi.extra/usr/lib/systemd/system/multi-user.target.d/10-lodge.confmkosi.images/lodge/mkosi.extra/usr/lib/extension-release.d/extension-release.lodgemkosi.images/base/mkosi.extra/usr/lib/sysupdate.lodge.d/lodge.featuremkosi.images/base/mkosi.extra/usr/lib/sysupdate.lodge.d/lodge.transfershared/download/sysext-checksums.jsondocs/design/sysexts.md- snosi#1173 (merged 2026-10-28, merge commit
0ede646cd878f82e91b891d9c3f6eba77227573b) and snosi#1175 (merged 2026-10-28, merge commite423112908540aa3a8045fa9d8022a3742e3cd17) - core
docs/adr/0058-ship-suite-desktop-apps-as-flatpaks-and-services-as-sysexts.md - lodge
packaging/systemd/lodge.service,packaging/sysusers.d/lodge.conf,packaging/tmpfiles.d/lodge.conf,packaging/README.md,cmd/lodgectl/main.go - lodge
docs/design/overview.md(back up/var/lib/lodge; a restore keeps the node names),internal/store/store.go(the migration refusal),cmd/lodge/main.go(lodge starting),docs/specs/lodge-http.md(lodgectl status) - lodge
docs/adr/0015-codex-uses-the-owners-chatgpt-plan-through-sign-in-with-chatgpt.md(refresh tokens rotate; a refused refresh signs the provider out),internal/codexauth/codexauth.go,internal/inference/usage.go(provider_keysis in the database),cmd/lodgectl/main.go(provider login,provider import),internal/inference/codex.go(the signed-out reason gives the login command),docs/specs/inference.md(the sign-in and the import) - Irulan’s review (frostyard-org#58)
- Updex v2.1.3
cmd/updex/features.go(features updaterefreshes the merge unless--no-refresh) - snosi
docs/adr/0024-verify-attestations-of-first-party-sysext-debs.md(attestation checked at pin time and in every CI build) - Murbella’s review (frostyard-org#57)
- Updex v2.1.3
README.md(accounts and paths: a live refresh runssystemd-sysusersandsystemd-tmpfiles --create; a link on an older image shows an update, andfeatures updaterepoints it) - Updex
docs/adr/0019-apply-merged-sysusers-and-tmpfiles-on-a-live-refresh.md(Updex does not restart units; a unit at its start limit stays failed untilsystemctl reset-failedor a reboot) - Updex v2.1.3
updex/features.go(activation did not finish: the images are merged, and a reboot finishes the activation) - Updex v2.1.3 release
- lodge#41 (merged 2026-10-28)
- lodge
docs/specs/app-contract.md(each app’ssysusers.dentry declares userlodge, with the line in lodge’s ownpackaging/sysusers.d/lodge.conf) - lab
docs/plans/2026-10-28-snow-sysext-probe-run.md(runjvzfn: with Updex v2.1.3 a unit can still fail in the enable boot) and lab#164 (merged 2026-10-28) src/data/extensions.json