From 8dc7749fa932d55abfa64c789eadf5a1fea97b4b Mon Sep 17 00:00:00 2001 From: hexin-codex-bot Date: Sun, 4 Oct 2026 21:48:34 +0800 Subject: [PATCH] ci: deploy persistent systemd service --- .gitea/workflows/ci.yml | 106 +++++++++++++++++++++++++++++++--------- 1 file changed, 82 insertions(+), 24 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 62085b9..f40af08 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -13,12 +13,17 @@ on: jobs: verify-and-deploy: runs-on: ces-control - container: node:24-bookworm-slim timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + - name: Install dependencies run: npm ci @@ -31,40 +36,93 @@ jobs: - name: Production build run: npm run build - - name: Deploy persistent production container + - name: Deploy persistent production service if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main' env: STUDIO_USERNAME: studio STUDIO_PASSWORD: ${{ secrets.STUDIO_PASSWORD }} DEPLOY_PORT: ${{ vars.DEPLOY_PORT }} - DEPLOY_DOCKERFILE: Dockerfile.deploy - GITHUB_SHA: ${{ gitea.sha }} + RELEASE_SHA: ${{ gitea.sha }} run: | + set -eu if [ -z "$STUDIO_PASSWORD" ]; then echo "STUDIO_PASSWORD repository secret is required for production deployment" exit 1 fi - node scripts/deploy.mjs - - name: Verify persistent deployment settings - if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main' - run: | - node --input-type=module <<'JS' - import Docker from "dockerode"; + if [ "$(id -u)" -eq 0 ]; then + SUDO="" + elif sudo -n true 2>/dev/null; then + SUDO="sudo -n" + else + echo "The ces-control runner needs root or passwordless sudo for persistent systemd deployment" + exit 1 + fi - const docker = new Docker({ socketPath: "/var/run/docker.sock" }); - const container = docker.getContainer("rednote-tool-studio"); - const state = await container.inspect(); - const restartPolicy = state.HostConfig.RestartPolicy?.Name; - const health = state.State.Health?.Status; - const dataMount = state.Mounts?.find((mount) => mount.Destination === "/app/data/projects"); + PORT="${DEPLOY_PORT:-3000}" + APP_ROOT=/opt/rednote-tool-studio + RELEASE_DIR="$APP_ROOT/releases/$RELEASE_SHA" + DATA_ROOT=/var/lib/rednote-tool-studio + SERVICE_USER=rednote-studio - if (!state.State.Running) throw new Error("Production container is not running"); - if (health !== "healthy") throw new Error(`Production container health is ${health || "unavailable"}`); - if (restartPolicy !== "unless-stopped") throw new Error(`Unexpected restart policy: ${restartPolicy || "none"}`); - if (dataMount?.Type !== "volume" || dataMount.Name !== "rednote-tool-studio-data") { - throw new Error("Persistent project data volume is not mounted correctly"); - } + if ! id "$SERVICE_USER" >/dev/null 2>&1; then + $SUDO useradd --system --home "$DATA_ROOT" --shell /usr/sbin/nologin "$SERVICE_USER" + fi - console.log(`Persistent deployment verified: health=${health}, restart=${restartPolicy}, volume=${dataMount.Name}`); - JS + $SUDO mkdir -p "$APP_ROOT/releases" "$APP_ROOT/bin" "$DATA_ROOT/projects" + $SUDO install -m 0755 "$(command -v node)" "$APP_ROOT/bin/node" + $SUDO mkdir -p "$RELEASE_DIR/.next" "$RELEASE_DIR/data" + $SUDO cp -a .next/standalone/. "$RELEASE_DIR/" + $SUDO cp -a .next/static "$RELEASE_DIR/.next/static" + $SUDO cp -a templates vendor "$RELEASE_DIR/" + $SUDO ln -sfn "$DATA_ROOT/projects" "$RELEASE_DIR/data/projects" + $SUDO chown -R root:root "$APP_ROOT" + $SUDO chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_ROOT" + $SUDO ln -sfn "$RELEASE_DIR" "$APP_ROOT/current" + + printf '%s' "$STUDIO_PASSWORD" | $SUDO tee /etc/rednote-tool-studio.password >/dev/null + $SUDO chmod 600 /etc/rednote-tool-studio.password + + cat > rednote-tool-studio.service <{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"; then + break + fi + if [ "$attempt" -eq 30 ]; then + $SUDO systemctl status rednote-tool-studio.service --no-pager || true + $SUDO journalctl -u rednote-tool-studio.service -n 100 --no-pager || true + exit 1 + fi + sleep 2 + done + + $SUDO systemctl is-enabled rednote-tool-studio.service + $SUDO systemctl is-active rednote-tool-studio.service + echo "Persistent deployment verified: service=rednote-tool-studio port=$PORT data=$DATA_ROOT/projects release=$RELEASE_SHA"