name: Deploy (yggdrasil) on: push: branches: ["main"] jobs: deploy: runs-on: [ mainhost ] env: SSH_HOST: ${{ secrets.SSH_HOST }} SSH_USER: ${{ secrets.SSH_USER }} SSH_KEY_PATH: /home/gitea-runner/.ssh/id_ed25519 SSH_OPTS: >- -F /dev/null -o IdentitiesOnly=yes -o IdentityAgent=none -o PreferredAuthentications=publickey -o PubkeyAuthentication=yes -o PasswordAuthentication=no -o NumberOfPasswordPrompts=0 -o BatchMode=yes -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=20 -o StrictHostKeyChecking=no APP_ROOT: /var/www/yggdrasil KEEP_N: "5" SHARED_DIRS: "uploads:cache" HEALTH_URL: "https://yggdrasil.corpintech.net/" SERVICE_NAME: "apache2" steps: - name: Checkout (pure git, private repo) env: GIT_TOKEN: ${{ secrets.GIT_TOKEN }} run: | set -euo pipefail export GIT_TERMINAL_PROMPT=0 git init -b main git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" git -c http.extraHeader="Authorization: token ${GIT_TOKEN}" \ fetch --no-tags --depth=1 origin "${GITHUB_SHA}" git checkout -q "${GITHUB_SHA}" - name: SSH smoke test run: | set -euo pipefail ssh $SSH_OPTS -i "$SSH_KEY_PATH" "${SSH_USER}@${SSH_HOST}" true - name: Deploy atomically run: | set -euo pipefail REL="$(date -u +%Y%m%d-%H%M%SZ)-${GITHUB_SHA}" TAR_LOCAL="release/${REL}.tar.gz" mkdir -p release IFS=':' read -r -a SHARED_DIR_ARR <<< "${SHARED_DIRS}" EXCLUDES=(--exclude-vcs --exclude='./node_modules' --exclude='./release') for d in "${SHARED_DIR_ARR[@]}"; do EXCLUDES+=( "--exclude=./${d}" ) done tar -czf "${TAR_LOCAL}" "${EXCLUDES[@]}" . ssh $SSH_OPTS -i "$SSH_KEY_PATH" "${SSH_USER}@${SSH_HOST}" \ "set -e; install -d -m 2755 '${APP_ROOT}' '${APP_ROOT}/releases' '${APP_ROOT}/shared' '${APP_ROOT}/logs';" for d in "${SHARED_DIR_ARR[@]}"; do ssh $SSH_OPTS -i "$SSH_KEY_PATH" "${SSH_USER}@${SSH_HOST}" \ "install -d -m 2755 '${APP_ROOT}/shared/${d}'" done scp -O $SSH_OPTS -i "$SSH_KEY_PATH" "${TAR_LOCAL}" "${SSH_USER}@${SSH_HOST}:/tmp/${REL}.tar.gz" ssh $SSH_OPTS -i "$SSH_KEY_PATH" "${SSH_USER}@${SSH_HOST}" bash -s -- \ "${APP_ROOT}" \ "${REL}" \ "${KEEP_N}" \ "${SHARED_DIRS}" \ "${HEALTH_URL}" \ "${SERVICE_NAME}" \ "${GITHUB_SHA}" <<'REMOTE' set -euo pipefail APP_ROOT="$1" REL="$2" KEEP_N="$3" SHARED_DIRS="$4" HEALTH_URL="$5" SERVICE_NAME="$6" GITSHA="$7" IFS=':' read -r -a SHARED_DIR_ARR <<< "${SHARED_DIRS}" RELEASES="${APP_ROOT}/releases" SHARED="${APP_ROOT}/shared" CUR="${APP_ROOT}/current" NEW="${RELEASES}/${REL}" TAR="/tmp/${REL}.tar.gz" echo "--> Extracting ${REL}" mkdir -p "${NEW}" tar -xzf "${TAR}" -C "${NEW}" rm -f "${TAR}" echo "--> Linking shared dirs" for d in "${SHARED_DIR_ARR[@]}"; do echo " ${d}" rm -rf "${NEW:?}/${d}" ln -s "${SHARED}/${d}" "${NEW}/${d}" done if [ -f "${SHARED}/.env" ]; then ln -sf "${SHARED}/.env" "${NEW}/.env" fi printf "sha=%s\nbuilt_at=%s\n" "${GITSHA}" "$(date -u +%FT%TZ)" > "${NEW}/RELEASE" PREV="$(readlink -f "${CUR}" 2>/dev/null || true)" echo "--> Swapping symlink" ln -sfn "${NEW}" "${CUR}" echo "--> Restarting Apache" sudo /usr/bin/systemctl restart "${SERVICE_NAME}" if command -v curl >/dev/null 2>&1; then echo "--> Health check ${HEALTH_URL}" if ! curl -fsS --max-time 10 "${HEALTH_URL}" >/dev/null; then echo "Health check failed, rolling back..." if [ -n "${PREV}" ] && [ -e "${PREV}" ]; then ln -sfn "${PREV}" "${CUR}" sudo /usr/bin/systemctl restart "${SERVICE_NAME}" fi exit 1 fi fi echo "--> Cleaning old releases" CUR_REAL="$(readlink -f "${CUR}" 2>/dev/null || true)" cd "${RELEASES}" i=0 for name in $(ls -1t); do path="${RELEASES}/${name}" if [ "${path}" = "${CUR_REAL}" ] || [ "${path}" = "${PREV}" ] || [ "${path}" = "${NEW}" ]; then continue fi i=$((i+1)) if [ "${i}" -gt "${KEEP_N}" ]; then rm -rf -- "${path}" fi done echo "--> Deploy complete" REMOTE