cautpartener / deploy / publish-repo.sh
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
#!/usr/bin/env bash
# deploy/publish-repo.sh — builds this repo's public source site into
# deploy/.site/ (gitignored):
#
#   .site/index.html          landing page (from site/index.html)
#   .site/cautpartener.git/   dumb-HTTP-clonable bare mirror, ALL-loose objects
#   .site/browse/             static in-browser source viewer (gen-browse.py)
#
# The result is plain static files — pin the folder on IPFS and point the
# cod-sursa.cautpartener.ro DNSLink domain at it; `git clone
# https://cod-sursa.cautpartener.ro/cautpartener.git` then works over git's
# dumb-HTTP transport (info/refs + loose objects, no git server).
#
# The anonymity gate runs FIRST (hard exit on any failure) and the mirror is
# re-scanned object-by-object after the clone. Identity patterns for the leak
# scan are read from an untracked local file (IDPAT_FILE, default
# ~/.config/cod-sursa/idpat) — NEVER write real identity strings (or fragments
# of them) into this script: it gets published with the repo.
set -euo pipefail
cd "$(dirname "$0")/.."

APP=cautpartener         # path under the host: <APP>.git
IDENT=cautpartener       # anonymous commit identity
IDPAT_FILE=${IDPAT_FILE:-"$HOME/.config/cod-sursa/idpat"}
OUT=deploy/.site

# ---- anonymity gate on the SOURCE repo, before anything is built ------------
bash deploy/check-anonymity.sh . "$IDENT" || {
  echo "ABORT: anonymity audit failed — nothing was built" >&2
  exit 1
}

rm -rf "$OUT"
mkdir -p "$OUT"

cp site/index.html "$OUT/index.html"

# --no-local: a plain path clone hardlinks the whole objects dir, which can
# drag along unreachable/rewritten history — transport clone copies only
# what's reachable from refs
git clone --no-local --bare --quiet . "$OUT/$APP.git"

# Explode packs into loose objects: the IPFS gateway answers missing paths
# with a 200 fallback page, which makes git's loose-object probes error
# noisily before the pack fallback. All-loose means every path a dumb-HTTP
# client asks for exists, so clones stay clean.
(
  cd "$OUT/$APP.git"
  for p in objects/pack/pack-*.pack; do
    [ -e "$p" ] || continue
    tmp=$(mktemp)
    mv "$p" "$tmp"
    rm -f "${p%.pack}".*
    git unpack-objects -q < "$tmp"
    rm -f "$tmp"
  done
  git update-server-info   # writes info/refs — the dumb-HTTP entry point
)
# strip local-only noise from the published mirror
rm -rf "$OUT/$APP.git/hooks" "$OUT/$APP.git/config" 2>/dev/null || true

# ---- re-scan the MIRROR itself: refuse to ship one that leaks identity ------
AUTHORS=$(git -C "$OUT/$APP.git" log --all --format='%an <%ae>%n%cn <%ce>' | sort -u)
if [ "$AUTHORS" != "$IDENT <$IDENT@noreply.local>" ]; then
  echo "ABORT: non-anonymous commit identity in mirror:" >&2
  echo "$AUTHORS" >&2
  exit 1
fi
if git -C "$OUT/$APP.git" ls-tree -r --name-only HEAD | grep -qE '\.site|^client/dist/'; then
  echo "ABORT: build output committed into the repo (deploy/.site or client/dist leak)" >&2
  exit 1
fi
# identity patterns live OUTSIDE the repo (untracked local file), so the
# published bytes never contain them; refuse to publish blind
if [ ! -s "$IDPAT_FILE" ]; then
  echo "ABORT: identity patterns file missing/empty: $IDPAT_FILE" >&2
  echo "       one extended-regex per line ('#' comments); the leak scan needs it" >&2
  exit 1
fi
IDPAT=$(grep -v '^[[:space:]]*#' "$IDPAT_FILE" | grep -v '^[[:space:]]*$' | paste -sd'|' -)
LEAK=$(cd "$OUT/$APP.git" && find objects -type f | while read -r f; do
  # filename via argv, never interpolated into source (injection-proof pattern)
  python3 -c "import zlib,sys;sys.stdout.buffer.write(zlib.decompress(open(sys.argv[1],'rb').read()))" "$f" 2>/dev/null
done | grep -aicE "$IDPAT" || true)
if [ "${LEAK:-0}" != "0" ]; then
  echo "ABORT: identity strings found inside $LEAK mirror object line(s)" >&2
  exit 1
fi
echo "mirror anonymity gate: OK (single anonymous author, no identity bytes, no build output)"

# ---- static source viewer ---------------------------------------------------
python3 deploy/gen-browse.py --repo . --app "$APP" --out "$OUT/browse" \
  --clone-url "https://cod-sursa.cautpartener.ro/$APP.git"

# ---- final sweep over every published byte ----------------------------------
FINAL=$( (grep -raicE "$IDPAT" "$OUT/index.html" "$OUT/browse" 2>/dev/null || true) | awk -F: '{s+=$NF} END {print s+0}')
if [ "${FINAL:-0}" != "0" ]; then
  echo "ABORT: identity strings in generated site pages" >&2
  exit 1
fi

echo "site built at $OUT ($(du -sh "$OUT" | cut -f1), $(find "$OUT" -type f | wc -l | tr -d ' ') files)"
echo "verify clone: git clone \"\$PWD/$OUT/$APP.git\" /tmp/verify-$APP"
echo "publish: ird ipfs add $OUT → ird ipns publish cod-sursa.cautpartener.ro <cid> → unpin previous"

static mirror of HEAD · about · clone: git clone https://cod-sursa.cautpartener.ro/cautpartener.git