gowalk-cicd 1.0.73 → 1.0.75

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 CHANGED
@@ -149,6 +149,9 @@ node /path/to/gowalk-cicd/bin/cli.mjs
149
149
  It retains the account proxy, retries transient TLS/transport errors within 60 seconds, and emits
150
150
  only complete JSON. Partial responses and private curl stderr never enter action outputs;
151
151
  authentication/certificate failures remain terminal. SDK archive calls retain upstream behavior.
152
+ - Web Release selects bundled renderer resources with `--no-web-resources-cdn` inside
153
+ the existing proxied build command. This is not a font or integration offline guarantee;
154
+ preserve app-authored font declarations, bootstrap configuration and other build inputs.
152
155
  - Flutter setup scopes `SEGMENT_DOWNLOAD_TIMEOUT_MINS=1` to the composite action's optional
153
156
  SDK/pub cache restores. This bounds each cache segment, not the whole cache or SDK download.
154
157
  A cache timeout becomes a cache miss; normal proxied SDK setup remains mandatory and its
@@ -173,6 +176,12 @@ node /path/to/gowalk-cicd/bin/cli.mjs
173
176
  public HTTPS health probe. `select_certbot_account.sh` chooses one existing
174
177
  Let's Encrypt ACME v2 account deterministically so multi-account hosts stay
175
178
  non-interactive; legacy v1 account directories are not valid candidates.
179
+ `ensure_certificate.sh` preserves failed inventory status and retries only Certbot's
180
+ explicit lock-busy response (six attempts, 75 seconds total backoff). Every retry reads
181
+ inventory before issuance; absence needs recognized complete SAN records or Certbot's explicit
182
+ no-certificate sentinel. Empty/malformed output and arbitrary errors never mean absence. Raw command
183
+ output stays in an owned private temporary directory removed on exit. Do not remove
184
+ Certbot locks, stop other processes, force renewal or bypass the public HTTPS health gate.
176
185
  - The Android action serves two build systems. `android_config.project_kind()`
177
186
  is the only place that decides which; every downstream step branches on the
178
187
  `project_kind` output rather than re-sniffing the repo. Flutter wins the tie
package/README.md CHANGED
@@ -38,6 +38,11 @@ The conditional `deploy-web.yml` workflow runs on changes to Flutter source,
38
38
  web assets, package metadata, localization config, or the workflow itself. It
39
39
  also supports manual dispatch. The build uses `GITHUB_RUN_NUMBER` as its build
40
40
  number and uploads the complete `build/web` directory as a release artifact.
41
+ It builds with `--no-web-resources-cdn` so Flutter selects the renderer bundled in
42
+ that artifact. This does not make an app's fonts or integrations offline: declare
43
+ the actual default Roboto fallback and required locale fonts in the app's source
44
+ when offline rendering is required. Keep app-specific build inputs when adopting
45
+ the workflow; rebuild source rather than editing a retained release artifact.
41
46
 
42
47
  Private git dependencies use the same optional `GIT_PRIVATE_TOKEN` secret as
43
48
  the mobile workflow. No hosting provider is assumed: consumers can deploy the
@@ -1 +1 @@
1
- 1.0.73
1
+ 1.0.75
@@ -1 +1 @@
1
- 1.0.73
1
+ 1.0.75
@@ -1 +1 @@
1
- 1.0.73
1
+ 1.0.75
@@ -122,11 +122,12 @@ runs:
122
122
  rsync -az -e "$SSH" "$COMPOSE_FILE" \
123
123
  "${{ inputs.ssh-user }}@${{ inputs.host }}:$DEST/docker-compose.yml"
124
124
  fi
125
- # Ship remote helpers after the application mirror. Both are package
125
+ # Ship remote helpers after the application mirror. They are package
126
126
  # owned and kept out of the consumer's source tree.
127
127
  rsync -az -e "$SSH" \
128
128
  "${{ github.action_path }}/remote_deploy.sh" \
129
129
  "${{ github.action_path }}/select_certbot_account.sh" \
130
+ "${{ github.action_path }}/ensure_certificate.sh" \
130
131
  "${{ inputs.ssh-user }}@${{ inputs.host }}:$DEST/"
131
132
 
