feat(appstore): add service volumes and published ports
This commit is contained in:
@@ -185,7 +185,12 @@ export class BackupManager {
|
||||
await this.exportDockerImage(service.image, `${tempDir}/data/image/image.tar`);
|
||||
}
|
||||
|
||||
// 4. Build ingest items from temp directory files
|
||||
// 4. Export declared service volume data when the volume opts into backup.
|
||||
if (service.volumes?.some((volumeArg) => volumeArg.backup !== false)) {
|
||||
await this.exportServiceVolumes(service, tempDir);
|
||||
}
|
||||
|
||||
// 5. Build ingest items from temp directory files
|
||||
const items: Array<{ stream: NodeJS.ReadableStream; name: string; type?: string }> = [];
|
||||
|
||||
// Service config
|
||||
@@ -218,6 +223,19 @@ export class BackupManager {
|
||||
}
|
||||
}
|
||||
|
||||
const volumeDataDir = `${tempDir}/data/volumes`;
|
||||
try {
|
||||
for await (const filePath of this.walkFiles(volumeDataDir)) {
|
||||
items.push({
|
||||
stream: plugins.nodeFs.createReadStream(filePath),
|
||||
name: plugins.path.relative(tempDir, filePath).replaceAll('\\', '/'),
|
||||
type: 'volume',
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// No service volume data was exported.
|
||||
}
|
||||
|
||||
// Docker image
|
||||
if (includeImage && service.image) {
|
||||
const imagePath = `${tempDir}/data/image/image.tar`;
|
||||
@@ -233,7 +251,7 @@ export class BackupManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Build snapshot tags
|
||||
// 6. Build snapshot tags
|
||||
const tags: Record<string, string> = {
|
||||
serviceName: service.name,
|
||||
serviceId: String(service.id),
|
||||
@@ -245,10 +263,10 @@ export class BackupManager {
|
||||
tags.scheduleId = String(options.scheduleId);
|
||||
}
|
||||
|
||||
// 6. Ingest multi-item snapshot into containerarchive
|
||||
// 7. Ingest multi-item snapshot into containerarchive
|
||||
const snapshot = await this.archive.ingestMulti(items, { tags });
|
||||
|
||||
// 7. Store backup record in database
|
||||
// 8. Store backup record in database
|
||||
const backup: IBackup = {
|
||||
serviceId: service.id!,
|
||||
serviceName: service.name,
|
||||
@@ -675,6 +693,8 @@ export class BackupManager {
|
||||
registry: serviceConfig.registry,
|
||||
port: serviceConfig.port,
|
||||
domain: serviceConfig.domain,
|
||||
volumes: serviceConfig.volumes,
|
||||
publishedPorts: serviceConfig.publishedPorts,
|
||||
useOneboxRegistry: serviceConfig.useOneboxRegistry,
|
||||
registryRepository: serviceConfig.registryRepository,
|
||||
registryImageTag: serviceConfig.registryImageTag,
|
||||
@@ -705,6 +725,8 @@ export class BackupManager {
|
||||
port: serviceConfig.port,
|
||||
domain: options.mode === 'clone' ? undefined : serviceConfig.domain,
|
||||
envVars: serviceConfig.envVars,
|
||||
volumes: serviceConfig.volumes,
|
||||
publishedPorts: serviceConfig.publishedPorts,
|
||||
useOneboxRegistry: serviceConfig.useOneboxRegistry,
|
||||
registryImageTag: serviceConfig.registryImageTag,
|
||||
autoUpdateOnPush: serviceConfig.autoUpdateOnPush,
|
||||
@@ -729,6 +751,8 @@ export class BackupManager {
|
||||
}
|
||||
}
|
||||
|
||||
await this.restoreServiceVolumes(service, serviceConfig.volumes || [], tempDir, warnings);
|
||||
|
||||
// Cleanup
|
||||
await Deno.remove(tempDir, { recursive: true });
|
||||
|
||||
@@ -791,6 +815,8 @@ export class BackupManager {
|
||||
image: service.image,
|
||||
registry: service.registry,
|
||||
envVars: service.envVars,
|
||||
volumes: service.volumes,
|
||||
publishedPorts: service.publishedPorts,
|
||||
port: service.port,
|
||||
domain: service.domain,
|
||||
useOneboxRegistry: service.useOneboxRegistry,
|
||||
@@ -802,6 +828,62 @@ export class BackupManager {
|
||||
};
|
||||
}
|
||||
|
||||
private getVolumeBackupName(volumeArg: { mountPath: string }, indexArg: number): string {
|
||||
const safeMountPath = volumeArg.mountPath
|
||||
.replace(/^\/+/, '')
|
||||
.replace(/\/+$/g, '')
|
||||
.replace(/[^a-zA-Z0-9_.-]+/g, '-') || 'root';
|
||||
return `${String(indexArg).padStart(3, '0')}-${safeMountPath}`;
|
||||
}
|
||||
|
||||
private async exportServiceVolumes(serviceArg: IService, tempDirArg: string): Promise<void> {
|
||||
if (!serviceArg.containerID) {
|
||||
throw new Error(`Cannot export service volumes for ${serviceArg.name}: service has no container ID`);
|
||||
}
|
||||
|
||||
const volumes = (serviceArg.volumes || []).filter((volumeArg) => volumeArg.backup !== false);
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
const volume = volumes[i];
|
||||
const backupName = this.getVolumeBackupName(volume, i);
|
||||
const outputPath = `${tempDirArg}/data/volumes/${backupName}`;
|
||||
await Deno.mkdir(outputPath, { recursive: true });
|
||||
await this.copyFromContainer(serviceArg.containerID, `${volume.mountPath}/.`, outputPath);
|
||||
logger.info(`Exported volume ${volume.mountPath} for service ${serviceArg.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async restoreServiceVolumes(
|
||||
serviceArg: IService,
|
||||
volumesArg: NonNullable<IBackupServiceConfig['volumes']>,
|
||||
tempDirArg: string,
|
||||
warningsArg: string[],
|
||||
): Promise<void> {
|
||||
if (!serviceArg.containerID) {
|
||||
if (volumesArg.some((volumeArg) => volumeArg.backup !== false)) {
|
||||
warningsArg.push(`Could not restore service volumes for ${serviceArg.name}: service has no container ID`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const volumes = volumesArg.filter((volumeArg) => volumeArg.backup !== false);
|
||||
for (let i = 0; i < volumes.length; i++) {
|
||||
const volume = volumes[i];
|
||||
const backupName = this.getVolumeBackupName(volume, i);
|
||||
const inputPath = `${tempDirArg}/data/volumes/${backupName}`;
|
||||
try {
|
||||
await Deno.stat(inputPath);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await this.copyToContainer(`${inputPath}/.`, serviceArg.containerID, volume.mountPath);
|
||||
logger.info(`Restored volume ${volume.mountPath} for service ${serviceArg.name}`);
|
||||
} catch (error) {
|
||||
warningsArg.push(`Volume restore failed for ${volume.mountPath}: ${getErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export MongoDB database
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user