Files
magpie/update_container_cli_test.go
T
yetone 1734d2b33d update: magpie web in a Docker container says to pull the new image instead of only linking the release page, and magpie update in one says the same instead of failing on the write (Chapin on Discord: 网页端的更新按钮…跳转到github)
The image runs /magpie as nonroot (uid 65532) in a read-only distroless root with no pkexec, so the updater's placeExe found the binary's folder unwritable and no way to elevate: stuck "not-writable", and the page said to run `sudo magpie update` or reinstall from the release page, neither of which works in a container (no sudo, and a replaced binary is lost when the container is recreated). The Download button there opens the release page, which is what Chapin saw if they run the image. Now, when the folder can't be written, nothing can elevate, and gateway.InContainer() (the existing .dockerenv / run/.containerenv / cgroup check, now exported) says this is a container, the stuck reason is "container" and the page says: pull ghcr.io/yetone/magpie:latest and recreate the container (new string in en, zh, zh-TW, ja, de). `magpie update` in such a container fails up front with the same advice, instead of downloading and then failing to replace itself. A writable binary in a container (a user's own image) still updates itself as before.

Tested: TestUpdateInContainerSaysPullTheImage in internal/gui (unwritable folder in a container: stuck "container", no StuckDir; outside one: not-writable with the dir; writable folder in a container: the exe is taken) and in package main (in a container the error carries the docker pull advice; outside one it doesn't). Both fail with the container check taken out (gui: Stuck not-writable; main: "the release lists no checksum"). update-stuck.test.cjs now has the container case in en/zh/zh-TW/ja/de; with the app.js line removed it fails 11 of 20; with it, 20/20 in Chromium and WebKit. i18n-keys, gui-ja, gui-de, gui-zh-tw, click-scroll and every update-* GUI test pass in both engines. Real run: the image built from this tree (VERSION=0.1.10), `magpie web` in it with MAGPIE_UPDATE_FEED at a host feed listing 0.1.11: /api/update answered state available, stuck "container"; `docker exec … /magpie update` printed the pull-the-image message and exited 1, the binary stayed 0.1.10. gofmt clean; go vet on ., ./internal/gui/, ./internal/gateway/ for darwin, GOOS=linux and GOOS=windows -tags nogui; full go test -tags nogui ./... under a temp HOME. The page's look in a real browser against the container was not looked at (the GUI test renders the same pill).
2026-10-09 10:43:14 +08:00

56 lines
2.0 KiB
Go

package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/yetone/magpie/internal/update"
)
// `magpie update` in the Docker image (/magpie, run as nonroot) failed with
// the rename's "permission denied"; it now says to pull the new image.
// Outside a container it goes on to the download as before.
func TestUpdateInContainerSaysPullTheImage(t *testing.T) {
if runtime.GOOS == "windows" || os.Geteuid() == 0 {
t.Skip("needs a folder this user may not write to")
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// no checksum: a download is refused before anything is fetched or written
json.NewEncoder(w).Encode(update.Release{Version: "0.1.11", URL: "https://github.com/yetone/magpie-releases/releases/tag/v0.1.11",
Assets: map[string]update.Asset{update.BinaryAsset(): {URL: "http://" + r.Host + "/asset"}}})
}))
defer srv.Close()
t.Setenv("MAGPIE_UPDATE_FEED", srv.URL)
dir := t.TempDir()
exe := filepath.Join(dir, "magpie")
if err := os.WriteFile(exe, []byte("magpie"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Chmod(dir, 0o555); err != nil {
t.Fatal(err)
}
defer os.Chmod(dir, 0o755)
wasV, wasX, wasE, wasC := version, updateExecutable, updateCanElevate, updateInContainer
t.Cleanup(func() { version, updateExecutable, updateCanElevate, updateInContainer = wasV, wasX, wasE, wasC })
version = "0.1.10"
updateExecutable = func() (string, error) { return exe, nil }
updateCanElevate = func() bool { return false }
updateInContainer = func() bool { return true }
err := updateCmd([]string{"update"})
if err == nil || !strings.Contains(err.Error(), "docker pull ghcr.io/yetone/magpie:latest") {
t.Fatalf("in a container: %v", err)
}
updateInContainer = func() bool { return false }
if err := updateCmd([]string{"update"}); err == nil || strings.Contains(err.Error(), "docker pull") {
t.Fatalf("outside one: %v", err)
}
}