deploy/sync.sh: daily Wikisource -> divan-data -> PostgreSQL sync for the server

- updates a divan-data checkout, fetches new/edited works (incremental), rebuilds, upserts
  into Postgres; optional commit/push of data (DIVAN_DATA_PUSH=1); flock against overlap
- import prints progress only on a terminal
Validated locally: full run 1m46s, 382 poets / 11,087 poems imported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Anas Rashid 2026-10-08 21:00:15 +02:00
parent 17200fc9aa
commit 13610569cc
3 changed files with 51 additions and 1 deletions

View File

@ -33,6 +33,17 @@ Settings: `DATABASE_URL` (API, default `postgres://divan:divan_local@localhost:5
The import upserts, so re-running it after a divan-data sync applies the changes. The import upserts, so re-running it after a divan-data sync applies the changes.
## Daily content sync (server)
`deploy/sync.sh` keeps a server current: it updates a divan-data checkout, fetches new and edited works from Wikisource (incremental, about a minute), rebuilds the export and upserts it into PostgreSQL. The site shows new content immediately. Runs are locked so they never overlap.
```sh
# crontab -e (daily at 03:15; DATABASE_URL as for the API)
15 3 * * * DATABASE_URL=postgres://divan:...@localhost:5432/divan /opt/divan/deploy/sync.sh >> /var/log/divan-sync.log 2>&1
```
Settings: `DIVAN_DATA_DIR` (default `/opt/divan-data`, cloned on first run), `DIVAN_APP_DIR` (default: this repo), `DIVAN_DATA_PUSH=1` to also commit and push data changes (needs git push access). Needs git, python3 and Node 24+.
## API ## API
| Endpoint | Returns | | Endpoint | Returns |

View File

@ -80,7 +80,7 @@ for (const entry of manifest.Poets) {
} finally { } finally {
client.release(); client.release();
} }
process.stdout.write(`\r${poems} poems`); if (process.stdout.isTTY) process.stdout.write(`\r${poems} poems`); // progress only when interactive (cron logs stay clean)
} }
await pool.query(await readFile(new URL('../../db/featured.sql', import.meta.url), 'utf8')); await pool.query(await readFile(new URL('../../db/featured.sql', import.meta.url), 'utf8'));
console.log(`\nimported ${manifest.Poets.length} poets, ${poems} poems`); console.log(`\nimported ${manifest.Poets.length} poets, ${poems} poems`);

39
deploy/sync.sh Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Daily content sync for a Divan server: Wikisource -> divan-data -> PostgreSQL.
#
# 1. update the divan-data checkout and fetch new/edited works from Wikisource (incremental)
# 2. rebuild its search index and site export
# 3. load the export into PostgreSQL (upserts; the site reads it live)
# 4. optionally commit + push the refreshed data (DIVAN_DATA_PUSH=1, needs git push access)
#
# Schedule with cron, e.g. daily at 03:15:
# 15 3 * * * /opt/divan/deploy/sync.sh >> /var/log/divan-sync.log 2>&1
#
# Settings (env): DIVAN_DATA_DIR (default /opt/divan-data), DIVAN_APP_DIR (default: this repo),
# DATABASE_URL (Postgres, as for the API), DIVAN_DATA_PUSH=1 to push data changes.
# Needs: git, python3 (stdlib only), node 24+.
set -euo pipefail
APP_DIR=${DIVAN_APP_DIR:-$(cd "$(dirname "$0")/.." && pwd)}
DATA_DIR=${DIVAN_DATA_DIR:-/opt/divan-data}
LOCK=${TMPDIR:-/tmp}/divan-sync.lock
# one run at a time (a slow Wikisource day must not overlap the next run)
exec 9>"$LOCK"
if command -v flock >/dev/null && ! flock -n 9; then echo "$(date -u +%FT%TZ) sync already running"; exit 0; fi
echo "== $(date -u +%FT%TZ) divan sync"
[ -d "$DATA_DIR/.git" ] || git clone -q https://github.com/anas-rashid/divan-data.git "$DATA_DIR"
cd "$DATA_DIR"
git pull -q --ff-only
if [ "${DIVAN_DATA_PUSH:-0}" = 1 ]; then
./update.sh # fetch + rebuild, commit and push if the data changed
else
python3 wikisource.py && python3 build_index.py && python3 export_divan.py
fi
cd "$APP_DIR/api"
[ -d node_modules ] || npm ci --omit=dev --silent
node src/import.ts "$DATA_DIR"
echo "== $(date -u +%FT%TZ) done"