Files
LockeShor 9ef967574e
Nightly build & deploy / build-and-deploy (push) Canceled after 3s
revert to regular schedule
2026-09-20 00:30:30 -04:00

136 lines
5.8 KiB
YAML

name: Nightly build & deploy
# Rebuilds the site (refreshing the Discord embed's "today" snapshot), uploads
# the built dist/ to the CMU AFS space over SFTP, then triggers CMU's publish
# CGI so the files go live.
on:
# Midnight run. NOTE: cron here is UTC. 00:00 UTC is 8pm/7pm Eastern.
# For midnight *Eastern*, use "0 5 * * *" (EST) or "0 4 * * *" (EDT).
schedule:
- cron: "0 5 * * *"
# Allow manual runs from the Gitea Actions UI.
workflow_dispatch: {}
# Also deploy on pushes to the main branch.
push:
branches:
- main
jobs:
build-and-deploy:
# Pinned to the onsite CMU runner. Only a runner registered with the
# "cmu-onsite" label can reach linux.andrew.cmu.edu, so this guarantees the
# deploy runs from inside CMU's network. A cloud/public runner will never
# pick up this job. The runner must be registered in Docker mode so this
# label maps to a Node-capable Debian image (see README).
runs-on: cmu-onsite
env:
# Absolute base URL where the site is served. Used to build the absolute
# icon URL embedded for Discord. Set SITE_BASE_URL as a secret/variable,
# e.g. https://www.andrew.cmu.edu/user/<username>
SITE_BASE_URL: ${{ secrets.SITE_BASE_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build (injects today's Red-plan embed)
env:
# Discord fetches this absolute URL for the embed thumbnail.
SITE_ICON_URL: "${{ secrets.SITE_BASE_URL }}/apple-touch-icon.png"
run: npm run build
- name: Install lftp
# The node:20-bookworm container runs as root and has no `sudo`, so call
# apt-get directly. openssh-client provides the `ssh` binary lftp uses
# for its SFTP connect-program.
run: |
apt-get update
apt-get install -y --no-install-recommends lftp openssh-client
- name: Preflight — check SFTP host reachability
env:
SFTP_HOST: ${{ secrets.SFTP_HOST }}
SFTP_PORT: ${{ secrets.SFTP_PORT }}
run: |
set -uo pipefail
PORT="${SFTP_PORT:-22}"
if [ -z "${SFTP_HOST}" ]; then
echo "::error::SFTP_HOST secret is empty. Set it in Gitea → Settings → Actions → Secrets."
exit 1
fi
echo "Testing TCP reachability of ${SFTP_HOST}:${PORT} ..."
if ! timeout 15 bash -c "cat < /dev/null > /dev/tcp/${SFTP_HOST}/${PORT}" 2>/dev/null; then
echo "::error::Cannot open a TCP connection to ${SFTP_HOST}:${PORT}. This is why lftp reports 'max-retries exceeded'."
echo "Likely causes: wrong SFTP_HOST, wrong port, or the runner cannot reach CMU (needs VPN / on-campus network / firewall allowance)."
exit 1
fi
echo "Host is reachable on ${PORT}. Showing the SSH banner:"
# Non-fatal: just prints what the server advertises (SSH version, etc.)
timeout 10 bash -c "exec 3<>/dev/tcp/${SFTP_HOST}/${PORT}; head -c 200 <&3" 2>/dev/null || true
echo
- name: Upload dist/ via SFTP
env:
SFTP_HOST: ${{ secrets.SFTP_HOST }}
SFTP_PORT: ${{ secrets.SFTP_PORT }}
SFTP_USERNAME: ${{ secrets.SFTP_USERNAME }}
SFTP_PASSWORD: ${{ secrets.SFTP_PASSWORD }}
SFTP_REMOTE_PATH: ${{ secrets.SFTP_REMOTE_PATH }}
run: |
set -euo pipefail
PORT="${SFTP_PORT:-22}"
# lftp drives SFTP by shelling out to the system `ssh`. We pass ssh
# options explicitly so this works on a fresh runner against CMU:
# - StrictHostKeyChecking/UserKnownHostsFile: don't fail on an
# unknown host key (the runner has never seen CMU's key).
# - PubkeyAuthentication=no / PreferredAuthentications=password:
# force password auth so ssh doesn't stall trying keys/agent.
# - the KexAlgorithms/HostKeyAlgorithms "+..." lines re-enable
# algorithms some CMU hosts still use that modern OpenSSH disables
# by default (a common cause of a silent connection failure).
#
# To debug connection problems, temporarily add `debug 3;` before the
# open line and `--verbose` to the mirror — both are omitted here to
# keep the job log small (large logs slow the "Complete job" upload
# back to the off-campus Gitea).
lftp -c "
set sftp:auto-confirm yes;
set sftp:connect-program 'ssh -a -x \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o PubkeyAuthentication=no \
-o PreferredAuthentications=password \
-o KexAlgorithms=+diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1 \
-o HostKeyAlgorithms=+ssh-rsa,ssh-dss';
set net:max-retries 2;
set net:timeout 30;
set net:reconnect-interval-base 5;
open -u '${SFTP_USERNAME}','${SFTP_PASSWORD}' sftp://${SFTP_HOST}:${PORT};
mkdir -f -p '${SFTP_REMOTE_PATH}';
mirror -R --delete --only-newer ./dist/ '${SFTP_REMOTE_PATH}';
bye
"
- name: Trigger CMU publish
env:
SFTP_USERNAME: ${{ secrets.SFTP_USERNAME }}
run: |
set -euo pipefail
# FLAG=0 publishes; NAME is the AFS username (same as the SFTP user).
# --fail makes curl exit non-zero on HTTP errors so the job fails
# loudly if publishing is rejected.
curl --fail --show-error --silent \
"https://www.andrew.cmu.edu/cgi-bin/publish?FLAG=0&NAME=${SFTP_USERNAME}"
echo "Publish request sent for ${SFTP_USERNAME}."