feat(repositories): allow importing existing repos

This commit is contained in:
Nicolas Meienberger
2025-11-15 11:58:52 +01:00
parent 54ee02deb9
commit 3ff6a04f8e
4 changed files with 131 additions and 22 deletions

View File

@@ -15,7 +15,11 @@ const listRepositories = async () => {
};
const encryptConfig = async (config: RepositoryConfig): Promise<RepositoryConfig> => {
const encryptedConfig: Record<string, string> = { ...config };
const encryptedConfig: Record<string, string | boolean> = { ...config };
if (config.customPassword) {
encryptedConfig.customPassword = await cryptoUtils.encrypt(config.customPassword);
}
switch (config.backend) {
case "s3":
@@ -65,23 +69,30 @@ const createRepository = async (name: string, config: RepositoryConfig, compress
throw new InternalServerError("Failed to create repository");
}
const { success, error } = await restic.init(encryptedConfig);
let error: string | null = null;
if (success) {
if (config.isExistingRepository) {
const result = await restic
.snapshots(encryptedConfig)
.then(() => ({ error: null }))
.catch((error) => ({ error }));
error = result.error;
} else {
const initResult = await restic.init(encryptedConfig);
error = initResult.error;
}
if (!error) {
await db
.update(repositoriesTable)
.set({
status: "healthy",
lastChecked: Date.now(),
lastError: null,
})
.set({ status: "healthy", lastChecked: Date.now(), lastError: null })
.where(eq(repositoriesTable.id, id));
return { repository: created, status: 201 };
}
const errorMessage = toMessage(error);
await db.delete(repositoriesTable).where(eq(repositoriesTable.id, id));
throw new InternalServerError(`Failed to initialize repository: ${errorMessage}`);

View File

@@ -75,7 +75,7 @@ const buildRepoUrl = (config: RepositoryConfig): string => {
case "s3":
return `s3:${config.endpoint}/${config.bucket}`;
case "r2": {
const endpoint = config.endpoint.replace(/^https?:\/\//, '');
const endpoint = config.endpoint.replace(/^https?:\/\//, "");
return `s3:${endpoint}/${config.bucket}`;
}
case "gcs":
@@ -93,10 +93,19 @@ const buildRepoUrl = (config: RepositoryConfig): string => {
const buildEnv = async (config: RepositoryConfig) => {
const env: Record<string, string> = {
RESTIC_CACHE_DIR: "/var/lib/ironmount/restic/cache",
RESTIC_PASSWORD_FILE: RESTIC_PASS_FILE,
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
};
if (config.isExistingRepository && config.customPassword) {
const decryptedPassword = await cryptoUtils.decrypt(config.customPassword);
const passwordFilePath = path.join("/tmp", `ironmount-pass-${crypto.randomBytes(8).toString("hex")}.txt`);
await fs.writeFile(passwordFilePath, decryptedPassword, { mode: 0o600 });
env.RESTIC_PASSWORD_FILE = passwordFilePath;
} else {
env.RESTIC_PASSWORD_FILE = RESTIC_PASS_FILE;
}
switch (config.backend) {
case "s3":
env.AWS_ACCESS_KEY_ID = await cryptoUtils.decrypt(config.accessKeyId);
@@ -110,7 +119,7 @@ const buildEnv = async (config: RepositoryConfig) => {
break;
case "gcs": {
const decryptedCredentials = await cryptoUtils.decrypt(config.credentialsJson);
const credentialsPath = path.join("/tmp", `gcs-credentials-${crypto.randomBytes(8).toString("hex")}.json`);
const credentialsPath = path.join("/tmp", `ironmount-gcs-${crypto.randomBytes(8).toString("hex")}.json`);
await fs.writeFile(credentialsPath, decryptedCredentials, { mode: 0o600 });
env.GOOGLE_PROJECT_ID = config.projectId;
env.GOOGLE_APPLICATION_CREDENTIALS = credentialsPath;
@@ -139,7 +148,7 @@ const init = async (config: RepositoryConfig) => {
if (res.exitCode !== 0) {
logger.error(`Restic init failed: ${res.stderr}`);
return { success: false, error: res.stderr };
return { success: false, error: res.stderr.toString() };
}
logger.info(`Restic repository initialized: ${repoUrl}`);