1import { envHToA } from '../k8sF/envHToAF.mts'2import { deployTmpl } from '../k8sF/genericDeploymentF.mts'3import { pvcVolumeMounts } from '../k8sF/pvcVolumeMountsF.mts'4import * as _ from 'lodash-es'5import type { V1Volume, V1Container } from '@kubernetes/client-node'6import { getKlusterCtx } from '../k8sF/ctxF/klusterCtxF.mts'7import { getAction } from '../ctxF/actionCtxF.mts'8import { secretFileTemplate } from '../k8sF/k8sSecF.mts'9import { kubeSvcTmpl } from '../k8sF/kubeSvcTmplF.mts'10import { genericIngressTmpl } from '../k8sF/generic1IngressTmplF.mts'11import { betLog } from '../../sharedF/betterConsLogF.mts'12import { getPlainNoMappedSec } from '../../serverF/secretsF/getPlainSecAI.mts'13import { kubeActionPvc } from '../k8sF/pvcF/kubeActionPvcF.mts'14import { resourcesAction } from '../k8sF/resourcesActionF.mts'15import { genPlainSecretIfMissing } from '../secretF/genSecretAI.mts'16import { tsSec } from '../../serverF/secretsF/tsSecF.mts'17import { litestreamImg, vwLitestreamResources, vwDbPath, vwLitestreamConfigFile, vwLitestreamEnvFrom } from './vwLitestreamAI.mts'18import { rcloneImg, vwRcloneEnvH, vwRcloneCopyUpCmd } from './vwRcloneAI.mts'20export const k8sVaultwarden = async ({hostname, name='bitwarden', withLitestream=false}: {21 hostname: string, name?: string, withLitestream?: boolean,22}) => {23 const {cluster_name} = getKlusterCtx()25 const portNo = 8027 const {volumes: pvcVolumes, volumeMounts: pvcMounts} = pvcVolumeMounts({name, mountPath: '/data'})29 await genPlainSecretIfMissing({secretName, autoYes: true})31 const volumes: V1Volume[] = [...pvcVolumes]32 const containers: V1Container[] = [{33 name, image: 'vaultwarden/server:1.37.1', // 26080234 volumeMounts: pvcMounts,35 env: [36 {37 name: 'ADMIN_TOKEN',38 valueFrom: {39 secretKeyRef: {40 name, key: secretName41 }42 }43 },45 SIGNUPS_ALLOWED: 'false', // can be overriden in /admin ?46 }),47 ] 48 }]50 const annotations: Record<string, string> = {}51 // configmap+secret always created so vwrestore works on any cluster (standby/restore targets52 // don't run the sidecar but still need the litestream config + haS3 creds to restore)53 const {configMap, secret, configVolName, configMount, configHash} = vwLitestreamResources({name, cluster_name})54 if (withLitestream) {55 volumes.push({name: configVolName, configMap: {name: configMap.metadata.name}})56 containers.push({57 name: 'litestream', image: litestreamImg,58 args: ['replicate', '-config', vwLitestreamConfigFile],59 volumeMounts: [...pvcMounts, configMount],60 envFrom: vwLitestreamEnvFrom(name),61 })62 // rclone sidecar: additive copy of attachments/sends/rsa_key -> haS3 (litestream covers db only)63 containers.push({64 name: 'rclone', image: rcloneImg,65 command: ['/bin/sh', '-c'], args: [vwRcloneCopyUpCmd()],66 volumeMounts: pvcMounts,67 env: envHToA(vwRcloneEnvH({cluster_name})),68 })69 annotations['litestream-config-hash'] = configHash70 }72 const resources = _.compact([73 secretFileTemplate({name: secretName, kubeName: name}),74 deployTmpl({75 volumes, name,76 strategy: {type: 'Recreate'},77 containers,78 annotations: _.isEmpty(annotations) ? undefined : annotations,79 }),80 kubeSvcTmpl({name, portNo}),81 genericIngressTmpl({name, hostname, portNo}),82 configMap,83 secret,84 ])86 await Promise.all([87 resourcesAction({resources, action, cluster_name}),88 kubeActionPvc({action, cluster_name, name, sizeGb: 10})89 ])91 if (_.includes(['info', 'apply'], action)) {92 const manageUrl = `${hostname}/admin`93 betLog({manageUrl, adminDashPass: getPlainNoMappedSec(secretName), dbPath: vwDbPath, withLitestream})94 }95}