fix(pkg-helper): restore group ownership on apk-packages persist file (#456)

* fix(pkg-helper): restore group ownership on apk-packages persist file

persistRemove() uses write-to-temp-then-rename which creates the new
file as root:root. The goclaw process (uid 1000, gid 1000) can't read
it, causing ListInstalledPackages to return nil for system packages.
After any package uninstall, the UI shows an empty package list.

Fix: chown listFile to root:goclaw (0:1000) after rename in
persistRemove and after initial creation in persistAdd.

* fix(ui): reset uninstall success icon to trash after timeout

After uninstalling a package, the success checkmark icon stayed
permanently instead of reverting to the delete (trash) icon.

Add setTimeout to reset actionStatus from "success" to "idle" after 2s,
matching the existing pattern used for error states.
This commit is contained in:
Thieu Nguyen authored and GitHub committed 2026-03-25 08:30:49 +07:00
1 parent 424f3a975c
commit 18824f841d
2 files changed
+14

No files matched your search

+13
View File
@@ -183,6 +183,10 @@ func persistAdd(pkg string) {
}
}
created := false
if _, err := os.Stat(listFile); os.IsNotExist(err) {
created = true
}
f, err := os.OpenFile(listFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640)
if err != nil {
slog.Warn("pkg-helper: persist add failed", "error", err)
@@ -190,6 +194,10 @@ func persistAdd(pkg string) {
}
defer f.Close()
fmt.Fprintln(f, pkg)
// Ensure group ownership allows the goclaw process to read the file.
if created {
os.Chown(listFile, 0, 1000) //nolint:errcheck
}
}
// persistRemove removes a package name from the apk persist file.
@@ -217,7 +225,12 @@ func persistRemove(pkg string) {
if err := os.Rename(tmpFile, listFile); err != nil {
slog.Warn("pkg-helper: persist remove rename failed", "error", err)
os.Remove(tmpFile) //nolint:errcheck
return
}
// Restore group ownership so the goclaw process (uid 1000, gid 1000) can read the file.
// Without this, the renamed file inherits root:root from the temp file,
// causing ListInstalledPackages to return nil for system packages.
os.Chown(listFile, 0, 1000) //nolint:errcheck
}
func apkListFile() string {
@@ -131,6 +131,7 @@ function PackageSection({ title, placeholder, packages, loading, onInstall, onUn
const res = await onUninstall(name);
if (res.ok) {
setActionStatuses((s) => ({ ...s, [name]: "success" }));
setTimeout(() => setActionStatuses((s) => ({ ...s, [name]: "idle" })), 2000);
} else {
setActionStatuses((s) => ({ ...s, [name]: "error" }));
setTimeout(() => setActionStatuses((s) => ({ ...s, [name]: "idle" })), 3000);