feat(update): 主机每日记录组件版本比对,面板更新页展示可用更新与应用命令

This commit is contained in:
Lemon-miaow committed 2026-09-25 21:50:49 +08:00
1 parent 24373823cc
commit 7f160e2feb
28 files changed
+994 -10

No files matched your search

+4 -1
View File
@@ -28,7 +28,10 @@ A grep across `*.md` and `*.go` returns both sets; only the Go ones are seams.
- `internal/updates/seams.go:32` — `Notifier`. `internal/mail` sends OTP over SMTP,
but nothing adapts it to this interface and no in-game channel exists. `felis
update` passes nil deliberately: a human typing the command is the notification.
update` passes nil deliberately. The notification is the panel instead:
`felis-update-check.timer` runs `felis update --record` daily on the host, which
stores the report under `platform_settings.update_report`, and **Admin → Updates →
Component versions** shows it with the command that applies each update.
- `internal/updates/seams.go:43` — `Applier`. Nothing applies an update anywhere. A
nil applier is not silent — `Run` records `errNoApplier` against every planned
apply, so a mis-scheduled apply is loud rather than lost.
+71
View File
@@ -383,6 +383,51 @@ components:
format: int64
description: The freshness limit (26h), shared with `felis db check` and FelisDBBackupStale.
UpdateReport:
type: object
description: >
The newest version check the host recorded (internal/api/handlers_updates.go
updateReportView; the record is internal/updates StatusReport, written by
`felis update --record`, which felis-update-check.timer runs daily).
required: [report, stale, max_age_seconds]
properties:
report:
type: object
nullable: true
description: >
Null until the first check has been recorded (internal/updates
StatusReport).
required: [checked_at, felis, components]
properties:
checked_at: { type: string, format: date-time }
felis: { type: string, description: Version of the felis binary that ran the check. }
components:
type: array
items: { $ref: '#/components/schemas/UpdateComponent' }
stale:
type: boolean
description: True when there is no record or it is older than max_age_seconds.
max_age_seconds:
type: integer
format: int64
description: The freshness limit (26h).
UpdateComponent:
type: object
description: >
One component's line. available has a newer stable release (latest);
unknown means the release feed could not be read and unreadable that the
installed version could not, both with error; pinned never changes by policy.
required: [name, state]
properties:
name: { type: string }
current: { type: string, description: Installed version; omitted when unreadable. }
latest: { type: string, description: The newer stable release; present only when state is available. }
state: { type: string, enum: [current, available, unknown, unreadable, pinned] }
selector: { type: string, description: 'The `felis update --<selector>` flag that prints how to apply it; omitted when none.' }
note: { type: string, description: What the release lookup learned beyond the version; omitted when none. }
error: { type: string, description: Why a version is missing; omitted otherwise. }
PasskeyCredential:
type: object
description: >
@@ -3308,6 +3353,32 @@ paths:
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/updates/report:
get:
tags: [admin-updates]
operationId: getUpdateReport
summary: The newest recorded version check of every tracked component (admin).
description: >-
What `felis update --record` last stored in platform_settings; the
installer's felis-update-check.timer runs it daily on the host, where the
installed versions are readable. report is null before the first check;
stale is true then, and whenever the check is older than max_age_seconds.
Read-only: Felis applies no update on its own.
x-felis-face: [external]
x-felis-tier: admin
security: [{ sessionCookie: [] }]
responses:
'200':
description: The newest recorded check and whether it is stale.
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateReport'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/fleet:
get:
tags: [admin-servers]
+14
View File
@@ -247,6 +247,20 @@ against their newest releases; `--k3s`, `--cloudflared`, `--jre` and `--postgres
it to one. PostgreSQL is compared within its major, since a minor release is a package
update, and a major past its end of life gets a note naming the current one.
The installer also sets up `felis-update-check.timer`, which runs `felis update --record`
once a day around 05:30 (and at boot after a missed run). `--record` stores the result
in `platform_settings`, and the panel's **Admin → Updates → Component versions** card
shows it: each component's installed and newest version, and for the ones with a newer
release the `sudo felis update --<component>` line that prints how to apply it. Felis
applies nothing on its own; the installer re-run above is the apply path. The card turns
red when the newest record is older than 26 hours, meaning the timer stopped:
```sh
systemctl list-timers felis-update-check.timer
journalctl -u felis-update-check -n 50 --no-pager
sudo felis update --record # record a fresh check now
```
### PostgreSQL major versions [CODE-ONLY]
The installer takes the major the distribution ships (13 on EL9) and never moves it. To