diff --git a/timemachine/Chart.yaml b/timemachine/Chart.yaml new file mode 100644 index 0000000..3dd0004 --- /dev/null +++ b/timemachine/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: timemachine +description: macOS Time Machine backup server (Samba) based on mbentley/timemachine +type: application +version: 0.1.0 +appVersion: "smb" diff --git a/timemachine/templates/_helpers.tpl b/timemachine/templates/_helpers.tpl new file mode 100644 index 0000000..f9d391e --- /dev/null +++ b/timemachine/templates/_helpers.tpl @@ -0,0 +1,50 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "timemachine.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "timemachine.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{- define "timemachine.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "timemachine.labels" -}} +helm.sh/chart: {{ include "timemachine.chart" . }} +{{ include "timemachine.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{- define "timemachine.selectorLabels" -}} +app.kubernetes.io/name: {{ include "timemachine.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Render extraShares as the comma-separated EXTRA_SHARES env value +(format expected by the image: "name1:/path1,name2:/path2"). +*/}} +{{- define "timemachine.extraShares" -}} +{{- $parts := list -}} +{{- range .Values.extraShares -}} +{{- $parts = append $parts (printf "%s:%s" .name .mountPath) -}} +{{- end -}} +{{- join "," $parts -}} +{{- end -}} diff --git a/timemachine/templates/deployment.yaml b/timemachine/templates/deployment.yaml new file mode 100644 index 0000000..77fc830 --- /dev/null +++ b/timemachine/templates/deployment.yaml @@ -0,0 +1,131 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "timemachine.fullname" . }} + labels: + {{- include "timemachine.labels" . | nindent 4 }} +spec: + # Always exactly one replica — Time Machine is single-writer. + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + {{- include "timemachine.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "timemachine.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.hostNetwork }} + hostNetwork: true + dnsPolicy: {{ .Values.dnsPolicy }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: 137 + name: netbios-ns + protocol: UDP + - containerPort: 138 + name: netbios-dgm + protocol: UDP + - containerPort: 139 + name: netbios-ssn + protocol: TCP + - containerPort: {{ .Values.timemachine.smbPort }} + name: smb + protocol: TCP + env: + - name: TM_USERNAME + value: {{ .Values.timemachine.username | quote }} + - name: TM_GROUPNAME + value: {{ .Values.timemachine.groupname | quote }} + - name: TM_UID + value: {{ .Values.timemachine.uid | quote }} + - name: TM_GID + value: {{ .Values.timemachine.gid | quote }} + - name: SHARE_NAME + value: {{ .Values.timemachine.shareName | quote }} + - name: WORKGROUP + value: {{ .Values.timemachine.workgroup | quote }} + - name: MIMIC_MODEL + value: {{ .Values.timemachine.mimicModel | quote }} + - name: VOLUME_SIZE_LIMIT + value: {{ .Values.timemachine.volumeSizeLimit | quote }} + - name: SMB_PORT + value: {{ .Values.timemachine.smbPort | quote }} + - name: SET_PERMISSIONS + value: {{ .Values.timemachine.setPermissions | quote }} + - name: HIDE_SHARES + value: {{ .Values.timemachine.hideShares | quote }} + - name: SMB_INHERIT_PERMISSIONS + value: {{ .Values.timemachine.smbInheritPermissions | quote }} + - name: SMB_NFS_ACES + value: {{ .Values.timemachine.smbNfsAces | quote }} + - name: SMB_METADATA + value: {{ .Values.timemachine.smbMetadata | quote }} + - name: SMB_VFS_OBJECTS + value: {{ .Values.timemachine.smbVfsObjects | quote }} + - name: DEBUG_LEVEL + value: {{ .Values.timemachine.debugLevel | quote }} + - name: ADVERTISED_HOSTNAME + value: {{ .Values.timemachine.advertisedHostname | quote }} + - name: CUSTOM_SMB_CONF + value: {{ .Values.timemachine.customSmbConf | quote }} + - name: CUSTOM_USER + value: {{ .Values.timemachine.customUser | quote }} + - name: EXTERNAL_CONF + value: {{ .Values.timemachine.externalConf | quote }} + {{- if .Values.extraShares }} + - name: EXTRA_SHARES + value: {{ include "timemachine.extraShares" . | quote }} + {{- end }} + - name: PASSWORD + valueFrom: + secretKeyRef: + name: {{ required "existingSecret is required (must contain the PASSWORD key)" .Values.existingSecret }} + key: {{ .Values.passwordKey }} + volumeMounts: + - name: tm + mountPath: /opt/timemachine + - name: run-samba + mountPath: /run/samba + {{- range .Values.extraShares }} + - name: extra-{{ .name }} + mountPath: {{ .mountPath }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + - name: tm + {{- if eq .Values.persistence.type "hostPath" }} + hostPath: + path: {{ .Values.persistence.hostPath }} + type: {{ .Values.persistence.hostPathType }} + {{- else }} + persistentVolumeClaim: + claimName: {{ include "timemachine.fullname" . }}-data + {{- end }} + - name: run-samba + emptyDir: {} + {{- range .Values.extraShares }} + - name: extra-{{ .name }} + hostPath: + path: {{ .hostPath }} + type: {{ .hostPathType | default "DirectoryOrCreate" }} + {{- end }} diff --git a/timemachine/templates/pvc.yaml b/timemachine/templates/pvc.yaml new file mode 100644 index 0000000..217d81d --- /dev/null +++ b/timemachine/templates/pvc.yaml @@ -0,0 +1,17 @@ +{{- if eq .Values.persistence.type "pvc" }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "timemachine.fullname" . }}-data + labels: + {{- include "timemachine.labels" . | nindent 4 }} +spec: + accessModes: + {{- toYaml .Values.persistence.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .Values.persistence.size }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} +{{- end }} diff --git a/timemachine/templates/service.yaml b/timemachine/templates/service.yaml new file mode 100644 index 0000000..e87bc44 --- /dev/null +++ b/timemachine/templates/service.yaml @@ -0,0 +1,29 @@ +{{- if .Values.service.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "timemachine.fullname" . }} + labels: + {{- include "timemachine.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "timemachine.selectorLabels" . | nindent 4 }} + ports: + - name: smb + port: {{ .Values.service.smbPort }} + targetPort: smb + protocol: TCP + - name: netbios-ssn + port: 139 + targetPort: netbios-ssn + protocol: TCP + - name: netbios-ns + port: 137 + targetPort: netbios-ns + protocol: UDP + - name: netbios-dgm + port: 138 + targetPort: netbios-dgm + protocol: UDP +{{- end }} diff --git a/timemachine/values.yaml b/timemachine/values.yaml new file mode 100644 index 0000000..a7813c1 --- /dev/null +++ b/timemachine/values.yaml @@ -0,0 +1,78 @@ +# Default values for timemachine. +# Wraps mbentley/docker-timemachine (https://github.com/mbentley/docker-timemachine). + +image: + repository: mbentley/timemachine + tag: smb + pullPolicy: IfNotPresent + +# Time Machine / Samba runtime configuration. These map directly to env vars +# documented in mbentley/docker-timemachine (smb variant). +timemachine: + username: timemachine + groupname: timemachine + uid: 1000 + gid: 1000 + shareName: TimeMachine + workgroup: WORKGROUP + mimicModel: TimeCapsule8,119 + # Per-user max backup size in MiB (0 = unlimited). + volumeSizeLimit: "0" + smbPort: 445 + setPermissions: "false" + hideShares: "no" + smbInheritPermissions: "no" + smbNfsAces: "no" + smbMetadata: "stream" + smbVfsObjects: "fruit streams_xattr" + debugLevel: "1" + advertisedHostname: "" + customSmbConf: "false" + customUser: "false" + externalConf: "" + +# Existing Secret with key PASSWORD. Required. +existingSecret: "" +passwordKey: PASSWORD + +# Backup storage. Default is a hostPath on the target node so the backups +# land on a mapped host drive/folder. +persistence: + type: hostPath # hostPath | pvc + hostPath: /storage/timemachine + hostPathType: DirectoryOrCreate + # Used only when type=pvc + size: 500Gi + storageClass: "" + accessModes: + - ReadWriteOnce + +# Additional non-Time-Machine SMB shares exposed by the same daemon. +# Each entry mounts hostPath at mountPath inside the container and is +# advertised as an SMB share via the EXTRA_SHARES env var. +extraShares: [] + # - name: backup + # hostPath: /storage/backups + # mountPath: /shares/backup + # hostPathType: DirectoryOrCreate + +# hostNetwork is required for macOS Bonjour auto-discovery to reach the pod. +hostNetwork: true +dnsPolicy: ClusterFirstWithHostNet + +service: + enabled: true + type: ClusterIP + smbPort: 445 + +resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 1 + memory: 512Mi + +nodeSelector: {} +tolerations: [] +affinity: {}