Setu

Your binaries. Your servers. One control panel.

Setu 0.1.5: followed manifest URLs and setu upgrade

Two changes that remove manual steps from keeping a Setu install current. Today a new app version reaches the Updates tab only after an operator publishes its manifest again, and Setu itself is upgraded by re-running install.sh with the flags of the first install.

1. Apps follow their manifest URL

An app published from a URL follows that URL: Setu fetches it again on a schedule and publishes any new version it finds. Nothing is installed or updated on a site; a new version only appears in the site's Updates tab.

  • Schema. Migration 0004_app_sources.sql adds a table app_sources with one row per app:

    A separate table keeps every existing apps query unchanged.
    • app_id, the primary key, references apps (id) with ON DELETE CASCADE;
    • url, the manifest URL;
    • checked_at, when the last check finished, and last_error, its error or NULL.
  • Publish. POST /api/v1/apps with url stores the URL as the app's source (replacing an earlier one). Publishing pasted TOML leaves the source as it is.
  • Check. One check of one app:

    A check never replaces a version that is already published, even when the manifest at the URL changed: a manifest names binaries and their checksums, and an unattended re-publish must not swap what a running version installs. Re-publishing by hand still replaces it, as today.
    1. fetches the URL with the publish guards (public addresses only, no redirects, 10 s, 1 MiB);
    2. parses and validates the manifest;
    3. refuses a manifest whose app.id is not the followed app's;
    4. when the version is not yet published, publishes it, audited as app.publish with no operator and the detail <app>/<version> (followed);
    5. records checked_at and last_error.
  • Schedule. The control plane checks every followed app every 6 hours (on the scheduler tick, the first check one tick after start). Failures are recorded and retried at the next round; they are not errors of the panel.
  • API.
    • AppOut gains source: { url, checked_at, last_error } | null.
    • POST /api/v1/apps/{id}/check (operator and above) runs a check now and returns { published: <version> | null, source }; 404 when the app follows no URL.
    • DELETE /api/v1/apps/{id}/source (operator and above) stops following; 404 when there is none. Audited as app.source_clear.
  • UI. The Apps page shows, per app, "Follows <url> · checked <time>" (or the last error) with Check now and Stop following.

2. setu upgrade

setu upgrade [--version <ver>], run as root on any Setu server, moves the installed binary to a release:

  1. Resolves the version: --version, else the latest release of setucp/setu (GitHub API). Already on it → prints so and exits 0.
  2. Downloads setu-<ver>-<target>.tar.gz and SHA256SUMS from the release, refuses an archive whose checksum does not match, extracts it (tar) and checks the new binary reports setu <ver>.
  3. On a control plane (setu.service enabled): takes a control-plane backup into the self-backup directory (the same dump and rotation as the nightly one), and stops if it fails.
  4. Installs the binary at /opt/setu/setu (write .setu.new, rename).
  5. Restarts setu.service (migrations run on boot) and, when enabled, setu-agent.service.
  6. On a control plane, waits up to 90 s for /healthz on the configured listener and fails loudly if it never answers.

The config (setu.toml) is read from /etc/setu when the current directory has none, so setu upgrade and setu backup work from any directory. Downgrades are refused (migrations are forward-only) unless --allow-downgrade.

3. Docs

docs/OPERATIONS.md and docs/INSTALL.md describe setu upgrade; the backup command no longer depends on the current directory.

Not in scope

Automatic upgrades of Setu itself, upgrading other servers from the panel, and following URLs of private repositories (no auth headers).

Tests

  • Follow: publishing from a URL stores the source; a check publishes a new version, leaves an existing one unchanged, refuses another app's id and records errors; stop following clears it. URL fetches in tests go through an injectable fetcher (the real one refuses local addresses).
  • Upgrade: checksum parsing and matching, version comparison and the downgrade refusal, archive name per target; the install-and-restart path is covered by scripts/test-install.sh.