跳转到内容

You're viewing the prerelease documentation siteContent here is for testing and may differ from the live docs.

Go to live docs

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 — and installing them unchanged is the shortest correct way to keep an instance running across restarts.

They assume a finished bare-metal install, so work through Install on Linux first.

Terminal window
sudo cp etc/examples/systemd/onetimesecret-*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now onetimesecret-web

That is the whole job for most instances. enable --now both starts the service and starts it at boot.

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:

Terminal window
sudo systemctl enable --now onetimesecret-worker onetimesecret-scheduler

While 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.

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.

Procfile.production 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:

Terminal window
set -a; source .env; set +a
foreman start -f Procfile.production

In 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.