Menu

Operate

Managed server updates

On this page

The CLI manages the complete update of a registered Linux server: authenticated release download, preparation, maintenance mode, backup, migration, activation, web and worker checks, and recovery. Both versioned Python/systemd installations and the supplied Docker Compose deployment are supported. Desktop installations use the launcher’s update button or quillsail update --app /path/to/application.

Once registered, a server needs one command:

Shell
sudo quillsail update

Starting with 1.9.1, setup and bootstrap install a protected command at /usr/local/bin/quillsail and verify that it is reachable through the installing operator’s sudo PATH. Python deployments link through current, so later updates automatically use the new executable. The full path /opt/copydesk/current/venv/bin/quillsail remains available. The copydesk package command remains an alias. Add --yes for unattended approval or --check to check availability without installing. The profile selects Stable or Beta; --channel stable or --channel beta overrides it for one invocation. An unpublished development version does not become available merely by being committed to GitHub. Maintainers must publish its tested packages and signed update metadata first.

Register a Python/systemd server once#

Install a release that includes server update support (1.9.0 or later). The standard production layout is:

/opt/copydesk/current -> /opt/copydesk/releases/VERSION
/opt/copydesk/releases/VERSION/venv
/var/lib/copydesk/copydesk.toml
/var/lib/copydesk/data/...

Application releases and the update profile must be root-owned, protected from other users, and readable by the service account. Publication files belong to the service account. The web and worker systemd units must use executables under /opt/copydesk/current/venv/bin/. See the supplied web and worker examples.

Shell
sudo /opt/copydesk/current/venv/bin/quillsail update setup \
  --health-url http://127.0.0.1:8000/healthz

For a site under /publication, use http://127.0.0.1:8000/publication/healthz. Specify the actual port. Options allow different installation/instance/backup roots, service names and service account. Use quillsail update setup --help. Setup records a protected /etc/copydesk/update.json; it does not replace live code or stop services. Use --profile /protected/path/update.json for another installation and pass that profile to subsequent update commands.

Command availability and existing installations#

Server setup creates the stable command for all accounts on that host. It does not grant sudo access. Administrators can configure a different sudo PATH for each account; setup verifies the sudo environment it runs in and does not edit sudoers, shell startup files or security policy.

Use --command /approved/bin/quillsail if /usr/local/bin is absent from sudo’s PATH. The chosen directory and earlier PATH locations must be protected from other users. Setup refuses conflicting commands and reports the issue before registering. Use --no-command to preserve an existing command and continue with the full executable path. A nondefault profile still needs --profile when updating; setup prints the complete command, including that argument.

After installing 1.9.1 or later, an already registered server can install or verify its command without stopping services:

Shell
sudo /opt/copydesk/current/venv/bin/quillsail update install-command
sudo quillsail update --check

Compose hosts run update install-command with their persistent host CLI. The link points to that host environment, which must remain installed. A command already provided by that environment is preserved. Use --command PATH to choose another name or directory. Repeating the command is safe and does not replace a conflicting executable.

For 1.9.0, use the full executable path or create the link manually once in a directory approved for sudo’s PATH:

Shell
sudo ln -s /opt/copydesk/current/venv/bin/quillsail /usr/local/bin/quillsail
sudo quillsail update --check

The link command fails if the name is already taken. Inspect that command before replacing it; do not overwrite another installation’s entry point.

If systemd uses environment files, supply each one using --environment-file /etc/copydesk/production.env. These files must be root-owned and must not be writable by the service account. They use literal dotenv KEY=value syntax without shell expansion. Register the same database and path overrides used by the services. The updater deliberately ignores ambient COPYDESK_* and Python import-path variables. Keep all mutable files, including the SQLite database, uploads, themes, extensions and file mail, inside the registered instance root. Filesystem links and special files in the instance are rejected; move their targets inside the instance first.

Bootstrap an older server#

Older commands cannot acquire update support themselves. Obtain the new wheel, its SHA-256 digest and deploy/bootstrap_server.py through a trusted first-install channel. Review the installer and verify the digest independently, then run:

Shell
sudo python3 -I deploy/bootstrap_server.py \
  --wheel /protected/copydesk-VERSION-py3-none-any.whl \
  --sha256 TRUSTED_64_CHARACTER_SHA256 \
  --health-url http://127.0.0.1:8000/healthz

