@highstate/restic 0.7.1 → 0.7.3
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/chunk-G3PMV62Z.js +36 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/highstate.manifest.json +6 -0
- package/dist/index.js +9041 -8
- package/dist/index.js.map +1 -0
- package/dist/repo/index.js +14 -10
- package/dist/repo/index.js.map +1 -0
- package/package.json +11 -11
- package/src/index.ts +1 -0
- package/src/job-pair.ts +288 -0
- package/src/repo/index.ts +37 -0
- package/src/scripts.ts +86 -0
- package/dist/index.d.ts +0 -82
package/dist/repo/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { forUnit, getOrCreateSecret } from '@highstate/pulumi';
|
|
3
|
-
import { generatePassword } from '@highstate/common';
|
|
1
|
+
import "../chunk-G3PMV62Z.js";
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
// src/repo/index.ts
|
|
4
|
+
import { restic } from "@highstate/library";
|
|
5
|
+
import { forUnit, getOrCreateSecret } from "@highstate/pulumi";
|
|
6
|
+
import { generatePassword } from "@highstate/common";
|
|
7
|
+
var { args, secrets, outputs } = forUnit(restic.repo);
|
|
8
|
+
var remoteName = secrets.rcloneConfig.apply((config) => {
|
|
7
9
|
const remoteNames = Array.from(config.matchAll(/(?<=\[).+?(?=\])/g));
|
|
8
10
|
if (remoteNames.length === 0) {
|
|
9
11
|
throw new Error("No remotes found in rclone config");
|
|
@@ -13,9 +15,9 @@ const remoteName = secrets.rcloneConfig.apply((config) => {
|
|
|
13
15
|
}
|
|
14
16
|
return remoteNames[0][0];
|
|
15
17
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var
|
|
18
|
+
var password = getOrCreateSecret(secrets, "password", generatePassword);
|
|
19
|
+
var basePath = args.basePath?.replace(/\/$/, "") ?? "backups";
|
|
20
|
+
var repo_default = outputs({
|
|
19
21
|
repo: {
|
|
20
22
|
password,
|
|
21
23
|
type: "rclone",
|
|
@@ -29,5 +31,7 @@ var index = outputs({
|
|
|
29
31
|
basePath
|
|
30
32
|
}
|
|
31
33
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
export {
|
|
35
|
+
repo_default as default
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/repo/index.ts"],"sourcesContent":["import { restic } from \"@highstate/library\"\nimport { forUnit, getOrCreateSecret } from \"@highstate/pulumi\"\nimport { generatePassword } from \"@highstate/common\"\n\nconst { args, secrets, outputs } = forUnit(restic.repo)\n\nconst remoteName = secrets.rcloneConfig.apply(config => {\n const remoteNames = Array.from(config.matchAll(/(?<=\\[).+?(?=\\])/g))\n\n if (remoteNames.length === 0) {\n throw new Error(\"No remotes found in rclone config\")\n }\n\n if (remoteNames.length > 1) {\n throw new Error(\"Multiple remotes found in rclone config\")\n }\n\n return remoteNames[0][0]\n})\n\nconst password = getOrCreateSecret(secrets, \"password\", generatePassword)\nconst basePath = args.basePath?.replace(/\\/$/, \"\") ?? \"backups\"\n\nexport default outputs({\n repo: {\n password,\n type: \"rclone\",\n rcloneConfig: secrets.rcloneConfig,\n remoteName,\n remoteDomains: args.remoteDomains ?? [],\n basePath,\n },\n $status: {\n remoteName,\n basePath,\n },\n})\n"],"mappings":";;;AAAA,SAAS,cAAc;AACvB,SAAS,SAAS,yBAAyB;AAC3C,SAAS,wBAAwB;AAEjC,IAAM,EAAE,MAAM,SAAS,QAAQ,IAAI,QAAQ,OAAO,IAAI;AAEtD,IAAM,aAAa,QAAQ,aAAa,MAAM,YAAU;AACtD,QAAM,cAAc,MAAM,KAAK,OAAO,SAAS,mBAAmB,CAAC;AAEnE,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAEA,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,IAAI,MAAM,yCAAyC;AAAA,EAC3D;AAEA,SAAO,YAAY,CAAC,EAAE,CAAC;AACzB,CAAC;AAED,IAAM,WAAW,kBAAkB,SAAS,YAAY,gBAAgB;AACxE,IAAM,WAAW,KAAK,UAAU,QAAQ,OAAO,EAAE,KAAK;AAEtD,IAAO,eAAQ,QAAQ;AAAA,EACrB,MAAM;AAAA,IACJ;AAAA,IACA,MAAM;AAAA,IACN,cAAc,QAAQ;AAAA,IACtB;AAAA,IACA,eAAe,KAAK,iBAAiB,CAAC;AAAA,IACtC;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@highstate/restic",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"dist",
|
|
7
|
+
"src"
|
|
8
8
|
],
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"./repo": {
|
|
15
15
|
"default": "./dist/repo/index.js"
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "
|
|
22
|
+
"build": "highstate build"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@highstate/common": "^0.7.
|
|
26
|
-
"@highstate/k8s": "^0.7.
|
|
27
|
-
"@highstate/pulumi": "^0.7.
|
|
25
|
+
"@highstate/common": "^0.7.3",
|
|
26
|
+
"@highstate/k8s": "^0.7.3",
|
|
27
|
+
"@highstate/pulumi": "^0.7.3",
|
|
28
28
|
"@pulumi/kubernetes": "^4.18.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@highstate/library": "workspace:^0.4.4"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"
|
|
34
|
+
"@highstate/cli": "^0.7.3"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "5cf7cec27262c8fa1d96f6478833b94841459d64"
|
|
37
37
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BackupJobPair, type BackupJobPairArgs } from "./job-pair"
|
package/src/job-pair.ts
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import type { k8s, restic } from "@highstate/library"
|
|
2
|
+
import { batch, core } from "@pulumi/kubernetes"
|
|
3
|
+
import {
|
|
4
|
+
createScriptContainer,
|
|
5
|
+
CronJob,
|
|
6
|
+
Job,
|
|
7
|
+
mapMetadata,
|
|
8
|
+
ScriptBundle,
|
|
9
|
+
type CommonArgs,
|
|
10
|
+
type Container,
|
|
11
|
+
type ScriptDistribution,
|
|
12
|
+
type ScriptEnvironment,
|
|
13
|
+
type WorkloadVolume,
|
|
14
|
+
} from "@highstate/k8s"
|
|
15
|
+
import {
|
|
16
|
+
ComponentResource,
|
|
17
|
+
normalize,
|
|
18
|
+
output,
|
|
19
|
+
Output,
|
|
20
|
+
type ComponentResourceOptions,
|
|
21
|
+
type Input,
|
|
22
|
+
type InputArray,
|
|
23
|
+
type InstanceTrigger,
|
|
24
|
+
type InstanceTriggerInvocation,
|
|
25
|
+
} from "@highstate/pulumi"
|
|
26
|
+
import { text } from "@highstate/contract"
|
|
27
|
+
import { backupEnvironment } from "./scripts"
|
|
28
|
+
|
|
29
|
+
export type BackupJobPairArgs = CommonArgs & {
|
|
30
|
+
/**
|
|
31
|
+
* The k8s cluster to calculate the repository path.
|
|
32
|
+
*/
|
|
33
|
+
k8sCluster: Input<k8s.Cluster>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The repository to backup/restore to/from.
|
|
37
|
+
*/
|
|
38
|
+
resticRepo: Input<restic.Repo>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The extra script environment to pass to the backup and restore scripts.
|
|
42
|
+
*/
|
|
43
|
+
environment?: Input<ScriptEnvironment>
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The extra script environments to pass to the backup and restore scripts.
|
|
47
|
+
*/
|
|
48
|
+
environments?: InputArray<ScriptEnvironment>
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The volume to backup.
|
|
52
|
+
*/
|
|
53
|
+
volume?: Input<WorkloadVolume>
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The sup path of the volume to restore/backup.
|
|
57
|
+
*/
|
|
58
|
+
subPath?: Input<string>
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The schedule for the backup job.
|
|
62
|
+
*
|
|
63
|
+
* By default, the backup job runs every day at midnight.
|
|
64
|
+
*/
|
|
65
|
+
schedule?: Input<string>
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The extra options to pass to the backup script.
|
|
69
|
+
*/
|
|
70
|
+
backupOptions?: InputArray<string>
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The extra options for the backup container.
|
|
74
|
+
*/
|
|
75
|
+
backupContainer?: Input<Container>
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The extra options for the restore container.
|
|
79
|
+
*/
|
|
80
|
+
restoreContainer?: Input<Container>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The distribution to use for the scripts.
|
|
84
|
+
*
|
|
85
|
+
* By default, the distribution is `alpine`.
|
|
86
|
+
*
|
|
87
|
+
* You can also use `ubuntu` if you need to install packages that are not available (or working) in Alpine.
|
|
88
|
+
*/
|
|
89
|
+
distribution?: ScriptDistribution
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class BackupJobPair extends ComponentResource {
|
|
93
|
+
/**
|
|
94
|
+
* The credentials used to access the repository and encrypt the backups.
|
|
95
|
+
*/
|
|
96
|
+
readonly credentials: Output<core.v1.Secret>
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The script bundle used by the backup and restore jobs.
|
|
100
|
+
*/
|
|
101
|
+
readonly scriptBundle: Output<ScriptBundle>
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The job resource which restores the volume from the backup before creating an application.
|
|
105
|
+
*/
|
|
106
|
+
readonly restoreJob: Output<Job>
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The cron job resource which backups the volume regularly.
|
|
110
|
+
*/
|
|
111
|
+
readonly backupJob: Output<CronJob>
|
|
112
|
+
|
|
113
|
+
constructor(
|
|
114
|
+
private readonly name: string,
|
|
115
|
+
args: BackupJobPairArgs,
|
|
116
|
+
private readonly opts?: ComponentResourceOptions,
|
|
117
|
+
) {
|
|
118
|
+
super("highstate:restic:BackupJobPair", name, args, opts)
|
|
119
|
+
|
|
120
|
+
this.credentials = output(args).apply(args => {
|
|
121
|
+
return new core.v1.Secret(
|
|
122
|
+
`${name}-backup-credentials`,
|
|
123
|
+
{
|
|
124
|
+
metadata: mapMetadata(args, `${name}-backup-credentials`),
|
|
125
|
+
|
|
126
|
+
stringData: {
|
|
127
|
+
password: args.resticRepo.password,
|
|
128
|
+
"rclone.conf": args.resticRepo.rcloneConfig,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{ parent: this, ...opts },
|
|
132
|
+
)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const environment = output(args).apply(args => {
|
|
136
|
+
return {
|
|
137
|
+
alpine: {
|
|
138
|
+
packages: ["rclone"],
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
ubuntu: {
|
|
142
|
+
preInstallPackages: ["curl", "unzip"],
|
|
143
|
+
|
|
144
|
+
preInstallScripts: {
|
|
145
|
+
"rclone.sh": text`
|
|
146
|
+
#!/bin/sh
|
|
147
|
+
set -e
|
|
148
|
+
|
|
149
|
+
curl https://rclone.org/install.sh | bash
|
|
150
|
+
`,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
environment: {
|
|
155
|
+
RESTIC_REPOSITORY: `rclone:${args.resticRepo.remoteName}:${args.resticRepo.basePath}/${args.k8sCluster.info.name}/${name}`,
|
|
156
|
+
RESTIC_PASSWORD_FILE: "/credentials/password",
|
|
157
|
+
RESTIC_HOSTNAME: "default",
|
|
158
|
+
RCLONE_CONFIG: "/credentials/rclone.conf",
|
|
159
|
+
EXTRA_BACKUP_OPTIONS: args.backupOptions?.join(" "),
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
volumes: [this.credentials, ...(args.volume ? [args.volume] : [])],
|
|
163
|
+
|
|
164
|
+
volumeMounts: [
|
|
165
|
+
{
|
|
166
|
+
volume: this.credentials,
|
|
167
|
+
mountPath: "/credentials",
|
|
168
|
+
readOnly: true,
|
|
169
|
+
},
|
|
170
|
+
...(args.volume
|
|
171
|
+
? [
|
|
172
|
+
{
|
|
173
|
+
volume: args.volume,
|
|
174
|
+
mountPath: "/data",
|
|
175
|
+
subPath: args.subPath,
|
|
176
|
+
},
|
|
177
|
+
]
|
|
178
|
+
: []),
|
|
179
|
+
],
|
|
180
|
+
} satisfies ScriptEnvironment
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
this.scriptBundle = output(args).apply(args => {
|
|
184
|
+
return new ScriptBundle(
|
|
185
|
+
`${name}-backup-scripts`,
|
|
186
|
+
{
|
|
187
|
+
namespace: args.namespace,
|
|
188
|
+
distribution: args.distribution ?? "alpine",
|
|
189
|
+
|
|
190
|
+
environments: [
|
|
191
|
+
backupEnvironment,
|
|
192
|
+
environment,
|
|
193
|
+
...normalize(args.environment, args.environments),
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
{ parent: this, ...opts },
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
this.restoreJob = output(args).apply(args => {
|
|
201
|
+
return new Job(
|
|
202
|
+
`${name}-restore`,
|
|
203
|
+
{
|
|
204
|
+
namespace: args.namespace,
|
|
205
|
+
|
|
206
|
+
container: createScriptContainer({
|
|
207
|
+
...args.restoreContainer,
|
|
208
|
+
|
|
209
|
+
main: "restore.sh",
|
|
210
|
+
bundle: this.scriptBundle,
|
|
211
|
+
}),
|
|
212
|
+
},
|
|
213
|
+
{ parent: this, ...opts },
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
this.backupJob = output(args).apply(args => {
|
|
218
|
+
return new CronJob(
|
|
219
|
+
`${name}-backup`,
|
|
220
|
+
{
|
|
221
|
+
namespace: args.namespace,
|
|
222
|
+
|
|
223
|
+
container: createScriptContainer({
|
|
224
|
+
...args.backupContainer,
|
|
225
|
+
|
|
226
|
+
main: "backup.sh",
|
|
227
|
+
bundle: this.scriptBundle,
|
|
228
|
+
}),
|
|
229
|
+
|
|
230
|
+
schedule: args.schedule ?? "0 0 * * *",
|
|
231
|
+
concurrencyPolicy: "Forbid",
|
|
232
|
+
|
|
233
|
+
jobTemplate: {
|
|
234
|
+
spec: {
|
|
235
|
+
backoffLimit: 1,
|
|
236
|
+
template: {
|
|
237
|
+
spec: {
|
|
238
|
+
restartPolicy: "Never",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{ parent: this, ...opts },
|
|
245
|
+
)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
this.registerOutputs({
|
|
249
|
+
credentials: this.credentials,
|
|
250
|
+
scriptBundle: this.scriptBundle,
|
|
251
|
+
restoreJob: this.restoreJob,
|
|
252
|
+
backupJob: this.backupJob,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
handleTrigger(triggers: InstanceTriggerInvocation[]): InstanceTrigger | undefined {
|
|
257
|
+
const triggerName = `restic.backup-on-destroy.${this.name}`
|
|
258
|
+
const invokedTrigger = triggers.find(trigger => trigger.name === triggerName)
|
|
259
|
+
|
|
260
|
+
if (invokedTrigger) {
|
|
261
|
+
this.createBackupOnDestroyJob()
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
name: triggerName,
|
|
267
|
+
title: "Backup on Destroy",
|
|
268
|
+
description: `Backup the "${this.name}" before destroying.`,
|
|
269
|
+
spec: {
|
|
270
|
+
type: "before-destroy",
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private createBackupOnDestroyJob(): void {
|
|
276
|
+
new batch.v1.Job(
|
|
277
|
+
`${this.name}-backup-on-destroy`,
|
|
278
|
+
{
|
|
279
|
+
metadata: {
|
|
280
|
+
name: `${this.name}-backup-on-destroy`,
|
|
281
|
+
namespace: this.backupJob.cronJob.metadata.namespace,
|
|
282
|
+
},
|
|
283
|
+
spec: this.backupJob.cronJob.spec.jobTemplate.spec,
|
|
284
|
+
},
|
|
285
|
+
{ parent: this, ...this.opts },
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { restic } from "@highstate/library"
|
|
2
|
+
import { forUnit, getOrCreateSecret } from "@highstate/pulumi"
|
|
3
|
+
import { generatePassword } from "@highstate/common"
|
|
4
|
+
|
|
5
|
+
const { args, secrets, outputs } = forUnit(restic.repo)
|
|
6
|
+
|
|
7
|
+
const remoteName = secrets.rcloneConfig.apply(config => {
|
|
8
|
+
const remoteNames = Array.from(config.matchAll(/(?<=\[).+?(?=\])/g))
|
|
9
|
+
|
|
10
|
+
if (remoteNames.length === 0) {
|
|
11
|
+
throw new Error("No remotes found in rclone config")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (remoteNames.length > 1) {
|
|
15
|
+
throw new Error("Multiple remotes found in rclone config")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return remoteNames[0][0]
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const password = getOrCreateSecret(secrets, "password", generatePassword)
|
|
22
|
+
const basePath = args.basePath?.replace(/\/$/, "") ?? "backups"
|
|
23
|
+
|
|
24
|
+
export default outputs({
|
|
25
|
+
repo: {
|
|
26
|
+
password,
|
|
27
|
+
type: "rclone",
|
|
28
|
+
rcloneConfig: secrets.rcloneConfig,
|
|
29
|
+
remoteName,
|
|
30
|
+
remoteDomains: args.remoteDomains ?? [],
|
|
31
|
+
basePath,
|
|
32
|
+
},
|
|
33
|
+
$status: {
|
|
34
|
+
remoteName,
|
|
35
|
+
basePath,
|
|
36
|
+
},
|
|
37
|
+
})
|
package/src/scripts.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ScriptEnvironment } from "@highstate/k8s"
|
|
2
|
+
import { text } from "@highstate/contract"
|
|
3
|
+
|
|
4
|
+
export const backupEnvironment: ScriptEnvironment = {
|
|
5
|
+
alpine: {
|
|
6
|
+
packages: ["restic"],
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
ubuntu: {
|
|
10
|
+
packages: ["restic"],
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
scripts: {
|
|
14
|
+
"backup.sh": text`
|
|
15
|
+
#!/bin/sh
|
|
16
|
+
set -e
|
|
17
|
+
|
|
18
|
+
# Init the repo if it doesn't exist
|
|
19
|
+
echo "| Checking the repository"
|
|
20
|
+
if restic snapshots > /dev/null 2>&1; then
|
|
21
|
+
echo "| Repository is ready"
|
|
22
|
+
else
|
|
23
|
+
echo "| Initializing new repository"
|
|
24
|
+
restic init
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Execute lock script if it exists
|
|
28
|
+
if [ -f /scripts/lock.sh ]; then
|
|
29
|
+
/scripts/lock.sh || (echo "| error: lock script failed" && exit 1)
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Unlock the data source on exit
|
|
33
|
+
if [ -f /scripts/unlock.sh ]; then
|
|
34
|
+
trap "echo '/scripts/unlock.sh || (echo '| error: unlock script failed' && exit 1)" EXIT
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Perform online backup if the corresponding script exists
|
|
38
|
+
if [ -f /scripts/online-backup.sh ]; then
|
|
39
|
+
/scripts/online-backup.sh || (echo "| error: online backup script failed" && exit 1)
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Backup the volume
|
|
43
|
+
echo "| Backing up /data"
|
|
44
|
+
restic backup -H "$RESTIC_HOSTNAME" /data $EXTRA_BACKUP_OPTIONS
|
|
45
|
+
|
|
46
|
+
# Forget old snapshots
|
|
47
|
+
echo "| Forgetting old snapshots"
|
|
48
|
+
restic forget --host "$RESTIC_HOSTNAME" --keep-daily 7 --keep-weekly 4 --keep-monthly 6
|
|
49
|
+
echo "| Backup complete"
|
|
50
|
+
`,
|
|
51
|
+
|
|
52
|
+
"restore.sh": text`
|
|
53
|
+
#!/bin/sh
|
|
54
|
+
set -e
|
|
55
|
+
|
|
56
|
+
# Check if /data is empty
|
|
57
|
+
echo "| Checking if volume is empty"
|
|
58
|
+
if [ "$(find /data -type f -print -quit 2>/dev/null)" ]; then
|
|
59
|
+
echo "| Volume is not empty. Skipping restore."
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Check if at least one snapshot exists
|
|
64
|
+
echo "| Checking for snapshots"
|
|
65
|
+
if ! result=$(restic list snapshots); then
|
|
66
|
+
echo "| No snapshots found. Skipping restore."
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [ -z "$result" ]; then
|
|
71
|
+
echo "| No snapshots found. Skipping restore."
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Restore the volume
|
|
76
|
+
echo "| Restoring /data"
|
|
77
|
+
restic restore -H "$RESTIC_HOSTNAME" latest --target /
|
|
78
|
+
echo "| Volume restored."
|
|
79
|
+
|
|
80
|
+
# Post-restore script
|
|
81
|
+
if [ -f /scripts/post-restore.sh ]; then
|
|
82
|
+
/scripts/post-restore.sh || (echo "| error: post-restore script failed" && exit 1)
|
|
83
|
+
fi
|
|
84
|
+
`,
|
|
85
|
+
},
|
|
86
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { k8s, restic } from '@highstate/library';
|
|
2
|
-
import { core } from '@pulumi/kubernetes';
|
|
3
|
-
import { ScriptBundle, Job, CronJob, CommonArgs, ScriptEnvironment, WorkloadVolume, Container, ScriptDistribution } from '@highstate/k8s';
|
|
4
|
-
import { ComponentResource, Output, Input, InputArray, ComponentResourceOptions, InstanceTriggerInvocation, InstanceTrigger } from '@highstate/pulumi';
|
|
5
|
-
|
|
6
|
-
type BackupJobPairArgs = CommonArgs & {
|
|
7
|
-
/**
|
|
8
|
-
* The k8s cluster to calculate the repository path.
|
|
9
|
-
*/
|
|
10
|
-
k8sCluster: Input<k8s.Cluster>;
|
|
11
|
-
/**
|
|
12
|
-
* The repository to backup/restore to/from.
|
|
13
|
-
*/
|
|
14
|
-
resticRepo: Input<restic.Repo>;
|
|
15
|
-
/**
|
|
16
|
-
* The extra script environment to pass to the backup and restore scripts.
|
|
17
|
-
*/
|
|
18
|
-
environment?: Input<ScriptEnvironment>;
|
|
19
|
-
/**
|
|
20
|
-
* The extra script environments to pass to the backup and restore scripts.
|
|
21
|
-
*/
|
|
22
|
-
environments?: InputArray<ScriptEnvironment>;
|
|
23
|
-
/**
|
|
24
|
-
* The volume to backup.
|
|
25
|
-
*/
|
|
26
|
-
volume?: Input<WorkloadVolume>;
|
|
27
|
-
/**
|
|
28
|
-
* The sup path of the volume to restore/backup.
|
|
29
|
-
*/
|
|
30
|
-
subPath?: Input<string>;
|
|
31
|
-
/**
|
|
32
|
-
* The schedule for the backup job.
|
|
33
|
-
*
|
|
34
|
-
* By default, the backup job runs every day at midnight.
|
|
35
|
-
*/
|
|
36
|
-
schedule?: Input<string>;
|
|
37
|
-
/**
|
|
38
|
-
* The extra options to pass to the backup script.
|
|
39
|
-
*/
|
|
40
|
-
backupOptions?: InputArray<string>;
|
|
41
|
-
/**
|
|
42
|
-
* The extra options for the backup container.
|
|
43
|
-
*/
|
|
44
|
-
backupContainer?: Input<Container>;
|
|
45
|
-
/**
|
|
46
|
-
* The extra options for the restore container.
|
|
47
|
-
*/
|
|
48
|
-
restoreContainer?: Input<Container>;
|
|
49
|
-
/**
|
|
50
|
-
* The distribution to use for the scripts.
|
|
51
|
-
*
|
|
52
|
-
* By default, the distribution is `alpine`.
|
|
53
|
-
*
|
|
54
|
-
* You can also use `ubuntu` if you need to install packages that are not available (or working) in Alpine.
|
|
55
|
-
*/
|
|
56
|
-
distribution?: ScriptDistribution;
|
|
57
|
-
};
|
|
58
|
-
declare class BackupJobPair extends ComponentResource {
|
|
59
|
-
private readonly name;
|
|
60
|
-
private readonly opts?;
|
|
61
|
-
/**
|
|
62
|
-
* The credentials used to access the repository and encrypt the backups.
|
|
63
|
-
*/
|
|
64
|
-
readonly credentials: Output<core.v1.Secret>;
|
|
65
|
-
/**
|
|
66
|
-
* The script bundle used by the backup and restore jobs.
|
|
67
|
-
*/
|
|
68
|
-
readonly scriptBundle: Output<ScriptBundle>;
|
|
69
|
-
/**
|
|
70
|
-
* The job resource which restores the volume from the backup before creating an application.
|
|
71
|
-
*/
|
|
72
|
-
readonly restoreJob: Output<Job>;
|
|
73
|
-
/**
|
|
74
|
-
* The cron job resource which backups the volume regularly.
|
|
75
|
-
*/
|
|
76
|
-
readonly backupJob: Output<CronJob>;
|
|
77
|
-
constructor(name: string, args: BackupJobPairArgs, opts?: ComponentResourceOptions | undefined);
|
|
78
|
-
handleTrigger(triggers: InstanceTriggerInvocation[]): InstanceTrigger | undefined;
|
|
79
|
-
private createBackupOnDestroyJob;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export { BackupJobPair, type BackupJobPairArgs };
|