132
133
  - name: Deploy on the host
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env bash
2
+ # Sourced by remote_deploy.sh. Only a proven Certbot lock conflict may be retried.
3
+
4
+ certbot_lock_busy() {
5
+ grep -Fxq 'Another instance of Certbot is already running.' \
6
+ "$CERTBOT_WORK/stdout" "$CERTBOT_WORK/stderr"
7
+ }
8
+
9
+ certbot_attempt() {
10
+ local phase="$1" attempt="$2" status
11
+ shift 2
12
+ if certbot "$@" > "$CERTBOT_WORK/stdout" 2> "$CERTBOT_WORK/stderr"; then
13
+ log "certificate phase=$phase attempt=$attempt state=ok exit=0"
14
+ return 0
15
+ else
16
+ status=$?
17
+ fi
18
+ if certbot_lock_busy; then
19
+ log "certificate phase=$phase attempt=$attempt state=lock_busy exit=$status"
20
+ else
21
+ log "certificate phase=$phase attempt=$attempt state=error exit=$status"
22
+ fi
23
+ return "$status"
24
+ }
25
+
26
+ certificate_present() {
27
+ # 0=present, 1=confirmed absent, 2=unrecognized/partial inventory; preserve other awk failures.
28
+ awk -v domain="$1" '
29
+ function valid_san(value, labels, count, i, compressed, groups) {
30
+ if (index(value, ":")) {
31
+ count = split(value, labels, "::"); if (count > 2) return 0
32
+ compressed = count == 2
33
+ if (value ~ /^:[^:]/ || value ~ /[^:]:$/ || value ~ /:::/) return 0
34
+ count = split(value, labels, ":"); groups = 0
35
+ for (i = 1; i <= count; i++) if (labels[i] != "") {
36
+ if (labels[i] !~ /^[0-9A-Fa-f]+$/ || length(labels[i]) > 4) return 0
37
+ groups++
38
+ }
39
+ return compressed ? groups < 8 : groups == 8
40
+ }
41
+ sub(/^[*][.]/, "", value); count = split(value, labels, ".")
42
+ if (count < 2 || length(value) > 253) return 0
43
+ for (i = 1; i <= count; i++)
44
+ if (labels[i] !~ /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$/ || length(labels[i]) > 63) return 0
45
+ return 1
46
+ }
47
+ /^[[:space:]-]*$/ { next }
48
+ /^No certificates found[.]$/ { if (mode) bad = 1; mode = 2; next }
49
+ /^Found the following certs:$/ { if (mode) bad = 1; mode = 1; next }
50
+ $1 == "Certificate" && $2 == "Name:" {
51
+ if (mode != 1 || NF < 3 || (records && sans != 1)) bad = 1
52
+ records++; sans = 0; next
53
+ }
54
+ $1 == "Domains:" || $1 == "Identifiers:" {
55
+ if (mode != 1 || !records || sans || NF < 2) bad = 1
56
+ sans++
57
+ for (i = 2; i <= NF; i++) { if (!valid_san($i)) bad = 1; if ($i == domain) found = 1 }
58
+ next
59
+ }
60
+ /^[[:space:]]+(Serial Number|Key Type|Expiry Date|Certificate Path|Private Key Path):/ {
61
+ if (mode != 1 || !records) bad = 1
62
+ next
63
+ }
64
+ { bad = 1 }
65
+ END {
66
+ if (bad || !mode || (mode == 1 && (!records || sans != 1))) exit 2
67
+ exit !found
68
+ }
69
+ ' "$CERTBOT_WORK/stdout"
70
+ }
71
+
72
+ request_certificate() {
73
+ local domain="$1" email="$2" attempt="$3" account
74
+ set -- --nginx -d "$domain" --non-interactive --agree-tos --email "$email" --redirect
75
+ account="$(select_certbot_account)" || return $?
76
+ if [ -n "$account" ]; then
77
+ log "using an existing Let's Encrypt account"
78
+ set -- "$@" --account "$account"
79
+ fi
80
+ certbot_attempt issuance "$attempt" "$@"
81
+ }
82
+
83
+ ensure_certificate() (
84
+ local domain="$1" email="$2" delay attempt=0 status=1 CERTBOT_WORK
85
+ CERTBOT_WORK="$(mktemp -d "${TMPDIR:-/tmp}/gowalk-certbot.XXXXXXXX")" || return $?
86
+ trap 'rm -rf -- "$CERTBOT_WORK"' EXIT
87
+ trap 'exit 130' INT
88
+ trap 'exit 143' TERM
89
+ # Six attempts, 75 seconds total backoff. Commands retain the deployment job's bound.
90
+ for delay in 0 5 10 15 20 25; do
91
+ attempt=$((attempt + 1))
92
+ if [ "$delay" -gt 0 ]; then
93
+ log "certificate state=lock_retry attempt=$attempt backoff_seconds=$delay"
94
+ sleep "$delay" || return $?
95
+ fi
96
+ if certbot_attempt inventory "$attempt" certificates; then
97
+ if certificate_present "$domain"; then
98
+ log "cert for $domain already present"
99
+ return 0
100
+ else
101
+ status=$?
102
+ if [ "$status" -ne 1 ]; then
103
+ log "certificate phase=inventory_parse state=error exit=$status"
104
+ return "$status"
105
+ fi
106
+ fi
107
+ else
108
+ status=$?
109
+ certbot_lock_busy && continue
110
+ return "$status"
111
+ fi
112
+ log "requesting LE cert for $domain"
113
+ if request_certificate "$domain" "$email" "$attempt"; then
114
+ return 0
115
+ else
116
+ status=$?
117
+ certbot_lock_busy || return "$status"
118
+ fi
119
+ # A contender may have created this certificate. Re-read before another issuance.
120
+ done
121
+ log "certificate state=lock_retry_exhausted attempts=$attempt exit=$status"
122
+ return "$status"
123
+ )
@@ -11,6 +11,7 @@ CERT_EMAIL="${4:-admin@gowalk.com}"
11
11
  DIR="/opt/gowalk-backends/$APP"
