7 Commits
Author SHA1 Message Date
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
yetone bc141ad834 update: install.sh and magpie update take --proxy and --mirror, and magpie update mirror keeps one for the app's updates too (akic404 on Discord: 安装和update如果没有梯子也是麻烦事,能否在install.sh和magpie update后加子选项?--proxy等可自定义或默认ghproxy) — install.sh had no options and fetched with curl alone, and magpie update took none; without a way out to GitHub both failed. Now both take --proxy <url> (http, https, socks5, socks5h; install.sh also MAGPIE_PROXY=) for the feed and the download, and --mirror <prefix> (or MAGPIE_MIRROR=), a GitHub download mirror the release file is fetched from as <prefix>https://github.com/…; only github.com / githubusercontent.com URLs are put through it. No mirror is used unless one is given: magpie names none and has none by default. The feed with each file's SHA-256 is always fetched from usemagpie.ai, never through the mirror, so a file the mirror changed is refused and deleted ("… from the mirror X does not match its checksum from https://usemagpie.ai; not installed"); macOS still checks the app's signing team. magpie update mirror <prefix>|off saves Settings' updateMirror (http(s) only), which the app's own background update follows too; magpie update --mirror off skips it once; the Settings page's save keeps it. The app's update already followed Settings → Proxy (#294); magpie update --proxy overrides it for one run. The environment's ALL_PROXY / all_proxy is now honoured when HTTPS_PROXY / HTTP_PROXY are unset (Go's ProxyFromEnvironment ignores it), with NO_PROXY as curl reads it. magpie update takes --flag value or --flag=value, and an unknown word is an error instead of an update. The help, README and the docs' FAQ (en/zh/ja) say how. install.test.mjs's proxy, mirror and tampered-mirror tests, TestAllProxyFromTheEnvironment, TestUpdateThroughAMirror, TestUpdateThroughAProxyGiven and TestUpdateFlags fail on the old code and pass; TestSettingsSaveKeepsWhatItDoesNotSend covers updateMirror. A dry run of install.sh as Linux amd64 into a temp HOME installed the real 0.1.773 with its SHA-256 checked, refused a local mirror serving a wrong file, and with --proxy to a closed port failed reaching the site through it. go vet, builds (darwin, GOOS=linux, GOOS=windows), sh -n, node --test site/install.test.mjs and go test -tags nogui -count=1 ./... under a temp HOME pass but TestClaudeIdentityServedWhileAsked, a timing test that passes alone. Not tried through a real proxy server or a real public mirror; no install.ps1 exists for Windows, and the app has no field for the mirror. 2026-10-04 00:19:47 +08:00
erlinerdandClaude Code 94008ce66f update: say so when the magpie command is a copy the app left behind (#623)
* update: say so when the `magpie` command is a copy the app left behind

install.sh links the terminal's magpie into ~/.local/bin to the app's
own binary, so it follows every GUI update for free. A command that
arrived as a copied file — an older installer, a hand cp — keeps
running its own build once magpie.app moves on, and a command behind
the app is a stale build: one that predates the WebDAV short-write
check (#531) truncated a remote backup on `magpie webdav now`, wedging
the sync on every tick after. The gap is macOS-only: on Linux the app
and the command are one file, and its update replaces it in place.

magpie doesn't rewrite the user's PATH itself — that's the installer's
job, and a background update has no business reaching there. What the
command *is* says whether it can follow the app, so update.StaleCLI is
a plain Lstat of the installer's own convention (MAGPIE_BIN_DIR, else
~/.local/bin): a regular file is behind, a link isn't, and nothing
runs the old binary to ask its version. When it's behind, magpie says
so where the user can see it: /api/state carries cliBehind and the
window's status line says it once per run (the panel stays quiet, too
small for advice), and `magpie update` adds the line after updating
the app it found through the same copied command.

TestStaleCLI pins the three file states over a temp bin dir;
cli-behind.test.cjs (WebKit and Chromium, en and zh) fakes a state
with cliBehind and checks the window says it, the panel doesn't, and
a following command is never mentioned. go vet, and go test -tags
nogui ./internal/update ./internal/gui under a temp HOME pass.

* update: only say the command is behind when it is, and how to fix it

Two things the review caught about the notice.

A copy that isn't actually behind the app shouldn't nag. The
standalone magpie-cli build is a legitimate regular file in
~/.local/bin beside the app, and the first cut warned about it on
every window load however current it was. StaleCLI now takes the
app's version and stays quiet unless the copy is a release that
trails it — a copy at or ahead of the app, a build from source, and
one that won't run are all left be. Reading the version runs the old
binary, whose start-up migrates things, but that's the only way to
tell a stale release from a current one, and it's reached only for a
file already known by Lstat to be a copy, once per run.

The advice named a step that didn't fix it. `magpie update` run from
a copied command has no bundle to update (the copy isn't inside a
.app), so it replaces the copy with another copy and the warning
comes back. Both the window's line and `magpie update`'s now point
at what actually works: re-run the installer, or link the command by
hand — `ln -sf <app>/Contents/MacOS/magpie <cli>`, the link
install.sh makes.

TestStaleCLI covers the version decision (behind → say, current,
ahead, source, unreadable → quiet) over a temp bin dir; the browser
test's wording follows. Verified by mutation: dropping the version
guard turns the stale case red, and Stat for Lstat turns the link
case red. go vet, and go test -tags nogui ./internal/update
./internal/gui under a temp HOME pass.

* gui: the window's copied-command advice is an Lstat with a per-version dismiss (yetone's review on #623)

What: the window no longer runs the old binary's `version` to tell a stale
copy from a current one. update.CopiedCLI is a bare Lstat, reached on every
launch; the version read stays in update.StaleCLI, which only `magpie
update` calls. The advice moved from a 9s status line to a small dialog
with "Hide until the next version", kept in settings' cli-behind-quiet
file as the version it was dismissed at, so the next app update asks once
more. POST /api/cli-behind/quiet writes it.

Why: `magpie version` isn't read-only — run() migrates settings before the
command answers, so an old copy rewrote the user's real config with the old
build's migrations (old MoveOffAccountIDs could drop fields), and the read
on the first /api/state cost up to 2s on a cold start. An Lstat can't tell
whether the copy trails, so what the window says is advice, not a warning
that knows — dismissable, and worded as a copy rather than a stale one.

Tests: TestCLIBehindIsLstatOnlyAndDismissible (a non-executable file is
reported — nothing ran; dismiss quiets it and the file holds this version;
the installer's symlink is quiet). cli-behind.test.cjs covers the dialog,
its dismiss POST and the quiet panel, chromium+webkit, en+zh, 4/4.
go vet, go build -tags nogui (darwin, GOOS=linux, GOOS=windows) and
go test -tags nogui -count=1 ./... under a temp HOME pass (netproxy's
TestBunThroughBridge fails the same way on the branch without this change).

Co-Authored-By: Claude Code <noreply@anthropic.com>

* gui: the dismiss survives a restart, and cliBehindVal is behind a lock (yetone's review on #623)

What: the POST handler wrote cliBehindVal = "" while state() read it on
other goroutines with no lock — a page dismissing while another view
polled /api/state raced. cliBehindVal now sits behind cliBehindMu on
both sides. The test also proves the promise the handler's one-line
clear couldn't: after a simulated restart (fresh Once, empty value)
the dismissed advice stays quiet, and the next version asks once more.

Why: the restart case was untested — short-circuiting cliQuiet() left
the suite green, because the handler's in-memory clear alone satisfied
it. The quiet file is what actually carries the dismiss across runs.

Tests: TestCLIBehindIsLstatOnlyAndDismissible extended with the
restart and next-version legs; `if false && cliQuiet()` now fails it
(mutation-checked). go vet, go test -tags nogui -race ./internal/gui
./internal/update pass; TestGatewayTakenOver fails the same way with
-race on the unmodified branch (pre-existing).

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-10-03 13:32:42 +08:00
yetone 134a788821 settings: magpie's own update checks can be turned off, and run every 30 minutes, hour, six hours or day (#472: 更新行为可配置) — the app asked the feed five seconds after start and every six hours, a constant, and downloaded any newer version at once; with releases out most days, a version out just after a check waited up to six hours, and there was no way to stop the checks or the downloads they start (Update button only hides the pill). Settings → About has Automatic updates (on by default) and Check every (30 min, 1 h, 6 h, 24 h; 6 h with nothing set), kept in settings.json as noAutoUpdate and updateEvery, and magpie update auto [on|off] [30m|1h|6h|24h] sets them from the terminal. The updater reads them each minute and asks once the interval since the last check, its own or a click on Check, is up, and never by itself while off; the version row, not asked yet, then says automatic updates are off and offers Check. Check every stays in place, dimmed, while off, so the click doesn't shorten the page under it. TestUpdateDue, TestUpdateAskedAt and TestUpdateAutoCmd fail without the change, as does update-auto.test.cjs (Chromium, WebKit; en, zh). go vet, go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build, go test -tags nogui ./... pass; update-hide, update-check, whats-new, settings-groups, click-scroll, old-webkit pass. 2026-10-02 06:42:15 +08:00
yetone 06dddf7f68 update: magpie update in a Homebrew install (brew install magpie, from the new tap in magpie-releases) says to brew upgrade magpie instead of replacing the binary in brew's Cellar, which would leave brew thinking the old version is there 2026-09-26 20:50:26 +08:00
yetone 90580ca7e1 update: Update updates magpie in place — a click downloads the new version and restarts into it, asking for the password when magpie can't replace itself, instead of opening the release page; only a magpie running from its disk image or a temporary copy is sent to the download page, with why 2026-09-25 01:35:24 +08:00
yetone 73ef0e5276 usemagpie.ai, and a magpie that updates itself
site/ is the website: a static page plus a worker that turns the newest
release of yetone/magpie-releases into an update feed (/api/latest, with
every file's SHA-256), stable download links and an install script.

The app asks the feed at start-up and every six hours, downloads a newer
version in the background, and installs it on Restart to Update or on
quit. A download must match its checksum and be signed by the same team
as the app it replaces. `magpie update` does the same from a terminal and
replaces a bare binary in place.
2026-09-24 01:57:50 +08:00