mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 20:16:35 +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
|
printf 'BetterFrame Kiosk\n\n' > /etc/issue
|
||||||
rm -f /etc/update-motd.d/10-uname /etc/update-motd.d/* 2>/dev/null || true
|
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"
|
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/pam.d/cage" /etc/pam.d/cage
|
||||||
install -m 644 "${REPO_ROOT}/deploy/systemd/betterframe-kiosk.service" \
|
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()
|
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||||
)`,
|
)`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_kiosk_logs_kiosk ON kiosk_logs(kiosk_id, received_at DESC)`,
|
`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:
|
* Strategy:
|
||||||
* 1. Delete artifact files for yanked releases (DB row kept for audit).
|
* 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.
|
* 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
|
* Runs every 6 hours. Safe to call concurrently — worst case is two passes
|
||||||
* trying to delete the same file (ENOENT is swallowed).
|
* 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> {
|
async function cleanupFirmware(repo: Repository, log: CleanupLog): Promise<number> {
|
||||||
let cleaned = 0;
|
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();
|
const yanked = await repo.listYankedFirmwareReleases();
|
||||||
for (const r of yanked) {
|
for (const r of yanked) {
|
||||||
|
if (activeReleaseIds.has(r.id)) continue;
|
||||||
if (await removeFile(r.artifact_path)) {
|
if (await removeFile(r.artifact_path)) {
|
||||||
log.info(`firmware cleanup: deleted artifact for yanked ${r.version} (${r.arch})`);
|
log.info(`firmware cleanup: deleted artifact for yanked ${r.version} (${r.arch})`);
|
||||||
cleaned++;
|
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));
|
releases.sort((a, b) => b.uploaded_at.localeCompare(a.uploaded_at));
|
||||||
const excess = releases.slice(KEEP_PER_CHANNEL);
|
const excess = releases.slice(KEEP_PER_CHANNEL);
|
||||||
for (const r of excess) {
|
for (const r of excess) {
|
||||||
|
if (activeReleaseIds.has(r.id)) continue;
|
||||||
if (await removeFile(r.artifact_path)) {
|
if (await removeFile(r.artifact_path)) {
|
||||||
log.info(`firmware cleanup: pruned old ${r.version} (${r.arch})`);
|
log.info(`firmware cleanup: pruned old ${r.version} (${r.arch})`);
|
||||||
cleaned++;
|
cleaned++;
|
||||||
|
|
@ -70,8 +81,16 @@ async function cleanupFirmware(repo: Repository, log: CleanupLog): Promise<numbe
|
||||||
async function cleanupOsUpdates(repo: Repository, log: CleanupLog): Promise<number> {
|
async function cleanupOsUpdates(repo: Repository, log: CleanupLog): Promise<number> {
|
||||||
let cleaned = 0;
|
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();
|
const yanked = await repo.listYankedOsUpdateReleases();
|
||||||
for (const r of yanked) {
|
for (const r of yanked) {
|
||||||
|
if (activeReleaseIds.has(r.id)) continue;
|
||||||
if (await removeFile(r.artifact_path)) {
|
if (await removeFile(r.artifact_path)) {
|
||||||
log.info(`os-update cleanup: deleted artifact for yanked ${r.version}`);
|
log.info(`os-update cleanup: deleted artifact for yanked ${r.version}`);
|
||||||
cleaned++;
|
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));
|
releases.sort((a, b) => b.uploaded_at.localeCompare(a.uploaded_at));
|
||||||
const excess = releases.slice(KEEP_PER_CHANNEL);
|
const excess = releases.slice(KEEP_PER_CHANNEL);
|
||||||
for (const r of excess) {
|
for (const r of excess) {
|
||||||
|
if (activeReleaseIds.has(r.id)) continue;
|
||||||
if (await removeFile(r.artifact_path)) {
|
if (await removeFile(r.artifact_path)) {
|
||||||
log.info(`os-update cleanup: pruned old ${r.version}`);
|
log.info(`os-update cleanup: pruned old ${r.version}`);
|
||||||
cleaned++;
|
cleaned++;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue