Server runtime directory: /root/maibot-offline
Release archive directory: /srv/maibot/releases

Repo-managed deployment files:
- deploy/server-maibot/Dockerfile.offline
- deploy/server-maibot/Dockerfile.release
- deploy/server-maibot/docker-entrypoint.offline.sh
- deploy/server-maibot/docker-compose.server.yml
- deploy/server-maibot/activate-release.sh
- deploy/server-maibot/bot.lecspace.com.nginx.conf

Persistent files that stay on the server and do not go into Git:
- /root/maibot-offline/docker-config/mmc/*
- /root/maibot-offline/docker-config/napcat/*
- /root/maibot-offline/data/*
- /root/maibot-offline/depends-data/*
- /root/maibot-offline/bot.lecspace.com.key
- /root/maibot-offline/bot.lecspace.com_bundle.pem

Gitea workflow:
- .gitea/workflows/release-offline.yml

Current pipeline mode:
- single-host release on the repo-level `build-host` runner
- fetches the source commit from the workflow repository itself
- defaults to `${gitea.server_url}/${gitea.repository}.git`
- can override the clone URL with the repository variable `MAIBOT_REPO_URL`
- authenticates Git over HTTP(S) with the built-in `GITEA_TOKEN`
- performs a shallow fetch of the triggering commit instead of a full clone
- stages source into `/srv/maibot/releases/<commit>`
- builds `maibot-offline:<commit>` from the staged release using local base image `maibot-offline:latest`
- tags the same image back to `maibot-offline:latest`
- deploys from `/root/maibot-offline` with `docker compose up -d`

Optional environment overrides for the workflow runtime:
- `MAIBOT_REPO_URL`
- `MAIBOT_RELEASE_ROOT`
- `MAIBOT_RUNTIME_ROOT`
- `MAIBOT_BASE_IMAGE`

Runner connectivity note:
- if the runner cannot access `${gitea.server_url}` directly, set `MAIBOT_REPO_URL` to a runner-reachable HTTPS clone URL
- for private repositories, the workflow uses the built-in `GITEA_TOKEN`, so no extra personal access token secret is needed
- the repository or owner Actions settings must allow the job token to read repository contents

Bootstrap note:
- `deploy/server-maibot/Dockerfile.offline` is only for the first bootstrap or for refreshing the runtime base image.
- The normal Gitea release pipeline uses `deploy/server-maibot/Dockerfile.release`, so it does not need Docker Hub or GitHub during each deploy.
- If `pyproject.toml` or `uv.lock` changes, refresh the local base image once before relying on the release pipeline again.
