From fe70100959f6e061f587066d679d174d5ed7fb0f Mon Sep 17 00:00:00 2001 From: tkinaba Date: Wed, 26 Aug 2026 16:52:06 -0300 Subject: [PATCH] =?UTF-8?q?fix(updater):=20setar=20docker=5Fconfig=20e=20r?= =?UTF-8?q?ecriar=20container=20com=20configura=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/tui/docker.go | 22 ++++++++++++++++++++-- internal/tui/docker_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/internal/tui/docker.go b/internal/tui/docker.go index 6aad510..e7e7907 100644 --- a/internal/tui/docker.go +++ b/internal/tui/docker.go @@ -182,6 +182,16 @@ func shellQuoteArgs(args []string) string { // app-cliente image. const updaterPollIntervalSeconds = 300 +// buildRecreateCmd takes the app-cliente run argv (built with the wizard-time port) and +// swaps the baked-in port for a `$PORT` shell reference, so the poll script's recreate +// step uses whatever port it reads live from the container (see the PORT= line in +// RunUpdaterDockerContainer) instead of always replaying the port typed into the wizard. +func buildRecreateCmd(recreateArgs []string, wizardPort string) string { + wizardPortArg := shellQuote(fmt.Sprintf("%s:%d", wizardPort, containerAppPort)) + livePortArg := fmt.Sprintf(`"$PORT:%d"`, containerAppPort) + return strings.Replace("docker "+shellQuoteArgs(recreateArgs), wizardPortArg, livePortArg, 1) +} + // RunUpdaterDockerContainer starts a tiny self-contained auto-updater for the // app-dono-cliente container: no third-party updater project, just the official // `docker:cli` image running a poll loop (docker pull, compare image IDs, recreate on @@ -192,6 +202,11 @@ const updaterPollIntervalSeconds = 300 // It mounts the docker socket (to pull/recreate) and the host's docker config.json // (written by the StepDockerLogin `docker login`) so `docker pull` can authenticate // against the private registry. +// +// The recreate command reuses appClienteRunArgs' host port only as a fallback: at +// recreate time the script re-reads the live container's actual published port via +// `docker inspect`, so a port changed by hand after install survives an auto-update +// instead of being silently reverted to whatever was typed into the wizard. func RunUpdaterDockerContainer(appImage, appContainerName, configPath, configDestinationPath string, cv ConfigValues) error { updaterName := "app-dono-updater" @@ -211,7 +226,7 @@ func RunUpdaterDockerContainer(appImage, appContainerName, configPath, configDes if err != nil { return err } - recreateCmd := "docker " + shellQuoteArgs(recreateArgs) + recreateCmd := buildRecreateCmd(recreateArgs, cv.Server["port"]) script := fmt.Sprintf(`set -e IMAGE=%s @@ -221,13 +236,15 @@ while true; do CURRENT=$(docker inspect --format '{{.Image}}' "$NAME" 2>/dev/null || true) LATEST=$(docker inspect --format '{{.Id}}' "$IMAGE" 2>/dev/null || true) if [ -n "$LATEST" ] && [ "$CURRENT" != "$LATEST" ]; then + PORT=$(docker inspect --format '{{(index (index .NetworkSettings.Ports "%d/tcp") 0).HostPort}}' "$NAME" 2>/dev/null || true) + if [ -z "$PORT" ]; then PORT=%s; fi docker stop "$NAME" >/dev/null 2>&1 || true docker rm "$NAME" >/dev/null 2>&1 || true %s fi sleep %d done -`, shellQuote(appImage), shellQuote(appContainerName), recreateCmd, updaterPollIntervalSeconds) +`, shellQuote(appImage), shellQuote(appContainerName), containerAppPort, shellQuote(cv.Server["port"]), recreateCmd, updaterPollIntervalSeconds) args := []string{ "run", "-d", @@ -235,6 +252,7 @@ done "--restart", "unless-stopped", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", fmt.Sprintf("%s:/config.json", dockerConfigPath), + "-e", "DOCKER_CONFIG=/", "--log-opt", "max-size=5m", "--log-opt", "max-file=1", "--entrypoint", "sh", diff --git a/internal/tui/docker_test.go b/internal/tui/docker_test.go index 94f045d..720abce 100644 --- a/internal/tui/docker_test.go +++ b/internal/tui/docker_test.go @@ -39,3 +39,35 @@ func TestShellQuoteArgsRoundTrip(t *testing.T) { } } } + +// TestBuildRecreateCmdUsesLivePort guards against the updater silently reverting a host +// port that was changed by hand after install: the recreate command must reference the +// live $PORT read at recreate time, not replay the port typed into the wizard. +func TestBuildRecreateCmdUsesLivePort(t *testing.T) { + if _, err := exec.LookPath("sh"); err != nil { + t.Skip("sh not available") + } + + args := []string{"run", "-d", "-p", "9999:8080", "--name", "app-dono-cliente"} + cmd := buildRecreateCmd(args, "9999") + + if strings.Contains(cmd, "9999:8080") { + t.Fatalf("recreate command still contains the wizard-time port literal: %s", cmd) + } + if !strings.Contains(cmd, `"$PORT:8080"`) { + t.Fatalf("recreate command missing live $PORT reference: %s", cmd) + } + + // Swap the leading `docker` for `printf` so we can inspect the argv sh would have + // passed to docker, with PORT set as the poll script would set it live. + script := "PORT=8081\n" + strings.Replace(cmd, "docker ", "printf '%s\\n' ", 1) + out, err := exec.Command("sh", "-c", script).Output() + if err != nil { + t.Fatalf("sh failed: %v", err) + } + got := strings.Split(strings.TrimRight(string(out), "\n"), "\n") + want := []string{"run", "-d", "-p", "8081:8080", "--name", "app-dono-cliente"} + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } +}