@highstate/restic 0.9.30 → 0.9.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/highstate.manifest.json +2 -2
- package/dist/index.js +34 -6
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/job-pair.ts +56 -9
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { text } from '@highstate/contract';
|
|
|
2
2
|
import { Secret, ScriptBundle, Job, createScriptContainer, CronJob, getProvider } from '@highstate/k8s';
|
|
3
3
|
import { ComponentResource, output, getUnitInstanceName, normalize, toPromise } from '@highstate/pulumi';
|
|
4
4
|
import { batch } from '@pulumi/kubernetes';
|
|
5
|
+
import { join } from 'remeda';
|
|
5
6
|
|
|
6
7
|
// src/job-pair.ts
|
|
7
8
|
|
|
@@ -103,7 +104,7 @@ var BackupJobPair = class extends ComponentResource {
|
|
|
103
104
|
{
|
|
104
105
|
namespace: args.namespace,
|
|
105
106
|
stringData: {
|
|
106
|
-
password: args.
|
|
107
|
+
password: args.backupKey,
|
|
107
108
|
"rclone.conf": output(args.resticRepo).rcloneConfig
|
|
108
109
|
}
|
|
109
110
|
},
|
|
@@ -116,6 +117,15 @@ var BackupJobPair = class extends ComponentResource {
|
|
|
116
117
|
const relativePath = resticRepo.pathPattern.replace(/\$clusterName/g, cluster2.name).replace(/\$appName/g, name).replace(/\$unitName/g, getUnitInstanceName());
|
|
117
118
|
return `rclone:${resticRepo.remoteName}:${relativePath}`;
|
|
118
119
|
});
|
|
120
|
+
const backupOptions = output({
|
|
121
|
+
customOptions: args.backupOptions,
|
|
122
|
+
include: args.include,
|
|
123
|
+
exclude: args.exclude
|
|
124
|
+
}).apply(({ customOptions, include, exclude }) => [
|
|
125
|
+
...customOptions ?? [],
|
|
126
|
+
...include ? include.map((pattern) => `--exclude=!${pattern}`) : [],
|
|
127
|
+
...exclude ? exclude.map((pattern) => `--exclude=${pattern}`) : []
|
|
128
|
+
]);
|
|
119
129
|
this.scriptBundle = new ScriptBundle(
|
|
120
130
|
`${name}-backup-scripts`,
|
|
121
131
|
{
|
|
@@ -154,13 +164,31 @@ var BackupJobPair = class extends ComponentResource {
|
|
|
154
164
|
RESTIC_PASSWORD_FILE: "/credentials/password",
|
|
155
165
|
RESTIC_HOSTNAME: "default",
|
|
156
166
|
RCLONE_CONFIG: "/credentials/rclone.conf",
|
|
157
|
-
EXTRA_BACKUP_OPTIONS:
|
|
167
|
+
EXTRA_BACKUP_OPTIONS: backupOptions.apply(join(" "))
|
|
158
168
|
},
|
|
159
|
-
volumes: [
|
|
169
|
+
volumes: [
|
|
170
|
+
this.credentials,
|
|
171
|
+
{
|
|
172
|
+
name: "credentials-temp",
|
|
173
|
+
emptyDir: {}
|
|
174
|
+
},
|
|
175
|
+
...args.volume ? [args.volume] : []
|
|
176
|
+
],
|
|
160
177
|
volumeMounts: [
|
|
178
|
+
{
|
|
179
|
+
name: "credentials-temp",
|
|
180
|
+
mountPath: "/credentials"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
volume: this.credentials,
|
|
184
|
+
mountPath: "/credentials/rclone.conf",
|
|
185
|
+
subPath: "rclone.conf",
|
|
186
|
+
readOnly: true
|
|
187
|
+
},
|
|
161
188
|
{
|
|
162
189
|
volume: this.credentials,
|
|
163
|
-
mountPath: "/credentials",
|
|
190
|
+
mountPath: "/credentials/password",
|
|
191
|
+
subPath: "password",
|
|
164
192
|
readOnly: true
|
|
165
193
|
},
|
|
166
194
|
...args.volume ? [
|
|
@@ -253,9 +281,9 @@ var BackupJobPair = class extends ComponentResource {
|
|
|
253
281
|
}
|
|
254
282
|
};
|
|
255
283
|
}
|
|
256
|
-
|
|
284
|
+
get terminal() {
|
|
257
285
|
return output({
|
|
258
|
-
backupPassword: this.args.
|
|
286
|
+
backupPassword: this.args.backupKey,
|
|
259
287
|
resticRepo: this.args.resticRepo,
|
|
260
288
|
resticRepoPath: this.resticRepoPath
|
|
261
289
|
}).apply(({ backupPassword, resticRepo, resticRepoPath }) => ({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../assets/images.json","../src/scripts.ts","../src/job-pair.ts"],"names":["terminal-restic","cluster","text"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"terminal-restic\": {\n \"name\": \"ghcr.io/exeteres/highstate/terminal-restic\",\n \"tag\": \"latest\",\n \"image\": \"ghcr.io/exeteres/highstate/terminal-restic:latest@sha256:10ff1f65bbc5d5f84e0accc388c28ddb97f8a1a824bd7a90d2d5674506f5dee3\"\n }\n}\n","import type { ScriptEnvironment } from \"@highstate/k8s\"\nimport { text } from \"@highstate/contract\"\n\nexport const backupEnvironment: ScriptEnvironment = {\n alpine: {\n packages: [\"restic\"],\n },\n\n ubuntu: {\n packages: [\"restic\"],\n },\n\n files: {\n \"backup.sh\": text`\n #!/bin/sh\n set -e\n\n # Init the repo if it doesn't exist\n echo \"| Checking the repository\"\n if restic snapshots > /dev/null 2>&1; then\n echo \"| Repository is ready\"\n else\n echo \"| Initializing new repository\"\n restic init\n fi\n\n # Execute lock script if it exists\n if [ -f /scripts/lock.sh ]; then\n /scripts/lock.sh || (echo \"| error: lock script failed\" && exit 1)\n fi\n\n # Unlock the data source on exit\n if [ -f /scripts/unlock.sh ]; then\n trap \"echo '/scripts/unlock.sh || (echo '| error: unlock script failed' && exit 1)\" EXIT\n fi\n\n # Perform online backup if the corresponding script exists\n if [ -f /scripts/online-backup.sh ]; then\n /scripts/online-backup.sh || (echo \"| error: online backup script failed\" && exit 1)\n fi\n\n # Backup the volume\n echo \"| Backing up /data\"\n restic backup -H \"$RESTIC_HOSTNAME\" /data $EXTRA_BACKUP_OPTIONS\n\n # Forget old snapshots\n echo \"| Forgetting old snapshots\"\n restic forget --host \"$RESTIC_HOSTNAME\" --keep-daily 7 --keep-weekly 4 --keep-monthly 6\n echo \"| Backup complete\"\n `,\n\n \"restore.sh\": text`\n #!/bin/sh\n set -e\n\n # Check if /data is empty\n echo \"| Checking if volume is empty\"\n if [ \"$(find /data -type f -print -quit 2>/dev/null)\" ]; then\n echo \"| Volume is not empty. Skipping restore.\"\n exit 0\n fi\n\n # Check if at least one snapshot exists\n echo \"| Checking for snapshots\"\n if ! result=$(restic list snapshots); then\n echo \"| No snapshots found. Skipping restore.\"\n exit 0\n fi\n\n if [ -z \"$result\" ]; then\n echo \"| No snapshots found. Skipping restore.\"\n exit 0\n fi\n\n # Restore the volume\n echo \"| Restoring /data\"\n restic restore -H \"$RESTIC_HOSTNAME\" latest --target /\n echo \"| Volume restored.\"\n\n # Post-restore script\n if [ -f /scripts/post-restore.sh ]; then\n /scripts/post-restore.sh || (echo \"| error: post-restore script failed\" && exit 1)\n fi\n `,\n },\n}\n","import type { InputL34Endpoint } from \"@highstate/common\"\nimport type { restic } from \"@highstate/library\"\nimport {\n type TriggerInvocation,\n text,\n type UnitTerminal,\n type UnitTrigger,\n} from \"@highstate/contract\"\nimport {\n type Container,\n CronJob,\n createScriptContainer,\n getProvider,\n Job,\n type ScopedResourceArgs,\n ScriptBundle,\n type ScriptDistribution,\n type ScriptEnvironment,\n Secret,\n type WorkloadVolume,\n} from \"@highstate/k8s\"\nimport {\n ComponentResource,\n type ComponentResourceOptions,\n getUnitInstanceName,\n type Input,\n type InputArray,\n normalize,\n type Output,\n output,\n toPromise,\n} from \"@highstate/pulumi\"\nimport { batch } from \"@pulumi/kubernetes\"\nimport * as images from \"../assets/images.json\"\nimport { backupEnvironment } from \"./scripts\"\n\nexport type BackupJobPairArgs = ScopedResourceArgs & {\n /**\n * The repository to backup/restore to/from.\n */\n resticRepo: Input<restic.Repository>\n\n /**\n * The password used to encrypt the backups.\n */\n backupPassword: Input<string>\n\n /**\n * The extra script environment to pass to the backup and restore scripts.\n */\n environment?: Input<ScriptEnvironment>\n\n /**\n * The extra script environments to pass to the backup and restore scripts.\n */\n environments?: InputArray<ScriptEnvironment>\n\n /**\n * The volume to backup.\n */\n volume?: Input<WorkloadVolume>\n\n /**\n * The sup path of the volume to restore/backup.\n */\n subPath?: Input<string>\n\n /**\n * The schedule for the backup job.\n *\n * By default, the backup job runs every day at midnight.\n */\n schedule?: Input<string>\n\n /**\n * The extra options to pass to the backup script.\n */\n backupOptions?: InputArray<string>\n\n /**\n * The extra options for the backup container.\n */\n backupContainer?: Input<Container>\n\n /**\n * The extra options for the restore container.\n */\n restoreContainer?: Input<Container>\n\n /**\n * The distribution to use for the scripts.\n *\n * By default, the distribution is `alpine`.\n *\n * You can also use `ubuntu` if you need to install packages that are not available (or working) in Alpine.\n */\n distribution?: ScriptDistribution\n\n /**\n * Extra allowed endpoints for the backup and restore jobs.\n */\n allowedEndpoints?: InputArray<InputL34Endpoint>\n}\n\nexport class BackupJobPair extends ComponentResource {\n /**\n * The credentials used to access the repository and encrypt the backups.\n */\n readonly credentials: Secret\n\n /**\n * The script bundle used by the backup and restore jobs.\n */\n readonly scriptBundle: ScriptBundle\n\n /**\n * The job resource which restores the volume from the backup before creating an application.\n */\n readonly restoreJob: Job\n\n /**\n * The cron job resource which backups the volume regularly.\n */\n readonly backupJob: CronJob\n\n /**\n * The full name of the Restic repository.\n */\n readonly resticRepoPath: Output<string>\n\n constructor(\n private readonly name: string,\n private readonly args: BackupJobPairArgs,\n private readonly opts?: ComponentResourceOptions,\n ) {\n super(\"highstate:restic:BackupJobPair\", name, args, opts)\n\n const cluster = output(args.namespace).cluster\n\n this.credentials = Secret.create(\n `${name}-backup-credentials`,\n {\n namespace: args.namespace,\n\n stringData: {\n password: args.backupPassword,\n \"rclone.conf\": output(args.resticRepo).rcloneConfig,\n },\n },\n { ...opts, parent: this },\n )\n\n this.resticRepoPath = output({\n cluster,\n resticRepo: args.resticRepo,\n }).apply(({ cluster, resticRepo }) => {\n const relativePath = resticRepo.pathPattern\n .replace(/\\$clusterName/g, cluster.name)\n .replace(/\\$appName/g, name)\n .replace(/\\$unitName/g, getUnitInstanceName())\n\n return `rclone:${resticRepo.remoteName}:${relativePath}`\n })\n\n this.scriptBundle = new ScriptBundle(\n `${name}-backup-scripts`,\n {\n namespace: args.namespace,\n distribution: args.distribution ?? \"alpine\",\n\n environments: output({\n environment: args.environment,\n environments: args.environments,\n }).apply(({ environment, environments }) => [\n backupEnvironment,\n {\n alpine: {\n packages: [\"rclone\"],\n },\n\n ubuntu: {\n preInstallPackages: [\"curl\", \"unzip\"],\n\n preInstallScripts: {\n \"rclone.sh\": text`\n #!/bin/sh\n set -e\n\n curl https://rclone.org/install.sh | bash\n `,\n },\n\n allowedEndpoints: [\"rclone.org:443\", \"downloads.rclone.org:443\"],\n },\n\n allowedEndpoints: output({\n allowedEndpoints: args.allowedEndpoints,\n resticRepo: args.resticRepo,\n }).apply(({ allowedEndpoints, resticRepo }) => [\n ...(allowedEndpoints ?? []),\n ...(resticRepo.remoteEndpoints ?? []),\n ]),\n\n environment: {\n RESTIC_REPOSITORY: this.resticRepoPath,\n RESTIC_PASSWORD_FILE: \"/credentials/password\",\n RESTIC_HOSTNAME: \"default\",\n RCLONE_CONFIG: \"/credentials/rclone.conf\",\n EXTRA_BACKUP_OPTIONS: output(args.backupOptions).apply(options => options?.join(\" \")),\n },\n\n volumes: [this.credentials, ...(args.volume ? [args.volume] : [])],\n\n volumeMounts: [\n {\n volume: this.credentials,\n mountPath: \"/credentials\",\n readOnly: true,\n },\n ...(args.volume\n ? [\n {\n volume: args.volume,\n mountPath: \"/data\",\n subPath: args.subPath,\n },\n ]\n : []),\n ],\n } satisfies ScriptEnvironment,\n ...normalize(environment, environments),\n ]),\n },\n { ...opts, parent: this },\n )\n\n this.restoreJob = Job.create(\n `${name}-restore`,\n {\n namespace: args.namespace,\n\n container: output(args.restoreContainer).apply(restoreContainer =>\n createScriptContainer({\n ...restoreContainer,\n main: \"restore.sh\",\n bundle: this.scriptBundle,\n }),\n ),\n\n backoffLimit: 2,\n },\n { ...opts, parent: this },\n )\n\n this.backupJob = CronJob.create(\n `${name}-backup`,\n {\n namespace: args.namespace,\n\n container: output(args.backupContainer).apply(backupContainer =>\n createScriptContainer({\n ...backupContainer,\n main: \"backup.sh\",\n bundle: this.scriptBundle,\n }),\n ),\n\n schedule: args.schedule ?? \"0 0 * * *\",\n concurrencyPolicy: \"Forbid\",\n\n jobTemplate: {\n spec: {\n backoffLimit: 2,\n },\n },\n },\n { ...opts, parent: this },\n )\n }\n\n handleTrigger(triggers: TriggerInvocation[]): UnitTrigger | undefined {\n const triggerName = `restic.backup-on-destroy.${this.name}`\n const invokedTrigger = triggers.find(trigger => trigger.name === triggerName)\n\n if (invokedTrigger) {\n void this.createBackupOnDestroyJob()\n return\n }\n\n return {\n name: triggerName,\n meta: {\n title: \"Backup on Destroy\",\n description: `Backup the \"${this.name}\" before destroying.`,\n icon: \"material-symbols:backup\",\n },\n spec: {\n type: \"before-destroy\",\n },\n }\n }\n\n createTerminal(): Output<UnitTerminal> {\n return output({\n backupPassword: this.args.backupPassword,\n resticRepo: this.args.resticRepo,\n resticRepoPath: this.resticRepoPath,\n }).apply(({ backupPassword, resticRepo, resticRepoPath }) => ({\n name: \"restic\",\n\n meta: {\n title: \"Restic\",\n description: \"Manage Restic repository\",\n icon: \"material-symbols:backup\",\n },\n\n spec: {\n image: images[\"terminal-restic\"].image,\n command: [\"bash\", \"/welcome.sh\"],\n\n files: {\n \"/welcome.sh\": {\n meta: { name: \"/welcome.sh\" },\n content: {\n type: \"embedded\" as const,\n value: text`\n echo \"Use 'restic' to manage the repository.\"\n echo\n\n exec bash\n `,\n },\n },\n\n \"/credentials/password\": {\n meta: { name: \"/credentials/password\" },\n content: {\n type: \"embedded\" as const,\n value: backupPassword,\n },\n },\n\n \"/root/.config/rclone/rclone.conf\": {\n meta: { name: \"/root/.config/rclone/rclone.conf\" },\n content: {\n type: \"embedded\" as const,\n value: resticRepo.rcloneConfig,\n },\n },\n },\n\n env: {\n RESTIC_REPOSITORY: resticRepoPath,\n RESTIC_PASSWORD_FILE: \"/credentials/password\",\n },\n },\n }))\n }\n\n private async createBackupOnDestroyJob(): Promise<void> {\n const cluster = await toPromise(output(this.args.namespace).cluster)\n\n new batch.v1.Job(\n `${this.name}-backup-on-destroy`,\n {\n metadata: {\n name: `${this.name}-backup-on-destroy`,\n namespace: this.backupJob.metadata.namespace,\n },\n spec: this.backupJob.spec.jobTemplate.spec,\n },\n { ...this.opts, parent: this, provider: getProvider(cluster) },\n )\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../assets/images.json","../src/scripts.ts","../src/job-pair.ts"],"names":["terminal-restic","cluster","text"],"mappings":";;;;;;;;;AACE,IAAAA,eAAAA,GAAmB;AAAA,EAGjB,KAAA,EAAS;AACX,CAAA;ACFK,IAAM,iBAAA,GAAuC;AAAA,EAClD,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,CAAC,QAAQ;AAAA,GACrB;AAAA,EAEA,MAAA,EAAQ;AAAA,IACN,QAAA,EAAU,CAAC,QAAQ;AAAA,GACrB;AAAA,EAEA,KAAA,EAAO;AAAA,IACL,WAAA,EAAa,IAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAAA,IAsCb,YAAA,EAAc,IAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA;AAAA;AAkClB,CAAA;;;ACsCO,IAAM,aAAA,GAAN,cAA4B,iBAAA,CAAkB;AAAA,EA0BnD,WAAA,CACmB,IAAA,EACA,IAAA,EACA,IAAA,EACjB;AACA,IAAA,KAAA,CAAM,gCAAA,EAAkC,IAAA,EAAM,IAAA,EAAM,IAAI,CAAA;AAJvC,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AACA,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAIjB,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,CAAE,OAAA;AAEvC,IAAA,IAAA,CAAK,cAAc,MAAA,CAAO,MAAA;AAAA,MACxB,GAAG,IAAI,CAAA,mBAAA,CAAA;AAAA,MACP;AAAA,QACE,WAAW,IAAA,CAAK,SAAA;AAAA,QAEhB,UAAA,EAAY;AAAA,UACV,UAAU,IAAA,CAAK,SAAA;AAAA,UACf,aAAA,EAAe,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CAAE;AAAA;AACzC,OACF;AAAA,MACA,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,IAAA;AAAK,KAC1B;AAEA,IAAA,IAAA,CAAK,iBAAiB,MAAA,CAAO;AAAA,MAC3B,OAAA;AAAA,MACA,YAAY,IAAA,CAAK;AAAA,KAClB,EAAE,KAAA,CAAM,CAAC,EAAE,OAAA,EAAAC,QAAAA,EAAS,YAAW,KAAM;AACpC,MAAA,MAAM,YAAA,GAAe,UAAA,CAAW,WAAA,CAC7B,OAAA,CAAQ,kBAAkBA,QAAAA,CAAQ,IAAI,CAAA,CACtC,OAAA,CAAQ,cAAc,IAAI,CAAA,CAC1B,OAAA,CAAQ,aAAA,EAAe,qBAAqB,CAAA;AAE/C,MAAA,OAAO,CAAA,OAAA,EAAU,UAAA,CAAW,UAAU,CAAA,CAAA,EAAI,YAAY,CAAA,CAAA;AAAA,IACxD,CAAC,CAAA;AAED,IAAA,MAAM,gBAAgB,MAAA,CAAO;AAAA,MAC3B,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,SAAS,IAAA,CAAK;AAAA,KACf,EAAE,KAAA,CAAM,CAAC,EAAE,aAAA,EAAe,OAAA,EAAS,SAAQ,KAAM;AAAA,MAChD,GAAI,iBAAiB,EAAC;AAAA,MACtB,GAAI,UAAU,OAAA,CAAQ,GAAA,CAAI,aAAW,CAAA,WAAA,EAAc,OAAO,CAAA,CAAE,CAAA,GAAI,EAAC;AAAA,MACjE,GAAI,UAAU,OAAA,CAAQ,GAAA,CAAI,aAAW,CAAA,UAAA,EAAa,OAAO,CAAA,CAAE,CAAA,GAAI;AAAC,KACjE,CAAA;AAED,IAAA,IAAA,CAAK,eAAe,IAAI,YAAA;AAAA,MACtB,GAAG,IAAI,CAAA,eAAA,CAAA;AAAA,MACP;AAAA,QACE,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,YAAA,EAAc,KAAK,YAAA,IAAgB,QAAA;AAAA,QAEnC,cAAc,MAAA,CAAO;AAAA,UACnB,aAAa,IAAA,CAAK,WAAA;AAAA,UAClB,cAAc,IAAA,CAAK;AAAA,SACpB,CAAA,CAAE,KAAA,CAAM,CAAC,EAAE,WAAA,EAAa,cAAa,KAAM;AAAA,UAC1C,iBAAA;AAAA,UACA;AAAA,YACE,MAAA,EAAQ;AAAA,cACN,QAAA,EAAU,CAAC,QAAQ;AAAA,aACrB;AAAA,YAEA,MAAA,EAAQ;AAAA,cACN,kBAAA,EAAoB,CAAC,MAAA,EAAQ,OAAO,CAAA;AAAA,cAEpC,iBAAA,EAAmB;AAAA,gBACjB,WAAA,EAAaC,IAAAA;AAAA;AAAA;;AAAA;AAAA,gBAAA;AAAA,eAMf;AAAA,cAEA,gBAAA,EAAkB,CAAC,gBAAA,EAAkB,0BAA0B;AAAA,aACjE;AAAA,YAEA,kBAAkB,MAAA,CAAO;AAAA,cACvB,kBAAkB,IAAA,CAAK,gBAAA;AAAA,cACvB,YAAY,IAAA,CAAK;AAAA,aAClB,CAAA,CAAE,KAAA,CAAM,CAAC,EAAE,gBAAA,EAAkB,YAAW,KAAM;AAAA,cAC7C,GAAI,oBAAoB,EAAC;AAAA,cACzB,GAAI,UAAA,CAAW,eAAA,IAAmB;AAAC,aACpC,CAAA;AAAA,YAED,WAAA,EAAa;AAAA,cACX,mBAAmB,IAAA,CAAK,cAAA;AAAA,cACxB,oBAAA,EAAsB,uBAAA;AAAA,cACtB,eAAA,EAAiB,SAAA;AAAA,cACjB,aAAA,EAAe,0BAAA;AAAA,cACf,oBAAA,EAAsB,aAAA,CAAc,KAAA,CAAM,IAAA,CAAK,GAAG,CAAC;AAAA,aACrD;AAAA,YAEA,OAAA,EAAS;AAAA,cACP,IAAA,CAAK,WAAA;AAAA,cACL;AAAA,gBACE,IAAA,EAAM,kBAAA;AAAA,gBACN,UAAU;AAAC,eACb;AAAA,cACA,GAAI,IAAA,CAAK,MAAA,GAAS,CAAC,IAAA,CAAK,MAAM,IAAI;AAAC,aACrC;AAAA,YAEA,YAAA,EAAc;AAAA,cACZ;AAAA,gBACE,IAAA,EAAM,kBAAA;AAAA,gBACN,SAAA,EAAW;AAAA,eACb;AAAA,cACA;AAAA,gBACE,QAAQ,IAAA,CAAK,WAAA;AAAA,gBACb,SAAA,EAAW,0BAAA;AAAA,gBACX,OAAA,EAAS,aAAA;AAAA,gBACT,QAAA,EAAU;AAAA,eACZ;AAAA,cACA;AAAA,gBACE,QAAQ,IAAA,CAAK,WAAA;AAAA,gBACb,SAAA,EAAW,uBAAA;AAAA,gBACX,OAAA,EAAS,UAAA;AAAA,gBACT,QAAA,EAAU;AAAA,eACZ;AAAA,cACA,GAAI,KAAK,MAAA,GACL;AAAA,gBACE;AAAA,kBACE,QAAQ,IAAA,CAAK,MAAA;AAAA,kBACb,SAAA,EAAW,OAAA;AAAA,kBACX,SAAS,IAAA,CAAK;AAAA;AAChB,kBAEF;AAAC;AACP,WACF;AAAA,UACA,GAAG,SAAA,CAAU,WAAA,EAAa,YAAY;AAAA,SACvC;AAAA,OACH;AAAA,MACA,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,IAAA;AAAK,KAC1B;AAEA,IAAA,IAAA,CAAK,aAAa,GAAA,CAAI,MAAA;AAAA,MACpB,GAAG,IAAI,CAAA,QAAA,CAAA;AAAA,MACP;AAAA,QACE,WAAW,IAAA,CAAK,SAAA;AAAA,QAEhB,SAAA,EAAW,MAAA,CAAO,IAAA,CAAK,gBAAgB,CAAA,CAAE,KAAA;AAAA,UAAM,sBAC7C,qBAAA,CAAsB;AAAA,YACpB,GAAG,gBAAA;AAAA,YACH,IAAA,EAAM,YAAA;AAAA,YACN,QAAQ,IAAA,CAAK;AAAA,WACd;AAAA,SACH;AAAA,QAEA,YAAA,EAAc;AAAA,OAChB;AAAA,MACA,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,IAAA;AAAK,KAC1B;AAEA,IAAA,IAAA,CAAK,YAAY,OAAA,CAAQ,MAAA;AAAA,MACvB,GAAG,IAAI,CAAA,OAAA,CAAA;AAAA,MACP;AAAA,QACE,WAAW,IAAA,CAAK,SAAA;AAAA,QAEhB,SAAA,EAAW,MAAA,CAAO,IAAA,CAAK,eAAe,CAAA,CAAE,KAAA;AAAA,UAAM,qBAC5C,qBAAA,CAAsB;AAAA,YACpB,GAAG,eAAA;AAAA,YACH,IAAA,EAAM,WAAA;AAAA,YACN,QAAQ,IAAA,CAAK;AAAA,WACd;AAAA,SACH;AAAA,QAEA,QAAA,EAAU,KAAK,QAAA,IAAY,WAAA;AAAA,QAC3B,iBAAA,EAAmB,QAAA;AAAA,QAEnB,WAAA,EAAa;AAAA,UACX,IAAA,EAAM;AAAA,YACJ,YAAA,EAAc;AAAA;AAChB;AACF,OACF;AAAA,MACA,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,IAAA;AAAK,KAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAtMS,WAAA;AAAA;AAAA;AAAA;AAAA,EAKA,YAAA;AAAA;AAAA;AAAA;AAAA,EAKA,UAAA;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA;AAAA;AAAA;AAAA;AAAA,EAKA,cAAA;AAAA,EAoLT,cAAc,QAAA,EAAwD;AACpE,IAAA,MAAM,WAAA,GAAc,CAAA,yBAAA,EAA4B,IAAA,CAAK,IAAI,CAAA,CAAA;AACzD,IAAA,MAAM,iBAAiB,QAAA,CAAS,IAAA,CAAK,CAAA,OAAA,KAAW,OAAA,CAAQ,SAAS,WAAW,CAAA;AAE5E,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,KAAK,KAAK,wBAAA,EAAyB;AACnC,MAAA;AAAA,IACF;AAEA,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,WAAA;AAAA,MACN,IAAA,EAAM;AAAA,QACJ,KAAA,EAAO,mBAAA;AAAA,QACP,WAAA,EAAa,CAAA,YAAA,EAAe,IAAA,CAAK,IAAI,CAAA,oBAAA,CAAA;AAAA,QACrC,IAAA,EAAM;AAAA,OACR;AAAA,MACA,IAAA,EAAM;AAAA,QACJ,IAAA,EAAM;AAAA;AACR,KACF;AAAA,EACF;AAAA,EAEA,IAAI,QAAA,GAAiC;AACnC,IAAA,OAAO,MAAA,CAAO;AAAA,MACZ,cAAA,EAAgB,KAAK,IAAA,CAAK,SAAA;AAAA,MAC1B,UAAA,EAAY,KAAK,IAAA,CAAK,UAAA;AAAA,MACtB,gBAAgB,IAAA,CAAK;AAAA,KACtB,EAAE,KAAA,CAAM,CAAC,EAAE,cAAA,EAAgB,UAAA,EAAY,gBAAe,MAAO;AAAA,MAC5D,IAAA,EAAM,QAAA;AAAA,MAEN,IAAA,EAAM;AAAA,QACJ,KAAA,EAAO,QAAA;AAAA,QACP,WAAA,EAAa,0BAAA;AAAA,QACb,IAAA,EAAM;AAAA,OACR;AAAA,MAEA,IAAA,EAAM;AAAA,QACJ,OAAcF,eAAAA,CAAmB,KAAA;AAAA,QACjC,OAAA,EAAS,CAAC,MAAA,EAAQ,aAAa,CAAA;AAAA,QAE/B,KAAA,EAAO;AAAA,UACL,aAAA,EAAe;AAAA,YACb,IAAA,EAAM,EAAE,IAAA,EAAM,aAAA,EAAc;AAAA,YAC5B,OAAA,EAAS;AAAA,cACP,IAAA,EAAM,UAAA;AAAA,cACN,KAAA,EAAOE,IAAAA;AAAA;AAAA;;AAAA;AAAA,cAAA;AAAA;AAMT,WACF;AAAA,UAEA,uBAAA,EAAyB;AAAA,YACvB,IAAA,EAAM,EAAE,IAAA,EAAM,uBAAA,EAAwB;AAAA,YACtC,OAAA,EAAS;AAAA,cACP,IAAA,EAAM,UAAA;AAAA,cACN,KAAA,EAAO;AAAA;AACT,WACF;AAAA,UAEA,kCAAA,EAAoC;AAAA,YAClC,IAAA,EAAM,EAAE,IAAA,EAAM,kCAAA,EAAmC;AAAA,YACjD,OAAA,EAAS;AAAA,cACP,IAAA,EAAM,UAAA;AAAA,cACN,OAAO,UAAA,CAAW;AAAA;AACpB;AACF,SACF;AAAA,QAEA,GAAA,EAAK;AAAA,UACH,iBAAA,EAAmB,cAAA;AAAA,UACnB,oBAAA,EAAsB;AAAA;AACxB;AACF,KACF,CAAE,CAAA;AAAA,EACJ;AAAA,EAEA,MAAc,wBAAA,GAA0C;AACtD,IAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,MAAA,CAAO,KAAK,IAAA,CAAK,SAAS,EAAE,OAAO,CAAA;AAEnE,IAAA,IAAI,MAAM,EAAA,CAAG,GAAA;AAAA,MACX,CAAA,EAAG,KAAK,IAAI,CAAA,kBAAA,CAAA;AAAA,MACZ;AAAA,QACE,QAAA,EAAU;AAAA,UACR,IAAA,EAAM,CAAA,EAAG,IAAA,CAAK,IAAI,CAAA,kBAAA,CAAA;AAAA,UAClB,SAAA,EAAW,IAAA,CAAK,SAAA,CAAU,QAAA,CAAS;AAAA,SACrC;AAAA,QACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,WAAA,CAAY;AAAA,OACxC;AAAA,MACA,EAAE,GAAG,IAAA,CAAK,IAAA,EAAM,QAAQ,IAAA,EAAM,QAAA,EAAU,WAAA,CAAY,OAAO,CAAA;AAAE,KAC/D;AAAA,EACF;AACF","file":"index.js","sourcesContent":["{\n \"terminal-restic\": {\n \"name\": \"ghcr.io/exeteres/highstate/terminal-restic\",\n \"tag\": \"latest\",\n \"image\": \"ghcr.io/exeteres/highstate/terminal-restic:latest@sha256:10ff1f65bbc5d5f84e0accc388c28ddb97f8a1a824bd7a90d2d5674506f5dee3\"\n }\n}\n","import type { ScriptEnvironment } from \"@highstate/k8s\"\nimport { text } from \"@highstate/contract\"\n\nexport const backupEnvironment: ScriptEnvironment = {\n alpine: {\n packages: [\"restic\"],\n },\n\n ubuntu: {\n packages: [\"restic\"],\n },\n\n files: {\n \"backup.sh\": text`\n #!/bin/sh\n set -e\n\n # Init the repo if it doesn't exist\n echo \"| Checking the repository\"\n if restic snapshots > /dev/null 2>&1; then\n echo \"| Repository is ready\"\n else\n echo \"| Initializing new repository\"\n restic init\n fi\n\n # Execute lock script if it exists\n if [ -f /scripts/lock.sh ]; then\n /scripts/lock.sh || (echo \"| error: lock script failed\" && exit 1)\n fi\n\n # Unlock the data source on exit\n if [ -f /scripts/unlock.sh ]; then\n trap \"echo '/scripts/unlock.sh || (echo '| error: unlock script failed' && exit 1)\" EXIT\n fi\n\n # Perform online backup if the corresponding script exists\n if [ -f /scripts/online-backup.sh ]; then\n /scripts/online-backup.sh || (echo \"| error: online backup script failed\" && exit 1)\n fi\n\n # Backup the volume\n echo \"| Backing up /data\"\n restic backup -H \"$RESTIC_HOSTNAME\" /data $EXTRA_BACKUP_OPTIONS\n\n # Forget old snapshots\n echo \"| Forgetting old snapshots\"\n restic forget --host \"$RESTIC_HOSTNAME\" --keep-daily 7 --keep-weekly 4 --keep-monthly 6\n echo \"| Backup complete\"\n `,\n\n \"restore.sh\": text`\n #!/bin/sh\n set -e\n\n # Check if /data is empty\n echo \"| Checking if volume is empty\"\n if [ \"$(find /data -type f -print -quit 2>/dev/null)\" ]; then\n echo \"| Volume is not empty. Skipping restore.\"\n exit 0\n fi\n\n # Check if at least one snapshot exists\n echo \"| Checking for snapshots\"\n if ! result=$(restic list snapshots); then\n echo \"| No snapshots found. Skipping restore.\"\n exit 0\n fi\n\n if [ -z \"$result\" ]; then\n echo \"| No snapshots found. Skipping restore.\"\n exit 0\n fi\n\n # Restore the volume\n echo \"| Restoring /data\"\n restic restore -H \"$RESTIC_HOSTNAME\" latest --target /\n echo \"| Volume restored.\"\n\n # Post-restore script\n if [ -f /scripts/post-restore.sh ]; then\n /scripts/post-restore.sh || (echo \"| error: post-restore script failed\" && exit 1)\n fi\n `,\n },\n}\n","import type { InputL34Endpoint } from \"@highstate/common\"\nimport type { restic } from \"@highstate/library\"\nimport {\n type TriggerInvocation,\n text,\n type UnitTerminal,\n type UnitTrigger,\n} from \"@highstate/contract\"\nimport {\n type Container,\n CronJob,\n createScriptContainer,\n getProvider,\n Job,\n type ScopedResourceArgs,\n ScriptBundle,\n type ScriptDistribution,\n type ScriptEnvironment,\n Secret,\n type WorkloadVolume,\n} from \"@highstate/k8s\"\nimport {\n ComponentResource,\n type ComponentResourceOptions,\n getUnitInstanceName,\n type Input,\n type InputArray,\n normalize,\n type Output,\n output,\n toPromise,\n} from \"@highstate/pulumi\"\nimport { batch } from \"@pulumi/kubernetes\"\nimport { join } from \"remeda\"\nimport * as images from \"../assets/images.json\"\nimport { backupEnvironment } from \"./scripts\"\n\nexport type BackupJobPairArgs = ScopedResourceArgs & {\n /**\n * The repository to backup/restore to/from.\n */\n resticRepo: Input<restic.Repository>\n\n /**\n * The key used to encrypt the backups.\n */\n backupKey: Input<string>\n\n /**\n * The extra script environment to pass to the backup and restore scripts.\n */\n environment?: Input<ScriptEnvironment>\n\n /**\n * The extra script environments to pass to the backup and restore scripts.\n */\n environments?: InputArray<ScriptEnvironment>\n\n /**\n * The volume to backup.\n */\n volume?: Input<WorkloadVolume>\n\n /**\n * The sup path of the volume to restore/backup.\n */\n subPath?: Input<string>\n\n /**\n * The schedule for the backup job.\n *\n * By default, the backup job runs every day at midnight.\n */\n schedule?: Input<string>\n\n /**\n * The extra options to pass to the restic backup command.\n */\n backupOptions?: InputArray<string>\n\n /**\n * The patterns to include in the backup.\n *\n * If not specified, everything is included.\n *\n * Under the hood, this adds `--exclude=!{pattern}` for each pattern.\n */\n include?: InputArray<string>\n\n /**\n * The patterns to exclude from the backup.\n *\n * By default, nothing is excluded.\n *\n * Under the hood, this adds `--exclude={pattern}` for each pattern.\n */\n exclude?: InputArray<string>\n\n /**\n * The extra options for the backup container.\n */\n backupContainer?: Input<Container>\n\n /**\n * The extra options for the restore container.\n */\n restoreContainer?: Input<Container>\n\n /**\n * The distribution to use for the scripts.\n *\n * By default, the distribution is `alpine`.\n *\n * You can also use `ubuntu` if you need to install packages that are not available (or working) in Alpine.\n */\n distribution?: ScriptDistribution\n\n /**\n * Extra allowed endpoints for the backup and restore jobs.\n */\n allowedEndpoints?: InputArray<InputL34Endpoint>\n}\n\nexport class BackupJobPair extends ComponentResource {\n /**\n * The credentials used to access the repository and encrypt the backups.\n */\n readonly credentials: Secret\n\n /**\n * The script bundle used by the backup and restore jobs.\n */\n readonly scriptBundle: ScriptBundle\n\n /**\n * The job resource which restores the volume from the backup before creating an application.\n */\n readonly restoreJob: Job\n\n /**\n * The cron job resource which backups the volume regularly.\n */\n readonly backupJob: CronJob\n\n /**\n * The full name of the Restic repository.\n */\n readonly resticRepoPath: Output<string>\n\n constructor(\n private readonly name: string,\n private readonly args: BackupJobPairArgs,\n private readonly opts?: ComponentResourceOptions,\n ) {\n super(\"highstate:restic:BackupJobPair\", name, args, opts)\n\n const cluster = output(args.namespace).cluster\n\n this.credentials = Secret.create(\n `${name}-backup-credentials`,\n {\n namespace: args.namespace,\n\n stringData: {\n password: args.backupKey,\n \"rclone.conf\": output(args.resticRepo).rcloneConfig,\n },\n },\n { ...opts, parent: this },\n )\n\n this.resticRepoPath = output({\n cluster,\n resticRepo: args.resticRepo,\n }).apply(({ cluster, resticRepo }) => {\n const relativePath = resticRepo.pathPattern\n .replace(/\\$clusterName/g, cluster.name)\n .replace(/\\$appName/g, name)\n .replace(/\\$unitName/g, getUnitInstanceName())\n\n return `rclone:${resticRepo.remoteName}:${relativePath}`\n })\n\n const backupOptions = output({\n customOptions: args.backupOptions,\n include: args.include,\n exclude: args.exclude,\n }).apply(({ customOptions, include, exclude }) => [\n ...(customOptions ?? []),\n ...(include ? include.map(pattern => `--exclude=!${pattern}`) : []),\n ...(exclude ? exclude.map(pattern => `--exclude=${pattern}`) : []),\n ])\n\n this.scriptBundle = new ScriptBundle(\n `${name}-backup-scripts`,\n {\n namespace: args.namespace,\n distribution: args.distribution ?? \"alpine\",\n\n environments: output({\n environment: args.environment,\n environments: args.environments,\n }).apply(({ environment, environments }) => [\n backupEnvironment,\n {\n alpine: {\n packages: [\"rclone\"],\n },\n\n ubuntu: {\n preInstallPackages: [\"curl\", \"unzip\"],\n\n preInstallScripts: {\n \"rclone.sh\": text`\n #!/bin/sh\n set -e\n\n curl https://rclone.org/install.sh | bash\n `,\n },\n\n allowedEndpoints: [\"rclone.org:443\", \"downloads.rclone.org:443\"],\n },\n\n allowedEndpoints: output({\n allowedEndpoints: args.allowedEndpoints,\n resticRepo: args.resticRepo,\n }).apply(({ allowedEndpoints, resticRepo }) => [\n ...(allowedEndpoints ?? []),\n ...(resticRepo.remoteEndpoints ?? []),\n ]),\n\n environment: {\n RESTIC_REPOSITORY: this.resticRepoPath,\n RESTIC_PASSWORD_FILE: \"/credentials/password\",\n RESTIC_HOSTNAME: \"default\",\n RCLONE_CONFIG: \"/credentials/rclone.conf\",\n EXTRA_BACKUP_OPTIONS: backupOptions.apply(join(\" \")),\n },\n\n volumes: [\n this.credentials,\n {\n name: \"credentials-temp\",\n emptyDir: {},\n },\n ...(args.volume ? [args.volume] : []),\n ],\n\n volumeMounts: [\n {\n name: \"credentials-temp\",\n mountPath: \"/credentials\",\n },\n {\n volume: this.credentials,\n mountPath: \"/credentials/rclone.conf\",\n subPath: \"rclone.conf\",\n readOnly: true,\n },\n {\n volume: this.credentials,\n mountPath: \"/credentials/password\",\n subPath: \"password\",\n readOnly: true,\n },\n ...(args.volume\n ? [\n {\n volume: args.volume,\n mountPath: \"/data\",\n subPath: args.subPath,\n },\n ]\n : []),\n ],\n } satisfies ScriptEnvironment,\n ...normalize(environment, environments),\n ]),\n },\n { ...opts, parent: this },\n )\n\n this.restoreJob = Job.create(\n `${name}-restore`,\n {\n namespace: args.namespace,\n\n container: output(args.restoreContainer).apply(restoreContainer =>\n createScriptContainer({\n ...restoreContainer,\n main: \"restore.sh\",\n bundle: this.scriptBundle,\n }),\n ),\n\n backoffLimit: 2,\n },\n { ...opts, parent: this },\n )\n\n this.backupJob = CronJob.create(\n `${name}-backup`,\n {\n namespace: args.namespace,\n\n container: output(args.backupContainer).apply(backupContainer =>\n createScriptContainer({\n ...backupContainer,\n main: \"backup.sh\",\n bundle: this.scriptBundle,\n }),\n ),\n\n schedule: args.schedule ?? \"0 0 * * *\",\n concurrencyPolicy: \"Forbid\",\n\n jobTemplate: {\n spec: {\n backoffLimit: 2,\n },\n },\n },\n { ...opts, parent: this },\n )\n }\n\n handleTrigger(triggers: TriggerInvocation[]): UnitTrigger | undefined {\n const triggerName = `restic.backup-on-destroy.${this.name}`\n const invokedTrigger = triggers.find(trigger => trigger.name === triggerName)\n\n if (invokedTrigger) {\n void this.createBackupOnDestroyJob()\n return\n }\n\n return {\n name: triggerName,\n meta: {\n title: \"Backup on Destroy\",\n description: `Backup the \"${this.name}\" before destroying.`,\n icon: \"material-symbols:backup\",\n },\n spec: {\n type: \"before-destroy\",\n },\n }\n }\n\n get terminal(): Output<UnitTerminal> {\n return output({\n backupPassword: this.args.backupKey,\n resticRepo: this.args.resticRepo,\n resticRepoPath: this.resticRepoPath,\n }).apply(({ backupPassword, resticRepo, resticRepoPath }) => ({\n name: \"restic\",\n\n meta: {\n title: \"Restic\",\n description: \"Manage Restic repository\",\n icon: \"material-symbols:backup\",\n },\n\n spec: {\n image: images[\"terminal-restic\"].image,\n command: [\"bash\", \"/welcome.sh\"],\n\n files: {\n \"/welcome.sh\": {\n meta: { name: \"/welcome.sh\" },\n content: {\n type: \"embedded\" as const,\n value: text`\n echo \"Use 'restic' to manage the repository.\"\n echo\n\n exec bash\n `,\n },\n },\n\n \"/credentials/password\": {\n meta: { name: \"/credentials/password\" },\n content: {\n type: \"embedded\" as const,\n value: backupPassword,\n },\n },\n\n \"/root/.config/rclone/rclone.conf\": {\n meta: { name: \"/root/.config/rclone/rclone.conf\" },\n content: {\n type: \"embedded\" as const,\n value: resticRepo.rcloneConfig,\n },\n },\n },\n\n env: {\n RESTIC_REPOSITORY: resticRepoPath,\n RESTIC_PASSWORD_FILE: \"/credentials/password\",\n },\n },\n }))\n }\n\n private async createBackupOnDestroyJob(): Promise<void> {\n const cluster = await toPromise(output(this.args.namespace).cluster)\n\n new batch.v1.Job(\n `${this.name}-backup-on-destroy`,\n {\n metadata: {\n name: `${this.name}-backup-on-destroy`,\n namespace: this.backupJob.metadata.namespace,\n },\n spec: this.backupJob.spec.jobTemplate.spec,\n },\n { ...this.opts, parent: this, provider: getProvider(cluster) },\n )\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@highstate/restic",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -24,18 +24,18 @@
|
|
|
24
24
|
"biome:check": "biome check --error-on-warnings"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@highstate/common": "^0.9.
|
|
28
|
-
"@highstate/contract": "^0.9.
|
|
29
|
-
"@highstate/k8s": "^0.9.
|
|
30
|
-
"@highstate/library": "^0.9.
|
|
31
|
-
"@highstate/pulumi": "^0.9.
|
|
27
|
+
"@highstate/common": "^0.9.32",
|
|
28
|
+
"@highstate/contract": "^0.9.32",
|
|
29
|
+
"@highstate/k8s": "^0.9.32",
|
|
30
|
+
"@highstate/library": "^0.9.32",
|
|
31
|
+
"@highstate/pulumi": "^0.9.32",
|
|
32
32
|
"@pulumi/kubernetes": "^4.18.0",
|
|
33
33
|
"remeda": "^2.21.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@biomejs/biome": "2.2.0",
|
|
37
|
-
"@highstate/cli": "^0.9.
|
|
37
|
+
"@highstate/cli": "^0.9.32",
|
|
38
38
|
"@typescript/native-preview": "^7.0.0-dev.20250920.1"
|
|
39
39
|
},
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "00a7c1c40e556daeecfa9e5f3f6a1e60d88911ee"
|
|
41
41
|
}
|
package/src/job-pair.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
toPromise,
|
|
32
32
|
} from "@highstate/pulumi"
|
|
33
33
|
import { batch } from "@pulumi/kubernetes"
|
|
34
|
+
import { join } from "remeda"
|
|
34
35
|
import * as images from "../assets/images.json"
|
|
35
36
|
import { backupEnvironment } from "./scripts"
|
|
36
37
|
|
|
@@ -41,9 +42,9 @@ export type BackupJobPairArgs = ScopedResourceArgs & {
|
|
|
41
42
|
resticRepo: Input<restic.Repository>
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
|
-
* The
|
|
45
|
+
* The key used to encrypt the backups.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
backupKey: Input<string>
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* The extra script environment to pass to the backup and restore scripts.
|
|
@@ -73,10 +74,28 @@ export type BackupJobPairArgs = ScopedResourceArgs & {
|
|
|
73
74
|
schedule?: Input<string>
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
* The extra options to pass to the backup
|
|
77
|
+
* The extra options to pass to the restic backup command.
|
|
77
78
|
*/
|
|
78
79
|
backupOptions?: InputArray<string>
|
|
79
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The patterns to include in the backup.
|
|
83
|
+
*
|
|
84
|
+
* If not specified, everything is included.
|
|
85
|
+
*
|
|
86
|
+
* Under the hood, this adds `--exclude=!{pattern}` for each pattern.
|
|
87
|
+
*/
|
|
88
|
+
include?: InputArray<string>
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The patterns to exclude from the backup.
|
|
92
|
+
*
|
|
93
|
+
* By default, nothing is excluded.
|
|
94
|
+
*
|
|
95
|
+
* Under the hood, this adds `--exclude={pattern}` for each pattern.
|
|
96
|
+
*/
|
|
97
|
+
exclude?: InputArray<string>
|
|
98
|
+
|
|
80
99
|
/**
|
|
81
100
|
* The extra options for the backup container.
|
|
82
101
|
*/
|
|
@@ -143,7 +162,7 @@ export class BackupJobPair extends ComponentResource {
|
|
|
143
162
|
namespace: args.namespace,
|
|
144
163
|
|
|
145
164
|
stringData: {
|
|
146
|
-
password: args.
|
|
165
|
+
password: args.backupKey,
|
|
147
166
|
"rclone.conf": output(args.resticRepo).rcloneConfig,
|
|
148
167
|
},
|
|
149
168
|
},
|
|
@@ -162,6 +181,16 @@ export class BackupJobPair extends ComponentResource {
|
|
|
162
181
|
return `rclone:${resticRepo.remoteName}:${relativePath}`
|
|
163
182
|
})
|
|
164
183
|
|
|
184
|
+
const backupOptions = output({
|
|
185
|
+
customOptions: args.backupOptions,
|
|
186
|
+
include: args.include,
|
|
187
|
+
exclude: args.exclude,
|
|
188
|
+
}).apply(({ customOptions, include, exclude }) => [
|
|
189
|
+
...(customOptions ?? []),
|
|
190
|
+
...(include ? include.map(pattern => `--exclude=!${pattern}`) : []),
|
|
191
|
+
...(exclude ? exclude.map(pattern => `--exclude=${pattern}`) : []),
|
|
192
|
+
])
|
|
193
|
+
|
|
165
194
|
this.scriptBundle = new ScriptBundle(
|
|
166
195
|
`${name}-backup-scripts`,
|
|
167
196
|
{
|
|
@@ -206,15 +235,33 @@ export class BackupJobPair extends ComponentResource {
|
|
|
206
235
|
RESTIC_PASSWORD_FILE: "/credentials/password",
|
|
207
236
|
RESTIC_HOSTNAME: "default",
|
|
208
237
|
RCLONE_CONFIG: "/credentials/rclone.conf",
|
|
209
|
-
EXTRA_BACKUP_OPTIONS:
|
|
238
|
+
EXTRA_BACKUP_OPTIONS: backupOptions.apply(join(" ")),
|
|
210
239
|
},
|
|
211
240
|
|
|
212
|
-
volumes: [
|
|
241
|
+
volumes: [
|
|
242
|
+
this.credentials,
|
|
243
|
+
{
|
|
244
|
+
name: "credentials-temp",
|
|
245
|
+
emptyDir: {},
|
|
246
|
+
},
|
|
247
|
+
...(args.volume ? [args.volume] : []),
|
|
248
|
+
],
|
|
213
249
|
|
|
214
250
|
volumeMounts: [
|
|
215
251
|
{
|
|
216
|
-
|
|
252
|
+
name: "credentials-temp",
|
|
217
253
|
mountPath: "/credentials",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
volume: this.credentials,
|
|
257
|
+
mountPath: "/credentials/rclone.conf",
|
|
258
|
+
subPath: "rclone.conf",
|
|
259
|
+
readOnly: true,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
volume: this.credentials,
|
|
263
|
+
mountPath: "/credentials/password",
|
|
264
|
+
subPath: "password",
|
|
218
265
|
readOnly: true,
|
|
219
266
|
},
|
|
220
267
|
...(args.volume
|
|
@@ -300,9 +347,9 @@ export class BackupJobPair extends ComponentResource {
|
|
|
300
347
|
}
|
|
301
348
|
}
|
|
302
349
|
|
|
303
|
-
|
|
350
|
+
get terminal(): Output<UnitTerminal> {
|
|
304
351
|
return output({
|
|
305
|
-
backupPassword: this.args.
|
|
352
|
+
backupPassword: this.args.backupKey,
|
|
306
353
|
resticRepo: this.args.resticRepo,
|
|
307
354
|
resticRepoPath: this.resticRepoPath,
|
|
308
355
|
}).apply(({ backupPassword, resticRepo, resticRepoPath }) => ({
|