Portable install: the binary installs itself; static builds on five architectures; root via sudo, doas or uid 0; every package manager; older systemd; no lsblk
- hamlab install / uninstall (internal/setup): systemd or OpenRC, elevates through sudo or doas, runs as the invoking user, sets or generates the password, names only journal groups that exist, waits for /healthz, reports an active firewalld/ufw. --purge and the data chown never touch a directory that isn't Ham Lab's alone. - install.sh: curl-able bootstrap (arch detection, SHA256SUMS, offline beside the binaries, noexec /tmp fallback). make release builds dist/ for amd64, arm64, arm (ARMv6), 386, riscv64. Every build is CGO_ENABLED=0; the native one used to link glibc. - internal/priv replaces every literal `sudo -n`: uid 0, sudo or doas. - Updates acts on dnf, zypper, pacman and apk as well as apt; dnf shows installed versions. - Services falls back to the plain systemctl table before systemd 246 and says so on hosts without systemd. Disks falls back to /sys/block without lsblk. - Listener binds with IP_FREEBIND, so a LAN address that isn't up at boot is no failure. - Self-update: the new build must pass `check` against the live config, is put in place by atomic rename, and the daemon re-execs itself (same under any supervisor). - State and config writes are fsynced; a corrupt state.json is set aside, not fatal. - Terminal shutdown no longer empties the saved list, so names come back after a restart by design rather than by winning a race. - Config default is ~/.config/hamlab; an existing ~/apps/hamlab is still found. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
91b7f4f009
commit
488d929733
+3
-4
@@ -62,9 +62,8 @@ export default function Updates() {
|
||||
|
||||
return (
|
||||
<div className="dash updates">
|
||||
{!st.manager && <div className="notice">No supported package manager found (looked for apt, dnf, pacman).</div>}
|
||||
{st.manager && st.manager !== 'apt' && <div className="notice">{st.manager}: listing only; upgrading from here isn't supported yet.</div>}
|
||||
{st.manager === 'apt' && !st.sudo && <div className="notice">Read-only: refreshing and upgrading need passwordless sudo on the host.</div>}
|
||||
{!st.manager && <div className="notice">No supported package manager found (looked for apt, dnf, zypper, pacman, apk).</div>}
|
||||
{st.manager && !st.sudo && <div className="notice">Read-only: refreshing and upgrading need root. Run Ham Lab as root, or give its user passwordless sudo or doas.</div>}
|
||||
{st.reboot_needed && (
|
||||
<div className="notice attn">
|
||||
<b>Reboot required</b>{st.reboot_pkgs.length > 0 && <> · {st.reboot_pkgs.join(', ')}</>} — use the ⏻ button in the header.
|
||||
@@ -82,7 +81,7 @@ export default function Updates() {
|
||||
<div className="svc-actions">
|
||||
<Btn className="btn" disabled={!canAct} onClick={() => start(api.refreshUpdates)}>Refresh index</Btn>
|
||||
<Btn className="btn on" disabled={!canAct || st.packages.length === 0} confirm={`Upgrade all ${st.packages.length} packages?`} onClick={() => start(() => api.upgrade([]))}>Upgrade all</Btn>
|
||||
<Btn className="btn" disabled={!canAct || sel.size === 0} onClick={() => start(() => api.upgrade([...sel]))}>Upgrade selected ({sel.size})</Btn>
|
||||
<Btn className="btn" disabled={!canAct || sel.size === 0 || !st.selective} title={st.selective ? undefined : `${st.manager} upgrades everything or nothing`} onClick={() => start(() => api.upgrade([...sel]))}>Upgrade selected ({sel.size})</Btn>
|
||||
<span className="spacer" />
|
||||
{st.packages.length > 0 && <button className="btn" onClick={() => setSel(sel.size === st.packages.length ? new Set() : new Set(st.packages.map((p) => p.name)))}>{sel.size === st.packages.length ? 'None' : 'All'}</button>}
|
||||
<button className="btn" title="reload" disabled={reloading} onClick={load}>{reloading ? <Spinner /> : '↻'}</button>
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ export interface CronRun { id: string; job_id: string; command: string; user: st
|
||||
|
||||
export interface TaskInfo { id: string; kind: string; title: string; command: string; started: string; finished?: string; running: boolean; exit_code: number; error?: string; output?: string }
|
||||
export interface UpdatePackage { name: string; current: string; candidate: string; suite?: string; security: boolean; description?: string }
|
||||
export interface UpdatesStatus { manager: string; can_upgrade: boolean; packages: UpdatePackage[]; security_count: number; last_refresh?: number; last_upgrade?: number; reboot_needed: boolean; reboot_pkgs: string[]; auto_upgrades: boolean; running_task?: string; sudo: boolean }
|
||||
export interface UpdatesStatus { manager: string; can_upgrade: boolean; selective: boolean; packages: UpdatePackage[]; security_count: number; last_refresh?: number; last_upgrade?: number; reboot_needed: boolean; reboot_pkgs: string[]; auto_upgrades: boolean; running_task?: string; sudo: boolean }
|
||||
|
||||
export interface StoragePart { name: string; type: string; fstype?: string; mount?: string; label?: string; size: number; used?: number; fs_size?: number }
|
||||
export interface StorageDevice { name: string; kind: string; model?: string; vendor?: string; serial?: string; tran?: string; size: number; rotational: boolean; removable: boolean; temp?: number; parts: StoragePart[]; namespaces?: string[] }
|
||||
|
||||
Reference in New Issue
Block a user