From 0090c3c43c991683fdfcac5df18b730a804a5d82 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 21 Oct 2025 19:57:04 +0200 Subject: [PATCH] refactor(schemas): move restic schemas to a subfolder --- apps/server/src/db/schema.ts | 8 +++--- apps/server/src/utils/restic.ts | 29 +++++++++++++++++----- packages/schemas/package.json | 10 ++++++++ packages/schemas/src/index.ts | 38 ---------------------------- packages/schemas/src/restic.ts | 44 +++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 49 deletions(-) create mode 100644 packages/schemas/src/restic.ts diff --git a/apps/server/src/db/schema.ts b/apps/server/src/db/schema.ts index e3b6e9a..1950acb 100644 --- a/apps/server/src/db/schema.ts +++ b/apps/server/src/db/schema.ts @@ -1,12 +1,10 @@ +import type { BackendStatus, BackendType, volumeConfigSchema } from "@ironmount/schemas"; import type { - BackendStatus, - BackendType, CompressionMode, RepositoryBackend, - RepositoryStatus, repositoryConfigSchema, - volumeConfigSchema, -} from "@ironmount/schemas"; + RepositoryStatus, +} from "@ironmount/schemas/restic"; import { sql } from "drizzle-orm"; import { int, sqliteTable, text } from "drizzle-orm/sqlite-core"; diff --git a/apps/server/src/utils/restic.ts b/apps/server/src/utils/restic.ts index 01b46a8..fae9865 100644 --- a/apps/server/src/utils/restic.ts +++ b/apps/server/src/utils/restic.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; -import type { RepositoryConfig } from "@ironmount/schemas"; +import type { RepositoryConfig } from "@ironmount/schemas/restic"; import { type } from "arktype"; import { $ } from "bun"; import { RESTIC_PASS_FILE } from "../core/constants"; @@ -38,6 +38,8 @@ const ensurePassfile = async () => { const buildRepoUrl = (config: RepositoryConfig): string => { switch (config.backend) { + case "local": + return config.path; case "s3": return `s3:${config.endpoint}/${config.bucket}`; default: { @@ -47,7 +49,9 @@ const buildRepoUrl = (config: RepositoryConfig): string => { }; const buildEnv = async (config: RepositoryConfig) => { - const env: Record = {}; + const env: Record = { + RESTIC_PASSWORD_FILE: RESTIC_PASS_FILE, + }; switch (config.backend) { case "s3": @@ -65,7 +69,7 @@ const init = async (config: RepositoryConfig) => { const repoUrl = buildRepoUrl(config); const env = await buildEnv(config); - const res = await $`restic init --repo ${repoUrl} --password-file ${RESTIC_PASS_FILE} --json`.env(env).nothrow(); + const res = await $`restic init --repo ${repoUrl} --json`.env(env).nothrow(); if (res.exitCode !== 0) { logger.error(`Restic init failed: ${res.stderr}`); @@ -80,9 +84,7 @@ const backup = async (config: RepositoryConfig, source: string) => { const repoUrl = buildRepoUrl(config); const env = await buildEnv(config); - const res = await $`restic --repo ${repoUrl} backup ${source} --password-file /data/secrets/restic.pass --json` - .env(env) - .nothrow(); + const res = await $`restic --repo ${repoUrl} backup ${source} --json`.env(env).nothrow(); if (res.exitCode !== 0) { logger.error(`Restic backup failed: ${res.stderr}`); @@ -99,8 +101,23 @@ const backup = async (config: RepositoryConfig, source: string) => { return result; }; +const restore = async (config: RepositoryConfig, snapshotId: string, target: string) => { + const repoUrl = buildRepoUrl(config); + const env = await buildEnv(config); + + const res = await $`restic --repo ${repoUrl} restore ${snapshotId} --target ${target} --json`.env(env).nothrow(); + + if (res.exitCode !== 0) { + logger.error(`Restic restore failed: ${res.stderr}`); + throw new Error(`Restic restore failed: ${res.stderr}`); + } + + logger.info(`Restic restore completed for snapshot ${snapshotId} to target ${target}`); +}; + export const restic = { ensurePassfile, init, backup, + restore, }; diff --git a/packages/schemas/package.json b/packages/schemas/package.json index a21a19b..d654212 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -4,6 +4,16 @@ "private": true, "main": "./src/index.ts", "types": "./src/index.ts", + "exports": { + ".": { + "import": "./src/index.ts", + "require": "./src/index.ts" + }, + "./restic": { + "import": "./src/restic.ts", + "require": "./src/restic.ts" + } + }, "type": "module", "peerDependencies": { "arktype": ">=2" diff --git a/packages/schemas/src/index.ts b/packages/schemas/src/index.ts index 99a5fa5..28d8604 100644 --- a/packages/schemas/src/index.ts +++ b/packages/schemas/src/index.ts @@ -53,41 +53,3 @@ export const BACKEND_STATUS = { } as const; export type BackendStatus = keyof typeof BACKEND_STATUS; - -export const REPOSITORY_BACKENDS = { - local: "local", - sftp: "sftp", - s3: "s3", -} as const; - -export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS; - -export const s3RepositoryConfigSchema = type({ - backend: "'s3'", - endpoint: "string", - bucket: "string", - accessKeyId: "string", - secretAccessKey: "string", -}); - -export const repositoryConfigSchema = s3RepositoryConfigSchema; - -export type RepositoryConfig = typeof repositoryConfigSchema.infer; - -export const COMPRESSION_MODES = { - off: "off", - auto: "auto", - fastest: "fastest", - better: "better", - max: "max", -} as const; - -export type CompressionMode = keyof typeof COMPRESSION_MODES; - -export const REPOSITORY_STATUS = { - healthy: "healthy", - error: "error", - unknown: "unknown", -} as const; - -export type RepositoryStatus = keyof typeof REPOSITORY_STATUS; diff --git a/packages/schemas/src/restic.ts b/packages/schemas/src/restic.ts new file mode 100644 index 0000000..a3be96f --- /dev/null +++ b/packages/schemas/src/restic.ts @@ -0,0 +1,44 @@ +import { type } from "arktype"; + +export const REPOSITORY_BACKENDS = { + local: "local", + sftp: "sftp", + s3: "s3", +} as const; + +export type RepositoryBackend = keyof typeof REPOSITORY_BACKENDS; + +export const s3RepositoryConfigSchema = type({ + backend: "'s3'", + endpoint: "string", + bucket: "string", + accessKeyId: "string", + secretAccessKey: "string", +}); + +export const localRepositoryConfigSchema = type({ + backend: "'local'", + path: "string", +}); + +export const repositoryConfigSchema = s3RepositoryConfigSchema.or(localRepositoryConfigSchema); + +export type RepositoryConfig = typeof repositoryConfigSchema.infer; + +export const COMPRESSION_MODES = { + off: "off", + auto: "auto", + fastest: "fastest", + better: "better", + max: "max", +} as const; + +export type CompressionMode = keyof typeof COMPRESSION_MODES; + +export const REPOSITORY_STATUS = { + healthy: "healthy", + error: "error", + unknown: "unknown", +} as const; + +export type RepositoryStatus = keyof typeof REPOSITORY_STATUS;