mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 16:56:33 +00:00
fix: PG cloud_accounts migration + rollout-safe cleanup + setup cursor
- Add cloud_accounts table to PostgreSQL tenant migrations (was only in SQLite). - Artifact cleanup now skips releases referenced by active/queued/paused rollouts (CASCADE would delete the rollout). - Add invisible cursor theme install to setup-pi-kiosk.sh (was only in pi-gen image build). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1a87c97479
commit
851274d05d
3 changed files with 61 additions and 1 deletions
|
|
@ -245,6 +245,32 @@ if [ "${INSTALL_KIOSK}" = "1" ]; then
|
|||
printf 'BetterFrame Kiosk\n\n' > /etc/issue
|
||||
rm -f /etc/update-motd.d/10-uname /etc/update-motd.d/* 2>/dev/null || true
|
||||
|
||||
echo "==> Installing invisible cursor theme"
|
||||
CURSOR_DIR=/usr/share/icons/betterframe-empty/cursors
|
||||
install -d -m 755 "$CURSOR_DIR"
|
||||
install -m 644 "${REPO_ROOT}/deploy/cursor-theme/betterframe-empty/cursor.theme" \
|
||||
/usr/share/icons/betterframe-empty/index.theme
|
||||
install -m 644 "${REPO_ROOT}/deploy/cursor-theme/betterframe-empty/cursor.theme" \
|
||||
/usr/share/icons/betterframe-empty/cursor.theme
|
||||
python3 -c "
|
||||
import struct, os
|
||||
hdr = b'Xcur' + struct.pack('<III', 16, 0x00010000, 1)
|
||||
toc = struct.pack('<III', 0xfffd0002, 1, 28)
|
||||
img = struct.pack('<IIIIIIIII', 36, 0xfffd0002, 1, 1, 1, 1, 0, 0, 0)
|
||||
px = struct.pack('<I', 0)
|
||||
data = hdr + toc + img + px
|
||||
for name in ['default','left_ptr','arrow','watch','hand2','text','xterm',
|
||||
'top_left_corner','top_right_corner','bottom_left_corner',
|
||||
'bottom_right_corner','sb_h_double_arrow','sb_v_double_arrow',
|
||||
'fleur','crosshair','question_arrow','x_cursor','pirate',
|
||||
'sb_left_arrow','sb_right_arrow','sb_up_arrow','sb_down_arrow',
|
||||
'top_side','bottom_side','left_side','right_side']:
|
||||
with open(os.path.join('$CURSOR_DIR', name), 'wb') as f:
|
||||
f.write(data)
|
||||
"
|
||||
update-alternatives --install /usr/share/icons/default/index.theme x-cursor-theme \
|
||||
/usr/share/icons/betterframe-empty/cursor.theme 100 2>/dev/null || true
|
||||
|
||||
echo "==> Installing PAM + systemd unit + firmware rollback hook"
|
||||
install -m 644 "${REPO_ROOT}/deploy/pam.d/cage" /etc/pam.d/cage
|
||||
install -m 644 "${REPO_ROOT}/deploy/systemd/betterframe-kiosk.service" \
|
||||
|
|
|
|||
|
|
@ -434,4 +434,18 @@ export const TENANT_MIGRATIONS: readonly string[] = [
|
|||
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_kiosk_logs_kiosk ON kiosk_logs(kiosk_id, received_at DESC)`,
|
||||
|
||||
// ---- cloud_accounts -------------------------------------------------------
|
||||
`CREATE TABLE IF NOT EXISTS cloud_accounts (
|
||||
id TEXT PRIMARY KEY,
|
||||
vendor TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
credentials_encrypted TEXT NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
last_sync_at TIMESTAMPTZ,
|
||||
last_sync_error TEXT,
|
||||
camera_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_cloud_accounts_vendor ON cloud_accounts(vendor)`,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
*
|
||||
* Strategy:
|
||||
* 1. Delete artifact files for yanked releases (DB row kept for audit).
|
||||
* Skip if an active/queued/paused rollout references the release.
|
||||
* 2. For each channel, keep only the N most recent non-yanked releases.
|
||||
* Older ones are yanked + artifact deleted.
|
||||
* Older ones get artifact deleted + DB row removed. Skip if referenced
|
||||
* by an active rollout.
|
||||
*
|
||||
* Runs every 6 hours. Safe to call concurrently — worst case is two passes
|
||||
* trying to delete the same file (ENOENT is swallowed).
|
||||
|
|
@ -33,8 +35,16 @@ async function removeFile(path: string): Promise<boolean> {
|
|||
async function cleanupFirmware(repo: Repository, log: CleanupLog): Promise<number> {
|
||||
let cleaned = 0;
|
||||
|
||||
const rollouts = await repo.listFirmwareRollouts();
|
||||
const activeReleaseIds = new Set(
|
||||
rollouts
|
||||
.filter((r) => r.state === "queued" || r.state === "active" || r.state === "paused")
|
||||
.map((r) => r.release_id),
|
||||
);
|
||||
|
||||
const yanked = await repo.listYankedFirmwareReleases();
|
||||
for (const r of yanked) {
|
||||
if (activeReleaseIds.has(r.id)) continue;
|
||||
if (await removeFile(r.artifact_path)) {
|
||||
log.info(`firmware cleanup: deleted artifact for yanked ${r.version} (${r.arch})`);
|
||||
cleaned++;
|
||||
|
|
@ -56,6 +66,7 @@ async function cleanupFirmware(repo: Repository, log: CleanupLog): Promise<numbe
|
|||
releases.sort((a, b) => b.uploaded_at.localeCompare(a.uploaded_at));
|
||||
const excess = releases.slice(KEEP_PER_CHANNEL);
|
||||
for (const r of excess) {
|
||||
if (activeReleaseIds.has(r.id)) continue;
|
||||
if (await removeFile(r.artifact_path)) {
|
||||
log.info(`firmware cleanup: pruned old ${r.version} (${r.arch})`);
|
||||
cleaned++;
|
||||
|
|
@ -70,8 +81,16 @@ async function cleanupFirmware(repo: Repository, log: CleanupLog): Promise<numbe
|
|||
async function cleanupOsUpdates(repo: Repository, log: CleanupLog): Promise<number> {
|
||||
let cleaned = 0;
|
||||
|
||||
const rollouts = await repo.listOsUpdateRollouts();
|
||||
const activeReleaseIds = new Set(
|
||||
rollouts
|
||||
.filter((r) => r.state === "queued" || r.state === "active" || r.state === "paused")
|
||||
.map((r) => r.release_id),
|
||||
);
|
||||
|
||||
const yanked = await repo.listYankedOsUpdateReleases();
|
||||
for (const r of yanked) {
|
||||
if (activeReleaseIds.has(r.id)) continue;
|
||||
if (await removeFile(r.artifact_path)) {
|
||||
log.info(`os-update cleanup: deleted artifact for yanked ${r.version}`);
|
||||
cleaned++;
|
||||
|
|
@ -93,6 +112,7 @@ async function cleanupOsUpdates(repo: Repository, log: CleanupLog): Promise<numb
|
|||
releases.sort((a, b) => b.uploaded_at.localeCompare(a.uploaded_at));
|
||||
const excess = releases.slice(KEEP_PER_CHANNEL);
|
||||
for (const r of excess) {
|
||||
if (activeReleaseIds.has(r.id)) continue;
|
||||
if (await removeFile(r.artifact_path)) {
|
||||
log.info(`os-update cleanup: pruned old ${r.version}`);
|
||||
cleaned++;
|
||||
|
|
|
|||
Loading…
Reference in a new issue