The daemon job assumed a GitHub-hosted runner, where the build user is
unprivileged and sudo exists. Our act runner is a container that runs
as root and ships no sudo, so the step died with "sudo: command not
found" before the toolchain was ever installed — which also blocked the
publish job that waits on it.
Use sudo only when we are not already root, so the step works on both.
docker-compose.panel.yml deploys every service from a published image.
A control panel writes only a compose file and an .env into its project
directory, so the build: stanzas of docker-compose.yml cannot resolve
their context there.
CI pushes api, migrate, web and daemon images to the Gitea container
registry on v* tags. The migrate stage ships as its own image because
the panel compose runs it as a one-shot service before the API starts.
The web port is named HOST_PORT: panels reverse-proxy "the" port of an
installation and need to know which one that is when a stack publishes
more than one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>