publish-public (2951B)
1 #!/bin/sh 2 # Sync ~/repos/www-public/ to Bunny Storage via the HTTP API (HTTPS). 3 # Requires: 4 # - $BUNNY_ZONE in ~/repos/config.env (set via push from .env) 5 # - ~/repos/bunny.key (chmod 600) containing the storage zone access key 6 # 7 # Tracks previously-uploaded files via a local sha256 manifest, so each run 8 # only PUTs files whose content changed. Does NOT delete remote files that 9 # are absent locally; clean those up via the Bunny dashboard if needed. 10 set -eu 11 12 . "$HOME/repos/config.env" 2>/dev/null || true 13 : "${BUNNY_ZONE:?set BUNNY_ZONE in .env (laptop) and re-run push}" 14 15 KEY_FILE=$HOME/repos/bunny.key 16 if [ ! -r "$KEY_FILE" ]; then 17 echo "publish-public: $KEY_FILE missing (storage zone access key)" >&2 18 exit 0 19 fi 20 KEY=$(cat "$KEY_FILE") 21 22 SRC=$HOME/repos/www-public 23 ENDPOINT=https://storage.bunnycdn.com/$BUNNY_ZONE 24 STATE=$HOME/repos/.publish-state 25 MANIFEST=$STATE/manifest.tsv 26 mkdir -p "$STATE" 27 touch "$MANIFEST" 28 29 CUR=$(mktemp) 30 OLD=$(mktemp) 31 TODO=$(mktemp) 32 FAILED=$(mktemp) 33 FAILED_SORTED=$(mktemp) 34 trap 'rm -f "$CUR" "$OLD" "$TODO" "$FAILED" "$FAILED_SORTED"' EXIT 35 36 # Build current state: lines of "<sha256>\t<path>", byte-sorted so comm 37 # is safe. Paths are unique, so full-line order is deterministic. 38 cd "$SRC" 39 find -L . -type f -print0 \ 40 | xargs -0 -r sha256sum \ 41 | sed 's| \./|\t|' \ 42 | LC_ALL=C sort > "$CUR" 43 44 LC_ALL=C sort "$MANIFEST" > "$OLD" 45 LC_ALL=C comm -23 "$CUR" "$OLD" > "$TODO" 46 47 n=$(wc -l < "$TODO" | tr -d ' ') 48 total=$(wc -l < "$CUR" | tr -d ' ') 49 echo "publish-public: $n/$total files to upload" 50 51 if [ "$n" -gt 0 ]; then 52 export ENDPOINT KEY FAILED 53 awk -F'\t' '{print $2}' "$TODO" | xargs -P 8 -I{} sh -c ' 54 f=$1 55 code=$(curl -sS -T "$f" "$ENDPOINT/$f" \ 56 -H "AccessKey: $KEY" -o /dev/null -w "%{http_code}") 57 case $code in 58 200|201) ;; 59 *) echo "PUT $code: $f" >&2; echo "$f" >> "$FAILED" ;; 60 esac 61 ' _ {} 62 fi 63 64 [ -s "$FAILED" ] && LC_ALL=C sort -u "$FAILED" > "$FAILED_SORTED" 65 66 # Rebuild manifest as "what we believe is on Bunny": 67 # - paths in CUR whose PUT succeeded (or were unchanged): fresh CUR entry 68 # - paths in CUR whose PUT failed: keep OLD entry (what's actually there) 69 # - paths in OLD but not CUR (orphans): keep OLD entry 70 # Orphans get cleaned up later by publish-public-prune. 71 awk -F'\t' -v failed="$FAILED_SORTED" ' 72 BEGIN { while ((getline p < failed) > 0) bad[p]=1 } 73 NR==FNR { old[$2]=$0; next } 74 { 75 if (($2) in bad) { 76 if (($2) in old) { print old[$2]; delete old[$2] } 77 } else { 78 print 79 if (($2) in old) delete old[$2] 80 } 81 } 82 END { for (p in old) print old[p] } 83 ' "$OLD" "$CUR" > "$MANIFEST.new" 84 mv "$MANIFEST.new" "$MANIFEST" 85 86 if [ -s "$FAILED" ]; then 87 fail_n=$(wc -l < "$FAILED" | tr -d ' ') 88 echo "publish-public: $((n - fail_n)) uploaded, $fail_n failed" 89 exit 1 90 fi 91 [ "$n" -gt 0 ] && echo "publish-public: $n uploaded" || true