-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·222 lines (191 loc) · 6.24 KB
/
Copy pathdeploy.sh
File metadata and controls
executable file
·222 lines (191 loc) · 6.24 KB
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 Dong Lab, Yale School of Medicine <https://donglab.org>
#
# SPDX-License-Identifier: Apache-2.0
# Deploy an AtlasForge static export to S3 and CloudFront
#
# Run this on the machine that holds the complete export. The script uploads only
# changed files and does not delete coverage, GWAS, or structure data
#
# scripts/deploy.sh # sync dist/ -> the bucket, then invalidate
# scripts/deploy.sh --dry-run # show every action, change nothing
# scripts/deploy.sh --prune # also delete stale assets + removed HTML/JSON
# scripts/deploy.sh --site out/ # a different export dir
#
# Override the default CloudFront distribution with:
# export CLOUDFRONT_DISTRIBUTION_ID=EXXXXXXXXXXXX
set -euo pipefail
BUCKET="s3://slc.yalepages.org"
DISTRIBUTION_ID="${CLOUDFRONT_DISTRIBUTION_ID:-E3885KZHROH1N2}"
SITE_DIR="dist"
ASSET_CACHE="public, max-age=31536000, immutable"
BIN_CACHE="public, max-age=604800"
APP_CACHE="no-cache"
# These data trees are never pruned
BINARY_DIRS=(
"api/browser/coverage"
"api/browser/gwas"
"api/structure/models"
)
MODELS_BB="api/browser/models.bb"
DRY_RUN=0
PRUNE=0
DRY_RUN_ARGS=()
DELETE_ARGS=()
usage() {
awk '
!seen { if ($0 ~ /SPDX-License-Identifier/) seen = 1; next }
/^#/ { sub(/^# ?/, ""); print; body = 1; next }
body { exit }
' "$0"
}
fail() {
echo "Error: $*" >&2
exit 1
}
while [ $# -gt 0 ]; do
case "$1" in
--dry-run)
DRY_RUN=1
DRY_RUN_ARGS=(--dryrun)
;;
--prune)
PRUNE=1
DELETE_ARGS=(--delete)
;;
--site)
[ "$#" -ge 2 ] || fail "--site requires a path"
SITE_DIR="$2"
shift
;;
-h | --help)
usage
exit 0
;;
*)
echo "Error: Unknown argument: $1" >&2
echo "Run '$0 --help' for usage." >&2
exit 2
;;
esac
shift
done
command -v aws >/dev/null 2>&1 || fail "AWS CLI not found on PATH"
[ -d "$SITE_DIR" ] || fail "Site directory '$SITE_DIR' not found"
# Verify the export before uploading anything
for required in index.html api/genes.json api/capabilities.json; do
if [ ! -f "$SITE_DIR/$required" ]; then
echo "Error: '$SITE_DIR/$required' is missing; '$SITE_DIR' is not a complete export." >&2
echo "Run 'atlasforge export $SITE_DIR --web-dir web/dist' first." >&2
exit 1
fi
done
MODE="live"
[ "$DRY_RUN" -eq 1 ] && MODE="dry run"
[ "$PRUNE" -eq 1 ] && MODE="$MODE with pruning"
echo "Bucket: $BUCKET"
echo "Site: $SITE_DIR"
echo "Distribution: $DISTRIBUTION_ID"
echo "Mode: $MODE"
echo
CHANGED_PATHS=()
run_sync() {
if ! aws s3 sync "$@"; then
fail "Sync failed: $*"
fi
}
run_tracked_sync() {
local invalidation_path="$1"
local log
shift
log="$(mktemp)"
if ! aws s3 sync "$@" | tee "$log"; then
rm -f "$log"
fail "Sync failed for $invalidation_path"
fi
if grep -qE '(upload|copy|delete):' "$log"; then
CHANGED_PATHS+=("$invalidation_path")
fi
rm -f "$log"
}
run_app_sync() {
local log
log="$(mktemp)"
if ! aws s3 sync "$@" | tee "$log"; then
rm -f "$log"
fail "Sync failed for app and API"
fi
while IFS= read -r path; do
CHANGED_PATHS+=("$path")
done < <(
grep -E '^(\(dryrun\) )?(upload|copy):' "$log" | sed -E 's#.* to s3://[^/]+/#/#'
grep -E '^(\(dryrun\) )?delete:' "$log" | sed -E 's#.* s3://[^/]+/#/#'
)
rm -f "$log"
}
sync_binary_tree() {
local sub="$1"
local source="$SITE_DIR/$sub"
if [ ! -d "$source" ] || [ -z "$(ls -A "$source" 2>/dev/null)" ]; then
echo ">> Skipping $sub (missing or empty locally; remote files are unchanged)"
return
fi
echo ">> Binary data: $sub"
run_tracked_sync "/$sub/*" "$source" "$BUCKET/$sub" "${DRY_RUN_ARGS[@]}" \
--size-only --cache-control "$BIN_CACHE"
}
echo ">> Assets (immutable cache)"
run_sync "$SITE_DIR/assets/" "$BUCKET/assets/" "${DRY_RUN_ARGS[@]}" "${DELETE_ARGS[@]}" \
--size-only --cache-control "$ASSET_CACHE"
echo
for dir in "${BINARY_DIRS[@]}"; do
sync_binary_tree "$dir"
done
# Sync the model index without touching neighboring data trees
if [ -f "$SITE_DIR/$MODELS_BB" ]; then
echo ">> Binary data: $MODELS_BB"
run_tracked_sync "/$MODELS_BB" "$SITE_DIR/api/browser/" "$BUCKET/api/browser/" \
"${DRY_RUN_ARGS[@]}" --exclude "*" --include "models.bb" --size-only \
--cache-control "$BIN_CACHE"
fi
echo
# Upload entry points last, after their assets are available
echo ">> App and API (no cache)"
run_app_sync "$SITE_DIR/" "$BUCKET/" "${DRY_RUN_ARGS[@]}" "${DELETE_ARGS[@]}" \
--cache-control "$APP_CACHE" \
--exclude "assets/*" \
--exclude "api/browser/coverage/*" \
--exclude "api/browser/gwas/*" \
--exclude "api/structure/models/*" \
--exclude "api/browser/models.bb"
echo
# Invalidate every changed path
INVAL_PATHS=()
if [ ${#CHANGED_PATHS[@]} -gt 0 ]; then
while IFS= read -r p; do
INVAL_PATHS+=("$p")
done < <(printf '%s\n' "${CHANGED_PATHS[@]}" | awk 'NF && !seen[$0]++')
fi
if [ ${#INVAL_PATHS[@]} -eq 0 ]; then
echo ">> No changed files to invalidate"
elif [ "$DRY_RUN" -eq 1 ]; then
echo "[dry run] Would invalidate ${#INVAL_PATHS[@]} path(s): ${INVAL_PATHS[*]}"
elif [ "$DISTRIBUTION_ID" = "REPLACE_WITH_DISTRIBUTION_ID" ]; then
echo "No CloudFront distribution is set; skipping invalidation."
echo "Set CLOUDFRONT_DISTRIBUTION_ID and rerun, or invalidate manually:"
echo " aws cloudfront create-invalidation --distribution-id <ID> \\"
echo " --paths ${INVAL_PATHS[*]}"
else
echo ">> Invalidating ${#INVAL_PATHS[@]} path(s)"
if ! aws cloudfront create-invalidation \
--distribution-id "$DISTRIBUTION_ID" \
--paths "${INVAL_PATHS[@]}" \
--query 'Invalidation.{Id:Id,Status:Status}' --output table; then
echo "Warning: Invalidation failed (is cloudfront:CreateInvalidation permitted?)." >&2
echo "Content was uploaded, but CloudFront caches these paths, so invalidate manually:" >&2
echo " aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID \\" >&2
echo " --paths ${INVAL_PATHS[*]}" >&2
fi
fi
echo
echo "Deployment complete."