feat(web,docs): the Updates card — version, channel, install kind, and the exact update command
android / android (push) Canceled after 1m54s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
ci / docs-site (push) Successful in 1m15s
arch / build-publish (push) Canceled after 1m50s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 14s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 14s
ci / rust (push) Canceled after 59s
ci / rust-arm64 (push) Canceled after 2m31s
ci / web (push) Canceled after 0s
deb / build-publish (push) Canceled after 18s
deb / build-publish-host (push) Canceled after 19s
deb / build-publish-client-arm64 (push) Canceled after 12s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 1m15s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 36s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 2m28s
windows-host / package (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
decky / build-publish (push) Successful in 50s
android / android (push) Canceled after 1m54s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
ci / docs-site (push) Successful in 1m15s
arch / build-publish (push) Canceled after 1m50s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 14s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 14s
ci / rust (push) Canceled after 59s
ci / rust-arm64 (push) Canceled after 2m31s
ci / web (push) Canceled after 0s
deb / build-publish (push) Canceled after 18s
deb / build-publish-host (push) Canceled after 19s
deb / build-publish-client-arm64 (push) Canceled after 12s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 1m15s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 36s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 2m28s
windows-host / package (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
decky / build-publish (push) Successful in 50s
Notify-only (U0): polls /update/status (which keeps the host's manifest cache warm), Check-now with the 30s limit surfaced, release-notes link, stale-feed and check-disabled states, copyable per-install-kind command. en+de strings; docs-site 'Updating the Host' page wired into the install section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
"steamos-host",
|
"steamos-host",
|
||||||
"windows-host",
|
"windows-host",
|
||||||
"web-console",
|
"web-console",
|
||||||
|
"updating",
|
||||||
"---Configure your desktop---",
|
"---Configure your desktop---",
|
||||||
"configuration",
|
"configuration",
|
||||||
"kde",
|
"kde",
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
title: Updating the Host
|
||||||
|
description: How to see when a newer punktfunk host is available — the web console's update card — and the update command for every install method.
|
||||||
|
---
|
||||||
|
|
||||||
|
The web console tells you when a newer host is out. The **Host** page has an **Updates** card
|
||||||
|
showing the version you run, the channel you follow (stable or canary), how this host was
|
||||||
|
installed, and — once a newer release exists — the exact command that updates it. The
|
||||||
|
"update available" state also fires an `update.available` event on the host
|
||||||
|
[event stream](/docs/automation), so hooks and scripts can react to it too.
|
||||||
|
|
||||||
|
The check is a small signed manifest the host fetches from the punktfunk release feed and
|
||||||
|
verifies against keys built into the host itself — a tampered or replayed feed is rejected, and
|
||||||
|
the console will tell you when a check failed rather than silently showing stale facts.
|
||||||
|
|
||||||
|
## Updating, per install method
|
||||||
|
|
||||||
|
The console shows the right one of these automatically; for reference:
|
||||||
|
|
||||||
|
| How you installed | How to update |
|
||||||
|
|---|---|
|
||||||
|
| Windows installer / winget | `winget upgrade unom.PunktfunkHost`, or run the newer `punktfunk-host-setup.exe` |
|
||||||
|
| Ubuntu / Debian (apt) | `sudo apt update && sudo apt install --only-upgrade punktfunk-host` |
|
||||||
|
| Fedora (dnf) | `sudo dnf upgrade punktfunk` |
|
||||||
|
| Bazzite sysext (recommended) | `sudo punktfunk-sysext update` |
|
||||||
|
| Bazzite rpm-ostree layer | `sudo /usr/share/punktfunk/update-punktfunk.sh` (staged — reboot to finish) |
|
||||||
|
| Arch / CachyOS (pacman) | `sudo pacman -Syu` (a normal full system upgrade) |
|
||||||
|
| Steam Deck (on-device build) | `bash ~/punktfunk/scripts/steamdeck/update.sh --pull` |
|
||||||
|
| NixOS | update the flake input and rebuild |
|
||||||
|
|
||||||
|
After a Linux package update, restart the host to pick up the new binary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user restart punktfunk-host
|
||||||
|
```
|
||||||
|
|
||||||
|
(The Windows installer restarts the service itself; `punktfunk-sysext update` prints the same
|
||||||
|
restart hint when it's needed.)
|
||||||
|
|
||||||
|
One-click updating from the console is on the way, per install method — the card will grow an
|
||||||
|
**Apply** button where the platform supports it.
|
||||||
|
|
||||||
|
## Turning the check off
|
||||||
|
|
||||||
|
The check contacts `git.unom.io` (the punktfunk forge) and nothing else, and sends nothing but a
|
||||||
|
normal download request. If you'd rather the host never checks, set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PUNKTFUNK_UPDATE_CHECK=0
|
||||||
|
```
|
||||||
|
|
||||||
|
in the host's environment (`host.env` on Windows, the systemd user unit environment on Linux).
|
||||||
|
The card then shows checks as disabled; everything else keeps working.
|
||||||
|
|
||||||
|
## If the card says the feed is stale
|
||||||
|
|
||||||
|
"Feed hasn't changed in over 45 days" means checks *succeed* but nothing new arrives. Usually
|
||||||
|
that just means no release happened for a while; if the [releases page](https://git.unom.io/unom/punktfunk/releases)
|
||||||
|
shows something newer than the card does, something between this host and the feed is pinning old
|
||||||
|
data — worth a look at proxies or DNS on the way to `git.unom.io`.
|
||||||
+18
-1
@@ -439,5 +439,22 @@
|
|||||||
"display_monitor_unsupported": "Das Übertragen eines dieser Bildschirme wird auf diesem Host noch nicht unterstützt — das gibt es bisher nur auf Linux-Hosts. Die Bildschirme sind hier aufgeführt, damit du siehst, was dieser Computer hat; Clients bekommen weiterhin ihren eigenen virtuellen Bildschirm.",
|
"display_monitor_unsupported": "Das Übertragen eines dieser Bildschirme wird auf diesem Host noch nicht unterstützt — das gibt es bisher nur auf Linux-Hosts. Die Bildschirme sind hier aufgeführt, damit du siehst, was dieser Computer hat; Clients bekommen weiterhin ihren eigenen virtuellen Bildschirm.",
|
||||||
"display_monitor_primary": "primär",
|
"display_monitor_primary": "primär",
|
||||||
"display_monitor_disabled": "aus",
|
"display_monitor_disabled": "aus",
|
||||||
"display_monitor_saved": "Übertragener Bildschirm gespeichert"
|
"display_monitor_saved": "Übertragener Bildschirm gespeichert",
|
||||||
|
"update_title": "Updates",
|
||||||
|
"update_available_badge": "Update verfügbar",
|
||||||
|
"update_current": "Installiert",
|
||||||
|
"update_install_kind": "Installationsart",
|
||||||
|
"update_latest": "Neueste Version",
|
||||||
|
"update_notes": "Versionshinweise",
|
||||||
|
"update_up_to_date": "Du bist auf dem neuesten Stand.",
|
||||||
|
"update_how": "Diesen Host aktualisieren mit:",
|
||||||
|
"update_check_now": "Jetzt prüfen",
|
||||||
|
"update_checking": "Prüfe…",
|
||||||
|
"update_last_checked": "Zuletzt geprüft",
|
||||||
|
"update_never_checked": "Noch nicht geprüft",
|
||||||
|
"update_stale": "Der Update-Feed hat sich seit über 45 Tagen nicht geändert — Prüfungen gelingen, aber es kommt nichts Neues an. Falls das nicht stimmen kann, prüfe die Verbindung dieses Hosts zu git.unom.io.",
|
||||||
|
"update_disabled": "Update-Prüfungen sind auf diesem Host deaktiviert (PUNKTFUNK_UPDATE_CHECK=0).",
|
||||||
|
"update_error": "Letzte Prüfung fehlgeschlagen:",
|
||||||
|
"update_copy": "Kopieren",
|
||||||
|
"update_copied": "Kopiert"
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-1
@@ -439,5 +439,22 @@
|
|||||||
"display_monitor_unsupported": "Streaming one of these screens isn't supported on this host yet — it's available on Linux hosts only. The screens are listed so you can see what this computer has; clients keep getting their own virtual screen.",
|
"display_monitor_unsupported": "Streaming one of these screens isn't supported on this host yet — it's available on Linux hosts only. The screens are listed so you can see what this computer has; clients keep getting their own virtual screen.",
|
||||||
"display_monitor_primary": "primary",
|
"display_monitor_primary": "primary",
|
||||||
"display_monitor_disabled": "off",
|
"display_monitor_disabled": "off",
|
||||||
"display_monitor_saved": "Streamed screen saved"
|
"display_monitor_saved": "Streamed screen saved",
|
||||||
|
"update_title": "Updates",
|
||||||
|
"update_available_badge": "Update available",
|
||||||
|
"update_current": "Installed",
|
||||||
|
"update_install_kind": "Install type",
|
||||||
|
"update_latest": "Latest release",
|
||||||
|
"update_notes": "Release notes",
|
||||||
|
"update_up_to_date": "You're up to date.",
|
||||||
|
"update_how": "Update this host with:",
|
||||||
|
"update_check_now": "Check now",
|
||||||
|
"update_checking": "Checking…",
|
||||||
|
"update_last_checked": "Last checked",
|
||||||
|
"update_never_checked": "Not checked yet",
|
||||||
|
"update_stale": "The update feed hasn't changed in over 45 days — checks succeed but nothing new arrives. If that seems wrong, check this host's connectivity to git.unom.io.",
|
||||||
|
"update_disabled": "Update checks are disabled on this host (PUNKTFUNK_UPDATE_CHECK=0).",
|
||||||
|
"update_error": "Last check failed:",
|
||||||
|
"update_copy": "Copy",
|
||||||
|
"update_copied": "Copied"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { Button } from "@unom/ui/button";
|
||||||
|
import { type FC, type ReactNode, useState } from "react";
|
||||||
|
import {
|
||||||
|
getGetUpdateStatusQueryKey,
|
||||||
|
useForceUpdateCheck,
|
||||||
|
useGetUpdateStatus,
|
||||||
|
} from "@/api/gen/update/update";
|
||||||
|
import type { UpdateStatus } from "@/api/gen/model";
|
||||||
|
import { QueryState } from "@/components/query-state";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import type { Loadable } from "@/lib/query";
|
||||||
|
import { m } from "@/paraglide/messages";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container: the host update-check card (U0 — notify-only). Reading status is what keeps the
|
||||||
|
* host's manifest cache warm (the host kicks its own background refresh when the cache is >6 h
|
||||||
|
* old), so a modest poll doubles as the check cadence while the console is open. Apply buttons
|
||||||
|
* arrive with the per-channel apply legs (U1 Windows, U2 Linux helper); until then the card's
|
||||||
|
* action is the exact update command for this install kind.
|
||||||
|
*/
|
||||||
|
export const UpdateSection: FC = () => {
|
||||||
|
const qc = useQueryClient();
|
||||||
|
const status = useGetUpdateStatus({ query: { refetchInterval: 60_000 } });
|
||||||
|
const check = useForceUpdateCheck();
|
||||||
|
|
||||||
|
const checkNow = () =>
|
||||||
|
check.mutate(undefined, {
|
||||||
|
onSuccess: (fresh) => {
|
||||||
|
qc.setQueryData(getGetUpdateStatusQueryKey(), fresh);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return <UpdateCard state={status} onCheck={checkNow} busy={check.isPending} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UpdateCard: FC<{
|
||||||
|
state: Loadable<UpdateStatus>;
|
||||||
|
onCheck: () => void;
|
||||||
|
busy: boolean;
|
||||||
|
}> = ({ state, onCheck, busy }) => {
|
||||||
|
const s = state.data;
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
|
<CardTitle>{m.update_title()}</CardTitle>
|
||||||
|
{s?.available && <Badge>{m.update_available_badge()}</Badge>}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<QueryState
|
||||||
|
isLoading={state.isLoading}
|
||||||
|
error={state.error}
|
||||||
|
refetch={state.refetch}
|
||||||
|
>
|
||||||
|
{s && (
|
||||||
|
<>
|
||||||
|
<dl className="grid grid-cols-1 gap-3">
|
||||||
|
<UpdateRow
|
||||||
|
label={m.update_current()}
|
||||||
|
value={
|
||||||
|
<span className="flex items-center gap-2 font-medium">
|
||||||
|
{s.current_version}
|
||||||
|
<Badge variant="secondary">{s.channel}</Badge>
|
||||||
|
<Badge variant="outline" title={m.update_install_kind()}>
|
||||||
|
{s.install_kind}
|
||||||
|
</Badge>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<UpdateRow
|
||||||
|
label={m.update_latest()}
|
||||||
|
value={
|
||||||
|
s.manifest ? (
|
||||||
|
<span className="flex items-center gap-2 font-medium">
|
||||||
|
{s.manifest.version}
|
||||||
|
{s.manifest.notes_url && (
|
||||||
|
<a
|
||||||
|
href={s.manifest.notes_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-sm font-normal underline underline-offset-2"
|
||||||
|
>
|
||||||
|
{m.update_notes()}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{m.update_never_checked()}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
{s.available ? (
|
||||||
|
<div className="space-y-2 rounded-md border p-4">
|
||||||
|
<p className="text-sm">{m.update_how()}</p>
|
||||||
|
<CommandLine command={s.channel_hint} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
s.manifest && (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{m.update_up_to_date()}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
|
{s.manifest?.stale && (
|
||||||
|
<p className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm">
|
||||||
|
{m.update_stale()}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{s.last_error && (
|
||||||
|
<p className="text-sm text-destructive">
|
||||||
|
{m.update_error()} {s.last_error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{s.check_disabled ? (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{m.update_disabled()}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onCheck}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
{busy ? m.update_checking() : m.update_check_now()}
|
||||||
|
</Button>
|
||||||
|
{s.last_checked_unix != null && (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{m.update_last_checked()}{" "}
|
||||||
|
{new Date(s.last_checked_unix * 1000).toLocaleString()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</QueryState>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateRow: FC<{ label: string; value: ReactNode }> = ({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
}) => (
|
||||||
|
<div className="flex items-baseline justify-between gap-4">
|
||||||
|
<dt className="text-sm text-muted-foreground">{label}</dt>
|
||||||
|
<dd>{value}</dd>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
/** The copy-pastable update command, with a small clipboard affordance. */
|
||||||
|
const CommandLine: FC<{ command: string }> = ({ command }) => {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const copy = () => {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(command)
|
||||||
|
.then(() => {
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
/* clipboard denied — the text is selectable, nothing to do */
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<code className="min-w-0 flex-1 overflow-x-auto rounded bg-muted px-2 py-1.5 text-xs">
|
||||||
|
{command}
|
||||||
|
</code>
|
||||||
|
<Button variant="ghost" size="sm" onClick={copy}>
|
||||||
|
{copied ? m.update_copied() : m.update_copy()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ import type { FC } from "react";
|
|||||||
import { useGetHostInfo, useListCompositors } from "@/api/gen/host/host";
|
import { useGetHostInfo, useListCompositors } from "@/api/gen/host/host";
|
||||||
import { useLocale } from "@/lib/i18n";
|
import { useLocale } from "@/lib/i18n";
|
||||||
import { GpuSection } from "./GpuCard";
|
import { GpuSection } from "./GpuCard";
|
||||||
|
import { UpdateSection } from "./UpdateCard";
|
||||||
import { HostView } from "./view";
|
import { HostView } from "./view";
|
||||||
|
|
||||||
export const SectionHost: FC = () => {
|
export const SectionHost: FC = () => {
|
||||||
@@ -9,5 +10,12 @@ export const SectionHost: FC = () => {
|
|||||||
const host = useGetHostInfo();
|
const host = useGetHostInfo();
|
||||||
const compositors = useListCompositors();
|
const compositors = useListCompositors();
|
||||||
|
|
||||||
return <HostView host={host} compositors={compositors} gpu={<GpuSection />} />;
|
return (
|
||||||
|
<HostView
|
||||||
|
host={host}
|
||||||
|
compositors={compositors}
|
||||||
|
gpu={<GpuSection />}
|
||||||
|
update={<UpdateSection />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ export const HostView: FC<{
|
|||||||
compositors: Loadable<AvailableCompositor[]>;
|
compositors: Loadable<AvailableCompositor[]>;
|
||||||
/** The GPU inventory/selection card (a self-contained container — see `GpuCard.tsx`). */
|
/** The GPU inventory/selection card (a self-contained container — see `GpuCard.tsx`). */
|
||||||
gpu?: ReactNode;
|
gpu?: ReactNode;
|
||||||
}> = ({ host, compositors, gpu }) => {
|
/** The update-check card (a self-contained container — see `UpdateCard.tsx`). */
|
||||||
|
update?: ReactNode;
|
||||||
|
}> = ({ host, compositors, gpu, update }) => {
|
||||||
const h = host.data;
|
const h = host.data;
|
||||||
return (
|
return (
|
||||||
<Section maxWidth={false}>
|
<Section maxWidth={false}>
|
||||||
@@ -88,6 +90,8 @@ export const HostView: FC<{
|
|||||||
)}
|
)}
|
||||||
</QueryState>
|
</QueryState>
|
||||||
|
|
||||||
|
{update}
|
||||||
|
|
||||||
{gpu}
|
{gpu}
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user