This commit is contained in:
ponzischeme89
2026-08-17 13:13:10 +12:00
parent 12b27c77c3
commit 43ab18854c
39 changed files with 1707 additions and 1528 deletions
+25 -121
View File
@@ -26,8 +26,7 @@ SSH performs the password prompt directly. The password is never read or stored
by this script.
This deploys the current local working tree, including uncommitted server changes.
Use -SkipAppRelease -SkipBuilder for an admin/server-only deployment: no APK is built or
published, and the running Android builder container and image are left untouched.
Use -SkipAppRelease for an admin/server-only deployment: no APK is built or published.
.EXAMPLE
.\deploy-server.ps1
@@ -48,7 +47,7 @@ published, and the running Android builder container and image are left untouche
.\deploy-server.ps1 -EstimateOnly
.EXAMPLE
.\deploy-server.ps1 -SkipAppRelease -SkipBuilder
.\deploy-server.ps1 -SkipAppRelease
#>
#Requires -Version 7.2
@@ -88,11 +87,6 @@ param(
[Parameter()]
[switch] $SkipAppRelease,
# Preserve the Android builder container and reuse its image. This skips the SDK
# image build and leaves the release controller untouched during an admin/server deploy.
[Parameter()]
[switch] $SkipBuilder,
[Parameter()]
[Alias('m')]
[switch] $MandatoryUpdate,
@@ -427,9 +421,6 @@ function Write-Banner {
if ($quietDeployment) {
Write-Styled -Message '│ NOTICE quiet (no advance television announcement)' -Colour Gray
}
if ($SkipBuilder) {
Write-Styled -Message '│ BUILDER preserve the running Android builder' -Colour Gray
}
$initialEstimate = ($script:PhaseOrder | ForEach-Object { (Get-PhaseEstimate -Key $_).Seconds } |
Measure-Object -Sum).Sum
$sampleCounts = @($script:PhaseOrder | ForEach-Object { (Get-PhaseEstimate -Key $_).Samples })
@@ -630,8 +621,7 @@ function New-DeploymentArchive {
'--exclude', 'admin-ui/node_modules', '--exclude', 'admin-ui/node_modules/*',
'--exclude', 'admin-ui/dist', '--exclude', 'admin-ui/dist/*',
'-C', $RepositoryDirectory,
'server', 'admin-ui', 'builder', 'docker-compose.yml', '.env.example',
'builder.env.example'
'server', 'admin-ui', 'docker-compose.yml', '.env.example'
)
if ($ReleaseDirectory) {
$arguments += @(
@@ -789,13 +779,8 @@ try {
(Join-Path $checkoutDirectory 'admin-ui/Dockerfile'),
(Join-Path $checkoutDirectory 'admin-ui/package.json'),
(Join-Path $checkoutDirectory 'admin-ui/src'),
(Join-Path $checkoutDirectory 'builder'),
(Join-Path $checkoutDirectory 'builder/Dockerfile'),
(Join-Path $checkoutDirectory 'builder/release.sh'),
(Join-Path $checkoutDirectory 'builder/controller.go'),
(Join-Path $checkoutDirectory 'docker-compose.yml'),
(Join-Path $checkoutDirectory '.env.example'),
(Join-Path $checkoutDirectory 'builder.env.example')
(Join-Path $checkoutDirectory '.env.example')
)
foreach ($requiredPath in $requiredPaths) {
if (-not (Test-Path -LiteralPath $requiredPath)) {
@@ -804,7 +789,6 @@ try {
}
Write-Detail 'server/ build context'
Write-Detail 'admin-ui/ build context'
Write-Detail 'builder/ release toolchain'
Write-Detail 'docker-compose.yml'
Write-Detail '.env.example'
$releaseDirectory = ''
@@ -914,7 +898,6 @@ set -eu
destination='__DESTINATION__'
health_timeout=__HEALTH_TIMEOUT__
publish_release=__PUBLISH_RELEASE__
skip_builder=__SKIP_BUILDER__
mandatory_update=__MANDATORY_UPDATE__
quiet_deployment=__QUIET_DEPLOYMENT__
colour_output=__COLOUR_OUTPUT__
@@ -950,14 +933,7 @@ failure() {
}
start_restored_stack() {
if [ "$skip_builder" -eq 1 ]; then
# Restore the previous gateway/admin sources without rebuilding the large
# Android SDK image or touching the still-running builder, database and cache.
docker compose build server memby-admin >/dev/null 2>&1 &&
docker compose up -d --no-build --no-deps memby-admin server >/dev/null 2>&1
else
docker compose up -d --build --remove-orphans >/dev/null 2>&1
fi
docker compose up -d --build --remove-orphans >/dev/null 2>&1
}
rollback() {
@@ -976,12 +952,7 @@ rollback() {
detail "Stopping the incomplete application release"
(
cd "$destination"
if [ "$skip_builder" -eq 1 ]; then
docker compose stop server memby-admin >/dev/null 2>&1
docker compose rm -f server memby-admin >/dev/null 2>&1
else
docker compose down --remove-orphans >/dev/null 2>&1
fi
docker compose down --remove-orphans >/dev/null 2>&1
) || true
fi
@@ -1090,9 +1061,6 @@ test -f "$staging/docker-compose.yml"
test -f "$staging/server/Dockerfile"
test -f "$staging/admin-ui/Dockerfile"
test -f "$staging/admin-ui/package.json"
test -f "$staging/builder/Dockerfile"
test -f "$staging/builder/release.sh"
test -f "$staging/builder/controller.go"
if [ "$publish_release" -eq 1 ]; then
test -f "$staging/release/version.txt"
test -f "$staging/release/sha256.txt"
@@ -1112,7 +1080,7 @@ if [ -f "$destination/.env" ]; then
# tell the televisions anything. It is usually the same as the incoming one, but
# reading it from the release being replaced is what makes that not a requirement.
previous_admin_token=$(sed -n 's/^MEMBY_ADMIN_TOKEN=//p' "$destination/.env" | head -n 1 | tr -d '\r')
# First Docker-builder deployment only: migrate the established publish token out
# First file-backed-secret deployment only: migrate the established publish token out
# of the old environment file rather than making the operator rotate it mid-release.
previous_release_token=$(sed -n 's/^MEMBY_RELEASE_PUBLISH_TOKEN=//p' "$destination/.env" | head -n 1 | tr -d '\r')
# Kept beside the new one purely so a bad edit is recoverable by hand.
@@ -1168,44 +1136,18 @@ if [ ! -s "$release_token_file" ]; then
fi
fi
missing_builder_secrets=0
check_required_secret() {
required_secret="$1"
if [ ! -s "$required_secret" ]; then
failure "Required secret is missing or empty: $required_secret"
missing_builder_secrets=1
fi
}
check_required_secret "$release_token_file"
if [ "$skip_builder" -ne 1 ]; then
check_required_secret "$secrets_dir/memby-release.jks"
check_required_secret "$secrets_dir/memby-keystore-password"
check_required_secret "$secrets_dir/memby-key-alias"
check_required_secret "$secrets_dir/memby-key-password"
fi
if [ "$missing_builder_secrets" -ne 0 ]; then
if [ "$skip_builder" -eq 1 ]; then
detail 'Restore the existing gateway release token at the path above, then rerun this deployment'
else
detail 'Copy the existing signing identity and its three values to the paths above, then rerun this deployment'
detail 'Never create a new keystore: installed Memby clients can upgrade only from the existing certificate'
fi
if [ ! -s "$release_token_file" ]; then
failure "Required secret is missing or empty: $release_token_file"
detail 'Restore the existing gateway release token at the path above, then rerun this deployment'
exit 1
fi
# Docker Compose file-backed secrets are read-only bind mounts on the NAS. The gateway
# and builder deliberately run as uid 65532, so files created as the SSH user with 0600
# would be present but unreadable in those containers. The 0700 parent prevents every
# other NAS account from traversing to them; 0444 makes only the read-only secret mounts
# usable by the non-root container processes and also prevents accidental host writes.
# deliberately runs as uid 65532, so a file created as the SSH user with 0600 would be
# present but unreadable in that container. The 0700 parent prevents every other NAS
# account from traversing to it; 0444 makes only the read-only secret mount usable by the
# non-root container process and also prevents accidental host writes.
chmod 700 "$secrets_dir"
chmod 444 "$release_token_file"
if [ "$skip_builder" -ne 1 ]; then
chmod 444 \
"$secrets_dir/memby-release.jks" \
"$secrets_dir/memby-keystore-password" \
"$secrets_dir/memby-key-alias" \
"$secrets_dir/memby-key-password"
fi
success 'Required gateway configuration is present'
if [ -n "$previous_password" ] && [ "$previous_password" != "$new_password" ]; then
failure 'POSTGRES_PASSWORD differs from the deployed value'
@@ -1256,34 +1198,15 @@ step 'Pulling PostgreSQL and Redis'
)
success 'Dependency images are ready'
if [ "$skip_builder" -eq 1 ]; then
step 'Building the gateway and admin console; reusing the Android builder'
else
step 'Building the gateway, admin console and Android builder'
fi
step 'Building the gateway and admin console'
(
cd "$staging"
# `up` reuses an existing image when one is present. Build both local contexts here,
# otherwise a new React/nginx console can be packaged and activated while the NAS
# continues to serve the previous console image (and its old route configuration).
if [ "$skip_builder" -eq 1 ]; then
existing_builder=$(docker compose ps -q memby-builder 2>/dev/null || true)
if [ -z "$existing_builder" ] ||
[ "$(docker inspect --format '{{.State.Status}}' "$existing_builder" 2>/dev/null || true)" != 'running' ]; then
failure 'No running memby-builder container is available to preserve'
detail 'Run once without -SkipBuilder to install and start the Android builder'
exit 1
fi
docker compose build --pull server memby-admin
else
docker compose build --pull server memby-admin memby-builder
fi
docker compose build --pull server memby-admin
)
if [ "$skip_builder" -eq 1 ]; then
success 'Gateway and admin console images built; Android builder container retained'
else
success 'Gateway, admin console and Android builder images built'
fi
success 'Gateway and admin console images built'
step 'Activating the release'
rm -rf -- "$backup"
@@ -1292,20 +1215,11 @@ if [ -e "$destination" ] || [ -L "$destination" ]; then
# Compose projects created by older releases may use a different project
# name. Stop them from their original directory before moving it so their
# published ports (especially 32768) are released for the new stack.
if [ "$skip_builder" -eq 1 ]; then
detail 'Stopping only the gateway and admin console; preserving builder, database and cache'
(
cd "$destination"
docker compose stop server memby-admin
docker compose rm -f server memby-admin
)
else
detail 'Stopping the previous Compose application'
(
cd "$destination"
docker compose down --remove-orphans
)
fi
detail 'Stopping the previous Compose application'
(
cd "$destination"
docker compose down --remove-orphans
)
previous_stopped=1
success 'Previous Compose application stopped'
fi
@@ -1318,17 +1232,12 @@ success 'Release activated'
step 'Starting the Compose stack'
cd "$destination"
if [ "$skip_builder" -eq 1 ]; then
compose_start='docker compose up -d --no-build --no-deps memby-admin server'
else
compose_start='docker compose up -d --no-build --remove-orphans'
fi
if ! $compose_start; then
if ! docker compose up -d --no-build --remove-orphans; then
failure 'Compose could not start the complete application'
detail 'Container state before rollback:'
docker compose ps --all || true
detail 'Gateway and builder logs before rollback:'
docker compose logs --no-color --tail 100 server memby-builder || true
detail 'Gateway logs before rollback:'
docker compose logs --no-color --tail 100 server || true
exit 1
fi
success 'Compose start command completed'
@@ -1338,7 +1247,6 @@ wait_for_service postgres
wait_for_service redis
wait_for_service memby-admin
wait_for_service server
wait_for_service memby-builder
published_address=$(docker compose port server 32768 | head -n 1)
actual_port=${published_address##*:}
@@ -1418,10 +1326,6 @@ success 'Memby gateway: https://mserver.sublogue.com'
'__PUBLISH_RELEASE__',
$(if ($SkipAppRelease) { '0' } else { '1' })
)
$remoteCommand = $remoteCommand.Replace(
'__SKIP_BUILDER__',
$(if ($SkipBuilder) { '1' } else { '0' })
)
$remoteCommand = $remoteCommand.Replace(
'__MANDATORY_UPDATE__',
$(if ($mandatoryRelease) { '1' } else { '0' })