-
-
Notifications
You must be signed in to change notification settings - Fork 0
142 lines (120 loc) · 5.57 KB
/
Copy pathdeploy.yml
File metadata and controls
142 lines (120 loc) · 5.57 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
name: Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
deploy:
name: ${{ github.event_name == 'pull_request' && 'Dry-run' || 'Deploy' }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- name: Install dependencies
run: npm ci
- name: Verify license is AGPL-3.0
run: |
grep -q "GNU AFFERO GENERAL PUBLIC LICENSE" LICENSE || { echo "::error::LICENSE file does not contain AGPL-3.0"; exit 1; }
grep -q '"AGPL-3.0-or-later"' package.json || { echo "::error::package.json license is not AGPL-3.0-or-later"; exit 1; }
echo "License check passed"
- name: Syntax check
run: node --check src/worker.js
- name: Sync public/ to R2 bucket pqc
if: github.event_name != 'pull_request'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
set -euo pipefail
declare -A MIME=(
[svg]="image/svg+xml"
[png]="image/png"
[jpg]="image/jpeg"
[jpeg]="image/jpeg"
[webp]="image/webp"
[ico]="image/x-icon"
[json]="application/json"
)
find public/ -type f | while read -r f; do
key="${f#public/}"
ext="${f##*.}"
ct="${MIME[$ext]:-application/octet-stream}"
echo "Uploading $f -> pqc/$key ($ct)"
npx wrangler r2 object put "pqc/$key" --file="$f" --content-type="$ct" --remote
done
- name: Compute asset SHA3-512
run: |
set -euo pipefail
HASH=$(openssl dgst -sha3-512 public/pqc.svg | awk '{print $NF}')
if [ -z "$HASH" ]; then
echo "::error::Failed to compute SHA3-512 of pqc.svg"
exit 1
fi
echo "ASSET_VERSION=$HASH" >> "$GITHUB_ENV"
echo "Computed SHA3-512: $HASH"
- name: Deploy (dry-run on PRs)
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: ${{ github.event_name == 'pull_request' && 'deploy --dry-run' || 'deploy' }} --var ASSET_VERSION:${{ env.ASSET_VERSION }}
- name: Post-deploy smoke test
if: github.event_name != 'pull_request'
continue-on-error: true
run: |
set -euo pipefail
UA="Mozilla/5.0 (compatible; smoke-test)"
BROWSER_UA="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
BASE="https://hermitstash.com"
echo "--- Browser UA: expect 200 ---"
code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/" \
-H "User-Agent: $BROWSER_UA" \
-H "Accept: text/html" \
-H "Accept-Language: en-US" \
-H "Sec-Fetch-Dest: document")
[ "$code" = "200" ] || { echo "::error::Browser UA got $code, expected 200"; exit 1; }
echo "--- Googlebot: expect 200 ---"
code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/" \
-H "User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)")
[ "$code" = "200" ] || { echo "::error::Googlebot got $code, expected 200"; exit 1; }
echo "--- curl UA: expect 403 ---"
code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/" -H "User-Agent: curl/8.0")
[ "$code" = "403" ] || { echo "::error::curl UA got $code, expected 403"; exit 1; }
echo "--- robots.txt: expect 200 ---"
code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/robots.txt")
[ "$code" = "200" ] || { echo "::error::robots.txt got $code, expected 200"; exit 1; }
echo "--- security.txt: expect 200 ---"
code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/.well-known/security.txt")
[ "$code" = "200" ] || { echo "::error::security.txt got $code, expected 200"; exit 1; }
echo "--- favicon.ico: expect 302 ---"
code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/favicon.ico")
[ "$code" = "302" ] || { echo "::error::favicon.ico got $code, expected 302"; exit 1; }
echo "--- OG tags present ---"
body=$(curl -s "$BASE/" -H "User-Agent: facebookexternalhit/1.1" -H "Accept: */*")
echo "$body" | grep -q 'og:image' || { echo "::error::og:image missing from HTML"; exit 1; }
echo "$body" | grep -q 'application/ld+json' || { echo "::error::JSON-LD missing from HTML"; exit 1; }
echo "--- Security headers present ---"
headers=$(curl -sI "$BASE/" \
-H "User-Agent: $BROWSER_UA" \
-H "Accept: text/html" \
-H "Accept-Language: en-US" \
-H "Sec-Fetch-Dest: document")
echo "$headers" | grep -qi "strict-transport-security" || { echo "::error::HSTS header missing"; exit 1; }
echo "$headers" | grep -qi "content-security-policy" || { echo "::error::CSP header missing"; exit 1; }
echo "$headers" | grep -qi "cross-origin-embedder-policy" || { echo "::error::COEP header missing"; exit 1; }
echo "All smoke tests passed"