1#!/usr/bin/env bash
2set -euo pipefail
3
4: "${STUDIO_DEPLOY_HOST:?Set STUDIO_DEPLOY_HOST to the production target}"
5source_host=${STUDIO_MIGRATION_SOURCE:-zenith}
6target_host=$STUDIO_DEPLOY_HOST
7target_port=${STUDIO_DEPLOY_PORT:-22}
8remote=(ssh -p "$target_port" "$target_host")
9target_dir=/srv/prod/clover-source-of-truth/data
10tree_hash="$(dirname "$0")/tree-hash.py"
11
12if [[ -n ${STUDIO_MIGRATION_SNAPSHOT:-} ]]; then
13 [[ $STUDIO_MIGRATION_SNAPSHOT =~ ^[A-Za-z0-9_-]+$ ]] || { echo 'Invalid source snapshot name' >&2; exit 1; }
14 source=("${remote[@]}")
15 source_dir="/srv/clover/.zfs/snapshot/$STUDIO_MIGRATION_SNAPSHOT/Documents/Config/paper clover"
16 "${source[@]}" "test -d '$source_dir'"
17else
18 source=(ssh "$source_host")
19 source_dir='/mnt/storage1/clover/Documents/Config/paper clover'
20 [[ $("${source[@]}" "sudo -n docker inspect -f '{{.State.Running}}' clover-source-of-truth") == false ]] || {
21 echo 'Stop Zenith Source of Truth before importing production data' >&2
22 exit 1
23 }
24fi
25"${remote[@]}" "set -e; test \$(findmnt -n -o FSTYPE --mountpoint /srv/clover) = zfs; test -d /srv/clover/Published; ! findmnt -n -R '$target_dir' >/dev/null"
26dataset=$("${remote[@]}" 'findmnt -n -o SOURCE --mountpoint /srv/prod/clover-source-of-truth')
27[[ $dataset == */prod/clover-source-of-truth ]] || { echo 'Production service dataset is not mounted' >&2; exit 1; }
28job_response=$("${remote[@]}" 'curl -s -w "\n%{http_code}" -H "X-Nomad-Token: $(cat /var/lib/studio/nomad.token)" http://127.0.0.1:4646/v1/job/clover-source-of-truth')
29job_status=${job_response##*$'\n'}
30if [[ $job_status == 200 ]]; then
31 stopped=$(python3 -c 'import json,sys; print(str(json.load(sys.stdin)["Stop"]).lower())' <<<"${job_response%$'\n'*}")
32 [[ $stopped == true ]] || { echo 'Stop Source of Truth on the home server before importing production data' >&2; exit 1; }
33 for _ in {1..30}; do
34 running=$("${remote[@]}" 'NOMAD_TOKEN=$(cat /var/lib/studio/nomad.token) nomad job allocs -json clover-source-of-truth' |
35 python3 -c 'import json,sys; print(sum(a["ClientStatus"] == "running" for a in json.load(sys.stdin)))')
36 [[ $running == 0 ]] && break
37 sleep 2
38 done
39 [[ $running == 0 ]] || { echo 'The home server allocation did not stop' >&2; exit 1; }
40elif [[ $job_status != 404 ]]; then
41 echo "Could not verify the home server job: $job_status" >&2
42 exit 1
43fi
44
45source_manifest=$("${source[@]}" "python3 - '$source_dir'" < "$tree_hash")
46source_bytes=$(python3 -c 'import json,sys; print(json.load(sys.stdin)["bytes"])' <<<"$source_manifest")
47available=$("${remote[@]}" "df -B1 --output=avail /srv/prod/clover-source-of-truth | tail -n 1")
48(( available > source_bytes )) || { echo 'Production dataset has too little free space for the source tree' >&2; exit 1; }
49
50snapshot=$dataset@before-source-data-import-$(date +%s)-$$
51"${remote[@]}" "set -e; zfs snapshot $snapshot; rm -rf -- '$target_dir'; mkdir -p -- '$target_dir'"
52if [[ -n ${STUDIO_MIGRATION_SNAPSHOT:-} ]]; then
53 "${remote[@]}" "bash -c \"set -euo pipefail; tar -C '$source_dir' -cf - . | tar -C '$target_dir' -xf -\""
54else
55 "${source[@]}" "tar -C '$source_dir' -cf - ." |
56 "${remote[@]}" "tar -C '$target_dir' -xf -"
57fi
58target_manifest=$("${remote[@]}" "python3 - '$target_dir'" < "$tree_hash")
59[[ $source_manifest == "$target_manifest" ]] || { echo "Source and target trees differ; restore $snapshot" >&2; exit 1; }
60uid=$("${remote[@]}" "python3 -c 'import json; print(json.load(open(\"/var/lib/studio/identities.json\"))[\"clover-source-of-truth\"])'")
61"${remote[@]}" "set -e; chown -R $uid:$uid '$target_dir'; python3 -c 'import sqlite3; db=sqlite3.connect(\"file:$target_dir/cache.sqlite?mode=ro\", uri=True); assert db.execute(\"PRAGMA integrity_check\").fetchone()[0] == \"ok\"'"
62if [[ -z ${STUDIO_MIGRATION_SNAPSHOT:-} ]]; then
63 [[ $("${source[@]}" "sudo -n docker inspect -f '{{.State.Running}}' clover-source-of-truth") == false ]] || {
64 echo 'Zenith Source of Truth restarted during import; leave the home server stopped' >&2
65 exit 1
66 }
67fi
68echo "Imported ${source_bytes} bytes without local staging. Previous dataset state: $snapshot"