diff --git a/deploy/server-maibot/Dockerfile.release b/deploy/server-maibot/Dockerfile.release new file mode 100644 index 00000000..503071bd --- /dev/null +++ b/deploy/server-maibot/Dockerfile.release @@ -0,0 +1,14 @@ +ARG MAIBOT_BASE_IMAGE=maibot-offline:latest +FROM ${MAIBOT_BASE_IMAGE} + +WORKDIR /MaiMBot + +RUN find /MaiMBot -mindepth 1 -maxdepth 1 ! -name '.venv' -exec rm -rf {} + + +COPY . . + +RUN chmod +x deploy/server-maibot/docker-entrypoint.offline.sh + +EXPOSE 8001 + +ENTRYPOINT ["./deploy/server-maibot/docker-entrypoint.offline.sh"] diff --git a/deploy/server-maibot/README_DEPLOY_STEPS.txt b/deploy/server-maibot/README_DEPLOY_STEPS.txt index 08176e36..6124450d 100644 --- a/deploy/server-maibot/README_DEPLOY_STEPS.txt +++ b/deploy/server-maibot/README_DEPLOY_STEPS.txt @@ -3,6 +3,7 @@ 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 @@ -23,10 +24,18 @@ Current pipeline mode: - single-host release on the repo-level `build-host` runner - clones from local Gitea HTTP on `127.0.0.1:3000` - stages source into `/srv/maibot/releases/` -- activates it into `/root/maibot-offline` +- builds `maibot-offline:` 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_RELEASE_ROOT` - `MAIBOT_RUNTIME_ROOT` +- `MAIBOT_BASE_IMAGE` No repository secrets are required for the default same-host pipeline. + +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. diff --git a/deploy/server-maibot/activate-release.sh b/deploy/server-maibot/activate-release.sh index 7f224ddc..82abfbb3 100644 --- a/deploy/server-maibot/activate-release.sh +++ b/deploy/server-maibot/activate-release.sh @@ -3,6 +3,7 @@ set -euo pipefail release_dir="${1:?usage: activate-release.sh }" runtime_root="${MAIBOT_RUNTIME_ROOT:-/root/maibot-offline}" +base_image="${MAIBOT_BASE_IMAGE:-maibot-offline:latest}" case "$runtime_root" in /root/maibot-offline|/root/maibot-offline/*) ;; @@ -17,6 +18,23 @@ if [ ! -d "$release_dir" ]; then exit 1 fi +if ! docker image inspect "$base_image" >/dev/null 2>&1; then + echo "base image not found locally: $base_image" >&2 + echo "bootstrap it once with deploy/server-maibot/Dockerfile.offline before using the release pipeline" >&2 + exit 1 +fi + +for dep_file in pyproject.toml uv.lock; do + if [ -f "$runtime_root/$dep_file" ] && ! cmp -s "$release_dir/$dep_file" "$runtime_root/$dep_file"; then + echo "dependency metadata changed: $dep_file" >&2 + echo "refresh the local base image with deploy/server-maibot/Dockerfile.offline before using the release pipeline" >&2 + exit 1 + fi +done + +release_id="$(basename "$release_dir")" +release_image="maibot-offline:${release_id}" + mkdir -p \ "$runtime_root" \ "$runtime_root/data/MaiMBot" \ @@ -28,7 +46,7 @@ mkdir -p \ "$runtime_root/docker-config/mmc" \ "$runtime_root/docker-config/napcat" -python3 - "$release_dir" "$runtime_root" <<'PY' +python3 - "$release_dir/deploy/server-maibot" "$runtime_root/deploy/server-maibot" <<'PY' from pathlib import Path import shutil import sys @@ -36,44 +54,23 @@ import sys src = Path(sys.argv[1]).resolve() dst = Path(sys.argv[2]).resolve() -skip_names = { - ".git", - ".gitea", - ".github", - ".venv", - "data", - "docker-config", - "depends-data", - "logs", - "plugins", - "acme", - "backups", - "bin", - "_staging", -} -skip_suffixes = {".pem", ".key", ".tar", ".tgz", ".zip"} +if dst.exists(): + shutil.rmtree(dst) -for item in src.iterdir(): - if item.name in skip_names or item.suffix in skip_suffixes: - continue - - target = dst / item.name - if target.exists(): - if target.is_dir() and not target.is_symlink(): - shutil.rmtree(target) - else: - target.unlink() - - if item.is_dir(): - shutil.copytree( - item, - target, - ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"), - ) - else: - shutil.copy2(item, target) +shutil.copytree( + src, + dst, + ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo"), +) PY +docker build \ + --build-arg "MAIBOT_BASE_IMAGE=${base_image}" \ + -f "$release_dir/deploy/server-maibot/Dockerfile.release" \ + -t "$release_image" \ + "$release_dir" + +docker tag "$release_image" maibot-offline:latest + cd "$runtime_root" -docker build -f deploy/server-maibot/Dockerfile.offline -t maibot-offline:latest . -docker compose -f deploy/server-maibot/docker-compose.server.yml up -d +MAIBOT_CORE_IMAGE="$release_image" docker compose -f deploy/server-maibot/docker-compose.server.yml up -d