🌳
pt0/deployF/servicesF/vwRestoreAI.mts
12import type { V1Volume, V1VolumeMount } from '@kubernetes/client-node'
14const scaleDeploy = async ({cluster_name, name, replicas}: {cluster_name: string, name: string, replicas: number}) => {
15 await eptKubeCli([cluster_name, 'scale', 'deployment', name, `--replicas=${replicas}`])
18const waitForNoPods = async ({cluster_name, name}: {cluster_name: string, name: string}) => {
19 await pollUntil({
20 predicate: async () => (await getAppPods({cluster_name, name})).length > 0,
21 timeoutLabel: `vaultwarden ${name} scale-down`,
22 timeoutMs: 300_000,
23 isQuiet: true,
24 })
27// runs a one-off Job to completion: delete any stale prior (specs immutable), apply, stream logs,
28// fail on backoff, then clean up. returns when the job succeeds.
29const runRestoreJob = async ({job, jobName, cluster_name}: {job: KubeResource, jobName: string, cluster_name: string}) => {
30 await resourcesActionLite({resources: [job], action: 'delete', cluster_name, isQuiet: true})
31 await resourcesActionLite({resources: [job], action: 'apply', cluster_name, isQuiet: true})
32 const podName = (await getAppPods({cluster_name, name: jobName}))[0]?.metadata?.name
33 assertDefined(podName)
34 const failed = await awaitJobPod({job, podName, containerName: jobName, cluster_name})
35 if (failed) throw new Error(`${jobName} failed: ${failed.failReason}`)
36 await resourcesActionLite({resources: [job], action: 'delete', cluster_name, isQuiet: true})
39// restores vaultwarden /data from haS3: db.sqlite3 via litestream, then attachments/sends/rsa_key
40// via rclone. scales the deployment to 0 for exclusive PVC access, runs both restore Jobs, scales up.
41export const vwRestoreAction = ({name}: {name: string}) => {
42 const vwrestore = async () => {
43 const {cluster_name} = getReqKlusterCtx()
44 const {configVolName, configMount} = vwLitestreamResources({name, cluster_name})
45 const secName = vwLitestreamSecName(name)
46 const pvcVolume: V1Volume = {name, persistentVolumeClaim: {claimName: name}}
47 const dataMount: V1VolumeMount = {name, mountPath: '/data'}
48 const awsCredsH = {
49 AWS_ACCESS_KEY_ID: {secretKeyRef: {name: secName, key: 'AWS_ACCESS_KEY_ID'}},
50 AWS_SECRET_ACCESS_KEY: {secretKeyRef: {name: secName, key: 'AWS_SECRET_ACCESS_KEY'}},
51 }
52 const gitSha = String(Date.now())
54 await scaleDeploy({cluster_name, name, replicas: 0})
55 await waitForNoPods({cluster_name, name})
57 const dbJobName = `${name}-litestream-restore`
58 const dbJob = await resourceForJobType({
59 jobType: {kind: 'Job', backoffLimit: 0, ttlSecondsAfterFinished: 60},
60 name: dbJobName, cluster_name, image: litestreamImg, git_sha: gitSha,
61 volumes: [pvcVolume, {name: configVolName, configMap: {name: configVolName}}],
62 volumeMounts: [dataMount, configMount],
63 envLocal: awsCredsH,
64 taskCmd: `rm -f ${vwDbPath}* && litestream restore -config ${vwLitestreamConfigFile} ${vwDbPath}`,
65 })
66 await runRestoreJob({job: dbJob, jobName: dbJobName, cluster_name})
68 const filesJobName = `${name}-rclone-restore`
69 const filesJob = await resourceForJobType({
70 jobType: {kind: 'Job', backoffLimit: 0, ttlSecondsAfterFinished: 60},
71 name: filesJobName, cluster_name, image: rcloneImg, git_sha: gitSha,
72 volumes: [pvcVolume],
73 volumeMounts: [dataMount],
74 envLocal: vwRcloneEnvH({cluster_name}),
76 })
77 await runRestoreJob({job: filesJob, jobName: filesJobName, cluster_name})
79 await scaleDeploy({cluster_name, name, replicas: 1})
80 console.log(`vaultwarden ${name} restored from haS3 on ${cluster_name} (db + attachments/sends/rsa_key)`)
81 }
82 vwrestore.cliDescript = `restore ${name} from haS3 (litestream db + rclone files): scale->0, restore, scale->1`
83 return vwrestore