This one command stages the updater in a temporary environment, registers the existing versioned installation, and runs the normal backed-up update transaction. It installs or verifies the stable command for new and previously registered Python servers. --command PATH chooses its location; --no-command skips that step. Use a 1.9.1+ wheel for automatic command installation, or the matching older installer when bootstrapping 1.9.0. Adjust its path/service options for the existing installation. Python 3.11+ and working package-index access are required. First installation uses the operator’s explicitly pinned local installer; later network updates authenticate their metadata and wheel with TUF. There is no unsigned network fallback.

Register Docker Compose once#

Run the updater on the Linux Docker host as root, using a host Python environment with QuillSail 1.9.0+ installed. Keep that host tool available for future updates. Start the existing web and worker containers, then register their project:

Shell
sudo /opt/quillsail-tools/bin/quillsail update setup \
  --kind compose --compose-file /srv/quillsail/compose.yaml \
  --project copydesk --install-root /opt/quillsail-compose \
  --health-url http://127.0.0.1:8080/healthz
sudo /opt/quillsail-tools/bin/quillsail update

Supply your actual Compose project name and settings. Setup records a private, fully resolved Compose configuration, discovers the persistent /var/lib/copydesk mount, and pins the Python base image by digest. Pass --base-image IMAGE@sha256:DIGEST to choose that image explicitly. Environment files can supply Compose interpolation values. After registration, use the managed configuration for lifecycle commands: the original source Compose file still describes its original image. Do not run two independent managers against the same project. Inspect the protected profile for compose_file, project and install_root; the updater’s current-image.json override selects the active images. Reregister into a new protected installation/profile when intentionally changing the resolved deployment configuration.

The updater builds a local image from the verified release wheel before taking the site offline. It preserves the old image IDs for recovery. It does not publish an image or require a QuillSail image registry. The Docker daemon, pinned base-image distribution and Python dependency index remain trusted installation dependencies, as they are for Python deployments.

For an external database whose container hostname is unavailable on the host, use --backup-host HOST --backup-port PORT with a host-reachable endpoint for the same database. Keep it private. SQLite needs no external client.

Backup and recovery#

New packages/images are prepared while the current site stays online. Then requests receive a short maintenance response, web and worker stop, and the updater copies the complete instance and takes a consistent native database snapshot. It checks backup hashes, runs migrations, starts the new web service, checks the exact version and database, and starts the worker while queued work is still paused. A fresh worker identity is required before committing and reopening the site. No test email is sent.

For PostgreSQL, install pg_dump matching the server’s major version (or newer) and pg_restore. PostgreSQL updates manage the application’s dedicated public schema. For MySQL/MariaDB, install mariadb-dump/mariadb or mysqldump/mysql. Use a dedicated application database and transactional tables; the account needs dump and restore privileges. Unsupported URL connection options are rejected before downtime. Resolve any job still marked running before updating; the updater will not guess whether an email was delivered.

Protected recovery records, command diagnostics and backups remain under /var/backups/copydesk by default. They contain credentials and personal data. Retain them according to your backup policy and replicate them to protected off-host storage. Automatic updates do not prune recovery data. Preparation and restoration need space for new code and multiple instance/database copies.

Shell
sudo quillsail update status
sudo quillsail update --resume
sudo quillsail update rollback --acknowledge-mail-replay

Failures before commit restore the old code and matching data automatically. If power or the process is lost, --resume uses the durable journal to recover or finish. Keep the maintenance marker and recovery files intact until recovery completes. After commit, recovery never automatically overwrites new reader content or replays old queued mail by restoring an earlier database.

Explicit rollback first saves the current instance and database, then restores the previous update’s matching snapshot and application. New content received since that snapshot is absent from the active publication after rollback and remains in the safety backup. Review the snapshot and delivery history on an isolated instance first: restored pending jobs can resend mail already delivered after the snapshot. The command requires explicit acknowledgment of that risk. Alembic downgrade is not used.

This lifecycle requires a registered versioned systemd or Compose deployment. An arbitrary editable checkout, shared database, custom process supervisor or multi-host deployment needs its own orchestration; the updater refuses to guess how to stop or recover it.