From ec89eb7d0fd3418986a8bcfed52dfbe33c451532 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 25 May 2026 17:01:48 -0700 Subject: [PATCH] Add advantech router app updates --- Dockerfile | 2 +- updates/advantech.go | 48 +++++++++++++++++++++++++++++++++++++++++++ updates/selfupdate.go | 15 +++++++++++--- 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 updates/advantech.go diff --git a/Dockerfile b/Dockerfile index 6887923..10309d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ COPY entrypoint.sh / # Marks this as an official Fossorial container image. # Auto-update is disabled in official images — update by pulling a new image tag. -ENV NEWT_OFFICIAL_CONTAINER=true +ENV NEWT_SYSTEM_SUBSTRATE="CONTAINER" # Admin/metrics endpoint (Prometheus scrape) EXPOSE 2112 diff --git a/updates/advantech.go b/updates/advantech.go new file mode 100644 index 0000000..18cacdf --- /dev/null +++ b/updates/advantech.go @@ -0,0 +1,48 @@ +//go:build !windows + +package updates + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/fosrl/newt/logger" +) + +const advantechVersionFile = "/opt/newt/etc/version" + +// postUpdateAdvantech performs Advantech Router App-specific post-update steps: +// - Writes the new version string to /opt/newt/etc/version so that the +// router firmware's package management reflects the installed version. +// - Updates the PID file at pidFile (if non-empty) with the current process +// PID, in case the re-exec lands with a new PID on platforms where +// syscall.Exec behaviour differs. +func postUpdateAdvantech(newVersion, pidFile string) error { + // --- Write version file --- + versionDir := filepath.Dir(advantechVersionFile) + if err := os.MkdirAll(versionDir, 0755); err != nil { + return fmt.Errorf("advantech: failed to create version directory %s: %w", versionDir, err) + } + + if err := os.WriteFile(advantechVersionFile, []byte(newVersion+"\n"), 0644); err != nil { + return fmt.Errorf("advantech: failed to write version file %s: %w", advantechVersionFile, err) + } + logger.Debug("postUpdateAdvantech: wrote version %s to %s", newVersion, advantechVersionFile) + + // --- Update PID file --- + // syscall.Exec replaces the process image in-place so the PID is preserved. + // We update the PID file here anyway so that any race between the old and + // new binary is covered (e.g. on platforms that fork before exec). + if pidFile != "" { + pid := fmt.Sprintf("%d\n", os.Getpid()) + if err := os.WriteFile(pidFile, []byte(pid), 0644); err != nil { + // Non-fatal: log and continue so the update still proceeds. + logger.Debug("postUpdateAdvantech: warning: failed to update PID file %s: %v", pidFile, err) + } else { + logger.Debug("postUpdateAdvantech: updated PID file %s with PID %d", pidFile, os.Getpid()) + } + } + + return nil +} diff --git a/updates/selfupdate.go b/updates/selfupdate.go index f80dc2d..c828236 100644 --- a/updates/selfupdate.go +++ b/updates/selfupdate.go @@ -52,10 +52,10 @@ type versionResponse struct { // isOfficialContainer returns true when the process is running inside an // official Fossorial-built container image. The image sets -// NEWT_OFFICIAL_CONTAINER=true at build time; users running newt in their own +// NEWT_SYSTEM_SUBSTRATE="CONTAINER" at build time; users running newt in their own // containers (or bare-metal) will not have this variable set. func isOfficialContainer() bool { - return os.Getenv("NEWT_OFFICIAL_CONTAINER") == "true" + return os.Getenv("NEWT_SYSTEM_SUBSTRATE") == "CONTAINER" } // platform returns the OS+arch string used in the newt release binary names, @@ -109,7 +109,7 @@ func verifySHA256(path, expected string) error { // the function does not return – the process is replaced by the new binary via // syscall.Exec. func CheckAndSelfUpdate(cfg SelfUpdateConfig) error { - logger.Debug("++++++++++++++++++++++++++++++++++++++++++++checkAndSelfUpdate: starting update check (currentVersion=%s)", cfg.CurrentVersion) + logger.Debug("checkAndSelfUpdate: starting update check (currentVersion=%s)", cfg.CurrentVersion) if isOfficialContainer() { logger.Debug("checkAndSelfUpdate: running inside official container, skipping auto-update") @@ -280,6 +280,15 @@ func CheckAndSelfUpdate(cfg SelfUpdateConfig) error { return fmt.Errorf("failed to replace binary (you may need to run as root): %w", err) } + systemSubstrate := os.Getenv("NEWT_SYSTEM_SUBSTRATE") + logger.Debug("checkAndSelfUpdate: system substrate: %s", systemSubstrate) + if systemSubstrate == "ADVANTECH_ROUTER_APP" { + pidFile := os.Getenv("NEWT_PID_FILE") + if err := postUpdateAdvantech(verResp.Data.LatestVersion, pidFile); err != nil { + logger.Debug("checkAndSelfUpdate: advantech post-update steps failed: %v", err) + } + } + // --- Step 4: Re-exec --- logger.Debug("checkAndSelfUpdate: re-executing new binary") return reexec(exePath)