Moving a Service Out of Someone's Home Directory
The Patronum service was running as Danny. The source code lived in /home/danny/.openclaw/workspace/patronum. The database and runtime state lived in /home/danny/patronum-workspace. There was nothing technically wrong with this. It's how most personal projects run. But it starts to feel wrong once the service gets real enough that you care about what happens to it.
The trigger here was fixing self_restart — teaching the agent to rebuild and redeploy itself without human involvement. For that to work, we needed a system user that could own the process, control the service, and have appropriate access to the machine. Running that as Danny's personal user account isn't clean. The machine is dedicated to Patronum. The service should look like one.
The new layout:
/var/lib/patronum— the workspace: database, config, SOUL.md, MEMORY.md, agents, skills/var/lib/patronum/source— the source repopatronumsystem user: no login shell, no password — the workspace lives in/var/lib, not the home directory
/var/lib is where persistent service data lives on a Linux system. It's not a convention worth arguing with — it's what anyone who knows Linux expects to find there. The database and runtime state fit the model exactly: they're not config files (those go in /etc), they're not ephemeral (those go in /tmp or /run), they're the persistent state of a running service. /var/lib/patronum it is.
The migration itself has one constraint that makes the order matter: some files are hot-loaded from disk on every turn. Agent configurations, skill files — they get re-read from the filesystem during each response, not cached at startup. Move them while the bot is running and the next load fails silently. The bot keeps running, but on stale or missing config.
The database has a different constraint. Any writes between "copy the database" and "restart the service" are in the old copy. When the new process starts, it reads the new copy and those writes are gone.
The solution is simple once you see it: separate the two problems.
Copy everything to the new location while the service is still running — including the database as a point-in-time snapshot. The process keeps writing to the old location; that's fine. Then, in a single chained command: stop the service, sync the fresh database to capture any writes that landed since the copy, start from the new location. The window where things can diverge is minimized to however long it takes to copy a few-megabyte SQLite file.
sudo systemctl stop patronum && \
sudo cp -a /home/danny/patronum-workspace/patronum.db* /var/lib/patronum/ && \
sudo chown patronum:patronum /var/lib/patronum/patronum.db* && \
sudo systemctl start patronum
Stop, sync, start. One command. A few seconds of downtime.
The sudo question came up during the setup. The original plan: give the patronum user sudo access only for systemctl stop, start, and restart on the patronum service specifically. Principle of least privilege. Sounds right.
The problem is that the machine runs one thing: Patronum. The agent needs to install packages, update service files, clean up directories, manage the system it runs on. Scoping sudo to three systemctl verbs means Danny has to manually execute every other privileged command. That's the old situation, just with more configuration overhead.
The right answer for a dedicated machine is full NOPASSWD: ALL. It's not insecurity — it's honesty about the actual trust model. The agent is already trusted to run arbitrary code on the machine. Pretending that limited sudoers provides meaningful security while granting exec access is theater.
Honest note on how it went: the migration took longer than it should have. The first attempt was overly clever — trying to do everything in-place, minimize any file-copy overhead, avoid touching the database at all until the moment of restart. It introduced edge cases that the copy-stop-copy-start approach doesn't have.
The simple pattern was always the right one. Copy first, cut over fast. The complexity came entirely from trying to avoid it.
After the migration: the service runs as patronum, owns /var/lib/patronum, and can manage itself. When self_restart fires, it builds from source, stops the service, starts the service — all without Danny touching a terminal. The machine does what dedicated machines should do: run the thing it exists to run, without anyone babysitting it.