import type { Context, Hono } from "hono"; import { html } from "../pages/html.ts"; import { formatSize } from "../pages/layout.ts"; import { attemptBackup, backupBytes, backups, backupTarball, makeBackup, } from "../server/backup.ts"; import { setting } from "../server/db.ts"; import { type AdminContext, adminPage, formField, notice } from "./console.ts"; /** `20260926T031500Z` as `2026-09-26 03:15:00 UTC`. */ function backupTime(name: string): string { return name.replace(/^(\d{4})(\d\d)(\d\d)T(\d\d)(\d\d)(\d\d)Z$/, "$1-$2-$3 $4:$5:$6 UTC"); } /** ยง24's Backups section: every kept backup, back up now, the last failure, and how to restore. */ export function backupsPage(c: Context, ctx: AdminContext, error = ""): string { const kept = backups(ctx.dataDir).reverse(); const newest = kept[0]; const failure = setting(ctx.kepr, "backup_error"); const body = html`${notice(error)} ${failure && !error && html``} ${ newest ? html`

Last backup: ${backupTime(newest)}. download backup

` : html`

No backup yet.

` }
${formField(c)}

Kepr keeps the last 14 daily backups of its database and assets.

restoring one

  1. Stop Kepr.
  2. Unpack the download: it holds a folder with kepr.db and assets/.
  3. Put kepr.db and assets/ in the data directory named in kepr.toml, over the old ones.
  4. Start Kepr. Its first sync rebuilds the mirrors, and everyone signs in again.

Backups stay on this server. Keep a copy elsewhere, along with kepr.toml.

Sessions and bucket credentials are not included.

`; return adminPage(c, ctx, "backups", body); } export function registerBackups(app: Hono, ctx: AdminContext): void { app.get("/admin/backup", (c) => { const backup = backupTarball(ctx.dataDir, c.req.query("name")); if (!backup) return c.notFound(); return c.body(backup.bytes, 200, { "Content-Type": "application/gzip", "Content-Disposition": `attachment; filename="kepr-backup-${backup.name}.tar.gz"`, }); }); app.post("/admin/backup", (c) => { const failure = attemptBackup(ctx.kepr, () => makeBackup(ctx.kepr, ctx.dataDir)); if (failure) return c.html(backupsPage(c, ctx, `The backup failed: ${failure}`), 500); return c.redirect("/admin?section=backups&saved=1", 303); }); }