-
-
Notifications
You must be signed in to change notification settings - Fork 282
213 lines (186 loc) · 8.13 KB
/
Copy pathdeploy-aws-lambda.yml
File metadata and controls
213 lines (186 loc) · 8.13 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
name: Deploy to AWS Lambda
# Triggered by the top-level Deploy dispatcher (.github/workflows/deploy.yml)
# when AWS + Turso secrets are configured. Manual runs via the Actions UI
# also supported.
on:
workflow_call:
workflow_dispatch:
inputs:
version:
description: 'Release tag to deploy (e.g. v2.5.0). Leave empty for latest.'
required: false
# Prevent overlapping deployments.
concurrency:
group: deploy-aws-lambda
cancel-in-progress: false
permissions:
contents: read
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: Disable upstream-only workflows
env:
GH_TOKEN: ${{ github.token }}
run: |
for workflow in ci.yml release.yml; do
gh api -X PUT "repos/${{ github.repository }}/actions/workflows/$workflow/disable" 2>/dev/null && \
echo "Disabled $workflow" || echo "$workflow already disabled"
done
- name: Check required secrets
env:
HAS_TURSO_URL: ${{ secrets.TURSO_DATABASE_URL != '' }}
HAS_TURSO_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN != '' }}
HAS_AWS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID != '' }}
HAS_AWS_SECRET: ${{ secrets.AWS_SECRET_ACCESS_KEY != '' }}
HAS_AWS_REGION: ${{ secrets.AWS_REGION != '' }}
run: |
missing=""
[ "$HAS_TURSO_URL" != "true" ] && missing="$missing TURSO_DATABASE_URL"
[ "$HAS_TURSO_TOKEN" != "true" ] && missing="$missing TURSO_AUTH_TOKEN"
[ "$HAS_AWS_KEY" != "true" ] && missing="$missing AWS_ACCESS_KEY_ID"
[ "$HAS_AWS_SECRET" != "true" ] && missing="$missing AWS_SECRET_ACCESS_KEY"
[ "$HAS_AWS_REGION" != "true" ] && missing="$missing AWS_REGION"
if [ -n "$missing" ]; then
echo "::error::Missing required secrets:$missing. Go to Settings → Secrets and variables → Actions and add them."
exit 1
fi
- name: Resolve release tag
id: release
env:
GH_TOKEN: ${{ github.token }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ -n "$INPUT_VERSION" ]; then
TAG="$INPUT_VERSION"
else
TAG=$(gh api repos/saltbo/zpan/releases/latest --jq '.tag_name')
fi
if [ -z "$TAG" ]; then
echo "::error::No release found in saltbo/zpan"
exit 1
fi
echo "version=$TAG" >> "$GITHUB_OUTPUT"
echo "### 🚀 Deploying $TAG to AWS Lambda" >> "$GITHUB_STEP_SUMMARY"
- uses: actions/checkout@v4
with:
repository: saltbo/zpan
ref: ${{ steps.release.outputs.version }}
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Setup SAM CLI
uses: aws-actions/setup-sam@v2
- run: pnpm install --frozen-lockfile
- name: Ensure SAM artifact bucket exists
id: bucket
run: |
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
BUCKET="zpan-sam-artifacts-${ACCOUNT}-${{ secrets.AWS_REGION }}"
if ! aws s3api head-bucket --bucket "$BUCKET" 2>/dev/null; then
aws s3 mb "s3://$BUCKET" --region "${{ secrets.AWS_REGION }}"
echo "Created SAM artifact bucket: $BUCKET"
else
echo "Reusing SAM artifact bucket: $BUCKET"
fi
echo "name=$BUCKET" >> "$GITHUB_OUTPUT"
- name: Apply Turso migrations
env:
TURSO_DATABASE_URL: ${{ secrets.TURSO_DATABASE_URL }}
TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }}
run: pnpm exec drizzle-kit migrate
- name: Resolve existing deployment state
id: state
env:
USER_SECRET: ${{ secrets.BETTER_AUTH_SECRET }}
run: |
# Read current function config if it exists (redeployment case)
FUNC_JSON=$(aws lambda get-function-configuration --function-name zpan 2>/dev/null || echo "")
if [ -n "$FUNC_JSON" ]; then
EXISTING_SECRET=$(echo "$FUNC_JSON" | jq -r '.Environment.Variables.BETTER_AUTH_SECRET // empty')
EXISTING_URL=$(aws lambda get-function-url-config --function-name zpan \
--query FunctionUrl --output text 2>/dev/null || echo "")
fi
# Determine BETTER_AUTH_SECRET (priority: user-supplied > existing > auto-generate)
if [ -n "$USER_SECRET" ]; then
SECRET="$USER_SECRET"
echo "Using BETTER_AUTH_SECRET from GitHub secret"
elif [ -n "$EXISTING_SECRET" ]; then
SECRET="$EXISTING_SECRET"
echo "Reusing existing BETTER_AUTH_SECRET"
else
SECRET=$(openssl rand -base64 32)
echo "Auto-generated BETTER_AUTH_SECRET"
fi
echo "secret=$SECRET" >> "$GITHUB_OUTPUT"
echo "app_url=${EXISTING_URL:-}" >> "$GITHUB_OUTPUT"
- name: Build
run: |
pnpm exec vite build --mode node
# Bundle Lambda entry; @libsql/client is external (native binding)
pnpm exec tsup server/entry-lambda.ts --format cjs --outDir dist-lambda --external @libsql/client
# Assemble minimal Lambda deployment package
mkdir -p dist-lambda-pkg
cp dist-lambda/entry-lambda.cjs dist-lambda-pkg/
cp -r dist dist-lambda-pkg/
cp -r migrations dist-lambda-pkg/
# Minimal package.json so SAM installs only @libsql/client
node -e "
const p = require('./package.json');
const pkg = {
name: 'zpan-lambda',
version: p.version,
dependencies: { '@libsql/client': p.dependencies['@libsql/client'] }
};
require('fs').writeFileSync('dist-lambda-pkg/package.json', JSON.stringify(pkg, null, 2));
"
- name: SAM build
run: sam build --template-file deploy/aws-lambda/template.yaml
- name: SAM deploy
run: |
sam deploy \
--stack-name zpan \
--s3-bucket "${{ steps.bucket.outputs.name }}" \
--capabilities CAPABILITY_IAM \
--no-confirm-changeset \
--no-fail-on-empty-changeset \
--parameter-overrides \
TursoDatabaseUrl="${{ secrets.TURSO_DATABASE_URL }}" \
TursoAuthToken="${{ secrets.TURSO_AUTH_TOKEN }}" \
BetterAuthSecret="${{ steps.state.outputs.secret }}" \
AppUrl="${{ steps.state.outputs.app_url }}"
- name: Finalize — set BETTER_AUTH_URL and write summary
run: |
# Get the actual Function URL from CloudFormation outputs
URL=$(aws cloudformation describe-stacks \
--stack-name zpan \
--query "Stacks[0].Outputs[?OutputKey=='FunctionUrl'].OutputValue" \
--output text)
# On first deploy AppUrl was empty; patch the live function so auth works immediately.
CURRENT_URL="${{ steps.state.outputs.app_url }}"
if [ "$URL" != "$CURRENT_URL" ]; then
aws lambda update-function-configuration \
--function-name zpan \
--environment "Variables={
NODE_ENV=production,
TURSO_DATABASE_URL=${{ secrets.TURSO_DATABASE_URL }},
TURSO_AUTH_TOKEN=${{ secrets.TURSO_AUTH_TOKEN }},
BETTER_AUTH_SECRET=${{ steps.state.outputs.secret }},
APP_URL=$URL,
BETTER_AUTH_URL=$URL
}" > /dev/null
echo "Updated BETTER_AUTH_URL → $URL"
fi
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**URL:** $URL" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "**Next step:** Open the URL, register the first admin account, then go to Admin → Storages to configure your S3-compatible bucket." >> "$GITHUB_STEP_SUMMARY"