obsidian deployment

This commit is contained in:
rblundon committed 2026-10-06 13:49:13 -05:00
1 parent ffc8feddff
commit 8b81c090f6
9 files changed
+259

No files matched your search

@@ -0,0 +1,24 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: obsidian-vault
namespace: argocd
spec:
project: obsidian
source:
repoURL: https://gitea.mk-labs.cloud/rblundon/homelab.git
targetRevision: main
path: deploy
directory:
# Exclude the Application manifest itself (already in argocd)
exclude: "application.yaml"
destination:
server: https://kubernetes.default.svc
namespace: obsidian
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- ServerSideApply=true
- PruneLast=true
@@ -0,0 +1,94 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: obsidian-vault
labels:
app.kubernetes.io/name: obsidian-vault
annotations:
argocd.argoproj.io/sync-wave: "8"
spec:
replicas: 1 # exactly one sync "device" per vault copy
strategy:
type: Recreate # never two sync clients on the same folder
selector:
matchLabels:
app.kubernetes.io/name: obsidian-vault
template:
metadata:
labels:
app.kubernetes.io/name: obsidian-vault
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10000
runAsGroup: 10000
fsGroup: 10000
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containers:
- name: ob
image: ghcr.io/CHANGE_ME/obsidian-headless:0.0.13 # build from ./image
env:
- name: SYNC_MODE # mirror-remote = download only, revert local changes
value: mirror-remote
- name: DEBUG # "1" = idle instead of syncing, then kubectl exec in
value: "0"
- name: VAULT_PATH
value: /vault
- name: DEVICE_NAME # shows up in Obsidian's sync version history
value: k8s-obsidian
- name: EXCLUDED_FOLDERS # comma-separated folders that must never reach the cluster
value: ""
- name: HOME
value: /state
- name: XDG_CONFIG_HOME
value: /state/config
- name: VAULT_NAME
valueFrom:
secretKeyRef:
name: obsidian-vault
key: vault_name
- name: E2EE_PASSWORD
valueFrom:
secretKeyRef:
name: obsidian-vault
key: e2ee_password
optional: true
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
volumeMounts:
- name: vault
mountPath: /vault
- name: state
mountPath: /state
- name: auth
mountPath: /secrets
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: vault
persistentVolumeClaim:
claimName: obsidian-vault
- name: state
persistentVolumeClaim:
claimName: obsidian-sync-state
- name: auth
secret:
secretName: obsidian-sync
items:
- key: auth_token
path: auth_token
- name: tmp
emptyDir: {}
@@ -0,0 +1,9 @@
# No official image exists. Pin the npm version: `npm view obsidian-headless version`
FROM node:22-bookworm-slim
ARG OBSIDIAN_HEADLESS_VERSION=0.0.13
RUN npm install -g obsidian-headless@${OBSIDIAN_HEADLESS_VERSION} \
&& useradd -u 10000 -m -d /state ob
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 0755 /usr/local/bin/entrypoint.sh
USER 10000
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
@@ -0,0 +1,35 @@
#!/bin/sh
set -eu
: "${VAULT_PATH:=/vault}"
: "${SYNC_MODE:=mirror-remote}"
: "${DEVICE_NAME:=k8s-obsidian}"
: "${VAULT_NAME:?VAULT_NAME is required}"
CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/obsidian-headless"
mkdir -p "$CFG_DIR"
install -m 0600 /secrets/auth_token "$CFG_DIR/auth_token"
# Debug mode: token is in place, but don't sync. Exec in and run `ob ...` by hand.
if [ "${DEBUG:-0}" = "1" ]; then
echo "DEBUG=1: idling. kubectl exec -it deploy/obsidian-sync -- sh"
exec sleep infinity
fi
# Link the folder to the remote vault once. UNTESTED assumption: sync-status exits
# non-zero when the folder isn't configured yet; verify on your obsidian-headless version.
if ! ob sync-status --path "$VAULT_PATH" >/dev/null 2>&1; then
set -- --vault "$VAULT_NAME" --path "$VAULT_PATH" --device-name "$DEVICE_NAME"
if [ -n "${E2EE_PASSWORD:-}" ]; then
set -- "$@" --password "$E2EE_PASSWORD"
fi
ob sync-setup "$@"
fi
# --configs "" = don't sync .obsidian app/plugin settings from this device
ob sync-config --path "$VAULT_PATH" \
--mode "$SYNC_MODE" \
--conflict-strategy merge \
--configs "" \
--excluded-folders "${EXCLUDED_FOLDERS:-}"
exec ob sync --path "$VAULT_PATH" --continuous
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: obsidian
resources:
- namespace.yaml
- pvcs.yaml
- deployment.yaml
- networkpolicy.yaml
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Namespace
metadata:
name: obsidian
labels:
# The sync pod is fully compliant with the "restricted" Pod Security Standard.
pod-security.kubernetes.io/enforce: restricted
annotations:
argocd.argoproj.io/sync-wave: "-1"
@@ -0,0 +1,33 @@
# Requires a CNI that enforces NetworkPolicy. Nothing may call the pod; it may only
# resolve DNS and reach the Obsidian Sync servers over HTTPS/WebSocket.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: obsidian-vault
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: obsidian-vault
policyTypes: ["Ingress", "Egress"]
ingress: []
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 169.254.169.254/32
ports:
- { protocol: TCP, port: 443 }
@@ -0,0 +1,18 @@
# DO NOT COMMIT real values. Create out-of-band in namespace "obsidian":
#
# ob login # once, on a trusted machine
# kubectl create namespace obsidian # if Argo hasn't created it yet
# kubectl -n obsidian create secret generic obsidian-sync \
# --from-file=auth_token=<path to auth_token in your obsidian-headless config dir> \
# --from-literal=vault_name='<exact remote vault name>' \
# --from-literal=e2ee_password='<only if the vault uses E2EE>'
apiVersion: v1
kind: Secret
metadata:
name: obsidian-sync
namespace: obsidian
type: Opaque
stringData:
auth_token: "REPLACE"
vault_name: "REPLACE"
# e2ee_password: "REPLACE"
@@ -0,0 +1,29 @@
# Vault copy. One writer (the sync pod), so RWO is enough for now.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: obsidian-vault
annotations:
argocd.argoproj.io/sync-wave: "0"
argocd.argoproj.io/sync-options: Prune=false
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: px-fa-direct-access
resources:
requests:
storage: 10Gi # size to your vault + attachments, with headroom
---
# Client config + state.db, so restarts don't trigger a full re-index.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: obsidian-sync-state
annotations:
argocd.argoproj.io/sync-wave: "0"
argocd.argoproj.io/sync-options: Prune=false
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: px-fa-direct-access
resources:
requests:
storage: 1Gi