Full configuration export/import system for Homarr with excellent DX: - Export/import boards, apps, integrations (with re-encrypted secrets), users (with bcrypt hashes), groups (with permissions and memberships), search engines, and server settings - Homepage services.yaml import with widget-type mapping - Generic resolveEntities() utility eliminates per-entity boilerplate - Declarative entity-fields.ts: adding a new user preference requires only one line change (no export/import/schema edits needed) - User schema uses .catchall(z.unknown()) for forward-compatible prefs - groupByKey() utility replaces manual Map-building - CONFIG_ENTITY_KEYS as single source of truth for entity metadata - Reuse existing entities (apps by name+icon+href, integrations by kind+name+url, users by email, groups by name, boards by name) - Skip duplicates with warnings rather than failing - Onboarding flow skips user creation step when config includes users - Reuse existing admin group during onboarding after config import
43 lines
1.5 KiB
TypeScript
43 lines
1.5 KiB
TypeScript
import { TRPCError } from "@trpc/server";
|
|
import { z } from "zod/v4";
|
|
|
|
import {
|
|
exportFullConfigAsync,
|
|
importFullConfigAsync,
|
|
parseAndValidateBundle,
|
|
previewExportFullConfigAsync,
|
|
previewImportFullConfigAsync,
|
|
} from "@homarr/board-portability";
|
|
|
|
import packageJson from "../../../../package.json";
|
|
import { createTRPCRouter, permissionRequiredProcedure } from "../trpc";
|
|
|
|
export const configRouter = createTRPCRouter({
|
|
previewExport: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => {
|
|
return await previewExportFullConfigAsync(ctx.db);
|
|
}),
|
|
|
|
previewImport: permissionRequiredProcedure
|
|
.requiresPermission("admin")
|
|
.input(z.object({ content: z.string().min(1) }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
return await previewImportFullConfigAsync(ctx.db, input.content, packageJson.version);
|
|
}),
|
|
|
|
exportFull: permissionRequiredProcedure.requiresPermission("admin").mutation(async ({ ctx }) => {
|
|
return await exportFullConfigAsync(ctx.db, packageJson.version);
|
|
}),
|
|
|
|
importFull: permissionRequiredProcedure
|
|
.requiresPermission("admin")
|
|
.input(z.object({ content: z.string().min(1) }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
try {
|
|
const bundle = parseAndValidateBundle(input.content, packageJson.version);
|
|
return await importFullConfigAsync(ctx.db, bundle, ctx.session.user.id);
|
|
} catch (error) {
|
|
throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "Invalid config" });
|
|
}
|
|
}),
|
|
});
|