Add Gitea Actions workflow to deploy /BLB content to WebDAV on push to main
Some checks failed
Deploy to WebDAV (/BLB) / deploy (push) Failing after 1m1s
Some checks failed
Deploy to WebDAV (/BLB) / deploy (push) Failing after 1m1s
This commit is contained in:
parent
e5d148e2a4
commit
3e72c29f74
119
.gitea/workflows/deploy.yml
Normal file
119
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
name: Deploy to WebDAV (/BLB)
|
||||||
|
|
||||||
|
# Mirror the site content from the repo to the WebDAV directory served at /BLB/.
|
||||||
|
# Runs on every push/merge to the default branch (main). Only the files that
|
||||||
|
# actually changed in the pushed range are uploaded (efficient for a ~32k-file
|
||||||
|
# static tree), using DELETE-then-PUT so the serving layer's cache is busted.
|
||||||
|
"on":
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy changed files to WebDAV
|
||||||
|
env:
|
||||||
|
WEBDAV_USER: ${{ secrets.WEBDAV_USER }}
|
||||||
|
WEBDAV_PASS: ${{ secrets.WEBDAV_PASS }}
|
||||||
|
WEBDAV_BASE: https://files.thecookiejar.me/www/static/BLB
|
||||||
|
GITHUB_BEFORE: ${{ github.event.before }}
|
||||||
|
GITHUB_SHA: ${{ github.sha }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$WEBDAV_USER" ] || [ -z "$WEBDAV_PASS" ]; then
|
||||||
|
echo "::error::WEBDAV_USER / WEBDAV_PASS secrets are not set on the exlim org."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
node - <<'NODE'
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const USER = process.env.WEBDAV_USER;
|
||||||
|
const PASS = process.env.WEBDAV_PASS;
|
||||||
|
const BASE = process.env.WEBDAV_BASE;
|
||||||
|
const BEFORE = process.env.GITHUB_BEFORE || '';
|
||||||
|
const SHA = process.env.GITHUB_SHA || '';
|
||||||
|
const vacant = '0'.repeat(40);
|
||||||
|
|
||||||
|
// Simple content-type map (node:20 image has no `mime` package).
|
||||||
|
const MIME = {
|
||||||
|
html: 'text/html', htm: 'text/html', js: 'application/javascript',
|
||||||
|
css: 'text/css', json: 'application/json', txt: 'text/plain',
|
||||||
|
md: 'text/markdown', xml: 'application/xml', gif: 'image/gif',
|
||||||
|
jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
|
||||||
|
ico: 'image/x-icon', svg: 'image/svg+xml', pdf: 'application/pdf',
|
||||||
|
mid: 'audio/midi', mp3: 'audio/mpeg', wav: 'audio/x-wav', rm: 'application/vnd.rn-realmedia',
|
||||||
|
swf: 'application/x-shockwave-flash', jsx: 'text/javascript', mjs: 'application/javascript',
|
||||||
|
};
|
||||||
|
const typeFor = (f) => {
|
||||||
|
const e = f.split('.').pop().toLowerCase();
|
||||||
|
return MIME[e] || 'application/octet-stream';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Which files to deploy? On a normal push, only those changed in the
|
||||||
|
// pushed range. On the very first deploy (empty before), every file.
|
||||||
|
let files;
|
||||||
|
if (BEFORE && BEFORE !== vacant) {
|
||||||
|
files = execSync(`git diff-tree -r --name-only -z "${BEFORE}" "${SHA}"`, { encoding: 'utf8' })
|
||||||
|
.split('\0').filter(Boolean);
|
||||||
|
} else {
|
||||||
|
files = execSync('git ls-files -z', { encoding: 'utf8' })
|
||||||
|
.split('\0').filter(Boolean);
|
||||||
|
}
|
||||||
|
// Never treat CI/config files as site content.
|
||||||
|
files = files.filter(f => !f.startsWith('.gitea') && !f.startsWith('.github'));
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
console.log('No site files changed; nothing to deploy.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
console.log(`Deploying ${files.length} changed file(s):`);
|
||||||
|
for (const f of files.slice(0, 20)) console.log(' ' + f);
|
||||||
|
if (files.length > 20) console.log(` ... and ${files.length - 20} more`);
|
||||||
|
|
||||||
|
const auth = 'Basic ' + Buffer.from(`${USER}:${PASS}`).toString('base64');
|
||||||
|
const enc = (p) => p.split('/').map(encodeURIComponent).join('/');
|
||||||
|
const urlFor = (f) => BASE + '/' + enc(f);
|
||||||
|
|
||||||
|
async function delThenPut(f) {
|
||||||
|
const url = urlFor(f);
|
||||||
|
const body = fs.readFileSync(f);
|
||||||
|
// DELETE first: serving layer caches PUTs, so overwrite without
|
||||||
|
// delete silently keeps serving the OLD copy.
|
||||||
|
try {
|
||||||
|
await fetch(url, { method: 'DELETE', headers: { Authorization: auth } });
|
||||||
|
} catch (e) { /* file may not exist yet -- fine */ }
|
||||||
|
const r = await fetch(url, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { Authorization: auth, 'Content-Type': typeFor(f) },
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
if (!r.ok) throw new Error(`PUT ${url} -> ${r.status} ${r.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small worker pool so large first deploys don't crush the server.
|
||||||
|
const N = 8;
|
||||||
|
let i = 0;
|
||||||
|
const failures = [];
|
||||||
|
const worker = async () => {
|
||||||
|
while (i < files.length) {
|
||||||
|
const f = files[i++];
|
||||||
|
try {
|
||||||
|
await delThenPut(f);
|
||||||
|
} catch (e) {
|
||||||
|
failures.push(`${f}: ${e.message}`);
|
||||||
|
console.log(`::warning::failed ${f} -- ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await Promise.all(Array.from({ length: N }, worker));
|
||||||
|
if (failures.length) {
|
||||||
|
console.log(`::error::${failures.length}/${files.length} files failed to deploy`);
|
||||||
|
for (const f of failures.slice(0, 20)) console.log(' ' + f);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Deployed ${files.length} file(s) successfully.`);
|
||||||
|
NODE
|
||||||
Loading…
x
Reference in New Issue
Block a user