release.yaml (all three jobs), sites.yaml and release-assets.yaml run the same cold production build that the review jobs do, so they get the same pre-install check. The header now says what the check rules out (an undersized box) and what it does not (two jobs sharing one box), so the MemAvailable warning is not read as a gate.
213 lines
9.1 KiB
YAML
213 lines
9.1 KiB
YAML
name: Sites
|
|
on:
|
|
push:
|
|
branches:
|
|
- 'release/**'
|
|
tags:
|
|
- 'v*'
|
|
paths-ignore:
|
|
- '**.md'
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
# the server the mocked services and app tests run against - bump deliberately
|
|
SASJS_SERVER_VERSION: '3.6.2'
|
|
NODE_VERSION: '24.15.0'
|
|
APP_LOC: /Public/app/dc
|
|
SURFER_HOST: https://demo.datacontroller.io
|
|
# A release branch publishes the staging site (a named site on the instance,
|
|
# served at its alias domain); a release tag publishes the demo, which is the
|
|
# default site and so needs no site name.
|
|
STAGING_SITE: staging
|
|
STAGING_DOMAIN: staging.datacontroller.io
|
|
|
|
# One deploy per target site: a newer push supersedes the one in flight, and two
|
|
# release branches (which share the staging site) cannot interleave their swaps.
|
|
# The demo and staging deploys are independent, so they do not queue.
|
|
concurrency:
|
|
group: surfer-${{ startsWith(gitea.ref, 'refs/tags/') && 'demo' || 'staging' }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
# The tag is what the client's build stamp describes (src/version.ts
|
|
# runs git-describe), so the demo build needs the tags in the clone.
|
|
fetch-depth: 0
|
|
- name: Check runner size
|
|
# The demo build is the same cold production build; an undersized
|
|
# runner fails inside it - see the script.
|
|
run: bash .gitea/scripts/check-runner-memory.sh
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Write .npmrc file
|
|
run: |
|
|
# the secret arrives through the environment, not interpolated into
|
|
# this script: a quote or $( in the value would otherwise break the
|
|
# step or execute
|
|
printf '%s\n' "$NPMRC" > "$RUNNER_TEMP/npmrc"
|
|
echo "NPM_CONFIG_USERCONFIG=$RUNNER_TEMP/npmrc" >> "$GITHUB_ENV"
|
|
env:
|
|
NPMRC: ${{ secrets.NPMRC}}
|
|
|
|
- name: Install client dependencies
|
|
run: |
|
|
cd client
|
|
# Decrypt and Install sheet
|
|
echo ${{ secrets.SHEET_PWD }} | gpg --batch --yes --passphrase-fd 0 ./libraries/sheet-crypto.tgz.gpg
|
|
npm ci
|
|
# client install scripts are off (client/.npmrc); the generator that
|
|
# creates src/environments/version.ts runs explicitly
|
|
npm run postinstall
|
|
|
|
- name: Build frontend
|
|
# The site serves this build. `sasjs build` rewrites its index.html to
|
|
# point the adapter at the in-browser backend.
|
|
run: |
|
|
cd client
|
|
npm run postinstall
|
|
npm run build
|
|
|
|
- name: Install the SASjs CLI
|
|
# Pinned in sas/package.json - the browser build needs CLI >= 6.1.0
|
|
run: |
|
|
cd sas
|
|
npm ci
|
|
|
|
- name: Cache the npm downloads
|
|
# The server is installed from npm at a pinned version, so the tarball is
|
|
# immutable and the key never needs invalidating by hand: a hit skips
|
|
# the 10.7MB download, a miss costs exactly what the zip cost before.
|
|
# continue-on-error, because a runner without cache support should still
|
|
# run the pipeline - this step only ever saves time.
|
|
continue-on-error: true
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.npm
|
|
key: npm-sasjs-server-${{ env.SASJS_SERVER_VERSION }}
|
|
|
|
- name: Start SASjs server
|
|
# Needed only to run makedata: the drive the site ships with is captured
|
|
# from a real server (see the snapshot step below).
|
|
run: |
|
|
touch .env
|
|
echo RUN_TIMES=js >> .env
|
|
echo NODE_PATH=node >> .env
|
|
echo HTTP_PORT=5000 >> .env
|
|
# The server refuses to run as root unless RUN_AS names an account
|
|
echo RUN_AS=root >> .env
|
|
nohup npx --yes @sasjs/server@${{ env.SASJS_SERVER_VERSION }} > server.log 2>&1 &
|
|
for i in $(seq 1 60); do
|
|
if curl -sf -o /dev/null http://localhost:5000/; then
|
|
echo "SASjs server answered on attempt ${i}"
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "SASjs server did not come up within 120s"
|
|
cat server.log
|
|
exit 1
|
|
|
|
- name: Deploy the mocked services and seed the database
|
|
run: |
|
|
cd sas/mocks/sasjs
|
|
# Start from a clean appLoc so the snapshot below is this build alone,
|
|
# not a merge with whatever a previous run left on the drive.
|
|
curl -sS -X DELETE "http://localhost:5000/SASjsApi/drive/folder/?_folderPath=$APP_LOC"
|
|
../../node_modules/.bin/sasjs cbd -t server-ci
|
|
# makedata replies with HTML (it is normally called as a URL redirect),
|
|
# which the CLI reports as "invalid Json string" - that exit is
|
|
# expected. A real failure must not be published as an empty site, so
|
|
# the drive is checked before the snapshot is taken.
|
|
../../node_modules/.bin/sasjs request services/admin/makedata -t server-ci -d deploy/makedata.json -o ./makedata_out.json || true
|
|
|
|
DATA_DIR="../../../sasjs_root/drive/files$APP_LOC/data"
|
|
SEEDED=$(find "$DATA_DIR" -name '*.json' 2>/dev/null | wc -l)
|
|
echo "makedata seeded $SEEDED table(s) under $DATA_DIR"
|
|
if [ -z "$(find "$DATA_DIR" -name 'mpe_tables.json' 2>/dev/null | head -1)" ] || [ "$SEEDED" -lt 10 ]; then
|
|
echo "makedata did not seed the drive - refusing to publish a data-less site"
|
|
find ../../../sasjs_root/drive/files -maxdepth 3 2>/dev/null | head -20
|
|
exit 1
|
|
fi
|
|
|
|
- name: Build the browser-hosted site
|
|
run: |
|
|
# The published sites ship a working licence so they are usable beyond
|
|
# the free tier. The pair is committed in sas/mocks/mock-licence.json
|
|
# rather than minted here, so every deploy of both sites carries the
|
|
# same key; the CI estate stays unlicensed because that file is outside
|
|
# the deployed sync folder, so the specs still exercise the free tier.
|
|
# The pair must round trip through the same WebCrypto the app uses,
|
|
# because a committed pair can drift (one half regenerated, a value
|
|
# truncated on copy) and nothing else would notice before publishing.
|
|
# Identical values in the two fields is the HTTP-format marker the app
|
|
# refuses in a secure context, so refuse to publish one.
|
|
node -e "
|
|
const d = require('./sas/mocks/mock-licence.json')
|
|
const b = (s) => Buffer.from(String(s || ''), 'base64')
|
|
if (!d.licenceKey || !d.activationKey || d.licenceKey === d.activationKey) {
|
|
console.error('sas/mocks/mock-licence.json must hold an HTTPS-format key pair')
|
|
process.exit(1)
|
|
}
|
|
crypto.subtle
|
|
.importKey('pkcs8', b(d.activationKey), { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['decrypt'])
|
|
.then((key) => crypto.subtle.decrypt({ name: 'RSA-OAEP', hash: 'SHA-256' }, key, b(d.licenceKey)))
|
|
.then((plain) => {
|
|
const data = JSON.parse(new TextDecoder().decode(plain))
|
|
console.log('licence pair round trips: site', data.site_id, 'until', data.valid_until)
|
|
})
|
|
.catch((e) => {
|
|
console.error('sas/mocks/mock-licence.json does not decrypt:', e.message || e)
|
|
process.exit(1)
|
|
})
|
|
"
|
|
LICENCE_DIR="sasjs_root/drive/files$APP_LOC/mock-storage"
|
|
mkdir -p "$LICENCE_DIR"
|
|
cp sas/mocks/mock-licence.json "$LICENCE_DIR/licence.json"
|
|
|
|
# DC's data files exist only after makedata has run, so the drive is
|
|
# captured first and shipped as the site's starting drive. services/web9
|
|
# is the streamed frontend, which is already the site itself.
|
|
node sas/mocks/snapshot.js sasjs_root/drive "$APP_LOC" sas/mocks/sasjsbuild/mock-snapshot.json services/web9
|
|
cd sas/mocks/sasjs
|
|
../../node_modules/.bin/sasjs b -t server-ci
|
|
|
|
- name: Publish to Surfer
|
|
env:
|
|
SURFER_USER: allan
|
|
SURFER_PASSWORD: ${{ secrets.DEMO_DATACONTROLLER_IO }}
|
|
REF: ${{ gitea.ref }}
|
|
MESSAGE: Build from ${{ gitea.ref_name }} @ ${{ gitea.sha }}
|
|
run: |
|
|
# `deploy` uploads the folder as an archive and swaps the site in one
|
|
# step, so a failed upload leaves the current site serving.
|
|
if [ -z "$REF" ]; then
|
|
echo "REF is empty - cannot tell a branch push from a tag push"
|
|
exit 1
|
|
fi
|
|
|
|
SITE_ARGS=()
|
|
case "$REF" in
|
|
refs/tags/*) ;;
|
|
*) SITE_ARGS=(--site-name "$STAGING_SITE" --domain "$STAGING_DOMAIN") ;;
|
|
esac
|
|
|
|
npx --yes @cloudron/surfer@7.2.0 deploy \
|
|
--server "$SURFER_HOST" \
|
|
--username "$SURFER_USER" \
|
|
--password "$SURFER_PASSWORD" \
|
|
--message "$MESSAGE" \
|
|
"${SITE_ARGS[@]}" \
|
|
sas/mocks/sasjsbuild/mock
|
|
|
|
- name: Remove registry config
|
|
# written earlier in this job; nothing deletes it on the way out
|
|
if: always()
|
|
run: rm -f "$RUNNER_TEMP/npmrc"
|