feat: add timemachine Helm chart with deployment, service, and persistence configurations

This commit is contained in:
sha
2026-04-26 17:11:54 +03:00
parent e1fef2b8ad
commit b72a820d7a
6 changed files with 311 additions and 0 deletions
+6
View File
@@ -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"
+50
View File
@@ -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 -}}
+131
View File
@@ -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 }}
+17
View File
@@ -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 }}
+29
View File
@@ -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 }}
+78
View File
@@ -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: {}