project-auto-wizard 0.13.3 → 0.14.0
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/README.md +1 -1
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +296 -257
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2279 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +64 -61
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +17 -12
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +59 -34
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +17 -10
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +54 -44
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +24 -17
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +85 -89
- package/src/commands/dry-run.js +63 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +45 -35
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +187 -0
- package/src/i18n/catalog/en/copy.js +19 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +187 -0
- package/src/i18n/catalog/ko/copy.js +19 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +128 -94
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +89 -88
|
@@ -1,62 +1,62 @@
|
|
|
1
1
|
# project-auto-wizard:managed-workflow
|
|
2
|
-
# Spring Boot
|
|
2
|
+
# Spring Boot zero-downtime deploy (Traefik Blue-Green, SSH + Docker)
|
|
3
3
|
#
|
|
4
|
-
# deploy
|
|
5
|
-
#
|
|
4
|
+
# On a push to the deploy branch, builds a Docker image and
|
|
5
|
+
# deploys it with no downtime to a server (any SSH-reachable host) using a Traefik label based Blue-Green toggle.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# This workflow is the zero-downtime deploy option.
|
|
8
|
+
# The default deploy is PROJECT-SPRING-SIMPLE-CICD.yaml.
|
|
9
|
+
# To switch, uncomment push.branches below and comment out the push trigger in SIMPLE-CICD.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
# APPLICATION_PROD_YML (
|
|
13
|
-
# DOCKERHUB_USERNAME: DockerHub
|
|
14
|
-
# DOCKERHUB_TOKEN: DockerHub
|
|
15
|
-
# SERVER_HOST:
|
|
16
|
-
# SERVER_USER: SSH
|
|
17
|
-
# SERVER_PASSWORD: SSH
|
|
18
|
-
# SSH_KEY: SSH
|
|
11
|
+
# Required GitHub Secrets
|
|
12
|
+
# APPLICATION_PROD_YML (optional): contents of application-prod.yml
|
|
13
|
+
# DOCKERHUB_USERNAME: DockerHub username
|
|
14
|
+
# DOCKERHUB_TOKEN: DockerHub access token
|
|
15
|
+
# SERVER_HOST: deployment server address
|
|
16
|
+
# SERVER_USER: SSH username
|
|
17
|
+
# SERVER_PASSWORD: SSH password (when SSH_AUTH_METHOD=password)
|
|
18
|
+
# SSH_KEY: contents of the SSH private key .pem (when SSH_AUTH_METHOD=key, e.g. AWS EC2)
|
|
19
19
|
#
|
|
20
|
-
#
|
|
20
|
+
# Environment variables (set in the env section)
|
|
21
21
|
#
|
|
22
|
-
#
|
|
23
|
-
# PROJECT_NAME:
|
|
24
|
-
# SERVICE_DOMAIN:
|
|
25
|
-
# CONTAINER_INTERNAL_PORT: Spring Boot
|
|
22
|
+
# Ports and containers:
|
|
23
|
+
# PROJECT_NAME: project name (container/image/Traefik router name)
|
|
24
|
+
# SERVICE_DOMAIN: external entry domain
|
|
25
|
+
# CONTAINER_INTERNAL_PORT: Spring Boot internal port (default: 8080)
|
|
26
26
|
#
|
|
27
27
|
# Traefik:
|
|
28
|
-
# TRAEFIK_NETWORK: Traefik docker
|
|
29
|
-
# TRAEFIK_ENTRYPOINT: Traefik entrypoint
|
|
30
|
-
# TRAEFIK_INTERNAL_PORT:
|
|
31
|
-
# EXTRA_NETWORKS:
|
|
28
|
+
# TRAEFIK_NETWORK: Traefik docker network (default: traefik-network)
|
|
29
|
+
# TRAEFIK_ENTRYPOINT: Traefik entrypoint name (default: web)
|
|
30
|
+
# TRAEFIK_INTERNAL_PORT: Traefik entry port on the NAS host (default: 8079)
|
|
31
|
+
# EXTRA_NETWORKS: extra docker networks (space separated, e.g. selenium-chrome-network)
|
|
32
32
|
#
|
|
33
|
-
#
|
|
34
|
-
# ENABLE_VOLUME_MOUNT:
|
|
35
|
-
# VOLUME_HOST_PATH:
|
|
36
|
-
# VOLUME_CONTAINER_PATH:
|
|
33
|
+
# Volume/storage:
|
|
34
|
+
# ENABLE_VOLUME_MOUNT: whether to mount a volume (true/false)
|
|
35
|
+
# VOLUME_HOST_PATH: host volume path
|
|
36
|
+
# VOLUME_CONTAINER_PATH: mount path inside the container
|
|
37
37
|
#
|
|
38
|
-
# SSH
|
|
39
|
-
# SSH_PORT: SSH
|
|
40
|
-
# SSH_COMMAND_TIMEOUT:
|
|
41
|
-
# SSH_CONNECTION_TIMEOUT:
|
|
38
|
+
# SSH connection:
|
|
39
|
+
# SSH_PORT: SSH port (default: 2022)
|
|
40
|
+
# SSH_COMMAND_TIMEOUT: command timeout (default: 600s)
|
|
41
|
+
# SSH_CONNECTION_TIMEOUT: connection timeout (default: 60s)
|
|
42
42
|
#
|
|
43
|
-
#
|
|
44
|
-
# HEALTHCHECK_PATH:
|
|
45
|
-
# HEALTHCHECK_ACCEPT_CODES:
|
|
46
|
-
# HEALTHCHECK_MAX_RETRIES:
|
|
47
|
-
# HEALTHCHECK_RETRY_INTERVAL:
|
|
48
|
-
# IN_FLIGHT_WAIT:
|
|
43
|
+
# Health check:
|
|
44
|
+
# HEALTHCHECK_PATH: path for the GET call through Traefik (default: /)
|
|
45
|
+
# HEALTHCHECK_ACCEPT_CODES: HTTP codes accepted as healthy (| separated, default: 200|301|302|308)
|
|
46
|
+
# HEALTHCHECK_MAX_RETRIES: max attempts (default: 36)
|
|
47
|
+
# HEALTHCHECK_RETRY_INTERVAL: retry interval in seconds (default: 5)
|
|
48
|
+
# IN_FLIGHT_WAIT: seconds to wait before removing the old container (default: 10)
|
|
49
49
|
#
|
|
50
|
-
#
|
|
51
|
-
# -
|
|
52
|
-
# -
|
|
53
|
-
# (
|
|
50
|
+
# Prerequisites:
|
|
51
|
+
# - a Traefik container must be running on the deploy server in traefik-network
|
|
52
|
+
# - reverse proxy: ${SERVICE_DOMAIN}:443 -> localhost:${TRAEFIK_INTERNAL_PORT}
|
|
53
|
+
# (e.g. Synology DSM reverse proxy, Nginx, Caddy)
|
|
54
54
|
|
|
55
55
|
name: PROJECT-SPRING-NONSTOP-TRAEFIK-CICD
|
|
56
56
|
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
57
|
+
# Trigger settings
|
|
58
|
+
# The automatic trigger on a push to the deploy branch is commented out.
|
|
59
|
+
# To switch to this workflow, uncomment push.branches below.
|
|
60
60
|
on:
|
|
61
61
|
# push:
|
|
62
62
|
# branches:
|
|
@@ -64,48 +64,48 @@ on:
|
|
|
64
64
|
workflow_dispatch:
|
|
65
65
|
|
|
66
66
|
env:
|
|
67
|
-
#
|
|
67
|
+
# Project settings - change to your actual project name
|
|
68
68
|
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
69
|
-
#
|
|
69
|
+
# Subdirectory of this project in a monorepo (--paths). Base for the build and Docker context ('.' = repo root)
|
|
70
70
|
PROJECT_PATH: "." # @wizard auto:project-path
|
|
71
|
-
#
|
|
72
|
-
#
|
|
71
|
+
# In a monorepo (PROJECT_PATH is not '.') '-spring' is appended to image, container and domain names
|
|
72
|
+
# so they do not collide with services of other types in the same repo (e.g. my-app-spring)
|
|
73
73
|
SERVICE_DOMAIN: "__SERVICE_DOMAIN__" # @wizard ask:example.com
|
|
74
74
|
|
|
75
|
-
# Spring Boot
|
|
75
|
+
# Spring Boot settings
|
|
76
76
|
SPRING_PROFILE: "prod"
|
|
77
77
|
JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:@jdk
|
|
78
78
|
GRADLE_OPTS: "-Dspring.profiles.active=prod"
|
|
79
79
|
|
|
80
|
-
#
|
|
80
|
+
# Path settings
|
|
81
81
|
APPLICATION_YML_DIR: "__APPLICATION_YML_DIR__" # @wizard auto:spring-app-yml-dir
|
|
82
|
-
DOCKERFILE_PATH: "./Dockerfile" #
|
|
82
|
+
DOCKERFILE_PATH: "./Dockerfile" # relative to PROJECT_PATH
|
|
83
83
|
|
|
84
|
-
#
|
|
84
|
+
# Port and container settings
|
|
85
85
|
CONTAINER_INTERNAL_PORT: "8080"
|
|
86
86
|
|
|
87
|
-
# Traefik
|
|
87
|
+
# Traefik settings (do not change after the server's Traefik environment is set up)
|
|
88
88
|
TRAEFIK_NETWORK: "traefik-network"
|
|
89
89
|
TRAEFIK_ENTRYPOINT: "web"
|
|
90
90
|
TRAEFIK_INTERNAL_PORT: "8079"
|
|
91
91
|
EXTRA_NETWORKS: ""
|
|
92
92
|
|
|
93
|
-
#
|
|
93
|
+
# Volume mount settings
|
|
94
94
|
ENABLE_VOLUME_MOUNT: "false" # @wizard ask:false
|
|
95
95
|
VOLUME_HOST_PATH: "/volume1/projects/__PROJECT_NAME__" # @wizard ask:/volume1/projects/__PROJECT_NAME__
|
|
96
96
|
VOLUME_CONTAINER_PATH: "/mnt/__PROJECT_NAME__" # @wizard ask:/mnt/__PROJECT_NAME__
|
|
97
97
|
|
|
98
|
-
# SSH
|
|
99
|
-
# password
|
|
100
|
-
# key
|
|
98
|
+
# SSH auth method: "password" (Synology / generic servers) | "key" (AWS EC2 / .pem)
|
|
99
|
+
# password -> uses the SERVER_PASSWORD secret
|
|
100
|
+
# key -> uses the SSH_KEY secret (the full .pem contents as the secret value)
|
|
101
101
|
SSH_AUTH_METHOD: "password" # @wizard ask:password
|
|
102
102
|
|
|
103
|
-
# SSH
|
|
103
|
+
# SSH connection settings
|
|
104
104
|
SSH_PORT: "__SSH_PORT__" # @wizard ask:2022
|
|
105
105
|
SSH_COMMAND_TIMEOUT: "600s"
|
|
106
106
|
SSH_CONNECTION_TIMEOUT: "60s"
|
|
107
107
|
|
|
108
|
-
#
|
|
108
|
+
# Health check settings
|
|
109
109
|
HEALTHCHECK_PATH: "/"
|
|
110
110
|
HEALTHCHECK_ACCEPT_CODES: "200|301|302|308"
|
|
111
111
|
HEALTHCHECK_MAX_RETRIES: "36"
|
|
@@ -113,17 +113,17 @@ env:
|
|
|
113
113
|
IN_FLIGHT_WAIT: "10"
|
|
114
114
|
|
|
115
115
|
jobs:
|
|
116
|
-
#
|
|
116
|
+
# Build job
|
|
117
117
|
build:
|
|
118
|
-
name: Spring Boot
|
|
118
|
+
name: Build Spring Boot application
|
|
119
119
|
runs-on: ubuntu-latest
|
|
120
120
|
|
|
121
121
|
steps:
|
|
122
|
-
- name:
|
|
122
|
+
- name: Checkout code
|
|
123
123
|
uses: actions/checkout@v7
|
|
124
124
|
|
|
125
|
-
#
|
|
126
|
-
- name:
|
|
125
|
+
# If a required Secret or the Dockerfile is missing, name it and stop instead of failing later in a third-party step such as docker login
|
|
126
|
+
- name: Deploy pre-check (Secrets and Dockerfile)
|
|
127
127
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
128
128
|
env:
|
|
129
129
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
@@ -134,11 +134,12 @@ jobs:
|
|
|
134
134
|
SSH_KEY: ${{ secrets.SSH_KEY }}
|
|
135
135
|
DOCKERFILE: ${{ env.DOCKERFILE_PATH }}
|
|
136
136
|
run: |
|
|
137
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
137
138
|
MISSING=""
|
|
138
139
|
for NAME in DOCKERHUB_USERNAME DOCKERHUB_TOKEN SERVER_HOST SERVER_USER; do
|
|
139
140
|
[ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
|
|
140
141
|
done
|
|
141
|
-
# SSH_AUTH_METHOD=key
|
|
142
|
+
# SSH_AUTH_METHOD=key needs SSH_KEY, anything else needs SERVER_PASSWORD
|
|
142
143
|
if [ "${SSH_AUTH_METHOD:-password}" = "key" ]; then
|
|
143
144
|
[ -n "$SSH_KEY" ] || MISSING="$MISSING SSH_KEY"
|
|
144
145
|
else
|
|
@@ -146,45 +147,46 @@ jobs:
|
|
|
146
147
|
fi
|
|
147
148
|
FAILED=false
|
|
148
149
|
if [ -n "$MISSING" ]; then
|
|
149
|
-
echo "::error title
|
|
150
|
+
echo "::error title=$(m cicd.precheck_secret_title)::$(m cicd.precheck_secret_body missing="$MISSING")"
|
|
150
151
|
FAILED=true
|
|
151
152
|
fi
|
|
152
153
|
if [ ! -f "$DOCKERFILE" ]; then
|
|
153
|
-
echo "::error title=
|
|
154
|
+
echo "::error title=$(m cicd.precheck_dockerfile_title)::$(m cicd.precheck_dockerfile_body path="${PROJECT_PATH}/${DOCKERFILE#./}")"
|
|
154
155
|
FAILED=true
|
|
155
156
|
fi
|
|
156
157
|
if [ "$FAILED" = "true" ]; then
|
|
157
158
|
exit 1
|
|
158
159
|
fi
|
|
159
|
-
|
|
160
|
+
m cicd.precheck_ok
|
|
160
161
|
|
|
161
|
-
- name: Java
|
|
162
|
+
- name: Set up Java
|
|
162
163
|
uses: actions/setup-java@v5
|
|
163
164
|
with:
|
|
164
165
|
java-version: ${{ env.JAVA_VERSION }}
|
|
165
166
|
distribution: 'temurin'
|
|
166
167
|
cache: 'gradle'
|
|
167
168
|
|
|
168
|
-
- name: Gradle
|
|
169
|
+
- name: Make Gradle wrapper executable
|
|
169
170
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
170
171
|
run: chmod +x gradlew
|
|
171
172
|
|
|
172
|
-
- name: application-prod.yml
|
|
173
|
+
- name: Generate application-prod.yml
|
|
173
174
|
run: |
|
|
175
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
174
176
|
mkdir -p ${{ env.APPLICATION_YML_DIR }}
|
|
175
177
|
cat << 'EOF' > ./${{ env.APPLICATION_YML_DIR }}/application-prod.yml
|
|
176
178
|
${{ secrets.APPLICATION_PROD_YML }}
|
|
177
179
|
EOF
|
|
178
|
-
|
|
180
|
+
m wf_spring_deploy.prod_yml_created dir="${{ env.APPLICATION_YML_DIR }}"
|
|
179
181
|
|
|
180
182
|
- name: Build with Gradle
|
|
181
183
|
working-directory: ${{ env.PROJECT_PATH }}
|
|
182
184
|
run: ./gradlew clean build -x test ${{ env.GRADLE_OPTS }}
|
|
183
185
|
|
|
184
|
-
- name: Docker
|
|
186
|
+
- name: Set up Docker Buildx
|
|
185
187
|
uses: docker/setup-buildx-action@v3
|
|
186
188
|
|
|
187
|
-
- name: DockerHub
|
|
189
|
+
- name: Log in to DockerHub
|
|
188
190
|
uses: docker/login-action@v3
|
|
189
191
|
with:
|
|
190
192
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
@@ -198,7 +200,7 @@ jobs:
|
|
|
198
200
|
restore-keys: |
|
|
199
201
|
${{ runner.os }}-buildx-
|
|
200
202
|
|
|
201
|
-
- name:
|
|
203
|
+
- name: Build and push Docker image
|
|
202
204
|
uses: docker/build-push-action@v5
|
|
203
205
|
with:
|
|
204
206
|
context: ${{ env.PROJECT_PATH }}
|
|
@@ -213,16 +215,34 @@ jobs:
|
|
|
213
215
|
rm -rf /tmp/.buildx-cache
|
|
214
216
|
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
215
217
|
|
|
216
|
-
#
|
|
218
|
+
# Deploy job (Blue-Green, zero downtime)
|
|
217
219
|
deploy:
|
|
218
|
-
name: Blue-Green
|
|
220
|
+
name: Blue-Green zero-downtime deploy
|
|
219
221
|
needs: build
|
|
220
222
|
runs-on: ubuntu-latest
|
|
221
223
|
|
|
222
224
|
steps:
|
|
225
|
+
# The deploy script runs on the server, which has no messages.py:
|
|
226
|
+
# render the message catalog here and pass it along as a base64 env var
|
|
227
|
+
- name: Checkout message catalog
|
|
228
|
+
uses: actions/checkout@v7
|
|
229
|
+
with:
|
|
230
|
+
sparse-checkout: |
|
|
231
|
+
.github/scripts/messages.py
|
|
232
|
+
version.yml
|
|
233
|
+
sparse-checkout-cone-mode: false
|
|
234
|
+
|
|
235
|
+
- name: Load messages
|
|
236
|
+
run: |
|
|
237
|
+
MSGS="$(python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" dump wf_spring_deploy. \
|
|
238
|
+
| python3 -c 'import json, shlex, sys; d = json.load(sys.stdin); print("\n".join("M_%s=%s" % (k.replace(".", "_"), shlex.quote(v)) for k, v in d.items()))' \
|
|
239
|
+
| base64 | tr -d '\n')"
|
|
240
|
+
echo "PAW_MSG_B64=$MSGS" >> "$GITHUB_ENV"
|
|
241
|
+
|
|
223
242
|
- name: Deploy
|
|
224
243
|
uses: appleboy/ssh-action@v1.0.3
|
|
225
244
|
env:
|
|
245
|
+
PAW_MSG_B64: ${{ env.PAW_MSG_B64 }}
|
|
226
246
|
PROJECT_NAME: ${{ env.PROJECT_PATH != '.' && format('{0}-spring', env.PROJECT_NAME) || env.PROJECT_NAME }}
|
|
227
247
|
SERVICE_DOMAIN: ${{ env.SERVICE_DOMAIN }}
|
|
228
248
|
CONTAINER_INTERNAL_PORT: ${{ env.CONTAINER_INTERNAL_PORT }}
|
|
@@ -251,38 +271,48 @@ jobs:
|
|
|
251
271
|
port: ${{ env.SSH_PORT }}
|
|
252
272
|
command_timeout: ${{ env.SSH_COMMAND_TIMEOUT }}
|
|
253
273
|
timeout: ${{ env.SSH_CONNECTION_TIMEOUT }}
|
|
254
|
-
envs: PROJECT_NAME,SERVICE_DOMAIN,CONTAINER_INTERNAL_PORT,SPRING_PROFILE,ENABLE_VOLUME_MOUNT,VOLUME_HOST_PATH,VOLUME_CONTAINER_PATH,EXTRA_NETWORKS,TRAEFIK_NETWORK,TRAEFIK_ENTRYPOINT,TRAEFIK_INTERNAL_PORT,HEALTHCHECK_PATH,HEALTHCHECK_ACCEPT_CODES,HEALTHCHECK_MAX_RETRIES,HEALTHCHECK_RETRY_INTERVAL,IN_FLIGHT_WAIT,BRANCH_REF,DOCKERHUB_USERNAME,SERVER_PASSWORD,SSH_AUTH_METHOD
|
|
274
|
+
envs: PROJECT_NAME,SERVICE_DOMAIN,CONTAINER_INTERNAL_PORT,SPRING_PROFILE,ENABLE_VOLUME_MOUNT,VOLUME_HOST_PATH,VOLUME_CONTAINER_PATH,EXTRA_NETWORKS,TRAEFIK_NETWORK,TRAEFIK_ENTRYPOINT,TRAEFIK_INTERNAL_PORT,HEALTHCHECK_PATH,HEALTHCHECK_ACCEPT_CODES,HEALTHCHECK_MAX_RETRIES,HEALTHCHECK_RETRY_INTERVAL,IN_FLIGHT_WAIT,BRANCH_REF,DOCKERHUB_USERNAME,SERVER_PASSWORD,SSH_AUTH_METHOD,PAW_MSG_B64
|
|
255
275
|
script: |
|
|
256
276
|
set -e
|
|
257
277
|
export PATH=$PATH:/usr/local/bin
|
|
258
278
|
export PW="${SERVER_PASSWORD}"
|
|
259
279
|
|
|
260
|
-
#
|
|
280
|
+
# Messages arrive as base64 shell assignments (the server has no messages.py)
|
|
281
|
+
eval "$(printf '%s' "$PAW_MSG_B64" | base64 -d)"
|
|
282
|
+
m() {
|
|
283
|
+
local _v="M_wf_spring_deploy_$1" _kv
|
|
284
|
+
local _s="${!_v}"
|
|
285
|
+
shift
|
|
286
|
+
for _kv in "$@"; do _s=${_s//\{${_kv%%=*}\}/${_kv#*=}}; done
|
|
287
|
+
printf '%s\n' "$_s"
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# Abstract sudo by SSH auth method
|
|
261
291
|
SSH_AUTH_METHOD="${SSH_AUTH_METHOD:-password}"
|
|
262
292
|
if [ "${SSH_AUTH_METHOD}" = "key" ]; then
|
|
263
293
|
SUDO() { sudo "$@"; }
|
|
264
294
|
else
|
|
265
295
|
SUDO() { echo "$PW" | sudo -S "$@"; }
|
|
266
296
|
fi
|
|
267
|
-
|
|
297
|
+
m ssh_auth_method method="${SSH_AUTH_METHOD}"
|
|
268
298
|
|
|
269
299
|
IMAGE="${DOCKERHUB_USERNAME}/${PROJECT_NAME}:${BRANCH_REF}"
|
|
270
300
|
ROUTER_NAME="${PROJECT_NAME}"
|
|
271
301
|
|
|
272
302
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
273
|
-
|
|
303
|
+
m traefik_deploy_start
|
|
274
304
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
m banner_project name="${PROJECT_NAME}"
|
|
306
|
+
m banner_domain domain="${SERVICE_DOMAIN}"
|
|
307
|
+
m banner_image image="${IMAGE}"
|
|
308
|
+
m traefik_entry port="${TRAEFIK_INTERNAL_PORT}" entrypoint="${TRAEFIK_ENTRYPOINT}"
|
|
279
309
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
280
310
|
|
|
281
|
-
# 1.
|
|
282
|
-
|
|
311
|
+
# 1. Pull the latest image
|
|
312
|
+
m image_pull image="${IMAGE}"
|
|
283
313
|
SUDO docker pull "${IMAGE}"
|
|
284
314
|
|
|
285
|
-
# 2.
|
|
315
|
+
# 2. Determine the color of the active container (by whether it carries the Traefik label)
|
|
286
316
|
ACTIVE=$(SUDO docker ps \
|
|
287
317
|
--filter "label=traefik.http.routers.${ROUTER_NAME}.rule" \
|
|
288
318
|
--filter "name=${PROJECT_NAME}-" \
|
|
@@ -295,7 +325,7 @@ jobs:
|
|
|
295
325
|
NEW_COLOR="blue"
|
|
296
326
|
OLD_COLOR="green"
|
|
297
327
|
else
|
|
298
|
-
#
|
|
328
|
+
# First deploy or no active container -> start with blue
|
|
299
329
|
NEW_COLOR="blue"
|
|
300
330
|
OLD_COLOR="green"
|
|
301
331
|
fi
|
|
@@ -305,25 +335,25 @@ jobs:
|
|
|
305
335
|
|
|
306
336
|
echo "🎨 active=${ACTIVE:-none} → new=${NEW_NAME}, old=${OLD_NAME}"
|
|
307
337
|
|
|
308
|
-
# 3.
|
|
309
|
-
|
|
338
|
+
# 3. Clean up a leftover container in the same (idle) slot
|
|
339
|
+
m idle_slot_cleanup name="${NEW_NAME}"
|
|
310
340
|
SUDO docker rm -f "${NEW_NAME}" 2>/dev/null || true
|
|
311
341
|
|
|
312
|
-
# 4.
|
|
342
|
+
# 4. Build the volume mount option
|
|
313
343
|
VOLUME_MOUNT_OPTION="-v /etc/localtime:/etc/localtime:ro"
|
|
314
344
|
if [ "${ENABLE_VOLUME_MOUNT}" = "true" ]; then
|
|
315
345
|
if [ ! -d "${VOLUME_HOST_PATH}" ]; then
|
|
316
|
-
|
|
346
|
+
m volume_mkdir path="${VOLUME_HOST_PATH}"
|
|
317
347
|
SUDO mkdir -p "${VOLUME_HOST_PATH}"
|
|
318
348
|
fi
|
|
319
349
|
VOLUME_MOUNT_OPTION="${VOLUME_MOUNT_OPTION} -v ${VOLUME_HOST_PATH}:${VOLUME_CONTAINER_PATH}"
|
|
320
|
-
|
|
350
|
+
m volume_enabled host="${VOLUME_HOST_PATH}" container="${VOLUME_CONTAINER_PATH}"
|
|
321
351
|
else
|
|
322
|
-
|
|
352
|
+
m volume_disabled
|
|
323
353
|
fi
|
|
324
354
|
|
|
325
|
-
# 5.
|
|
326
|
-
|
|
355
|
+
# 5. Start the new container (with Traefik labels)
|
|
356
|
+
m new_container_start name="${NEW_NAME}"
|
|
327
357
|
SUDO docker run -d \
|
|
328
358
|
--name "${NEW_NAME}" \
|
|
329
359
|
--network "${TRAEFIK_NETWORK}" \
|
|
@@ -336,21 +366,21 @@ jobs:
|
|
|
336
366
|
${VOLUME_MOUNT_OPTION} \
|
|
337
367
|
"${IMAGE}"
|
|
338
368
|
|
|
339
|
-
# 6.
|
|
369
|
+
# 6. Connect extra networks (EXTRA_NETWORKS, space separated)
|
|
340
370
|
if [ -n "${EXTRA_NETWORKS}" ]; then
|
|
341
371
|
for net in ${EXTRA_NETWORKS}; do
|
|
342
|
-
|
|
372
|
+
m traefik_extra_network net="${net}"
|
|
343
373
|
SUDO docker network connect "${net}" "${NEW_NAME}" 2>/dev/null || true
|
|
344
374
|
done
|
|
345
375
|
fi
|
|
346
376
|
|
|
347
|
-
# 7. Traefik
|
|
348
|
-
|
|
377
|
+
# 7. Print the Traefik labels for debugging (to verify escaping)
|
|
378
|
+
m traefik_labels
|
|
349
379
|
SUDO docker inspect "${NEW_NAME}" --format '{{range $k, $v := .Config.Labels}} {{$k}} = {{$v}}{{"\n"}}{{end}}' | grep traefik || true
|
|
350
380
|
|
|
351
|
-
# 8.
|
|
381
|
+
# 8. Health check - verify routing through Traefik
|
|
352
382
|
MAX_WAIT=$((HEALTHCHECK_MAX_RETRIES * HEALTHCHECK_RETRY_INTERVAL))
|
|
353
|
-
|
|
383
|
+
m traefik_hc_start max_wait="${MAX_WAIT}" retries="${HEALTHCHECK_MAX_RETRIES}" interval="${HEALTHCHECK_RETRY_INTERVAL}"
|
|
354
384
|
|
|
355
385
|
RETRY_COUNT=0
|
|
356
386
|
HEALTHCHECK_SUCCESS=false
|
|
@@ -362,12 +392,12 @@ jobs:
|
|
|
362
392
|
STATUS=$(SUDO docker inspect --format='{{.State.Status}}' "${NEW_NAME}" 2>/dev/null || echo "not_found")
|
|
363
393
|
|
|
364
394
|
if [ "$STATUS" = "exited" ]; then
|
|
365
|
-
|
|
395
|
+
m traefik_exited
|
|
366
396
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
367
397
|
SUDO docker logs --tail 200 "${NEW_NAME}"
|
|
368
398
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
369
|
-
|
|
370
|
-
|
|
399
|
+
m traefik_kept name="${NEW_NAME}"
|
|
400
|
+
m traefik_rollback_note name="${OLD_NAME}"
|
|
371
401
|
exit 1
|
|
372
402
|
fi
|
|
373
403
|
|
|
@@ -377,64 +407,64 @@ jobs:
|
|
|
377
407
|
"http://localhost:${TRAEFIK_INTERNAL_PORT}${HEALTHCHECK_PATH}" 2>/dev/null || echo "000")
|
|
378
408
|
|
|
379
409
|
if echo "$HTTP_CODE" | grep -Eq "^(${HEALTHCHECK_ACCEPT_CODES})$"; then
|
|
380
|
-
|
|
410
|
+
m traefik_hc_pass n="${RETRY_COUNT}" max="${HEALTHCHECK_MAX_RETRIES}" code="${HTTP_CODE}"
|
|
381
411
|
HEALTHCHECK_SUCCESS=true
|
|
382
412
|
break
|
|
383
413
|
fi
|
|
384
414
|
fi
|
|
385
415
|
|
|
386
|
-
|
|
416
|
+
m traefik_waiting n="${RETRY_COUNT}" max="${HEALTHCHECK_MAX_RETRIES}" status="${STATUS}" http="${HTTP_CODE:-?}"
|
|
387
417
|
done
|
|
388
418
|
|
|
389
419
|
if [ "$HEALTHCHECK_SUCCESS" = "false" ]; then
|
|
390
|
-
|
|
420
|
+
m traefik_hc_timeout sec="${MAX_WAIT}"
|
|
391
421
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
392
|
-
|
|
422
|
+
m traefik_logs_header name="${NEW_NAME}"
|
|
393
423
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
394
424
|
SUDO docker logs --tail 200 "${NEW_NAME}"
|
|
395
425
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
396
|
-
|
|
397
|
-
|
|
426
|
+
m traefik_kept name="${NEW_NAME}"
|
|
427
|
+
m traefik_rollback_note name="${OLD_NAME}"
|
|
398
428
|
exit 1
|
|
399
429
|
fi
|
|
400
430
|
|
|
401
|
-
# 9. in-flight
|
|
431
|
+
# 9. Let in-flight requests finish, then remove the old container automatically
|
|
402
432
|
if SUDO docker ps -a --format '{{.Names}}' | grep -Eq "^${OLD_NAME}$"; then
|
|
403
|
-
|
|
433
|
+
m inflight_wait sec="${IN_FLIGHT_WAIT}"
|
|
404
434
|
sleep ${IN_FLIGHT_WAIT}
|
|
405
|
-
|
|
435
|
+
m old_removed name="${OLD_NAME}"
|
|
406
436
|
SUDO docker rm -f "${OLD_NAME}"
|
|
407
437
|
else
|
|
408
|
-
|
|
438
|
+
m old_absent name="${OLD_NAME}"
|
|
409
439
|
fi
|
|
410
440
|
|
|
411
|
-
# 10. dangling
|
|
412
|
-
|
|
441
|
+
# 10. Clean up dangling images
|
|
442
|
+
m prune_dangling
|
|
413
443
|
SUDO docker image prune -af
|
|
414
444
|
|
|
415
445
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
416
|
-
|
|
417
|
-
|
|
446
|
+
m traefik_done name="${NEW_NAME}"
|
|
447
|
+
m banner_domain domain="https://${SERVICE_DOMAIN}"
|
|
418
448
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
419
449
|
|
|
420
|
-
#
|
|
450
|
+
# Usage
|
|
421
451
|
#
|
|
422
|
-
# 1) GitHub Secrets
|
|
452
|
+
# 1) Register the GitHub Secrets:
|
|
423
453
|
# APPLICATION_PROD_YML, DOCKERHUB_USERNAME, DOCKERHUB_TOKEN,
|
|
424
454
|
# SERVER_HOST, SERVER_USER, SERVER_PASSWORD
|
|
425
455
|
#
|
|
426
|
-
# 2)
|
|
456
|
+
# 2) Edit the project-specific settings in this workflow's env block:
|
|
427
457
|
# - PROJECT_NAME, SERVICE_DOMAIN
|
|
428
458
|
# - APPLICATION_YML_DIR, CONTAINER_INTERNAL_PORT
|
|
429
459
|
# - ENABLE_VOLUME_MOUNT, VOLUME_HOST_PATH, VOLUME_CONTAINER_PATH
|
|
430
|
-
# - EXTRA_NETWORKS (
|
|
460
|
+
# - EXTRA_NETWORKS (e.g. selenium-chrome-network)
|
|
431
461
|
# - HEALTHCHECK_PATH, HEALTHCHECK_ACCEPT_CODES
|
|
432
462
|
#
|
|
433
|
-
# 3)
|
|
434
|
-
# - Traefik
|
|
435
|
-
# -
|
|
436
|
-
# (
|
|
463
|
+
# 3) Server preparation:
|
|
464
|
+
# - a Traefik container must be running in traefik-network
|
|
465
|
+
# - reverse proxy: ${SERVICE_DOMAIN}:443 -> localhost:${TRAEFIK_INTERNAL_PORT}
|
|
466
|
+
# (e.g. Synology DSM reverse proxy, Nginx, Caddy)
|
|
437
467
|
#
|
|
438
|
-
# 4)
|
|
439
|
-
# -
|
|
440
|
-
# - PROJECT-SPRING-SIMPLE-CICD.yaml
|
|
468
|
+
# 4) Switch to this workflow:
|
|
469
|
+
# - uncomment push.branches in this file
|
|
470
|
+
# - comment out the push trigger in PROJECT-SPRING-SIMPLE-CICD.yaml
|