this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add atproto PDS with daily S3 backups

Personal PDS on sans-self.org with path-based Traefik routing,
cert-manager TLS, and daily CronJob backing up SQLite databases
and actor repos to Hetzner Object Storage.

+271 -2
+1 -1
.gitattributes
··· 2 2 # See: https://github.com/AGWA/git-crypt 3 3 4 4 # Encrypt environment variables and tokens 5 - envs/TF_VAR_hcloud_token filter=git-crypt diff=git-crypt 5 + envs/** filter=git-crypt diff=git-crypt 6 6 7 7 # Encrypt SSH keys 8 8 keypair/** filter=git-crypt diff=git-crypt
+11
dns.tf
··· 27 27 { value = local.cluster_ip }, 28 28 ] 29 29 } 30 + 31 + # atproto handle verification — populate value after first account creation 32 + resource "hcloud_zone_rrset" "atproto_txt" { 33 + zone = hcloud_zone.sans_self.id 34 + name = "_atproto" 35 + type = "TXT" 36 + ttl = 300 37 + records = [ 38 + { value = "did=did:plc:PLACEHOLDER" }, 39 + ] 40 + }
+2 -1
k8s/kustomization.yaml
··· 1 1 apiVersion: kustomize.config.k8s.io/v1beta1 2 2 kind: Kustomization 3 3 4 - resources: [] 4 + resources: 5 + - pds
k8s/pds/admin-password.secret

This is a binary file and will not be displayed.

+74
k8s/pds/backup-cronjob.yaml
··· 1 + apiVersion: batch/v1 2 + kind: CronJob 3 + metadata: 4 + name: pds-backup 5 + namespace: pds 6 + spec: 7 + schedule: "0 2 * * *" 8 + concurrencyPolicy: Forbid 9 + successfulJobsHistoryLimit: 3 10 + failedJobsHistoryLimit: 3 11 + jobTemplate: 12 + spec: 13 + template: 14 + spec: 15 + restartPolicy: OnFailure 16 + containers: 17 + - name: backup 18 + image: rclone/rclone:latest 19 + command: 20 + - sh 21 + - -ec 22 + - | 23 + apk add --no-cache sqlite > /dev/null 24 + 25 + S3_OPTS="--s3-provider Other --s3-access-key-id ${S3_ACCESS_KEY} --s3-secret-access-key ${S3_SECRET_KEY} --s3-endpoint nbg1.your-objectstorage.com --s3-region nbg1 --s3-no-check-bucket --s3-acl private" 26 + TIMESTAMP=$(date +%Y%m%d-%H%M%S) 27 + 28 + # Safe SQLite backup for each database 29 + for db in /pds/*.sqlite; do 30 + name=$(basename "$db" .sqlite) 31 + sqlite3 "$db" ".backup /tmp/${name}-${TIMESTAMP}.sqlite" 32 + rclone copyto "/tmp/${name}-${TIMESTAMP}.sqlite" \ 33 + ":s3:sans-self-net/pds/db/${name}-${TIMESTAMP}.sqlite" \ 34 + ${S3_OPTS} 35 + echo "backed up: ${name}-${TIMESTAMP}.sqlite" 36 + done 37 + 38 + # Sync actor repos and blobs — only uploads new/changed files 39 + rclone sync /pds/actors \ 40 + :s3:sans-self-net/pds/actors \ 41 + ${S3_OPTS} 42 + 43 + echo "backup complete: ${TIMESTAMP}" 44 + env: 45 + - name: S3_ACCESS_KEY 46 + valueFrom: 47 + secretKeyRef: 48 + name: pds-s3-credentials 49 + key: access-key 50 + - name: S3_SECRET_KEY 51 + valueFrom: 52 + secretKeyRef: 53 + name: pds-s3-credentials 54 + key: secret-key 55 + volumeMounts: 56 + - name: pds-data 57 + mountPath: /pds 58 + readOnly: true 59 + - name: tmp 60 + mountPath: /tmp 61 + resources: 62 + requests: 63 + cpu: 50m 64 + memory: 128Mi 65 + limits: 66 + cpu: 250m 67 + memory: 512Mi 68 + volumes: 69 + - name: pds-data 70 + persistentVolumeClaim: 71 + claimName: pds-data 72 + - name: tmp 73 + emptyDir: 74 + sizeLimit: 1Gi
+27
k8s/pds/cert.yaml
··· 1 + apiVersion: cert-manager.io/v1 2 + kind: ClusterIssuer 3 + metadata: 4 + name: letsencrypt-prod 5 + spec: 6 + acme: 7 + server: https://acme-v02.api.letsencrypt.org/directory 8 + email: noivm@proton.me 9 + privateKeySecretRef: 10 + name: letsencrypt-prod 11 + solvers: 12 + - http01: 13 + ingress: 14 + ingressClassName: traefik 15 + --- 16 + apiVersion: cert-manager.io/v1 17 + kind: Certificate 18 + metadata: 19 + name: sans-self-org 20 + namespace: pds 21 + spec: 22 + secretName: sans-self-org-tls 23 + issuerRef: 24 + name: letsencrypt-prod 25 + kind: ClusterIssuer 26 + dnsNames: 27 + - sans-self.org
+13
k8s/pds/configmap.yaml
··· 1 + apiVersion: v1 2 + kind: ConfigMap 3 + metadata: 4 + name: pds-config 5 + namespace: pds 6 + data: 7 + PDS_HOSTNAME: sans-self.org 8 + PDS_PORT: "3000" 9 + PDS_DATA_DIRECTORY: /pds 10 + PDS_BLOBSTORE_DISK_LOCATION: /pds/blocks 11 + PDS_EMAIL_FROM_ADDRESS: noreply@sans-self.org 12 + PDS_INVITE_REQUIRED: "true" 13 + PDS_SERVICE_HANDLE_DOMAINS: .sans-self.org
+54
k8s/pds/deployment.yaml
··· 1 + apiVersion: apps/v1 2 + kind: Deployment 3 + metadata: 4 + name: pds 5 + namespace: pds 6 + spec: 7 + replicas: 1 8 + strategy: 9 + type: Recreate 10 + selector: 11 + matchLabels: 12 + app: pds 13 + template: 14 + metadata: 15 + labels: 16 + app: pds 17 + spec: 18 + securityContext: 19 + fsGroup: 1000 20 + runAsUser: 1000 21 + runAsGroup: 1000 22 + initContainers: 23 + - name: fix-permissions 24 + image: busybox:1.37 25 + command: ["sh", "-c", "chown -R 1000:1000 /pds"] 26 + volumeMounts: 27 + - name: data 28 + mountPath: /pds 29 + securityContext: 30 + runAsUser: 0 31 + containers: 32 + - name: pds 33 + image: ghcr.io/bluesky-social/pds:0.4 34 + ports: 35 + - containerPort: 3000 36 + envFrom: 37 + - configMapRef: 38 + name: pds-config 39 + - secretRef: 40 + name: pds-secrets 41 + volumeMounts: 42 + - name: data 43 + mountPath: /pds 44 + resources: 45 + requests: 46 + cpu: 100m 47 + memory: 256Mi 48 + limits: 49 + cpu: 500m 50 + memory: 1Gi 51 + volumes: 52 + - name: data 53 + persistentVolumeClaim: 54 + claimName: pds-data
+31
k8s/pds/ingress.yaml
··· 1 + apiVersion: networking.k8s.io/v1 2 + kind: Ingress 3 + metadata: 4 + name: pds 5 + namespace: pds 6 + annotations: 7 + cert-manager.io/cluster-issuer: letsencrypt-prod 8 + spec: 9 + ingressClassName: traefik 10 + tls: 11 + - hosts: 12 + - sans-self.org 13 + secretName: sans-self-org-tls 14 + rules: 15 + - host: sans-self.org 16 + http: 17 + paths: 18 + - path: /xrpc 19 + pathType: Prefix 20 + backend: 21 + service: 22 + name: pds 23 + port: 24 + number: 3000 25 + - path: /.well-known/atproto-did 26 + pathType: Exact 27 + backend: 28 + service: 29 + name: pds 30 + port: 31 + number: 3000
k8s/pds/jwt.secret

