1#!/usr/bin/env bash
2set -euo pipefail
3
4instance=${1:?usage: tools/import-dawarich.sh dawarich-preview-XXXXXXXX}
5[[ $instance =~ ^dawarich-preview-[0-9a-f]{8}$ ]] || {
6 echo 'Expected a Dawarich preview ID' >&2
7 exit 1
8}
9
10source_host=${STUDIO_MIGRATION_SOURCE:-zenith}
11target_host=${STUDIO_DEPLOY_HOST:-root@127.0.0.1}
12target_port=${STUDIO_DEPLOY_PORT:-2222}
13remote=(ssh -p "$target_port" "$target_host")
14
15root=/srv/staging/$instance
16source_id=$("${remote[@]}" "python3 -c 'import json; print(json.load(open(\"/var/lib/studio/stages/$instance.json\"))[\"sourceId\"])'")
17[[ $source_id == dawarich ]] || { echo 'Preview belongs to another service' >&2; exit 1; }
18snapshot=$(ssh "$source_host" '/usr/sbin/zfs list -H -t snapshot -o name -s creation -r storage1/apps | tail -n 1')
19[[ $snapshot == storage1/apps@* ]] || { echo 'No Zenith app snapshot is available' >&2; exit 1; }
20source_root=/mnt/storage1/apps/.zfs/snapshot/${snapshot#*@}/dawarich_storage
21
22dataset=$("${remote[@]}" "findmnt -n -o SOURCE --mountpoint $root")
23[[ $dataset == */staging/$instance ]] || { echo 'Preview dataset is not mounted' >&2; exit 1; }
24
25source_secret=$(ssh "$source_host" "sudo -n docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' dawarich-app" |
26 python3 -c 'import hashlib,sys; values=[line.split("=",1)[1] for line in sys.stdin.read().splitlines() if line.startswith("SECRET_KEY_BASE=")]; assert len(values)==1; print(hashlib.sha256(values[0].encode()).hexdigest())')
27own=$("${remote[@]}" "NOMAD_TOKEN=\$(cat /var/lib/studio/nomad.token) nomad var get -out json nomad/jobs/$instance" |
28 python3 -c 'import hashlib,json,sys; print(hashlib.sha256(json.load(sys.stdin)["Items"]["secret_key_base"].encode()).hexdigest())')
29[[ $source_secret == "$own" ]] || { echo 'Dawarich secret differs from Zenith; import the original before restoring data' >&2; exit 1; }
30
31db_json=$("${remote[@]}" "NOMAD_TOKEN=\$(cat /var/lib/studio/nomad.token) nomad var get -out json nomad/jobs/$instance/inputs/database")
32read -r database owner < <(python3 -c 'import json,sys; d=json.load(sys.stdin)["Items"]; print(d["name"], d["username"])' <<<"$db_json")
33[[ $database =~ ^dawarich_s_[0-9a-f]{8}$ && $owner == svc_$database ]] || { echo 'Unexpected preview database' >&2; exit 1; }
34
35allocation=$("${remote[@]}" 'NOMAD_TOKEN=$(cat /var/lib/studio/nomad.token) nomad job allocs -json postgres' |
36 python3 -c 'import json,sys; ids=[a["ID"] for a in json.load(sys.stdin) if a["ClientStatus"]=="running" and a["DesiredStatus"]=="run"]; assert len(ids)==1; print(ids[0])')
37container=app-$allocation
38podman='podman --url unix:///run/podman/podman.sock'
39scratch=$(mktemp -d)
40trap 'rm -rf "$scratch"' EXIT
41rsync -a "$source_host:$source_root/" "$scratch/"
42drift=$(rsync -rlnc --delete --out-format='%n' "$source_host:$source_root/" "$scratch/")
43[[ -z $drift ]] || { echo 'Zenith storage changed during copy; retry the import' >&2; exit 1; }
44source_counts=$(ssh "$source_host" 'sudo -n docker exec postgres psql -U postgres -d dawarich -At -c "SELECT (SELECT count(*) FROM points), (SELECT count(*) FROM users)"')
45
46"${remote[@]}" "NOMAD_TOKEN=\$(cat /var/lib/studio/nomad.token) nomad job stop -yes $instance"
47for _ in {1..30}; do
48 running=$("${remote[@]}" "NOMAD_TOKEN=\$(cat /var/lib/studio/nomad.token) nomad job allocs -json $instance" |
49 python3 -c 'import json,sys; print(sum(a["ClientStatus"]=="running" for a in json.load(sys.stdin)))')
50 [[ $running == 0 ]] && break
51 sleep 2
52done
53[[ $running == 0 ]] || { echo 'Dawarich allocation is still running' >&2; exit 1; }
54
55backup_snapshot=$dataset@before-dawarich-import-$(date +%s)-$$
56"${remote[@]}" "zfs snapshot $backup_snapshot"
57rsync -a --delete -e "ssh -p $target_port" "$scratch/" "$target_host:$root/var/app/storage/"
58uid=$("${remote[@]}" "python3 -c 'import json; print(json.load(open(\"/var/lib/studio/identities.json\"))[\"dawarich\"])'")
59"${remote[@]}" "chown -R $uid:$uid $root/var/app/storage"
60drift=$(rsync -rlnc --delete --out-format='%n' -e "ssh -p $target_port" "$scratch/" "$target_host:$root/var/app/storage/")
61[[ -z $drift ]] || { echo "Storage copy differs from Zenith; restore $backup_snapshot" >&2; exit 1; }
62
63backup=/var/lib/studio/$instance-before-import.dump
64"${remote[@]}" "umask 077; $podman exec $container pg_dump -U postgres -Fc $database > $backup; test -s $backup"
65"${remote[@]}" "$podman exec $container psql -U postgres -d $database -v ON_ERROR_STOP=1 -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public AUTHORIZATION $owner; CREATE EXTENSION postgis;' >/dev/null"
66ssh "$source_host" 'sudo -n docker exec postgres pg_dump -U postgres -Fc --no-owner --no-acl --exclude-extension=postgis dawarich' |
67 "${remote[@]}" "$podman exec -i $container pg_restore -U postgres -d $database --no-owner --no-acl --role=$owner"
68target_counts=$("${remote[@]}" "$podman exec $container psql -U postgres -d $database -At -c 'SELECT (SELECT count(*) FROM points), (SELECT count(*) FROM users)'")
69[[ $source_counts == "$target_counts" ]] || { echo "Database counts differ from Zenith; restore $backup" >&2; exit 1; }
70
71python3 "$(dirname "$0")/deploy.py" stage dawarich
72"${remote[@]}" "zfs destroy $backup_snapshot"
73echo "Imported $instance from $snapshot. Previous database: $backup"