name: Sites on: push: branches: - 'release/**' tags: - 'v*' paths-ignore: - '**.md' workflow_dispatch: env: 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 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - name: Write .npmrc file run: | touch client/.npmrc echo '${{ secrets.NPMRC}}' > client/.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 - 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: 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: | curl -L https://github.com/sasjs/server/releases/latest/download/linux.zip > linux.zip unzip -q linux.zip touch .env echo RUN_TIMES=js >> .env echo NODE_PATH=node >> .env # The server refuses to run as root unless RUN_AS names an account echo RUN_AS=root >> .env nohup ./api-linux > 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