maybeSelfUpdate runs the pre-launch auto-update. No-op for local builds, an already-current hash, an unsupported platform (see update.assetName), or any network/filesystem refusal. On success it swaps the binary and re-execs via reExec (which only returns on failure). Any failure past "update availa
()
| 161 | // re-execs via reExec (which only returns on failure). Any failure past |
| 162 | // "update available" prints one stderr line and proceeds with the old binary. |
| 163 | func maybeSelfUpdate() { |
| 164 | // Skip local builds: hashing a `go run` temp binary against the |
| 165 | // published checksum would otherwise swap in the last release and hide |
| 166 | // unreleased work behind an "update applied" banner. |
| 167 | if isLocalBuild(version) { |
| 168 | return |
| 169 | } |
| 170 | exe, err := os.Executable() |
| 171 | if err != nil { |
| 172 | return |
| 173 | } |
| 174 | ctx, cancel := context.WithTimeout(context.Background(), updateBudget) |
| 175 | defer cancel() |
| 176 | if !update.Check(ctx, exe) { |
| 177 | return |
| 178 | } |
| 179 | fmt.Fprintln(os.Stderr, "◉ applying codehamr update...") |
| 180 | if err := update.Apply(ctx, exe); err != nil { |
| 181 | fmt.Fprintf(os.Stderr, "⚠ update failed: %v\n", err) |
| 182 | if os.IsPermission(err) { |
| 183 | fmt.Fprintln(os.Stderr, " tip: rerun with sudo, or reinstall with PREFIX=$HOME/.local") |
| 184 | } |
| 185 | return |
| 186 | } |
| 187 | // Re-launch the new binary. reExec is platform-split: unix execve (same |
| 188 | // PID) vs. Windows spawn-and-wait. CODEHAMR_NO_UPDATE_CHECK=1 stops the |
| 189 | // replacement run from re-checking its own freshly-written hash. On |
| 190 | // reExec failure we fall through to the old in-memory binary. |
| 191 | if err := reExec(exe, os.Args, reexecEnv()); err != nil { |
| 192 | fmt.Fprintf(os.Stderr, "⚠ re-exec failed: %v (continuing with previous version)\n", err) |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | // reexecEnv arms the update-loop guard and returns the environment for the |
| 197 | // re-exec'd child. os.Setenv overwrites in place so os.Environ() carries |
| 198 | // exactly one entry; append(os.Environ(), …) would leave a pre-existing |
| 199 | // user-set value first, and Unix execve resolves os.Getenv to the FIRST |
| 200 | // match, silently defeating the guard if someone exported |
| 201 | // CODEHAMR_NO_UPDATE_CHECK to a non-"1" value. |
| 202 | func reexecEnv() []string { |
| 203 | os.Setenv("CODEHAMR_NO_UPDATE_CHECK", "1") |
no test coverage detected