Refactor Dockerfiles to streamline PHP extension checks and remove unnecessary installations

This commit is contained in:
Jeppe Bundgaard
2026-06-30 10:58:08 +02:00
parent 4a7fc7c534
commit 7ccbb68ffa
6 changed files with 88 additions and 91 deletions
File diff suppressed because one or more lines are too long
@@ -1730,6 +1730,7 @@ CONFIG_PATH="$INSTALL_DIR/config.json"
CONFIG_TEMPLATE_PATH="$INSTALL_DIR/config.template.json"
HEARTBEAT_MARKER_PATH="$RUNTIME_DIR/last-heartbeat-ok.txt"
STACK_SERVICE_PATH="/etc/systemd/system/truckwash-edge-gateway-stack.service"
LEGACY_SERVICE_PATH="/etc/systemd/system/truckwash-edge-agent.service"
INSTALL_TOKEN="__INSTALL_TOKEN__"
INSTALL_STATUS_URL="__STATUS_URL__"
CURRENT_STEP="Preparing installer"
@@ -1737,7 +1738,6 @@ CURRENT_STEP_CODE="PENDING"
CURRENT_METHOD=""
CURRENT_URL=""
INSTALL_STARTED_AT="$(date +%s)"
REUSE_EXISTING_CREDENTIALS=0
DIAGNOSTIC_NAMES=()
DIAGNOSTIC_OUTPUTS=()
json_escape() {
@@ -1777,6 +1777,16 @@ emit_diagnostic_json() {
json="${json}]"
printf '%s' "$json"
}
print_collected_diagnostics() {
local index
for index in "${!DIAGNOSTIC_NAMES[@]}"; do
if [ -z "${DIAGNOSTIC_OUTPUTS[$index]:-}" ]; then
continue
fi
log_error "Diagnostic: ${DIAGNOSTIC_NAMES[$index]}"
printf '%s\n' "${DIAGNOSTIC_OUTPUTS[$index]}" | sed 's/^/[truckwash-edge-agent] /' >&2
done
}
capture_command_diagnostic() {
local name="$1"
shift
@@ -1859,6 +1869,7 @@ on_error() {
log_error "Last request: ${CURRENT_METHOD} ${CURRENT_URL}"
fi
diagnostics_json="$(collect_install_diagnostics)"
print_collected_diagnostics
gateway_id="$(read_config_value "$CONFIG_PATH" gatewayId 2>/dev/null || true)"
report_install_status "FAILED" "FAILED" "$failure_message" "$diagnostics_json" "$gateway_id"
exit "$exit_code"
@@ -1942,6 +1953,45 @@ fetch_http() {
[ "$cleanup_body" -eq 1 ] && rm -f "$body_path"
rm -f "$headers_path"
}
cleanup_existing_installation() {
if [ "$INSTALL_DIR" != "/opt/truckwash-edge-agent" ]; then
echo "Refusing to remove unexpected install directory: $INSTALL_DIR" >&2
return 1
fi
set +e
systemctl stop truckwash-edge-gateway-stack.service >/dev/null 2>&1
systemctl stop truckwash-edge-agent.service >/dev/null 2>&1
if [ -x "$INSTALL_DIR/gateway-launcher.sh" ]; then
TRUCKWASH_INSTALL_DIR="$INSTALL_DIR" "$INSTALL_DIR/gateway-launcher.sh" down >/dev/null 2>&1
elif [ -f "$INSTALL_DIR/docker-compose.gateway.yml" ]; then
if docker compose version >/dev/null 2>&1; then
docker compose -f "$INSTALL_DIR/docker-compose.gateway.yml" down >/dev/null 2>&1
elif command -v docker-compose >/dev/null 2>&1; then
docker-compose -f "$INSTALL_DIR/docker-compose.gateway.yml" down >/dev/null 2>&1
fi
fi
if command -v docker >/dev/null 2>&1; then
docker rm -f \
truckwash-edge-agent \
truckwash-lan-worker \
truckwash-auto-updater \
truckwash-redis \
truckwash-mariadb \
truckwash-minio >/dev/null 2>&1
fi
systemctl disable truckwash-edge-gateway-stack.service >/dev/null 2>&1
systemctl disable truckwash-edge-agent.service >/dev/null 2>&1
rm -f "$STACK_SERVICE_PATH" "$LEGACY_SERVICE_PATH"
rm -rf "$INSTALL_DIR"
systemctl daemon-reload >/dev/null 2>&1
systemctl reset-failed truckwash-edge-gateway-stack.service >/dev/null 2>&1
systemctl reset-failed truckwash-edge-agent.service >/dev/null 2>&1
set -e
}
config_has_claimed_gateway() {
local config_path="$1"
php -r '
@@ -1979,32 +2029,6 @@ read_config_value() {
echo (string)$value;
' "$config_path" "$key"
}
merge_agent_config() {
local template_path="$1"
local config_path="$2"
php -r '
$templatePath = $argv[1];
$configPath = $argv[2];
$template = json_decode((string)file_get_contents($templatePath), true);
if (!is_array($template)) {
fwrite(STDERR, "Invalid edge agent config template.\n");
exit(1);
}
$existing = [];
if (is_file($configPath)) {
$decoded = json_decode((string)file_get_contents($configPath), true);
if (is_array($decoded)) {
$existing = $decoded;
}
}
foreach (["gatewayId", "agentToken", "agentInstanceId", "installedVersion", "targetVersion", "lastStagedUpdate"] as $key) {
if (array_key_exists($key, $existing) && $existing[$key] !== null && $existing[$key] !== "") {
$template[$key] = $existing[$key];
}
}
file_put_contents($configPath, json_encode($template, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL);
' "$template_path" "$config_path"
}
heartbeat_marker_is_fresh() {
local heartbeat_path="$1"
local minimum_epoch="$2"
@@ -2083,25 +2107,10 @@ wait_for_gateway_claim() {
print_service_diagnostics
return 1
}
wait_for_post_restart_heartbeat() {
local heartbeat_path="$1"
local minimum_epoch="$2"
local timeout_seconds="${3:-30}"
local elapsed=0
while [ "$elapsed" -lt "$timeout_seconds" ]; do
if heartbeat_marker_is_fresh "$heartbeat_path" "$minimum_epoch"; then
return 0
fi
sleep 1
elapsed=$((elapsed + 1))
done
log_error "Gateway heartbeat was not observed within ${timeout_seconds}s after reinstall."
print_service_diagnostics
return 1
}
begin_install_phase "VERIFY_TOKEN" "Verifying install token"
fetch_http "Verify install token" "__VERIFY_URL__"
begin_install_phase "REMOVE_EXISTING_INSTALL" "Removing existing edge gateway installation"
run_step "Removing existing edge gateway installation" cleanup_existing_installation
begin_install_phase "INSTALL_PACKAGES" "Installing runtime dependencies"
run_step "Creating install directory" mkdir -p "$INSTALL_DIR" "$RUNTIME_DIR" "$RUNTIME_DIR/backups"
export DEBIAN_FRONTEND=noninteractive
@@ -2119,15 +2128,11 @@ fetch_http "Download auto-updater Dockerfile" "__AUTO_UPDATER_DOCKERFILE_URL__"
fetch_http "Download gateway launcher" "__LAUNCHER_URL__" "$INSTALL_DIR/gateway-launcher.sh"
fetch_http "Download compose stack service unit" "__STACK_SERVICE_URL__" "$INSTALL_DIR/truckwash-edge-gateway-stack.service"
fetch_http "Download compatibility service unit" "__LEGACY_SERVICE_URL__" "$INSTALL_DIR/truckwash-edge-agent.service"
if config_has_claimed_gateway "$CONFIG_PATH"; then
REUSE_EXISTING_CREDENTIALS=1
log_info "Existing claimed gateway detected; reinstall will reuse saved gateway credentials."
fi
begin_install_phase "WRITE_CONFIG" "Writing gateway configuration"
cat > "$CONFIG_TEMPLATE_PATH" <<'EOF_JSON'
__CONFIG_JSON__
EOF_JSON
run_step "Writing agent config" merge_agent_config "$CONFIG_TEMPLATE_PATH" "$CONFIG_PATH"
run_step "Writing fresh agent config" cp "$CONFIG_TEMPLATE_PATH" "$CONFIG_PATH"
rm -f "$CONFIG_TEMPLATE_PATH"
begin_install_phase "START_STACK" "Starting edge gateway stack"
run_step "Installing systemd stack definition" install -m 0644 "$INSTALL_DIR/truckwash-edge-gateway-stack.service" "$STACK_SERVICE_PATH"
@@ -2140,17 +2145,10 @@ run_step "Enabling truckwash-edge-gateway-stack.service" systemctl enable truckw
run_step "Restarting truckwash-edge-gateway-stack.service" systemctl restart truckwash-edge-gateway-stack.service
run_step "Verifying truckwash-edge-gateway-stack.service is active" systemctl is-active --quiet truckwash-edge-gateway-stack.service
begin_install_phase "WAIT_FOR_CLAIM" "Waiting for gateway heartbeat and claim"
if [ "$REUSE_EXISTING_CREDENTIALS" -eq 1 ]; then
run_step "Waiting for post-reinstall heartbeat" wait_for_post_restart_heartbeat "$HEARTBEAT_MARKER_PATH" "$INSTALL_STARTED_AT" 180
claimed_gateway_id="$(read_config_value "$CONFIG_PATH" gatewayId)"
report_install_status "CLAIMED" "CLAIMED" "Gateway reconnected using preserved credentials." "[]" "$claimed_gateway_id"
log_info "Reinstall reused gateway ${claimed_gateway_id}."
else
run_step "Waiting for gateway claim" wait_for_gateway_claim "$CONFIG_PATH" "$HEARTBEAT_MARKER_PATH" "$INSTALL_STARTED_AT" 180
claimed_gateway_id="$(read_config_value "$CONFIG_PATH" gatewayId)"
report_install_status "CLAIMED" "CLAIMED" "Gateway claim completed successfully." "[]" "$claimed_gateway_id"
log_info "Gateway claim completed for gateway ${claimed_gateway_id}."
fi
run_step "Waiting for gateway claim" wait_for_gateway_claim "$CONFIG_PATH" "$HEARTBEAT_MARKER_PATH" "$INSTALL_STARTED_AT" 180
claimed_gateway_id="$(read_config_value "$CONFIG_PATH" gatewayId)"
report_install_status "CLAIMED" "CLAIMED" "Gateway claim completed successfully." "[]" "$claimed_gateway_id"
log_info "Gateway claim completed for gateway ${claimed_gateway_id}."
echo 'TruckWash edge gateway stack installed.'
BASH;
@@ -3,10 +3,8 @@ FROM ${BASE_IMAGE}
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose libcurl4-openssl-dev libsqlite3-dev pkg-config; \
docker-php-ext-install sqlite3 pdo_sqlite; \
if ! php -m | grep -qi '^curl$'; then docker-php-ext-install curl; fi; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'; \
apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose; \
php -r 'foreach (["curl", "sqlite3", "pdo_sqlite"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'; \
rm -rf /var/lib/apt/lists/*
COPY auto-updater.php /usr/local/bin/auto-updater.php
@@ -1,13 +1,7 @@
ARG BASE_IMAGE=php:8.2-cli-bookworm
FROM ${BASE_IMAGE}
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev pkg-config; \
docker-php-ext-install sqlite3 pdo_sqlite; \
if ! php -m | grep -qi '^curl$'; then docker-php-ext-install curl; fi; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'; \
rm -rf /var/lib/apt/lists/*
RUN php -r 'foreach (["curl", "sqlite3", "pdo_sqlite"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'
WORKDIR /opt/truckwash-edge-agent
@@ -1,13 +1,7 @@
ARG BASE_IMAGE=php:8.2-cli-bookworm
FROM ${BASE_IMAGE}
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev pkg-config; \
docker-php-ext-install sqlite3 pdo_sqlite; \
if ! php -m | grep -qi '^curl$'; then docker-php-ext-install curl; fi; \
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'; \
rm -rf /var/lib/apt/lists/*
RUN php -r 'foreach (["curl", "sqlite3", "pdo_sqlite"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'
WORKDIR /opt/truckwash-edge-agent
@@ -37,18 +37,33 @@ it('builds the installer around the compose stack artifacts and management polli
expect($managerSource)->toContain('apt-get install -y docker-compose-plugin');
expect($managerSource)->toContain('apt-get install -y docker-compose');
expect($managerSource)->toContain('Unable to install Docker Compose using docker-compose-plugin or docker-compose.');
expect($managerSource)->toContain('Existing claimed gateway detected; reinstall will reuse saved gateway credentials.');
expect($managerSource)->toContain('begin_install_phase "REMOVE_EXISTING_INSTALL" "Removing existing edge gateway installation"');
expect($managerSource)->toContain('run_step "Removing existing edge gateway installation" cleanup_existing_installation');
expect($managerSource)->toContain('cleanup_existing_installation()');
expect($managerSource)->toContain('systemctl stop truckwash-edge-gateway-stack.service');
expect($managerSource)->toContain('systemctl stop truckwash-edge-agent.service');
expect($managerSource)->toContain('TRUCKWASH_INSTALL_DIR="$INSTALL_DIR" "$INSTALL_DIR/gateway-launcher.sh" down');
expect($managerSource)->toContain('docker rm -f');
expect($managerSource)->toContain('truckwash-minio');
expect($managerSource)->toContain('rm -f "$STACK_SERVICE_PATH" "$LEGACY_SERVICE_PATH"');
expect($managerSource)->toContain('rm -rf "$INSTALL_DIR"');
expect($managerSource)->toContain('report_install_status() {');
expect($managerSource)->toContain('begin_install_phase "START_STACK" "Starting edge gateway stack"');
expect($managerSource)->toContain('report_install_status "RUNNING" "$CURRENT_STEP_CODE" "$CURRENT_STEP"');
expect($managerSource)->toContain('report_install_status "FAILED" "FAILED" "$failure_message" "$diagnostics_json" "$gateway_id"');
expect($managerSource)->toContain('run_step "Writing agent config" merge_agent_config "$CONFIG_TEMPLATE_PATH" "$CONFIG_PATH"');
expect($managerSource)->toContain('print_collected_diagnostics()');
expect($managerSource)->toContain('log_error "Diagnostic: ${DIAGNOSTIC_NAMES[$index]}"');
expect($managerSource)->toContain('print_collected_diagnostics');
expect($managerSource)->toContain('run_step "Writing fresh agent config" cp "$CONFIG_TEMPLATE_PATH" "$CONFIG_PATH"');
expect($managerSource)->not->toContain('REUSE_EXISTING_CREDENTIALS');
expect($managerSource)->not->toContain('merge_agent_config()');
expect($managerSource)->toContain('chmod 0755 "$INSTALL_DIR/agent.php" "$INSTALL_DIR/lan-worker.php" "$INSTALL_DIR/auto-updater.php" "$INSTALL_DIR/gateway-launcher.sh"');
expect($managerSource)->toContain('systemctl enable truckwash-edge-gateway-stack.service');
expect($managerSource)->toContain('systemctl restart truckwash-edge-gateway-stack.service');
expect($managerSource)->toContain('systemctl is-active --quiet truckwash-edge-gateway-stack.service');
expect($managerSource)->toContain('run_step "Waiting for gateway claim" wait_for_gateway_claim "$CONFIG_PATH" "$HEARTBEAT_MARKER_PATH" "$INSTALL_STARTED_AT" 180');
expect($managerSource)->toContain('run_step "Waiting for post-reinstall heartbeat" wait_for_post_restart_heartbeat "$HEARTBEAT_MARKER_PATH" "$INSTALL_STARTED_AT" 180');
expect($managerSource)->not->toContain('run_step "Waiting for post-reinstall heartbeat"');
expect($managerSource)->not->toContain('Gateway reconnected using preserved credentials.');
expect($managerSource)->toContain('journalctl -u truckwash-edge-gateway-stack.service -n 60 --no-pager || true');
expect($managerSource)->not->toContain('agent.mjs');
expect($managerSource)->toContain("'brokerUrl' => \$this->buildBrokerPublicUrl()");
@@ -114,27 +129,25 @@ it('builds the installer around the compose stack artifacts and management polli
expect($agentSource)->toContain("'last_transport_error'");
expect($agentSource)->toContain('private function recordTransportFailure(string $context, Throwable $throwable): void');
expect($edgeDockerfileSource)->toContain('FROM ${BASE_IMAGE}');
expect($edgeDockerfileSource)->toContain('apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev pkg-config;');
expect($edgeDockerfileSource)->toContain('docker-php-ext-install sqlite3 pdo_sqlite;');
expect($edgeDockerfileSource)->toContain("if ! php -m | grep -qi '^curl$'; then docker-php-ext-install curl; fi;");
expect($edgeDockerfileSource)->not->toContain('docker-php-ext-install');
expect($edgeDockerfileSource)->toContain('extension_loaded($extension)');
expect($edgeDockerfileSource)->toContain('"pdo_sqlite"');
expect($edgeDockerfileSource)->toContain('Missing PHP extension: {$extension}');
expect($edgeDockerfileSource)->toContain('COPY agent.php /opt/truckwash-edge-agent/agent.php');
expect($workerDockerfileSource)->toContain('FROM ${BASE_IMAGE}');
expect($workerDockerfileSource)->toContain('apt-get install -y --no-install-recommends libcurl4-openssl-dev libsqlite3-dev pkg-config;');
expect($workerDockerfileSource)->toContain('docker-php-ext-install sqlite3 pdo_sqlite;');
expect($workerDockerfileSource)->toContain("if ! php -m | grep -qi '^curl$'; then docker-php-ext-install curl; fi;");
expect($workerDockerfileSource)->not->toContain('docker-php-ext-install');
expect($workerDockerfileSource)->toContain('extension_loaded($extension)');
expect($workerDockerfileSource)->toContain('"pdo_sqlite"');
expect($workerDockerfileSource)->toContain('Missing PHP extension: {$extension}');
expect($workerDockerfileSource)->toContain('COPY lan-worker.php /opt/truckwash-edge-agent/lan-worker.php');
expect($workerSource)->toContain('worker_require_authorization');
expect($agentSource)->toContain('X-Truckwash-Worker-Token: ');
expect($autoUpdaterSource)->toContain("'/bin/bash ' . escapeshellarg(\$launcherPath) . ' reconcile 2>&1'");
expect($autoUpdaterDockerfileSource)->toContain('COPY auto-updater.php /usr/local/bin/auto-updater.php');
expect($autoUpdaterDockerfileSource)->toContain('apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose libcurl4-openssl-dev libsqlite3-dev pkg-config;');
expect($autoUpdaterDockerfileSource)->toContain('docker-php-ext-install sqlite3 pdo_sqlite;');
expect($autoUpdaterDockerfileSource)->toContain("if ! php -m | grep -qi '^curl$'; then docker-php-ext-install curl; fi;");
expect($autoUpdaterDockerfileSource)->toContain('apt-get install -y --no-install-recommends bash ca-certificates curl docker.io docker-compose;');
expect($autoUpdaterDockerfileSource)->not->toContain('docker-php-ext-install');
expect($autoUpdaterDockerfileSource)->toContain('extension_loaded($extension)');
expect($autoUpdaterDockerfileSource)->toContain('"pdo_sqlite"');
expect($serviceSource)->not->toContain('node /opt/truckwash-edge-agent/agent.mjs');
});