Run as a service
このコンテンツは、現在ご利用の言語ではまだ提供されていません。
An install started from a shell dies with the shell. The project ships three
systemd unit files under etc/examples/systemd/ —
onetimesecret-web.service, onetimesecret-worker.service and
onetimesecret-scheduler.service. Use them as the starting point, then add the
storage permission below if you use full authentication with SQLite.
They assume a finished bare-metal install, so work through Install on Linux first.
Install the units
Section titled “Install the units”From the application checkout, copy the units:
Use your administrative shell for this section, not the onetime service account. Install on Linux enters that account’s login shell; run exit to return to your administrative shell, then change to /var/lib/onetimesecret before running the commands below. The service account does not need sudo rights. Keep the SQLite directory grant below when using the default full-mode database.
sudo cp etc/examples/systemd/onetimesecret-*.service /etc/systemd/system/Full mode with SQLite: allow the account database directory
Section titled “Full mode with SQLite: allow the account database directory”The default AUTH_DATABASE_URL is sqlite://data/auth.db. With the shipped
working directory, this is /var/lib/onetimesecret/data/auth.db. The units use
ProtectSystem=strict and grant writes to tmp and log only. Without an
additional grant, SQLite cannot create or update the database or its journal.
Before starting any service that uses this database, add a drop-in for all three units:
for unit in onetimesecret-web onetimesecret-worker onetimesecret-scheduler; do sudo install -d -m 0755 "/etc/systemd/system/$unit.service.d" printf '[Service]\nStateDirectory=onetimesecret/data\nStateDirectoryMode=0750\n' | sudo tee "/etc/systemd/system/$unit.service.d/sqlite.conf" >/dev/nulldoneStateDirectory creates the directory with the unit’s onetime ownership and
allows writes there despite ProtectSystem=strict. It adds to the shipped
tmp and log entries; do not reset that list or disable filesystem protection.
See the systemd directory settings.
This drop-in is for the default SQLite path. For a custom path, create its parent
directory with onetime ownership and grant that directory with ReadWritePaths=
in each unit instead. SQLite needs directory access for journals as well as
access to the database file. PostgreSQL deployments do not need this SQLite
directory grant.
Start the web service
Section titled “Start the web service”sudo systemctl daemon-reloadsudo systemctl enable --now onetimesecret-webenable --now starts the service and enables it at boot. If applying a drop-in
to running services, restart the web service and any worker or scheduler you
already run so they receive the new permissions.
Inspect systemctl cat onetimesecret-web and journalctl -u onetimesecret-web
if it fails. In full mode, create the first account,
sign in, restart the service, and sign in again to check database writes and
persistence. Merely reaching the homepage does not exercise those writes.
What the units assume
Section titled “What the units assume”Each unit runs as the user and group onetime, with WorkingDirectory set to
/var/lib/onetimesecret, and expects Valkey or Redis to be up already — the
web unit orders itself After=network.target redis-server.service valkey.service, hedging across both common service names. The worker and
scheduler additionally order themselves after onetimesecret-web.service.
The environment comes from .env in the working directory, sourced inside
ExecStart rather than through systemd’s EnvironmentFile:
ExecStart=/bin/bash -lc 'set -a; source .env; set +a; exec bundle exec puma -C etc/puma.rb'The worker and scheduler use the same form around bin/ots worker and
bin/ots scheduler.
The -l in bash -lc is load-bearing beyond .env. It is what puts a Ruby
installed for the onetime user on the service’s PATH — but only through the
files a login shell actually reads, which are /etc/profile and then the first
of ~/.bash_profile, ~/.bash_login and ~/.profile. ~/.bashrc is not among
them, so a version manager initialized there is invisible here and the unit dies
with bundle: command not found. Install on Linux writes
the rbenv line into ~/.profile for that reason; if you set Ruby up some other
way, put its initialization in one of those three files, or in an
/etc/profile.d/ snippet that /etc/profile picks up.
Setting PATH through systemd’s Environment= or EnvironmentFile= is not a
substitute. Those are applied to the process systemd starts, which here is the
login shell, and /etc/profile then runs and sets its own PATH over the top.
If the app is not at /var/lib/onetimesecret
Section titled “If the app is not at /var/lib/onetimesecret”WorkingDirectory is a literal path, not a search. An install that sits in the
onetime user’s home directory, or under /opt, will not be found, and
systemd fails the unit before Puma runs. Move the install to
/var/lib/onetimesecret rather than editing the units: it is the layout the
shipped files are written against, and it keeps all three of them correct as
delivered. If you keep the app elsewhere, change WorkingDirectory in all
three files and keep them identical — the web unit’s own header tells you to
adjust it.
ProtectHome=true is a second reason to keep the shipped layout. It makes
/home inaccessible to the service, so an install — or a version manager —
under a user’s home directory in /home stays unreachable even once
WorkingDirectory points at it. /var/lib/onetimesecret is not under /home
and is unaffected.
When you need the worker and the scheduler
Section titled “When you need the worker and the scheduler”The worker and scheduler unit headers say they are required for full
authentication mode with PostgreSQL and RabbitMQ. That is wrong in both halves,
and the real rule is simpler: background jobs are off unless you set
JOBS_ENABLED=true. While jobs are off, the web process sends email
synchronously in-process, nothing publishes work for the worker to consume, and
both extra services would sit idle. RabbitMQ matters in exactly the same case —
it is the broker the worker consumes from, and nothing needs it until jobs are
on.
Authentication mode does not decide this. Full mode adds a SQL account database, which defaults to SQLite, not a job queue; a full-mode instance with jobs off runs on the web unit alone.
JOBS_ENABLED is read from the environment, so it goes wherever the units get
their environment from: .env in the working directory, or
/etc/default/onetimesecret. Set it, then enable both units:
sudo systemctl enable --now onetimesecret-worker onetimesecret-schedulerWhile jobs are off, the health endpoint reports the job queue as
not_configured rather than as a failure, so an instance running on the web
unit alone does not look broken there.
What the units already handle
Section titled “What the units already handle”All three restart on-failure after 5 seconds, with a start limit of five
attempts per 60 seconds so a genuinely broken instance stops flapping and stays
down where you can see it. Stop timeouts differ deliberately: 30 seconds for
the web and scheduler units, 60 seconds for the worker so in-flight jobs can
drain before it is killed.
Each unit runs with NoNewPrivileges=true, ProtectSystem=strict,
ProtectHome=true and PrivateTmp=true, and declares
StateDirectory=onetimesecret/tmp onetimesecret/log. Under
ProtectSystem=strict the filesystem is read-only apart from what the unit is
granted, so any additional path the app has to write to needs granting
deliberately.
EnvironmentFile=-/etc/default/onetimesecret is present in all three units and
optional — the leading - means the service starts whether or not that file
exists. It is a second, additive source of environment variables, for values
you would rather keep outside the app directory or vary between hosts.
Without systemd
Section titled “Without systemd”Procfile.example defines the same processes for any Procfile runner —
foreman, hivemind or goreman. Only the web line is uncommented; the worker
and scheduler lines are shipped commented out. Export the environment first,
the same way the units do:
set -a; source .env; set +aforeman start -f Procfile.exampleIn a container none of this applies. The main image runs a single process, and multi-process supervision is the S6 variant’s job.
Once the services are running, confirm the instance actually serves: Verify your install.