feat: v9.8.1 pre-built Docker images and GHCR release workflow
Setup.py now asks whether to pull pre-built images from GHCR (recommended) or build from source. Pre-built mode skips the 15-minute compile step entirely. - Add .github/workflows/release.yml (builds+pushes 4 images on tag) - Add docker-compose.build.yml (source-build overlay) - Switch docker-compose.prod.yml from build: to image: refs - Add --build-mode CLI arg and wizard step to setup.py - Bump version to 9.8.1 across all files - Document TOD_VERSION env var in CONFIGURATION.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
87
.github/workflows/release.yml
vendored
Normal file
87
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_PREFIX: ghcr.io/staack/the-other-dude
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
name: Build & Push Docker Images
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: version
|
||||||
|
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
# Build and push each image sequentially to avoid OOM on the runner.
|
||||||
|
# Each multi-stage build (Go, Python/pip, Node/tsc) peaks at 1-2 GB.
|
||||||
|
|
||||||
|
- name: Build & push API
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: infrastructure/docker/Dockerfile.api
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.IMAGE_PREFIX }}/api:${{ steps.version.outputs.version }}
|
||||||
|
${{ env.IMAGE_PREFIX }}/api:latest
|
||||||
|
cache-from: type=gha,scope=api
|
||||||
|
cache-to: type=gha,mode=max,scope=api
|
||||||
|
|
||||||
|
- name: Build & push Poller
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./poller
|
||||||
|
file: poller/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.IMAGE_PREFIX }}/poller:${{ steps.version.outputs.version }}
|
||||||
|
${{ env.IMAGE_PREFIX }}/poller:latest
|
||||||
|
cache-from: type=gha,scope=poller
|
||||||
|
cache-to: type=gha,mode=max,scope=poller
|
||||||
|
|
||||||
|
- name: Build & push Frontend
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: infrastructure/docker/Dockerfile.frontend
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.IMAGE_PREFIX }}/frontend:${{ steps.version.outputs.version }}
|
||||||
|
${{ env.IMAGE_PREFIX }}/frontend:latest
|
||||||
|
cache-from: type=gha,scope=frontend
|
||||||
|
cache-to: type=gha,mode=max,scope=frontend
|
||||||
|
|
||||||
|
- name: Build & push WinBox Worker
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./winbox-worker
|
||||||
|
file: winbox-worker/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.IMAGE_PREFIX }}/winbox-worker:${{ steps.version.outputs.version }}
|
||||||
|
${{ env.IMAGE_PREFIX }}/winbox-worker:latest
|
||||||
|
cache-from: type=gha,scope=winbox-worker
|
||||||
|
cache-to: type=gha,mode=max,scope=winbox-worker
|
||||||
@@ -144,7 +144,7 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
# App settings
|
# App settings
|
||||||
APP_NAME: str = "TOD - The Other Dude"
|
APP_NAME: str = "TOD - The Other Dude"
|
||||||
APP_VERSION: str = "9.8.0"
|
APP_VERSION: str = "9.8.1"
|
||||||
DEBUG: bool = False
|
DEBUG: bool = False
|
||||||
|
|
||||||
@field_validator("CREDENTIAL_ENCRYPTION_KEY")
|
@field_validator("CREDENTIAL_ENCRYPTION_KEY")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "the-other-dude-backend"
|
name = "the-other-dude-backend"
|
||||||
version = "9.8.0"
|
version = "9.8.1"
|
||||||
description = "MikroTik Fleet Management Portal - Backend API"
|
description = "MikroTik Fleet Management Portal - Backend API"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
28
docker-compose.build.yml
Normal file
28
docker-compose.build.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# docker-compose.build.yml -- Build-from-source override
|
||||||
|
#
|
||||||
|
# Adds build contexts so Docker Compose builds images locally instead of
|
||||||
|
# pulling pre-built images from GHCR.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml \
|
||||||
|
# -f docker-compose.build.yml --env-file .env.prod up -d --build
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: infrastructure/docker/Dockerfile.api
|
||||||
|
|
||||||
|
poller:
|
||||||
|
build:
|
||||||
|
context: ./poller
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: infrastructure/docker/Dockerfile.frontend
|
||||||
|
|
||||||
|
winbox-worker:
|
||||||
|
build:
|
||||||
|
context: ./winbox-worker
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
# docker-compose.prod.yml -- Production environment override
|
# docker-compose.prod.yml -- Production environment override
|
||||||
# Usage: docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d
|
#
|
||||||
|
# Pre-built images (recommended):
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d
|
||||||
|
#
|
||||||
|
# Build from source:
|
||||||
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml -f docker-compose.build.yml --env-file .env.prod up -d
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
@@ -13,9 +18,7 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
api:
|
api:
|
||||||
build:
|
image: ghcr.io/staack/the-other-dude/api:${TOD_VERSION:-latest}
|
||||||
context: .
|
|
||||||
dockerfile: infrastructure/docker/Dockerfile.api
|
|
||||||
container_name: tod_api
|
container_name: tod_api
|
||||||
env_file: .env.prod
|
env_file: .env.prod
|
||||||
environment:
|
environment:
|
||||||
@@ -67,9 +70,7 @@ services:
|
|||||||
- tod_remote_worker
|
- tod_remote_worker
|
||||||
|
|
||||||
poller:
|
poller:
|
||||||
build:
|
image: ghcr.io/staack/the-other-dude/poller:${TOD_VERSION:-latest}
|
||||||
context: ./poller
|
|
||||||
dockerfile: ./Dockerfile
|
|
||||||
container_name: tod_poller
|
container_name: tod_poller
|
||||||
env_file: .env.prod
|
env_file: .env.prod
|
||||||
cap_add:
|
cap_add:
|
||||||
@@ -135,6 +136,7 @@ services:
|
|||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
winbox-worker:
|
winbox-worker:
|
||||||
|
image: ghcr.io/staack/the-other-dude/winbox-worker:${TOD_VERSION:-latest}
|
||||||
environment:
|
environment:
|
||||||
LOG_LEVEL: info
|
LOG_LEVEL: info
|
||||||
MAX_CONCURRENT_SESSIONS: 10
|
MAX_CONCURRENT_SESSIONS: 10
|
||||||
@@ -146,9 +148,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
image: ghcr.io/staack/the-other-dude/frontend:${TOD_VERSION:-latest}
|
||||||
context: .
|
|
||||||
dockerfile: infrastructure/docker/Dockerfile.frontend
|
|
||||||
container_name: tod_frontend
|
container_name: tod_frontend
|
||||||
ports:
|
ports:
|
||||||
- "3000:80"
|
- "3000:80"
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ TOD uses Pydantic Settings for configuration. All values can be set via environm
|
|||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `APP_NAME` | `TOD - The Other Dude` | Application display name |
|
| `APP_NAME` | `TOD - The Other Dude` | Application display name |
|
||||||
| `APP_VERSION` | `9.7.2` | Semantic version string (see VERSION file at project root) |
|
| `APP_VERSION` | `9.8.1` | Semantic version string (see VERSION file at project root) |
|
||||||
|
| `TOD_VERSION` | `latest` | Docker image tag for pre-built images (set by setup.py) |
|
||||||
| `ENVIRONMENT` | `dev` | Runtime environment: `dev`, `staging`, or `production` |
|
| `ENVIRONMENT` | `dev` | Runtime environment: `dev`, `staging`, or `production` |
|
||||||
| `DEBUG` | `false` | Enable debug mode |
|
| `DEBUG` | `false` | Enable debug mode |
|
||||||
| `CORS_ORIGINS` | `http://localhost:3000,http://localhost:5173,http://localhost:8080` | Comma-separated list of allowed CORS origins |
|
| `CORS_ORIGINS` | `http://localhost:3000,http://localhost:5173,http://localhost:8080` | Comma-separated list of allowed CORS origins |
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
"Zero-knowledge authentication (SRP-6a)"
|
"Zero-knowledge authentication (SRP-6a)"
|
||||||
],
|
],
|
||||||
"softwareRequirements": "Docker, PostgreSQL 17, Redis, NATS",
|
"softwareRequirements": "Docker, PostgreSQL 17, Redis, NATS",
|
||||||
"softwareVersion": "9.8.0",
|
"softwareVersion": "9.8.1",
|
||||||
"license": "https://mariadb.com/bsl11/"
|
"license": "https://mariadb.com/bsl11/"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -547,7 +547,7 @@
|
|||||||
<section class="wp-section">
|
<section class="wp-section">
|
||||||
<h2>Status</h2>
|
<h2>Status</h2>
|
||||||
<table class="wp-status-table">
|
<table class="wp-status-table">
|
||||||
<tr><td>Version</td><td>9.8.0</td></tr>
|
<tr><td>Version</td><td>9.8.1</td></tr>
|
||||||
<tr><td>License</td><td>BSL 1.1 (converts to Apache 2.0 in 2030)</td></tr>
|
<tr><td>License</td><td>BSL 1.1 (converts to Apache 2.0 in 2030)</td></tr>
|
||||||
<tr><td>Free tier</td><td>250 devices</td></tr>
|
<tr><td>Free tier</td><td>250 devices</td></tr>
|
||||||
<tr><td>Stability</td><td>Breaking changes expected before v11</td></tr>
|
<tr><td>Stability</td><td>Breaking changes expected before v11</td></tr>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "9.8.0",
|
"version": "9.8.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: tod
|
|||||||
description: The Other Dude — MikroTik fleet management platform
|
description: The Other Dude — MikroTik fleet management platform
|
||||||
type: application
|
type: application
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
appVersion: "9.8.0"
|
appVersion: "9.8.1"
|
||||||
kubeVersion: ">=1.28.0-0"
|
kubeVersion: ">=1.28.0-0"
|
||||||
keywords:
|
keywords:
|
||||||
- mikrotik
|
- mikrotik
|
||||||
|
|||||||
125
setup.py
125
setup.py
@@ -40,6 +40,7 @@ INIT_SQL_TEMPLATE = PROJECT_ROOT / "scripts" / "init-postgres.sql"
|
|||||||
INIT_SQL_PROD = PROJECT_ROOT / "scripts" / "init-postgres-prod.sql"
|
INIT_SQL_PROD = PROJECT_ROOT / "scripts" / "init-postgres-prod.sql"
|
||||||
COMPOSE_BASE = "docker-compose.yml"
|
COMPOSE_BASE = "docker-compose.yml"
|
||||||
COMPOSE_PROD = "docker-compose.prod.yml"
|
COMPOSE_PROD = "docker-compose.prod.yml"
|
||||||
|
COMPOSE_BUILD_OVERRIDE = "docker-compose.build.yml"
|
||||||
COMPOSE_CMD = [
|
COMPOSE_CMD = [
|
||||||
"docker", "compose",
|
"docker", "compose",
|
||||||
"-f", COMPOSE_BASE,
|
"-f", COMPOSE_BASE,
|
||||||
@@ -459,8 +460,8 @@ def preflight(args: argparse.Namespace) -> bool:
|
|||||||
"""Run all pre-flight checks. Returns True if OK to proceed."""
|
"""Run all pre-flight checks. Returns True if OK to proceed."""
|
||||||
banner("TOD Production Setup")
|
banner("TOD Production Setup")
|
||||||
print(" This wizard will configure your production environment,")
|
print(" This wizard will configure your production environment,")
|
||||||
print(" generate secrets, bootstrap OpenBao, build images, and")
|
print(" generate secrets, bootstrap OpenBao, pull or build images,")
|
||||||
print(" start the stack.")
|
print(" and start the stack.")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
section("Pre-flight Checks")
|
section("Pre-flight Checks")
|
||||||
@@ -989,6 +990,57 @@ def wizard_telemetry(config: dict, telem: SetupTelemetry, args: argparse.Namespa
|
|||||||
info("No diagnostics will be sent.")
|
info("No diagnostics will be sent.")
|
||||||
|
|
||||||
|
|
||||||
|
def _read_version() -> str:
|
||||||
|
"""Read the version string from the VERSION file."""
|
||||||
|
version_file = PROJECT_ROOT / "VERSION"
|
||||||
|
if version_file.exists():
|
||||||
|
return version_file.read_text().strip()
|
||||||
|
return "latest"
|
||||||
|
|
||||||
|
|
||||||
|
def wizard_build_mode(config: dict, args: argparse.Namespace) -> None:
|
||||||
|
"""Ask whether to use pre-built images or build from source."""
|
||||||
|
section("Build Mode")
|
||||||
|
|
||||||
|
version = _read_version()
|
||||||
|
config["tod_version"] = version
|
||||||
|
|
||||||
|
if args.non_interactive:
|
||||||
|
mode = getattr(args, "build_mode", None) or "prebuilt"
|
||||||
|
config["build_mode"] = mode
|
||||||
|
if mode == "source":
|
||||||
|
COMPOSE_CMD.extend(["-f", COMPOSE_BUILD_OVERRIDE])
|
||||||
|
ok(f"Build from source (v{version})")
|
||||||
|
else:
|
||||||
|
ok(f"Pre-built images from GHCR (v{version})")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f" TOD v{bold(version)} can be installed two ways:")
|
||||||
|
print()
|
||||||
|
print(f" {bold('1.')} {green('Pre-built images')} {dim('(recommended)')}")
|
||||||
|
print(" Pull ready-to-run images from GitHub Container Registry.")
|
||||||
|
print(" Fast install, no compilation needed.")
|
||||||
|
print()
|
||||||
|
print(f" {bold('2.')} Build from source")
|
||||||
|
print(" Compile Go, Python, and Node.js locally.")
|
||||||
|
print(" Requires 4+ GB RAM and takes 5-15 minutes.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = input(" Choice [1/2]: ").strip()
|
||||||
|
if choice in ("1", ""):
|
||||||
|
config["build_mode"] = "prebuilt"
|
||||||
|
ok("Pre-built images from GHCR")
|
||||||
|
break
|
||||||
|
elif choice == "2":
|
||||||
|
config["build_mode"] = "source"
|
||||||
|
COMPOSE_CMD.extend(["-f", COMPOSE_BUILD_OVERRIDE])
|
||||||
|
ok("Build from source")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
warn("Please enter 1 or 2.")
|
||||||
|
|
||||||
|
|
||||||
# ── Summary ──────────────────────────────────────────────────────────────────
|
# ── Summary ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def show_summary(config: dict, args: argparse.Namespace) -> bool:
|
def show_summary(config: dict, args: argparse.Namespace) -> bool:
|
||||||
@@ -1041,6 +1093,14 @@ def show_summary(config: dict, args: argparse.Namespace) -> bool:
|
|||||||
print(f" TELEMETRY_ENABLED = {dim('false')}")
|
print(f" TELEMETRY_ENABLED = {dim('false')}")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
print(f" {bold('Build Mode')}")
|
||||||
|
if config.get("build_mode") == "source":
|
||||||
|
print(" Mode = Build from source")
|
||||||
|
else:
|
||||||
|
print(f" Mode = {green('Pre-built images')}")
|
||||||
|
print(f" Version = {config.get('tod_version', 'latest')}")
|
||||||
|
print()
|
||||||
|
|
||||||
print(f" {bold('OpenBao')}")
|
print(f" {bold('OpenBao')}")
|
||||||
print(f" {dim('(will be captured automatically during bootstrap)')}")
|
print(f" {dim('(will be captured automatically during bootstrap)')}")
|
||||||
print()
|
print()
|
||||||
@@ -1121,6 +1181,7 @@ ENVIRONMENT=production
|
|||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
APP_NAME=TOD - The Other Dude
|
APP_NAME=TOD - The Other Dude
|
||||||
|
TOD_VERSION={config.get('tod_version', 'latest')}
|
||||||
|
|
||||||
# --- Storage ---
|
# --- Storage ---
|
||||||
GIT_STORE_PATH=/data/git-store
|
GIT_STORE_PATH=/data/git-store
|
||||||
@@ -1388,6 +1449,36 @@ def bootstrap_openbao(config: dict) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def pull_images() -> bool:
|
||||||
|
"""Pull pre-built images from GHCR."""
|
||||||
|
section("Pulling Images")
|
||||||
|
info("Downloading pre-built images from GitHub Container Registry...")
|
||||||
|
print()
|
||||||
|
|
||||||
|
services = ["api", "poller", "frontend", "winbox-worker"]
|
||||||
|
|
||||||
|
for i, service in enumerate(services, 1):
|
||||||
|
info(f"[{i}/{len(services)}] Pulling {service}...")
|
||||||
|
try:
|
||||||
|
run_compose("pull", service, timeout=600)
|
||||||
|
ok(f"{service} pulled successfully")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
fail(f"Failed to pull {service}")
|
||||||
|
print()
|
||||||
|
warn("Check your internet connection and that the image exists.")
|
||||||
|
warn("To retry:")
|
||||||
|
info(f" docker compose -f {COMPOSE_BASE} -f {COMPOSE_PROD} "
|
||||||
|
f"--env-file .env.prod pull {service}")
|
||||||
|
return False
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
fail(f"Pull of {service} timed out (10 min)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print()
|
||||||
|
ok("All images ready")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def build_images() -> bool:
|
def build_images() -> bool:
|
||||||
"""Build Docker images one at a time to avoid OOM."""
|
"""Build Docker images one at a time to avoid OOM."""
|
||||||
section("Building Images")
|
section("Building Images")
|
||||||
@@ -1405,7 +1496,8 @@ def build_images() -> bool:
|
|||||||
fail(f"Failed to build {service}")
|
fail(f"Failed to build {service}")
|
||||||
print()
|
print()
|
||||||
warn("To retry this build:")
|
warn("To retry this build:")
|
||||||
info(f" docker compose -f {COMPOSE_BASE} -f {COMPOSE_PROD} build {service}")
|
info(f" docker compose -f {COMPOSE_BASE} -f {COMPOSE_PROD} "
|
||||||
|
f"-f {COMPOSE_BUILD_OVERRIDE} build {service}")
|
||||||
return False
|
return False
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
fail(f"Build of {service} timed out (15 min)")
|
fail(f"Build of {service} timed out (15 min)")
|
||||||
@@ -1558,6 +1650,9 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
help="Enable anonymous diagnostics")
|
help="Enable anonymous diagnostics")
|
||||||
parser.add_argument("--no-telemetry", action="store_true", default=False,
|
parser.add_argument("--no-telemetry", action="store_true", default=False,
|
||||||
help="Disable anonymous diagnostics")
|
help="Disable anonymous diagnostics")
|
||||||
|
parser.add_argument("--build-mode", type=str, default=None,
|
||||||
|
choices=["prebuilt", "source"],
|
||||||
|
help="Image source: prebuilt (pull from GHCR) or source (compile locally)")
|
||||||
parser.add_argument("--yes", "-y", action="store_true", default=False,
|
parser.add_argument("--yes", "-y", action="store_true", default=False,
|
||||||
help="Auto-confirm summary (don't prompt for confirmation)")
|
help="Auto-confirm summary (don't prompt for confirmation)")
|
||||||
return parser
|
return parser
|
||||||
@@ -1602,6 +1697,7 @@ def main() -> int:
|
|||||||
|
|
||||||
# Phase 2: Wizard
|
# Phase 2: Wizard
|
||||||
try:
|
try:
|
||||||
|
wizard_build_mode(config, args)
|
||||||
wizard_database(config, args)
|
wizard_database(config, args)
|
||||||
wizard_security(config)
|
wizard_security(config)
|
||||||
wizard_admin(config, args)
|
wizard_admin(config, args)
|
||||||
@@ -1651,18 +1747,29 @@ def main() -> int:
|
|||||||
error_message="Aborted after OpenBao failure")
|
error_message="Aborted after OpenBao failure")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Phase 5: Build
|
# Phase 5: Build or Pull
|
||||||
t0 = time.monotonic()
|
t0 = time.monotonic()
|
||||||
if not build_images():
|
if config.get("build_mode") == "source":
|
||||||
|
images_ok = build_images()
|
||||||
|
step_name = "build_images"
|
||||||
|
fail_msg = "Docker build failed"
|
||||||
|
retry_hint = "Fix the build error and re-run setup.py to continue."
|
||||||
|
else:
|
||||||
|
images_ok = pull_images()
|
||||||
|
step_name = "pull_images"
|
||||||
|
fail_msg = "Image pull failed"
|
||||||
|
retry_hint = "Check your connection and re-run setup.py to continue."
|
||||||
|
|
||||||
|
if not images_ok:
|
||||||
duration_ms = int((time.monotonic() - t0) * 1000)
|
duration_ms = int((time.monotonic() - t0) * 1000)
|
||||||
telem.step("build_images", "failure", duration_ms=duration_ms)
|
telem.step(step_name, "failure", duration_ms=duration_ms)
|
||||||
warn("Fix the build error and re-run setup.py to continue.")
|
warn(retry_hint)
|
||||||
telem.step("setup_total", "failure",
|
telem.step("setup_total", "failure",
|
||||||
duration_ms=int((time.monotonic() - setup_start) * 1000),
|
duration_ms=int((time.monotonic() - setup_start) * 1000),
|
||||||
error_message="Docker build failed")
|
error_message=fail_msg)
|
||||||
return 1
|
return 1
|
||||||
duration_ms = int((time.monotonic() - t0) * 1000)
|
duration_ms = int((time.monotonic() - t0) * 1000)
|
||||||
telem.step("build_images", "success", duration_ms=duration_ms)
|
telem.step(step_name, "success", duration_ms=duration_ms)
|
||||||
|
|
||||||
# Phase 6: Start
|
# Phase 6: Start
|
||||||
t0 = time.monotonic()
|
t0 = time.monotonic()
|
||||||
|
|||||||
Reference in New Issue
Block a user