initial commit

This commit is contained in:
2026-06-17 22:37:47 +05:30
commit bc31b2508b
100 changed files with 18050 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Triggered by the Payload afterChange webhook.
# Pulls the latest source and rebuilds the static site.
set -euo pipefail
REPO_DIR="${REPO_DIR:-/opt/softvowels}"
BRANCH="${BRANCH:-main}"
LOG_FILE="${LOG_FILE:-/var/log/softvowels-rebuild.log}"
cd "$REPO_DIR"
echo "[$(date -Is)] Rebuild start" >> "$LOG_FILE"
if ! git fetch --depth 1 origin "$BRANCH" >> "$LOG_FILE" 2>&1; then
echo "[$(date -Is)] git fetch failed" >> "$LOG_FILE"
exit 1
fi
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse "origin/$BRANCH")
if [ "$LOCAL" = "$REMOTE" ]; then
echo "[$(date -Is)] No changes (HEAD=$LOCAL). Skip." >> "$LOG_FILE"
exit 0
fi
git reset --hard "origin/$BRANCH" >> "$LOG_FILE" 2>&1
echo "[$(date -Is)] Updated to $REMOTE" >> "$LOG_FILE"
if command -v pnpm >/dev/null 2>&1; then
PKG=pnpm
elif command -v npm >/dev/null 2>&1; then
PKG="npm run --workspace=web"
else
echo "[$(date -Is)] No package manager found" >> "$LOG_FILE"
exit 2
fi
case "$PKG" in
pnpm)
pnpm install --frozen-lockfile >> "$LOG_FILE" 2>&1
pnpm --filter web build >> "$LOG_FILE" 2>&1
;;
*)
(cd apps/web && npm ci --no-audit --no-fund >> "$LOG_FILE" 2>&1)
(cd apps/web && npm run build >> "$LOG_FILE" 2>&1)
;;
esac
echo "[$(date -Is)] Rebuild done" >> "$LOG_FILE"
+16
View File
@@ -0,0 +1,16 @@
[Unit]
Description=SoftVowels Astro rebuild webhook
After=network.target
[Service]
Type=simple
Environment=WEBHOOK_PORT=4321
Environment=REBUILD_TOKEN=change-me
Environment=REPO_DIR=/opt/softvowels
Environment=LOG_FILE=/var/log/softvowels-rebuild.log
ExecStart=/usr/bin/node /opt/softvowels/scripts/webhook.js
Restart=on-failure
User=root
[Install]
WantedBy=multi-user.target
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env node
// Tiny webhook receiver: validates the shared secret, then shells out to rebuild.sh.
// Run on the homeserver (NOT inside docker) on port 4321.
//
// WEBHOOK_PORT=4321 REPO_DIR=/opt/softvowels REBUILD_TOKEN=change-me node scripts/webhook.js
//
// Or via the included systemd unit (see scripts/softvowels-webhook.service).
import { createServer } from 'node:http';
import { spawn } from 'node:child_process';
import { writeFileSync } from 'node:fs';
const PORT = Number(process.env.WEBHOOK_PORT || 4321);
const TOKEN = process.env.REBUILD_TOKEN || process.env.ASTRO_REVALIDATE_TOKEN;
const REPO = process.env.REPO_DIR || '/opt/softvowels';
const SCRIPT = process.env.REBUILD_SCRIPT || `${REPO}/scripts/rebuild.sh`;
const LOG = process.env.LOG_FILE || '/var/log/softvowels-rebuild.log';
if (!TOKEN) {
console.error('REBUILD_TOKEN is required');
process.exit(1);
}
function log(line) {
const ts = new Date().toISOString();
const msg = `[${ts}] ${line}\n`;
try {
writeFileSync(LOG, msg, { flag: 'a' });
} catch {
/* noop */
}
process.stdout.write(msg);
}
function triggerRebuild(collection, op) {
log(`Trigger rebuild for ${collection}.${op}`);
const child = spawn('bash', [SCRIPT], { detached: true, stdio: 'ignore' });
child.unref();
}
const server = createServer((req, res) => {
if (req.method !== 'POST' || req.url === '/') {
res.writeHead(405);
res.end('Method Not Allowed');
return;
}
const url = new URL(req.url, 'http://x');
const secret = url.searchParams.get('secret');
const collection = url.searchParams.get('collection') ?? 'unknown';
const op = url.searchParams.get('op') ?? 'change';
if (secret !== TOKEN) {
log(`Bad secret from ${req.socket.remoteAddress}`);
res.writeHead(401);
res.end('Unauthorized');
return;
}
triggerRebuild(collection, op);
res.writeHead(202, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, queued: true }));
});
server.listen(PORT, '127.0.0.1', () => {
log(`Webhook listening on http://127.0.0.1:${PORT}/__revalidate`);
});