-
Notifications
You must be signed in to change notification settings - Fork 4
238 lines (210 loc) · 8.06 KB
/
Copy pathdeploy.yaml
File metadata and controls
238 lines (210 loc) · 8.06 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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
name: Deploy
on:
workflow_call:
inputs:
ref:
description: Git ref to deploy
required: false
type: string
default: ''
job:
description: Job to run (aws, hetzner, or all)
required: false
type: string
default: all
workflow_dispatch:
inputs:
ref:
description: Git ref to deploy (branch, tag, or SHA)
required: false
default: main
job:
description: Job to run (aws, hetzner, or all)
required: false
type: string
default: all
jobs:
hetzner:
name: Hetzner
if: inputs.job == 'all' || inputs.job == 'hetzner'
runs-on: ubuntu-latest
environment: hetzner
env:
ACME_EMAIL: ${{ vars.ACME_EMAIL }}
COMPOSE_FILE: docker-compose.yml:docker-compose.mock-oidc.yml:docker-compose.metrics.yml:infrastructure/docker-compose.hetzner.yml
DEPLOY_PATH: /home/user/deploy
DEPLOY_USER: user
EOAPI_DOMAIN: ${{ vars.EOAPI_DOMAIN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.ref || github.ref }}
- name: Validate deployment configuration
run: |
missing=0
for name in EOAPI_DOMAIN ACME_EMAIL; do
if [ -z "${!name}" ]; then
echo "::error::$name is not set (configure it in the hetzner environment variables)"
missing=1
fi
done
if [ -z "${{ secrets.HETZNER_SSH_KEY }}" ]; then
echo "::error::HETZNER_SSH_KEY is not set (configure it in the hetzner environment secrets)"
missing=1
fi
[ "$missing" -eq 0 ]
- name: Setup SSH key
env:
SSH_PRIVATE_KEY: ${{ secrets.HETZNER_SSH_KEY }}
run: |
if [ -z "$SSH_PRIVATE_KEY" ]; then
echo "::error::HETZNER_SSH_KEY is empty"
exit 1
fi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
key_file=~/.ssh/deploy_key
write_key() {
tr -d '\r' > "$key_file"
chmod 600 "$key_file"
}
validate_key() {
ssh-keygen -y -f "$key_file" >/dev/null 2>&1
}
printf '%s' "$SSH_PRIVATE_KEY" | write_key
if validate_key; then
echo "SSH key loaded (raw multiline format)"
else
printf '%s' "$SSH_PRIVATE_KEY" | sed 's/\\n/\n/g' | write_key
if validate_key; then
echo "SSH key loaded (escaped newline format)"
else
if printf '%s' "$SSH_PRIVATE_KEY" | tr -d '\n\r ' | base64 -d 2>/dev/null | write_key && validate_key; then
echo "SSH key loaded (base64 format)"
else
first_line="$(printf '%s' "$SSH_PRIVATE_KEY" | head -n1)"
line_count="$(printf '%s' "$SSH_PRIVATE_KEY" | wc -l | tr -d ' ')"
echo "::error::HETZNER_SSH_KEY is not a valid private key."
echo "Secret diagnostics: lines=${line_count}, starts_with=${first_line:0:40}..."
echo
echo "Store the key in the hetzner environment secret using one of:"
echo " A) Raw key (paste entire file, including BEGIN/END lines)"
echo " B) Base64 (recommended): base64 -w0 < ~/.ssh/eoapi-deploy"
exit 1
fi
fi
fi
echo "SSH_OPTS=-i $key_file -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" >> "$GITHUB_ENV"
- name: Add server to known hosts
run: ssh-keyscan -H "$EOAPI_DOMAIN" >> ~/.ssh/known_hosts
- name: Verify Docker on server
run: |
ssh $SSH_OPTS "${DEPLOY_USER}@${EOAPI_DOMAIN}" \
"command -v docker >/dev/null && docker compose version" \
|| {
echo "::error::Docker is not installed for ${DEPLOY_USER}@${EOAPI_DOMAIN}."
echo "Run scripts/hetzner-server-setup.sh on the server as root first."
exit 1
}
- name: Sync project to server
run: |
rsync -az --delete \
--exclude '.git' \
--exclude '.pgdata' \
--exclude '.env' \
--exclude '.venv' \
--exclude '__pycache__' \
-e "ssh $SSH_OPTS" \
./ "${DEPLOY_USER}@${EOAPI_DOMAIN}:${DEPLOY_PATH}/"
- name: Upload .env
env:
HETZNER_ENV: ${{ secrets.HETZNER_ENV }}
run: |
if [ -n "$HETZNER_ENV" ]; then
printf '%s\n' "$HETZNER_ENV" > .env.deploy
scp $SSH_OPTS .env.deploy "${DEPLOY_USER}@${EOAPI_DOMAIN}:${DEPLOY_PATH}/.env"
rm .env.deploy
fi
- name: Deploy services
run: |
ssh $SSH_OPTS "${DEPLOY_USER}@${EOAPI_DOMAIN}" \
"cd '${DEPLOY_PATH}' && \
export COMPOSE_FILE='${COMPOSE_FILE}' && \
export EOAPI_DOMAIN='${EOAPI_DOMAIN}' && \
export EOAPI_GRAFANA_ROOT_URL='https://${EOAPI_DOMAIN}/monitoring/' && \
export ACME_EMAIL='${ACME_EMAIL}' && \
docker compose down && \
bash .github/workflows/ingest.sh --wipe-pgdata-only && \
docker compose pull --ignore-pull-failures && \
docker compose up -d --build --remove-orphans && \
docker compose --profile ingest run --rm ingest && \
docker compose restart vector && \
docker compose ps"
- name: Install demo refresh cron
run: |
ssh $SSH_OPTS "${DEPLOY_USER}@${EOAPI_DOMAIN}" \
"cd '${DEPLOY_PATH}' && \
export DEPLOY_PATH='${DEPLOY_PATH}' && \
export EOAPI_DOMAIN='${EOAPI_DOMAIN}' && \
export EOAPI_GRAFANA_ROOT_URL='https://${EOAPI_DOMAIN}/monitoring/' && \
export ACME_EMAIL='${ACME_EMAIL}' && \
export COMPOSE_FILE='${COMPOSE_FILE}' && \
bash scripts/install_demo_cron.sh"
- name: Wait for services
run: |
wait_for_service() {
name="$1"
url="$2"
for i in $(seq 1 90); do
code=$(curl --silent --output /dev/null --write-out "%{http_code}" "$url" || echo "000")
if [ "$code" = "200" ] || [ "$code" = "204" ]; then
echo "$name is ready ($url → $code)"
return 0
fi
if [ "$i" -eq 90 ]; then
echo "$name did not become ready at $url (last status: $code)"
return 1
fi
sleep 2
done
}
base="https://${EOAPI_DOMAIN}"
wait_for_service stac-auth-proxy "${base}/stac/_mgmt/ping"
wait_for_service raster "${base}/raster/healthz"
wait_for_service vector "${base}/vector/"
shell: bash
aws:
name: AWS (CDK)
if: inputs.job == 'all' || inputs.job == 'aws'
environment: dev
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
env:
STACK_NAME: eoapi-dev
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.ref || github.ref }}
- name: Create config file
run: |
echo "${{ vars.CONFIG_YAML }}" > config.yaml
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6
with:
role-to-assume: arn:aws:iam::390960605471:role/eoapi-devseed
role-session-name: eoapi-devseed
aws-region: us-west-2
- name: Set up node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
- name: Install dependencies
run: |
uv sync --only-group deploy
uv run --only-group deploy npm install
- name: CDK Synth
run: uv run --only-group deploy npx cdk synth --all
- name: CDK Deploy
run: uv run --only-group deploy npx cdk deploy --all --require-approval never