0.2.76 - Icon Packs
This commit is contained in:
+331
-81
@@ -28,6 +28,14 @@ by this script.
|
||||
This deploys the current local working tree, including uncommitted server changes.
|
||||
Use -SkipAppRelease for an admin/server-only deployment: no APK is built or published.
|
||||
|
||||
Use -AdminOnly (or --Admin) to replace the operations console and nothing else. The
|
||||
console is its own container with its own health check and nothing depends on it, so
|
||||
only admin-ui/ is uploaded, only the memby-admin image is rebuilt and only that one
|
||||
container is restarted. The gateway, PostgreSQL and Redis keep running throughout,
|
||||
.env and docker-compose.yml are left exactly as deployed, no APK is built, and no
|
||||
television is told anything because nothing they use goes away. It amends an existing
|
||||
deployment and refuses to create one.
|
||||
|
||||
.EXAMPLE
|
||||
.\deploy-server.ps1
|
||||
|
||||
@@ -48,6 +56,12 @@ Use -SkipAppRelease for an admin/server-only deployment: no APK is built or publ
|
||||
|
||||
.EXAMPLE
|
||||
.\deploy-server.ps1 -SkipAppRelease
|
||||
|
||||
.EXAMPLE
|
||||
.\deploy-server.ps1 --Admin
|
||||
|
||||
.EXAMPLE
|
||||
.\deploy-server.ps1 -AdminOnly
|
||||
#>
|
||||
|
||||
#Requires -Version 7.2
|
||||
@@ -87,6 +101,15 @@ param(
|
||||
[Parameter()]
|
||||
[switch] $SkipAppRelease,
|
||||
|
||||
# Replace the operations console and nothing else. The console is its own container
|
||||
# with its own health check and nothing depends on it, so it can be rebuilt and
|
||||
# restarted while the gateway, PostgreSQL and Redis keep running — a console change is
|
||||
# then seconds rather than the several minutes a full stack swap costs, and no
|
||||
# television notices anything at all.
|
||||
[Parameter()]
|
||||
[Alias('Admin')]
|
||||
[switch] $AdminOnly,
|
||||
|
||||
[Parameter()]
|
||||
[Alias('m')]
|
||||
[switch] $MandatoryUpdate,
|
||||
@@ -113,20 +136,41 @@ param(
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$trailingFlags = @($TrailingFlag0, $TrailingFlag1) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
$unknownFlags = @($trailingFlags | Where-Object { $_ -notin @('--m', '--Quiet', '--quiet') })
|
||||
$unknownFlags = @($trailingFlags | Where-Object {
|
||||
$_ -notin @('--m', '--Quiet', '--quiet', '--Admin', '--admin')
|
||||
})
|
||||
if ($unknownFlags.Count -gt 0) {
|
||||
throw "Unknown deployment option: $($unknownFlags -join ', ')"
|
||||
}
|
||||
$mandatoryRelease = $MandatoryUpdate -or $trailingFlags -contains '--m'
|
||||
$quietDeployment = $Quiet -or $trailingFlags -contains '--Quiet' -or $trailingFlags -contains '--quiet'
|
||||
$consoleOnly = $AdminOnly -or $trailingFlags -contains '--Admin' -or $trailingFlags -contains '--admin'
|
||||
|
||||
if ($mandatoryRelease -and $SkipAppRelease) {
|
||||
throw '--m cannot be combined with -SkipAppRelease because no update would be published.'
|
||||
if ($mandatoryRelease -and ($SkipAppRelease -or $consoleOnly)) {
|
||||
throw '--m cannot be combined with -SkipAppRelease or --Admin because no update would be published.'
|
||||
}
|
||||
# Nothing about the gateway is rebuilt, so there is nothing for a television to be told
|
||||
# about. Accepting --Quiet here would imply the announcement was a choice on this path.
|
||||
if ($consoleOnly -and $quietDeployment) {
|
||||
throw '--Quiet has no meaning with --Admin: no televisions are affected, so none are told.'
|
||||
}
|
||||
# The console-only path never packages an APK. Stated rather than silently ignored, so a
|
||||
# combined invocation cannot look as though it published one.
|
||||
if ($consoleOnly) {
|
||||
$SkipAppRelease = $true
|
||||
}
|
||||
|
||||
# Timings are kept per kind of deployment, because the two are not the same operation
|
||||
# measured twice: a console run rebuilds one small image and a full one swaps the whole
|
||||
# stack. Averaged together, each estimate would be wrong for both. A record written before
|
||||
# this existed carries no kind and is read as 'full', which is what all of them were.
|
||||
$script:DeploymentKind = if ($consoleOnly) { 'console' } else { 'full' }
|
||||
|
||||
$script:CurrentStep = 0
|
||||
$script:LocalStepCount = if ($SkipAppRelease) { 4 } else { 5 }
|
||||
$script:RemoteStepCount = 10
|
||||
# A console deployment skips the .env install, the dependency pull, the gateway build, the
|
||||
# stack swap and the APK publish: what is left is check, extract, swap, build, restart, wait.
|
||||
$script:RemoteStepCount = if ($consoleOnly) { 6 } else { 10 }
|
||||
$script:TotalSteps = $script:LocalStepCount + $script:RemoteStepCount
|
||||
$script:PhaseOrder = @(
|
||||
'prerequisites',
|
||||
@@ -142,7 +186,9 @@ $script:FallbackSeconds = @{
|
||||
'payload' = 4
|
||||
'android-release' = 230
|
||||
'packaging' = 12
|
||||
'remote-deployment' = 330
|
||||
# A console deployment uploads one small build context and rebuilds one image, where a
|
||||
# full one ships the Go tree as well and rebuilds the whole stack.
|
||||
'remote-deployment' = if ($consoleOnly) { 90 } else { 330 }
|
||||
}
|
||||
$script:PhaseDurations = [ordered]@{}
|
||||
$script:CurrentPhaseKey = $null
|
||||
@@ -205,6 +251,9 @@ function Import-DeploymentHistory {
|
||||
function Get-PhaseHistory {
|
||||
param([Parameter(Mandatory)][string] $Key)
|
||||
$values = foreach ($run in $script:History) {
|
||||
$kindProperty = $run.PSObject.Properties['kind']
|
||||
$kind = if ($kindProperty -and $kindProperty.Value) { [string]$kindProperty.Value } else { 'full' }
|
||||
if ($kind -ne $script:DeploymentKind) { continue }
|
||||
$phasesProperty = $run.PSObject.Properties['phases']
|
||||
if (-not $phasesProperty -or -not $phasesProperty.Value) { continue }
|
||||
$property = $phasesProperty.Value.PSObject.Properties[$Key]
|
||||
@@ -360,6 +409,7 @@ function Save-DeploymentHistory {
|
||||
durationSeconds = [Math]::Round($DurationSeconds, 2)
|
||||
phases = $script:PhaseDurations
|
||||
appRelease = -not [bool]$SkipAppRelease
|
||||
kind = $script:DeploymentKind
|
||||
}
|
||||
$runs = @($script:History) + @([pscustomobject]$record) | Select-Object -Last 30
|
||||
$directory = Split-Path -Parent $script:HistoryPath
|
||||
@@ -607,7 +657,13 @@ function New-DeploymentArchive {
|
||||
[string] $ArchivePath,
|
||||
|
||||
[Parameter()]
|
||||
[string] $ReleaseDirectory
|
||||
[string] $ReleaseDirectory,
|
||||
|
||||
# Pack the console's build context alone. Everything else the remote side needs —
|
||||
# the Compose file, the environment — is already deployed and is deliberately left
|
||||
# alone, so it must not be shipped and cannot be changed by accident.
|
||||
[Parameter()]
|
||||
[switch] $ConsoleOnly
|
||||
)
|
||||
|
||||
# Build artefacts left in either build context are paid for on the wire. Both
|
||||
@@ -620,9 +676,12 @@ function New-DeploymentArchive {
|
||||
'--exclude', 'server/bin', '--exclude', 'server/bin/*',
|
||||
'--exclude', 'admin-ui/node_modules', '--exclude', 'admin-ui/node_modules/*',
|
||||
'--exclude', 'admin-ui/dist', '--exclude', 'admin-ui/dist/*',
|
||||
'-C', $RepositoryDirectory,
|
||||
'server', 'admin-ui', 'docker-compose.yml', '.env.example'
|
||||
)
|
||||
'-C', $RepositoryDirectory
|
||||
) + $(if ($ConsoleOnly) {
|
||||
@('admin-ui')
|
||||
} else {
|
||||
@('server', 'admin-ui', 'docker-compose.yml', '.env.example')
|
||||
})
|
||||
if ($ReleaseDirectory) {
|
||||
$arguments += @(
|
||||
'-C', (Split-Path -Parent $ReleaseDirectory),
|
||||
@@ -770,27 +829,39 @@ try {
|
||||
Write-Success "Using $checkoutDirectory"
|
||||
Write-Host ''
|
||||
|
||||
Write-Step 'Validating the Compose deployment payload' -Key 'payload'
|
||||
$requiredPaths = @(
|
||||
(Join-Path $checkoutDirectory 'server'),
|
||||
(Join-Path $checkoutDirectory 'server/Dockerfile'),
|
||||
(Join-Path $checkoutDirectory 'server/go.mod'),
|
||||
Write-Step $(if ($consoleOnly) {
|
||||
'Validating the console deployment payload'
|
||||
} else {
|
||||
'Validating the Compose deployment payload'
|
||||
}) -Key 'payload'
|
||||
$consolePaths = @(
|
||||
(Join-Path $checkoutDirectory 'admin-ui'),
|
||||
(Join-Path $checkoutDirectory 'admin-ui/Dockerfile'),
|
||||
(Join-Path $checkoutDirectory 'admin-ui/package.json'),
|
||||
(Join-Path $checkoutDirectory 'admin-ui/src'),
|
||||
(Join-Path $checkoutDirectory 'admin-ui/src')
|
||||
)
|
||||
# A console deployment reuses the deployed docker-compose.yml and .env rather than
|
||||
# shipping its own. That is the whole safety of it: the running gateway's configuration
|
||||
# is left exactly as it was, so nothing can be changed here that would need it to
|
||||
# restart to take effect.
|
||||
$requiredPaths = $consolePaths + $(if ($consoleOnly) { @() } else { @(
|
||||
(Join-Path $checkoutDirectory 'server'),
|
||||
(Join-Path $checkoutDirectory 'server/Dockerfile'),
|
||||
(Join-Path $checkoutDirectory 'server/go.mod'),
|
||||
(Join-Path $checkoutDirectory 'docker-compose.yml'),
|
||||
(Join-Path $checkoutDirectory '.env.example')
|
||||
)
|
||||
) })
|
||||
foreach ($requiredPath in $requiredPaths) {
|
||||
if (-not (Test-Path -LiteralPath $requiredPath)) {
|
||||
throw "Required deployment file is missing: $requiredPath"
|
||||
}
|
||||
}
|
||||
Write-Detail 'server/ build context'
|
||||
Write-Detail 'admin-ui/ build context'
|
||||
Write-Detail 'docker-compose.yml'
|
||||
Write-Detail '.env.example'
|
||||
if (-not $consoleOnly) {
|
||||
Write-Detail 'server/ build context'
|
||||
Write-Detail 'docker-compose.yml'
|
||||
Write-Detail '.env.example'
|
||||
}
|
||||
$releaseDirectory = ''
|
||||
$releaseVersion = ''
|
||||
$releaseSHA256 = ''
|
||||
@@ -882,34 +953,20 @@ try {
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
Write-Step 'Packaging the release' -Key 'packaging'
|
||||
Write-Step $(if ($consoleOnly) { 'Packaging the console' } else { 'Packaging the release' }) -Key 'packaging'
|
||||
New-DeploymentArchive -RepositoryDirectory $checkoutDirectory -ArchivePath $archivePath `
|
||||
-ReleaseDirectory $releaseDirectory
|
||||
-ReleaseDirectory $releaseDirectory -ConsoleOnly:$consoleOnly
|
||||
$archiveSize = (Get-Item -LiteralPath $archivePath).Length
|
||||
Write-Detail ("Archive size {0:N1} MiB" -f ($archiveSize / 1MB))
|
||||
Write-Success 'Release archive is ready'
|
||||
Write-Host ''
|
||||
|
||||
# This template is single-quoted so PowerShell does not expand the remote
|
||||
# shell's variables. Replacement values are validated before insertion.
|
||||
$remoteCommand = @'
|
||||
set -eu
|
||||
|
||||
destination='__DESTINATION__'
|
||||
health_timeout=__HEALTH_TIMEOUT__
|
||||
publish_release=__PUBLISH_RELEASE__
|
||||
mandatory_update=__MANDATORY_UPDATE__
|
||||
quiet_deployment=__QUIET_DEPLOYMENT__
|
||||
colour_output=__COLOUR_OUTPUT__
|
||||
remote_step_offset=__REMOTE_STEP_OFFSET__
|
||||
total_steps=__TOTAL_STEPS__
|
||||
parent=$(dirname "$destination")
|
||||
staging="${destination}.new.$$"
|
||||
backup="${destination}.previous.$$"
|
||||
activated=0
|
||||
previous_stopped=0
|
||||
remote_step=0
|
||||
|
||||
# The reporting shared by both remote scripts. Kept in one place because the step
|
||||
# counter, the wording and the health wait are what make a deployment readable, and two
|
||||
# copies of them are two things to keep in step. Every template that uses this defines
|
||||
# colour_output, remote_step, remote_step_offset, total_steps and health_timeout above
|
||||
# the point it is inserted.
|
||||
$remoteHelpers = @'
|
||||
step() {
|
||||
remote_step=$((remote_step + 1))
|
||||
overall_step=$((remote_step_offset + remote_step))
|
||||
@@ -932,6 +989,217 @@ failure() {
|
||||
if [ "$colour_output" -eq 1 ]; then printf '\033[31m ✗ %s\033[0m\n' "$1" >&2; else printf ' FAILED %s\n' "$1" >&2; fi
|
||||
}
|
||||
|
||||
wait_for_service() {
|
||||
service="$1"
|
||||
elapsed=0
|
||||
detail "Waiting for $service"
|
||||
|
||||
while [ "$elapsed" -lt "$health_timeout" ]; do
|
||||
container_id=$(docker compose ps --all -q "$service" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$container_id" ]; then
|
||||
state=$(docker inspect --format '{{.State.Status}}' "$container_id" 2>/dev/null || true)
|
||||
health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id" 2>/dev/null || true)
|
||||
|
||||
if [ "$state" = 'running' ] && { [ "$health" = 'healthy' ] || [ "$health" = 'none' ]; }; then
|
||||
success "$service is $state ($health)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$state" = 'exited' ] || [ "$state" = 'dead' ]; then
|
||||
failure "$service entered state: $state"
|
||||
docker compose logs --no-color --tail 60 "$service" || true
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
if [ $((elapsed % 10)) -eq 0 ]; then
|
||||
remaining=$((health_timeout - elapsed))
|
||||
detail "$service is still ${state:-starting} (${health:-health pending}) · ${elapsed}s elapsed · up to ${remaining}s remaining"
|
||||
fi
|
||||
done
|
||||
|
||||
failure "$service did not become healthy within ${health_timeout}s"
|
||||
docker compose logs --no-color --tail 60 "$service" || true
|
||||
return 1
|
||||
}
|
||||
'@
|
||||
|
||||
# Replacing the console alone.
|
||||
#
|
||||
# The console is its own container: nothing depends on it, it holds no state and it has
|
||||
# a health check of its own, so it can be rebuilt and restarted while the gateway,
|
||||
# PostgreSQL and Redis carry on serving. That is what makes this path safe enough to be
|
||||
# worth having — and it is only safe while it stays this narrow. Three things it must
|
||||
# never do: touch .env or docker-compose.yml (the running gateway's configuration would
|
||||
# then differ from the file describing it, with no restart to reconcile them), name any
|
||||
# service but memby-admin, or omit --no-deps (Compose would otherwise be free to
|
||||
# recreate the gateway as a dependency and take the house down for a CSS change).
|
||||
$consoleRemoteCommand = @'
|
||||
set -eu
|
||||
|
||||
destination='__DESTINATION__'
|
||||
health_timeout=__HEALTH_TIMEOUT__
|
||||
colour_output=__COLOUR_OUTPUT__
|
||||
remote_step_offset=__REMOTE_STEP_OFFSET__
|
||||
total_steps=__TOTAL_STEPS__
|
||||
# Staged and backed up *inside* the deployment directory, so the swap is a rename on one
|
||||
# filesystem rather than a copy that can be interrupted half way.
|
||||
staging="${destination}/admin-ui.new.$$"
|
||||
backup="${destination}/admin-ui.previous.$$"
|
||||
swapped=0
|
||||
remote_step=0
|
||||
|
||||
__SHELL_HELPERS__
|
||||
|
||||
restore_console() {
|
||||
status=$?
|
||||
trap - EXIT
|
||||
|
||||
if [ "$status" -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
failure 'Console deployment failed; cleaning up'
|
||||
rm -rf -- "$staging"
|
||||
|
||||
if [ "$swapped" -eq 1 ] && [ -d "$backup" ]; then
|
||||
detail 'Restoring the previous console'
|
||||
rm -rf -- "$destination/admin-ui"
|
||||
mv -- "$backup" "$destination/admin-ui"
|
||||
# Rebuilt as well as restored: the image that is running is the one that was just
|
||||
# built from the files being thrown away, so putting the directory back without
|
||||
# rebuilding would leave the NAS serving exactly what failed.
|
||||
(
|
||||
cd "$destination"
|
||||
docker compose build memby-admin >/dev/null 2>&1 &&
|
||||
docker compose up -d --no-deps --no-build memby-admin >/dev/null 2>&1
|
||||
) || true
|
||||
failure 'Previous console files were restored'
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
trap restore_console EXIT
|
||||
trap 'exit 130' INT TERM
|
||||
|
||||
step 'Checking Docker and the deployed stack'
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
failure 'Docker is not installed on the NAS'
|
||||
exit 1
|
||||
fi
|
||||
docker info >/dev/null 2>&1 || {
|
||||
failure 'Docker is installed but the daemon is unavailable'
|
||||
exit 1
|
||||
}
|
||||
# A console deployment amends a deployment that already exists. It cannot create one:
|
||||
# there is no .env and no Compose file in this archive, by design.
|
||||
if [ ! -f "$destination/docker-compose.yml" ]; then
|
||||
failure "No deployment found at $destination"
|
||||
detail 'Run a full deployment first; --Admin only replaces the console of an existing one'
|
||||
exit 1
|
||||
fi
|
||||
if ! (cd "$destination" && docker compose config --services 2>/dev/null | grep -qx 'memby-admin'); then
|
||||
failure 'The deployed docker-compose.yml has no memby-admin service'
|
||||
detail 'That release predates the separate console; run a full deployment'
|
||||
exit 1
|
||||
fi
|
||||
success "$(docker --version)"
|
||||
success 'Existing deployment found'
|
||||
|
||||
step 'Extracting the console'
|
||||
rm -rf -- "$staging"
|
||||
mkdir -- "$staging"
|
||||
tar -xf - -C "$staging"
|
||||
test -f "$staging/admin-ui/Dockerfile"
|
||||
test -f "$staging/admin-ui/package.json"
|
||||
test -d "$staging/admin-ui/src"
|
||||
success 'Console sources extracted'
|
||||
|
||||
step 'Replacing the console files'
|
||||
rm -rf -- "$backup"
|
||||
if [ -d "$destination/admin-ui" ]; then
|
||||
mv -- "$destination/admin-ui" "$backup"
|
||||
fi
|
||||
mv -- "$staging/admin-ui" "$destination/admin-ui"
|
||||
rm -rf -- "$staging"
|
||||
swapped=1
|
||||
success 'Console files replaced'
|
||||
|
||||
step 'Building the console image'
|
||||
# Built before anything is restarted, so a console that does not compile leaves the one
|
||||
# that is running untouched. The type check runs inside this build.
|
||||
(
|
||||
cd "$destination"
|
||||
docker compose build memby-admin
|
||||
)
|
||||
success 'Console image built'
|
||||
|
||||
step 'Restarting the console'
|
||||
(
|
||||
cd "$destination"
|
||||
# --no-deps is what keeps this to one container; --no-build because it was just built.
|
||||
if ! docker compose up -d --no-deps --no-build memby-admin; then
|
||||
failure 'Compose could not start the console'
|
||||
docker compose logs --no-color --tail 60 memby-admin || true
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
success 'Console container restarted'
|
||||
|
||||
step 'Waiting for the console'
|
||||
cd "$destination"
|
||||
wait_for_service memby-admin
|
||||
|
||||
# The gateway is what serves /admin, and it was never restarted — so this is a check that
|
||||
# the console is reachable the way an operator actually reaches it, not merely that its own
|
||||
# container is up.
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
console_status=$(curl --silent --show-error --max-time 5 \
|
||||
--output /dev/null --write-out '%{http_code}' \
|
||||
http://127.0.0.1:32768/admin/ 2>/dev/null || true)
|
||||
case "$console_status" in
|
||||
2??|3??) success 'Console is being served through the gateway' ;;
|
||||
*) detail "Console health is good but the gateway returned HTTP ${console_status:-no response} for /admin/" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
docker compose ps
|
||||
printf '\n'
|
||||
|
||||
rm -rf -- "$backup"
|
||||
swapped=0
|
||||
trap - EXIT INT TERM
|
||||
success 'Console deployment is healthy'
|
||||
success 'Memby console: https://mserver.sublogue.com/admin/'
|
||||
'@
|
||||
|
||||
# This template is single-quoted so PowerShell does not expand the remote
|
||||
# shell's variables. Replacement values are validated before insertion.
|
||||
$remoteCommand = @'
|
||||
set -eu
|
||||
|
||||
destination='__DESTINATION__'
|
||||
health_timeout=__HEALTH_TIMEOUT__
|
||||
publish_release=__PUBLISH_RELEASE__
|
||||
mandatory_update=__MANDATORY_UPDATE__
|
||||
quiet_deployment=__QUIET_DEPLOYMENT__
|
||||
colour_output=__COLOUR_OUTPUT__
|
||||
remote_step_offset=__REMOTE_STEP_OFFSET__
|
||||
total_steps=__TOTAL_STEPS__
|
||||
parent=$(dirname "$destination")
|
||||
staging="${destination}.new.$$"
|
||||
backup="${destination}.previous.$$"
|
||||
activated=0
|
||||
previous_stopped=0
|
||||
remote_step=0
|
||||
|
||||
__SHELL_HELPERS__
|
||||
|
||||
start_restored_stack() {
|
||||
docker compose up -d --build --remove-orphans >/dev/null 2>&1
|
||||
}
|
||||
@@ -977,43 +1245,6 @@ rollback() {
|
||||
exit "$status"
|
||||
}
|
||||
|
||||
wait_for_service() {
|
||||
service="$1"
|
||||
elapsed=0
|
||||
detail "Waiting for $service"
|
||||
|
||||
while [ "$elapsed" -lt "$health_timeout" ]; do
|
||||
container_id=$(docker compose ps --all -q "$service" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$container_id" ]; then
|
||||
state=$(docker inspect --format '{{.State.Status}}' "$container_id" 2>/dev/null || true)
|
||||
health=$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id" 2>/dev/null || true)
|
||||
|
||||
if [ "$state" = 'running' ] && { [ "$health" = 'healthy' ] || [ "$health" = 'none' ]; }; then
|
||||
success "$service is $state ($health)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$state" = 'exited' ] || [ "$state" = 'dead' ]; then
|
||||
failure "$service entered state: $state"
|
||||
docker compose logs --no-color --tail 60 "$service" || true
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
if [ $((elapsed % 10)) -eq 0 ]; then
|
||||
remaining=$((health_timeout - elapsed))
|
||||
detail "$service is still ${state:-starting} (${health:-health pending}) · ${elapsed}s elapsed · up to ${remaining}s remaining"
|
||||
fi
|
||||
done
|
||||
|
||||
failure "$service did not become healthy within ${health_timeout}s"
|
||||
docker compose logs --no-color --tail 60 "$service" || true
|
||||
return 1
|
||||
}
|
||||
|
||||
trap rollback EXIT
|
||||
trap 'exit 130' INT TERM
|
||||
|
||||
@@ -1320,6 +1551,9 @@ success 'Remote deployment is healthy'
|
||||
success 'Memby gateway: https://mserver.sublogue.com'
|
||||
'@
|
||||
|
||||
if ($consoleOnly) { $remoteCommand = $consoleRemoteCommand }
|
||||
# The helpers go in before anything else, so a token inside them is substituted too.
|
||||
$remoteCommand = $remoteCommand.Replace('__SHELL_HELPERS__', $remoteHelpers)
|
||||
$remoteCommand = $remoteCommand.Replace('__DESTINATION__', $Destination)
|
||||
$remoteCommand = $remoteCommand.Replace('__HEALTH_TIMEOUT__', $HealthTimeoutSeconds.ToString())
|
||||
$remoteCommand = $remoteCommand.Replace(
|
||||
@@ -1346,8 +1580,15 @@ success 'Memby gateway: https://mserver.sublogue.com'
|
||||
# script is normalised to LF here rather than depending on how it was saved.
|
||||
$remoteCommand = $remoteCommand.Replace("`r`n", "`n").Replace("`r", "`n")
|
||||
|
||||
Start-DeploymentPhase -Key 'remote-deployment' -Message "Deploying to $RemoteHost" -RemoteRange
|
||||
Start-DeploymentPhase -Key 'remote-deployment' -Message $(if ($consoleOnly) {
|
||||
"Deploying the console to $RemoteHost"
|
||||
} else {
|
||||
"Deploying to $RemoteHost"
|
||||
}) -RemoteRange
|
||||
Write-Detail 'One SSH password prompt will appear'
|
||||
if ($consoleOnly) {
|
||||
Write-Detail 'The gateway, PostgreSQL and Redis keep running throughout'
|
||||
}
|
||||
Write-Detail 'Remote build output follows; its steps continue the overall counter'
|
||||
Write-Host ''
|
||||
Send-ArchiveOverSsh -ArchivePath $archivePath -RemoteCommand $remoteCommand
|
||||
@@ -1357,8 +1598,17 @@ success 'Memby gateway: https://mserver.sublogue.com'
|
||||
Save-DeploymentHistory -Success $true -DurationSeconds $deploymentTimer.Elapsed.TotalSeconds
|
||||
if ($script:UseAnimation) { Write-Progress -Id 1 -Activity 'Memby deployment' -Completed }
|
||||
Write-Host ''
|
||||
Write-Success ("Deployment complete in {0:mm\:ss}" -f $deploymentTimer.Elapsed)
|
||||
Write-Styled -Message ' Memby gateway: https://mserver.sublogue.com' -Colour White
|
||||
Write-Success ($(if ($consoleOnly) {
|
||||
"Console deployment complete in {0:mm\:ss}"
|
||||
} else {
|
||||
"Deployment complete in {0:mm\:ss}"
|
||||
}) -f $deploymentTimer.Elapsed)
|
||||
if ($consoleOnly) {
|
||||
Write-Styled -Message ' Memby console: https://mserver.sublogue.com/admin/' -Colour White
|
||||
Write-Styled -Message ' Gateway: untouched and still serving' -Colour Gray
|
||||
} else {
|
||||
Write-Styled -Message ' Memby gateway: https://mserver.sublogue.com' -Colour White
|
||||
}
|
||||
Write-Styled -Message " NAS endpoint: http://${RemoteHost}:32768" -Colour Gray
|
||||
Write-Styled -Message " Install path: ${RemoteHost}:$Destination" -Colour Gray
|
||||
if (-not $SkipAppRelease) {
|
||||
|
||||
Reference in New Issue
Block a user