This is a binary file and will not be displayed.

+31
k8s/pds/kustomization.yaml
··· 1 + apiVersion: kustomize.config.k8s.io/v1beta1 2 + kind: Kustomization 3 + 4 + resources: 5 + - namespace.yaml 6 + - configmap.yaml 7 + - pvc.yaml 8 + - deployment.yaml 9 + - service.yaml 10 + - ingress.yaml 11 + - cert.yaml 12 + - backup-cronjob.yaml 13 + 14 + generatorOptions: 15 + disableNameSuffixHash: true 16 + 17 + secretGenerator: 18 + - name: pds-secrets 19 + namespace: pds 20 + type: Opaque 21 + files: 22 + - PDS_JWT_SECRET=jwt.secret 23 + - PDS_ADMIN_PASSWORD=admin-password.secret 24 + - PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=plc-rotation-key.secret 25 + - PDS_EMAIL_SMTP_URL=smtp-url.secret 26 + - name: pds-s3-credentials 27 + namespace: pds 28 + type: Opaque 29 + files: 30 + - access-key=s3-access-key.secret 31 + - secret-key=s3-secret-key.secret
+4
k8s/pds/namespace.yaml
··· 1 + apiVersion: v1 2 + kind: Namespace 3 + metadata: 4 + name: pds
k8s/pds/plc-rotation-key.secret

This is a binary file and will not be displayed.

+12
k8s/pds/pvc.yaml
··· 1 + apiVersion: v1 2 + kind: PersistentVolumeClaim 3 + metadata: 4 + name: pds-data 5 + namespace: pds 6 + spec: 7 + accessModes: 8 + - ReadWriteOnce 9 + storageClassName: hcloud-volumes 10 + resources: 11 + requests: 12 + storage: 20Gi
k8s/pds/s3-access-key.secret

This is a binary file and will not be displayed.

k8s/pds/s3-secret-key.secret

This is a binary file and will not be displayed.

+11
k8s/pds/service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + name: pds 5 + namespace: pds 6 + spec: 7 + selector: 8 + app: pds 9 + ports: 10 + - port: 3000 11 + targetPort: 3000
k8s/pds/smtp-url.secret

This is a binary file and will not be displayed.