summaryrefslogtreecommitdiffstats
path: root/scripts/deploy.sh
blob: 99129ad352c04ba8f35d8f1ef788c04780a0157e (plain) (blame)
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env bash
# Upload web/ and verify every published artifact over the public URL.
#
# Usage:
#   ./scripts/deploy.sh user@host:/srv/zagan.bet https://zagan.bet/
#   ./scripts/deploy.sh /srv/staging http://127.0.0.1:8899/
#   ./scripts/deploy.sh --verify-only user@host:/srv/zagan.bet https://zagan.bet/
#
# The target may be a local directory, which is handy for staging and for
# testing the verification itself. Old versions are left in place on purpose;
# prune them by hand when you want to drop them.
#
# Environment:
#   DEPLOY_TARGET, DEPLOY_URL   same as the two arguments
#   SSH_PORT                    ssh port for a remote target
set -euo pipefail

ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cd "$ROOT"

die() { echo "deploy: $*" >&2; exit 1; }

VERIFY_ONLY=0
if [ "${1:-}" = "--verify-only" ]; then
  VERIFY_ONLY=1
  shift
fi

TARGET=${1:-${DEPLOY_TARGET:-}}
URL=${2:-${DEPLOY_URL:-}}
if [ -z "$TARGET" ] || [ -z "$URL" ]; then
  cat >&2 <<'EOF'
usage: scripts/deploy.sh [--verify-only] <target> <url>

  target   user@host:/srv/zagan.bet   or a local directory
  url      public URL of the page, e.g. https://zagan.bet/
           --verify-only skips the upload and only checks the live page

environment:
  DEPLOY_TARGET, DEPLOY_URL   same as the two arguments
  SSH_PORT                    ssh port for a remote target
EOF
  exit 2
fi

for tool in rsync curl sha256sum; do
  command -v "$tool" >/dev/null || die "$tool is required"
done

[ -f web/index.php ] || die "web/index.php is missing"

shopt -s nullglob
artifacts=(web/bin/client/zagan-bet-* web/bin/server/zagan-bet-*)
shopt -u nullglob
[ ${#artifacts[@]} -gt 0 ] || die "no artifacts in web/bin; run scripts/publish.sh first"

if [ "$VERIFY_ONLY" -eq 0 ]; then
  echo "==> uploading web/ to $TARGET"

  # --delay-updates writes to temp names and renames at the end, so a visitor
  # never sees a half-written binary. Old versions are kept: no --delete.
  RSYNC_FLAGS=(
    -a --delay-updates --human-readable --chmod=D755,F644
    --exclude=README.md --exclude=.gitignore
  )

  if [[ "$TARGET" == *:* ]]; then
    host=${TARGET%%:*}
    path=${TARGET#*:}
    ssh_args=()
    if [ -n "${SSH_PORT:-}" ]; then
      ssh_args+=(-p "$SSH_PORT")
    fi
    ssh ${ssh_args[@]+"${ssh_args[@]}"} "$host" "mkdir -p $(printf '%q' "$path")"
    rsync "${RSYNC_FLAGS[@]}" -e "ssh ${ssh_args[*]:-}" web/ "$host:$path/"
  else
    mkdir -p "$TARGET"
    rsync "${RSYNC_FLAGS[@]}" web/ "$TARGET/"
  fi
fi

echo "==> verifying $URL"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

curl -fsSL --max-time 30 "$URL" -o "$tmp/page.html" \
  || die "could not fetch $URL"

origin=$(printf '%s' "$URL" | sed -E 's#^(https?://[^/]+).*#\1#')
grep -oE '[0-9a-f]{64}  [A-Za-z0-9._-]+' "$tmp/page.html" > "$tmp/sums" || true
[ -s "$tmp/sums" ] || die "no SHA-256 lines on the page (is index.php being served?)"

failed=0
verified=0
for local in "${artifacts[@]}"; do
  [ -f "$local" ] || continue

  file=$(basename "$local")
  kind=$(basename "$(dirname "$local")")
  local_hash=$(sha256sum "$local" | cut -d' ' -f1)

  href=$(grep -oE "href=\"[^\"]*/bin/$kind/$file\"" "$tmp/page.html" | head -1 \
    | sed 's/^href="//;s/"$//' || true)
  if [ -z "$href" ]; then
    echo "  $file: not listed on the page"
    failed=1
    continue
  fi

  shown=$(awk -v name="$file" '$2 == name { print $1 }' "$tmp/sums" | head -1)
  if [ "$shown" != "$local_hash" ]; then
    echo "  $file: page shows ${shown:-nothing}, local is $local_hash"
    failed=1
    continue
  fi

  if ! curl -fsSL --max-time 120 "$origin$href" -o "$tmp/$file"; then
    echo "  $file: download failed"
    failed=1
    continue
  fi

  remote_hash=$(sha256sum "$tmp/$file" | cut -d' ' -f1)
  if [ "$remote_hash" != "$local_hash" ]; then
    echo "  $file: downloaded bytes differ from local"
    failed=1
    continue
  fi

  echo "  $file: ok"
  verified=$((verified + 1))
done

[ "$failed" -eq 0 ] || die "verification failed"

echo "==> $verified artifact(s) verified at $URL"