12
12
  cd "$DIR"
13
13
  source "$DIR/select_certbot_account.sh"
14
+ source "$DIR/ensure_certificate.sh"
14
15
 
15
16
  log() { echo "[deploy $APP] $*"; }
16
17
 
@@ -93,22 +94,13 @@ NGINX
93
94
  nginx -t && systemctl reload nginx
94
95
  fi
95
96
 
96
- # ── Let's Encrypt cert (idempotent; certbot skips if current)
97
- if ! certbot certificates 2>/dev/null | grep -q "Domains: $DOMAIN\b"; then
98
- log "requesting LE cert for $DOMAIN"
99
- CERTBOT_ACCOUNT="$(select_certbot_account)"
100
- CERTBOT_ARGS=()
101
- if [ -n "$CERTBOT_ACCOUNT" ]; then
102
- log "using an existing Let's Encrypt account"
103
- CERTBOT_ARGS+=(--account "$CERTBOT_ACCOUNT")
104
- fi
105
- if ! certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos \
106
- --email "$CERT_EMAIL" --redirect "${CERTBOT_ARGS[@]}"; then
107
- log "ERROR: certbot failed for $DOMAIN"
108
- exit 1
109
- fi
97
+ # ── Let's Encrypt cert: failed inventory is never absence; only lock contention retries.
98
+ if ensure_certificate "$DOMAIN" "$CERT_EMAIL"; then
99
+ log "certificate ready for $DOMAIN"
110
100
  else
111
- log "cert for $DOMAIN already present"
101
+ status=$?
102
+ log "ERROR: certbot failed for $DOMAIN (exit=$status)"
103
+ exit "$status"
112
104
  fi
113
105
 
114
106
  # ── public health-check. This validates DNS, the certificate, nginx routing,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.73",
3
+ "version": "1.0.75",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,7 +53,7 @@ jobs:
53
53
  run: |
54
54
  flutter pub get
55
55
  if [ -f l10n.yaml ]; then flutter gen-l10n; fi
56
- flutter build web --release --build-number="${GITHUB_RUN_NUMBER}"
56
+ flutter build web --release --no-web-resources-cdn --build-number="${GITHUB_RUN_NUMBER}"
57
57
  - name: Retain Flutter web release
58
58
  uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
59
59
  env: