gowalk-cicd 1.0.37 → 1.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +5 -0
- package/README.md +10 -15
- package/action/.daemux-version +1 -1
- package/android-action/.daemux-version +1 -1
- package/backend-action/.daemux-version +1 -1
- package/backend-action/action.yml +14 -0
- package/backend-action/remote_deploy.sh +11 -4
- package/package.json +1 -1
- package/templates/deploy-backend.yml +1 -0
- package/templates/deploy.yml +10 -54
package/CLAUDE.md
CHANGED
|
@@ -25,6 +25,8 @@ gowalk-cicd/ ← repo root IS the gowalk-cicd package
|
|
|
25
25
|
├── bin/cli.mjs ← CLI entrypoint (parses flags, calls runInstall)
|
|
26
26
|
├── src/install.mjs ← copy logic, gitignore-check, summary print
|
|
27
27
|
├── templates/deploy.yml ← dual-platform workflow copied verbatim
|
|
28
|
+
├── backend-action/ ← backend deploy action + remote host script
|
|
29
|
+
├── templates/deploy-backend.yml ← backend workflow copied when compose exists
|
|
28
30
|
├── scripts/auto-version.mjs ← patch-bump-from-npm used by the publish workflow
|
|
29
31
|
├── .github/workflows/publish.yml ← auto-publish to npm on push to main
|
|
30
32
|
├── README.md ← consumer docs
|
|
@@ -96,6 +98,9 @@ node /path/to/gowalk-cicd/bin/cli.mjs
|
|
|
96
98
|
materializes `ASC_KEY_P8` + its ID/issuer and the Android keystore,
|
|
97
99
|
properties and Play service account into an ephemeral runner checkout.
|
|
98
100
|
Tracked `creds/` files remain a backward-compatible fallback.
|
|
101
|
+
- Backend runtime credentials use the single encrypted `BACKEND_RUNTIME_ENV`
|
|
102
|
+
secret. The action writes it to host-side `.runtime.env` with mode 0600 and
|
|
103
|
+
supplies it to Compose after the generated `.env`; never print its content.
|
|
99
104
|
- The Android action serves two build systems. `android_config.project_kind()`
|
|
100
105
|
is the only place that decides which; every downstream step branches on the
|
|
101
106
|
`project_kind` output rather than re-sniffing the repo. Flutter wins the tie
|
package/README.md
CHANGED
|
@@ -21,12 +21,6 @@ Writes three things into your repo:
|
|
|
21
21
|
|
|
22
22
|
Re-run the same command anytime to pull the latest version.
|
|
23
23
|
|
|
24
|
-
Flutter map apps may also provide `GOOGLE_MAPS_IOS_KEY` and
|
|
25
|
-
`GOOGLE_MAPS_ANDROID_KEY` as Actions secrets. When present, the workflow writes
|
|
26
|
-
the iOS key to `ios/Flutter/MapsKeys.xcconfig` and the Android key to
|
|
27
|
-
`android/app/src/main/res/values/google_maps_api.xml` as mode-restricted runner
|
|
28
|
-
files. Apps without either secret are unchanged.
|
|
29
|
-
|
|
30
24
|
## iOS credentials
|
|
31
25
|
|
|
32
26
|
1. Create an **App Store Connect API key** with the **App Manager** role
|
|
@@ -67,6 +61,9 @@ Requirements on the consumer repo:
|
|
|
67
61
|
never bake secrets in. A generated `.env` (with `POSTGRES_PASSWORD`) is created
|
|
68
62
|
on the host on first deploy and preserved across deploys (rsync excludes it).
|
|
69
63
|
- Repo **secret** `BACKEND_DEPLOY_SSH_KEY` (a private key with access to the host).
|
|
64
|
+
- Repo **secret** `BACKEND_RUNTIME_ENV` (optional): newline-delimited `KEY=value`
|
|
65
|
+
runtime secrets. The action writes it as mode 0600 on the host and passes it
|
|
66
|
+
to Compose without copying values into the repository or action log.
|
|
70
67
|
- Repo **variables** (optional): `BACKEND_API_DOMAIN` (its DNS A record must point
|
|
71
68
|
at the host for the cert; empty = deploy the container only, skip nginx/cert),
|
|
72
69
|
`BACKEND_APP_NAME` (defaults to the repo name), `BACKEND_HEALTH_PATH`
|
|
@@ -190,11 +187,11 @@ sent for review from the Google Play Console UI.
|
|
|
190
187
|
```
|
|
191
188
|
|
|
192
189
|
That fails the whole upload, so the bundle you just built never reaches Play at
|
|
193
|
-
all.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
all. The retry re-uploads with `changesNotSentForReview`, which lands the bundle
|
|
191
|
+
and leaves it waiting, then emits a `::warning::` naming the package. Someone
|
|
192
|
+
then presses **Send changes for review** on that app's Publishing overview page.
|
|
193
|
+
There is no API for that press — it is the one step Google deliberately reserves
|
|
194
|
+
for a human, and no amount of tooling removes it.
|
|
198
195
|
|
|
199
196
|
In the ordinary case the first upload submits normally and the retry is skipped.
|
|
200
197
|
|
|
@@ -210,10 +207,8 @@ long enough outlives one:
|
|
|
210
207
|
|
|
211
208
|
Neither says anything about the build, which is signed and retained by that
|
|
212
209
|
point. The upload step therefore runs with `continue-on-error` and a second
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
third attempt use `changesNotSentForReview`; setting that flag on an ordinary
|
|
216
|
-
app is itself a Play validation error.
|
|
210
|
+
attempt follows when the first fails — a fresh edit succeeds. Both attempts
|
|
211
|
+
failing still fails the job.
|
|
217
212
|
|
|
218
213
|
One caveat this makes explicit: **nothing else should hold an edit open on the
|
|
219
214
|
same package while a deploy runs.** A script that polls `edits.insert` /
|
package/action/.daemux-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.38
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.38
|
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.38
|
|
@@ -39,6 +39,13 @@ inputs:
|
|
|
39
39
|
description: Email for the Let's Encrypt registration.
|
|
40
40
|
required: false
|
|
41
41
|
default: "admin@gowalk.com"
|
|
42
|
+
runtime-env:
|
|
43
|
+
description: >
|
|
44
|
+
Optional newline-delimited KEY=value secrets written to a mode-0600
|
|
45
|
+
host-side env file and supplied to Docker Compose. Pass from one
|
|
46
|
+
encrypted secret such as BACKEND_RUNTIME_ENV; never commit the values.
|
|
47
|
+
required: false
|
|
48
|
+
default: ""
|
|
42
49
|
|
|
43
50
|
runs:
|
|
44
51
|
using: composite
|
|
@@ -83,10 +90,17 @@ runs:
|
|
|
83
90
|
|
|
84
91
|
- name: Deploy on the host
|
|
85
92
|
shell: bash
|
|
93
|
+
env:
|
|
94
|
+
RUNTIME_ENV: ${{ inputs.runtime-env }}
|
|
86
95
|
run: |
|
|
87
96
|
set -euo pipefail
|
|
88
97
|
SSH="ssh -i ~/.ssh/backend_deploy -o IdentitiesOnly=yes"
|
|
89
98
|
DEST="/opt/gowalk-backends/${{ inputs.app-name }}"
|
|
99
|
+
if [ -n "${RUNTIME_ENV:-}" ]; then
|
|
100
|
+
printf '%s\n' "$RUNTIME_ENV" | $SSH \
|
|
101
|
+
"${{ inputs.ssh-user }}@${{ inputs.host }}" \
|
|
102
|
+
"umask 077; cat > '$DEST/.runtime.env'"
|
|
103
|
+
fi
|
|
90
104
|
$SSH "${{ inputs.ssh-user }}@${{ inputs.host }}" \
|
|
91
105
|
"bash '$DEST/.remote_deploy.sh' \
|
|
92
106
|
'${{ inputs.app-name }}' '${{ inputs.api-domain }}' \
|
|
@@ -23,15 +23,22 @@ if [ ! -f .env ]; then
|
|
|
23
23
|
chmod 600 .env
|
|
24
24
|
fi
|
|
25
25
|
|
|
26
|
+
COMPOSE_ENV=(--env-file .env)
|
|
27
|
+
if [ -s .runtime.env ]; then
|
|
28
|
+
chmod 600 .runtime.env
|
|
29
|
+
COMPOSE_ENV+=(--env-file .runtime.env)
|
|
30
|
+
fi
|
|
31
|
+
|
|
26
32
|
# ── bring the stack up
|
|
27
33
|
log "docker compose up -d --build"
|
|
28
|
-
docker compose --project-name "$APP"
|
|
34
|
+
docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" \
|
|
35
|
+
up -d --build --remove-orphans
|
|
29
36
|
|
|
30
37
|
# ── discover the host port the service published on 127.0.0.1.
|
|
31
38
|
# The text `ps` PORTS column reliably shows `127.0.0.1:<port>-><target>/tcp`.
|
|
32
39
|
detect_port() {
|
|
33
40
|
# Capture ONLY the published port after 127.0.0.1: (not the IP's own digits).
|
|
34
|
-
docker compose --project-name "$APP" ps 2>/dev/null \
|
|
41
|
+
docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" ps 2>/dev/null \
|
|
35
42
|
| sed -nE 's/.*127\.0\.0\.1:([0-9]+)->.*/\1/p' | head -1
|
|
36
43
|
}
|
|
37
44
|
PORT="$(detect_port || true)"
|
|
@@ -51,8 +58,8 @@ for i in $(seq 1 90); do
|
|
|
51
58
|
done
|
|
52
59
|
if [ -z "$HEALTHY" ]; then
|
|
53
60
|
log "ERROR: container never became healthy on :$PORT$HEALTH"
|
|
54
|
-
docker compose --project-name "$APP" ps
|
|
55
|
-
docker compose --project-name "$APP" logs --tail 60
|
|
61
|
+
docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" ps
|
|
62
|
+
docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" logs --tail 60
|
|
56
63
|
exit 1
|
|
57
64
|
fi
|
|
58
65
|
|
package/package.json
CHANGED
package/templates/deploy.yml
CHANGED
|
@@ -57,15 +57,6 @@ jobs:
|
|
|
57
57
|
else
|
|
58
58
|
echo "enabled=false" >> "$GITHUB_OUTPUT"
|
|
59
59
|
fi
|
|
60
|
-
- name: Materialize restricted iOS Maps key
|
|
61
|
-
if: ${{ steps.flutter.outputs.enabled == 'true' }}
|
|
62
|
-
shell: bash
|
|
63
|
-
env:
|
|
64
|
-
GOOGLE_MAPS_IOS_KEY: ${{ secrets.GOOGLE_MAPS_IOS_KEY }}
|
|
65
|
-
run: |
|
|
66
|
-
[ -n "${GOOGLE_MAPS_IOS_KEY:-}" ] || exit 0
|
|
67
|
-
umask 077
|
|
68
|
-
printf 'GOOGLE_MAPS_IOS_KEY=%s\n' "$GOOGLE_MAPS_IOS_KEY" > ios/Flutter/MapsKeys.xcconfig
|
|
69
60
|
- name: Set up Flutter
|
|
70
61
|
if: ${{ steps.flutter.outputs.enabled == 'true' }}
|
|
71
62
|
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
|
|
@@ -315,21 +306,6 @@ jobs:
|
|
|
315
306
|
flutter pub get
|
|
316
307
|
flutter gen-l10n
|
|
317
308
|
fi
|
|
318
|
-
- name: Materialize restricted Android Maps key
|
|
319
|
-
if: >-
|
|
320
|
-
${{ steps.mode.outputs.mode == 'local'
|
|
321
|
-
&& steps.flutter.outputs.enabled == 'true' }}
|
|
322
|
-
shell: bash
|
|
323
|
-
env:
|
|
324
|
-
GOOGLE_MAPS_ANDROID_KEY: ${{ secrets.GOOGLE_MAPS_ANDROID_KEY }}
|
|
325
|
-
run: |
|
|
326
|
-
[ -n "${GOOGLE_MAPS_ANDROID_KEY:-}" ] || exit 0
|
|
327
|
-
umask 077
|
|
328
|
-
mkdir -p android/app/src/main/res/values
|
|
329
|
-
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' \
|
|
330
|
-
'<resources>' \
|
|
331
|
-
" <string name=\"google_maps_key\" translatable=\"false\">$GOOGLE_MAPS_ANDROID_KEY</string>" \
|
|
332
|
-
'</resources>' > android/app/src/main/res/values/google_maps_api.xml
|
|
333
309
|
- name: Build and sign Android release
|
|
334
310
|
id: android
|
|
335
311
|
if: ${{ steps.mode.outputs.mode == 'local' }}
|
|
@@ -387,39 +363,19 @@ jobs:
|
|
|
387
363
|
${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
|
|
388
364
|
whatsNewDirectory: ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
|
|
389
365
|
|
|
390
|
-
#
|
|
391
|
-
#
|
|
392
|
-
#
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
${{ steps.mode.outputs.mode == 'local'
|
|
398
|
-
&& steps.play.outputs.ready == 'true'
|
|
399
|
-
&& steps.play-upload.outcome == 'failure' }}
|
|
400
|
-
uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
|
|
401
|
-
with:
|
|
402
|
-
serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
|
|
403
|
-
packageName: ${{ steps.android.outputs.package-name }}
|
|
404
|
-
releaseFiles: ${{ steps.android.outputs.bundle-path }}
|
|
405
|
-
tracks: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }}
|
|
406
|
-
status: ${{ vars.GOOGLE_PLAY_STATUS || 'completed' }}
|
|
407
|
-
userFraction: >-
|
|
408
|
-
${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
|
|
409
|
-
whatsNewDirectory: >-
|
|
410
|
-
${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
|
|
411
|
-
|
|
412
|
-
# If the ordinary fresh-edit retry also fails, Play may be requiring the
|
|
413
|
-
# changesNotSentForReview mode used after a policy rejection. A third, fresh edit
|
|
414
|
-
# is intentionally reserved for that case. Setting the flag on the edit-expiry
|
|
415
|
-
# retry is invalid for apps whose changes are submitted automatically.
|
|
366
|
+
# The retry serves two different failures, and both need a fresh edit:
|
|
367
|
+
# * the edit expired mid-upload (a large bundle on a slow runner), and
|
|
368
|
+
# * Play refused to auto-submit: "Changes cannot be sent for review
|
|
369
|
+
# automatically. Please set changesNotSentForReview to true." Play returns
|
|
370
|
+
# this once an app has changes a human must submit — after a policy
|
|
371
|
+
# rejection, for instance — and it fails the whole upload, so the built
|
|
372
|
+
# bundle never lands at all.
|
|
416
373
|
- name: Retry Google Play upload without auto-submit
|
|
417
|
-
id: play-upload-
|
|
374
|
+
id: play-upload-retry
|
|
418
375
|
if: >-
|
|
419
376
|
${{ steps.mode.outputs.mode == 'local'
|
|
420
377
|
&& steps.play.outputs.ready == 'true'
|
|
421
|
-
&& steps.play-upload.outcome == 'failure'
|
|
422
|
-
&& steps.play-upload-fresh-retry.outcome == 'failure' }}
|
|
378
|
+
&& steps.play-upload.outcome == 'failure' }}
|
|
423
379
|
uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
|
|
424
380
|
with:
|
|
425
381
|
serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
|
|
@@ -434,7 +390,7 @@ jobs:
|
|
|
434
390
|
changesNotSentForReview: true
|
|
435
391
|
|
|
436
392
|
- name: Say what still needs a human
|
|
437
|
-
if: ${{ always() && steps.play-upload-
|
|
393
|
+
if: ${{ always() && steps.play-upload-retry.outcome == 'success' }}
|
|
438
394
|
shell: bash
|
|
439
395
|
run: |
|
|
440
396
|
echo "::warning::Play would not accept this release for review automatically, so it was \
|