name: Deploy (stellaamor) on: push: branches: [ "main" ] # change if you use main jobs: deploy: runs-on: [ self-hosted, mainhost, docker ] concurrency: group: deploy-stellaamor cancel-in-progress: false env: # ---- required (set these as repo/org SECRETS) ---- SSH_HOST: ${{ secrets.SSH_HOST }} # e.g. 192.168.122.50 (the stellaamor VM) SSH_USER: ${{ secrets.SSH_USER }} # e.g. deploy SSH_KEY: ${{ secrets.SSH_KEY }} # private key (ed25519), one line # optional but recommended: known_hosts entry for your VM SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} # ---- repo-scoped settings (safe to commit) ---- APP_ROOT: /var/www/stellaamor # base dir on the VM UPLOADS_DIR: uploads # relative to shared/ KEEP_N: "5" # how many releases to keep HEALTH_URL: https://stellaamor.com/ # simple GET should return 200 SERVICE_RELOAD: "systemctl reload apache2 || true" steps: - name: Checkout uses: actions/checkout@v4 # only build assets if you actually have a package.json with build script - name: Maybe build frontend (Vue/etc) if: hashFiles('package.json') != '' uses: actions/setup-node@v4 with: node-version: "20" - name: npm ci if: hashFiles('package.json') != '' run: npm ci - name: npm build if: hashFiles('package.json') != '' run: npm run build - name: Prepare release tarball run: | set -euo pipefail REL="$(date -u +%Y%m%d-%H%M%SZ)-${{ github.sha }}" echo "REL=$REL" >> $GITHUB_ENV mkdir -p release # include everything except VCS, node dev dirs, and your uploads (they live in shared/) tar \ --exclude-vcs \ --exclude='./node_modules' \ --exclude='./${{ env.UPLOADS_DIR }}' \ -czf "release/${REL}.tar.gz" \ . - name: Write SSH key run: | umask 077 printf "%s" "${SSH_KEY}" > ~/.ssh/id_ed25519 if [ -n "${SSH_KNOWN_HOSTS}" ]; then printf "%s\n" "${SSH_KNOWN_HOSTS}" > ~/.ssh/known_hosts else # fall back only if you must (less secure) echo "StrictHostKeyChecking no" >> ~/.ssh/config fi - name: Upload & activate atomically run: | set -euo pipefail REL="${{ env.REL }}" TAR="release/${REL}.tar.gz" APP="${{ env.APP_ROOT }}" SHARED="${APP}/shared" RELEASES="${APP}/releases" CUR="${APP}/current" # ensure layout exists ssh -i ~/.ssh/id_ed25519 ${SSH_USER}@${SSH_HOST} " set -e sudo install -d -o ${SSH_USER} -g ${SSH_USER} -m 755 ${RELEASES} ${SHARED} sudo install -d -o ${SSH_USER} -g ${SSH_USER} -m 755 ${SHARED}/${{ env.UPLOADS_DIR }} " # upload tar scp -i ~/.ssh/id_ed25519 ${TAR} ${SSH_USER}@${SSH_HOST}:/tmp/${REL}.tar.gz # unpack to new release, link shared, write metadata, flip symlink, reload, health check ssh -i ~/.ssh/id_ed25519 ${SSH_USER}@${SSH_HOST} ' set -euo pipefail REL="'${REL}'"; APP="'${APP}'"; SHARED="'${SHARED}'"; RELEASES="'${RELEASES}'"; CUR="'${CUR}'"; NEW="${RELEASES}/${REL}" mkdir -p "${NEW}" tar -xzf "/tmp/${REL}.tar.gz" -C "${NEW}" rm -f "/tmp/${REL}.tar.gz" # link shared paths (uploads, env/config if you keep one there) rm -rf "${NEW}/${UPLOADS_DIR:-'${{ env.UPLOADS_DIR }}'}" ln -s "${SHARED}/${UPLOADS_DIR:-'${{ env.UPLOADS_DIR }}'}" "${NEW}/${UPLOADS_DIR:-'${{ env.UPLOADS_DIR }}'}" # optional: link a shared .env if you use one if [ -f "${SHARED}/.env" ]; then ln -sf "${SHARED}/.env" "${NEW}/.env" fi # metadata printf "sha=%s\nbuilt_at=%s\n" "'${{ github.sha }}'" "$(date -u +%FT%TZ)" > "${NEW}/RELEASE" # keep previous target for rollback PREV="$(readlink -f "${CUR}" || true)" ln -sfn "${NEW}" "${CUR}" # reload services '"${{ env.SERVICE_RELOAD }}"' >/dev/null 2>&1 || true # health check (simple GET) if command -v curl >/dev/null 2>&1; then curl -fsS --max-time 5 "'"${{ env.HEALTH_URL }}"'" >/dev/null || { echo "Health check failed, rolling back..." [ -n "${PREV}" ] && ln -sfn "${PREV}" "${CUR}" && '"${{ env.SERVICE_RELOAD }}"' >/dev/null 2>&1 || true exit 1 } fi # prune old releases cd "${RELEASES}" ls -1tr | head -n -'${{ env.KEEP_N }}' | xargs -r -I{} rm -rf "{}" '