gitserver

self-hosted git server tooling
git clone https://git.ryansepassi.com/git/gitserver.git
Log | Files | Refs | README

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