Merge 'feat/notesnook-k8s': Notesnook on k3s + sealed secrets

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alex
2026-07-16 10:13:37 -07:00
38 changed files with 2599 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# cluster-secrets
SealedSecrets for credentials belonging to components **not** deployed from this
repo's ArgoCD apps (cert-manager, argocd itself, and solitaire — whose manifests
live in the Ferrous-Solitaire repo). They are committed here **encrypted**, purely so
the cluster's secrets are reproducible after a rebuild.
No ArgoCD Application watches this directory; these are applied manually:
kubectl apply -f apps/cluster-secrets/
## Disaster recovery
These are useless without the sealed-secrets controller's private key. Restore it
FIRST, or a fresh controller will generate a new key and none of these can be decrypted:
gpg -d /mnt/bulk/backups/sealed-secrets/master-key-YYYYMMDD.gpg | kubectl apply -f -
kubectl -n kube-system rollout restart deploy/sealed-secrets-controller
kubectl apply -f apps/cluster-secrets/
Passphrase for that file lives in the password manager. It is not recorded anywhere else.
## Contents
| File | Secret | Notes |
|---|---|---|
| cloudflare-api-token-sealedsecret.yaml | cert-manager/cloudflare-api-token | DNS-01 ACME; TLS renewal depends on it |
| argocd-gitea-repo-sealedsecret.yaml | argocd/gitea-rusty-solitare | repo creds; needs label argocd.argoproj.io/secret-type=repository |
| solitaire-gitea-registry-sealedsecret.yaml | solitaire/gitea-registry | image pull secret (dockerconfigjson) |
| solitaire-matomo-sealedsecret.yaml | solitaire/matomo-secret | |
| solitaire-secrets-sealedsecret.yaml | solitaire/solitaire-secrets | |
@@ -0,0 +1,21 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: gitea-rusty-solitare
namespace: argocd
spec:
encryptedData:
password: AgBhlHGmiLheVHJuHdg7nLAeriCka35zqVhi5VeugM+BT3JuMiFnZs7riC1U6BbCRwkVFZGA1ecegelKowtkwvohlegGWLIx0GJIfLKA2uuRqD9JdPc7x9x9+hPu8OqE9/vZrV4ojQ0BtD1C/XQDxHmVGn0p6doxIwoiQJMPNYceuvWiVEqMky93mX//8/DbgT/Wi8qK74OhI32oKgPwh9+AbF+kB3N+gnZHk68eETvrmQVMMHGy6JcjbknTdIThU5DI9OgUxhuPZO9kylD9A5M2cSdFVvuWb3Zp86aMO/fnLJxgPSnpqXYDJp4E1dxIpu5neq1OwVyNQwrbUMQKvjsIb9cOzhAojxoyruzzOfadYVafGUtiIdNQU4U1IQJPlSRZyFAYmOAiN+dF9ZokNtSdPEYJZijPovw06nXo+xvuO8JoK5lh4l6XbvN3ilRIdaBeJGGsjJrH0kf3C5vqD0fkBtopymZuhj74joo7MaDAXMlWaQqN/ER28vAfVcxz8mZ/673Td6a8AM4WLbL30IrJJOWrgBvrqbr0peXfXkDVgCXNDaG/PlwirhOzpc2E6kqppAZrqLvL2Seuo57pcnc79mUU79saewNeiHnv/t8lZhfXJ2Ya5UDqu3lha4mhckYb8OOSwk+GpJ8CUZqOJ1j4oruQaUtFb0y1l0lesLQL6CE38t2kyidMhSu05cg2NUqpZGH8xRLCClYjt0nHpfM3hZqp9Ka8coCQ9lVTHf0Kh88DkB8ZlXep
type: AgAPt1KdfoqkWYyAcGC7NmTow6+Emr3Uvav1vmYyGUQpiFpFTcmnpMwQBnrZ8hTLBvXNVgQvhgkl9iGHUr3CESd/nFkd68hDg5PN3mAL3fBn8Ng0e3ctD1zWYQSd2v1MqhIARRM3vOutAx2n2lpSJkEhwHGIadG+fdW1u/apqMuiQIAU9V15/rilY/riEJdZOZm+cRUWcdSHsR0lbkQ3wQWVoH9GT8yl+IUAhDjoZad+DuLCjvlC8oOWXtToGuKkcFgVVHow+3aXY49oXm8mBqdj2y+/dcBPhuw3RTKlVNYn4OR7AqOtmWvh88gghKXDSoS12QADyggNKxaLBVc5HA0CMe5hfJDPf7gIZpx32HBOxPtwLQkycfw9QFiZgkz61wu8htNO09kpZvq2TmuKKbae1vfat8iFmAljIH4xBlMFGRPvjZLm3psOgFZo+XYUZogYqvWlnmA4/H8Pj3fxRmjZohcwbR333jzBck9M74/d19Qv7Oyini+BTmk9iZwU2D9jZL82AjFc9fOgvcU57aMZ1UdOLLxLQwAUCd41flv0vR+7s30gyh0wKvc/ShSrGJUbgegS6JqoncuRu6thOjUuP6xeUCcWgM9xkg2L7N6Pzo/jwVPNUcxFyZ9ZLZXVtJEQubAXDze7Soui/VJAT1CEtH+9eLceRLk3xoIPnrWBK6kpxs5MrltvS8W/+9UCuc3GTAY=
url: AgCCMYfzUfvm3BlVnaCUu0FKglk77trG4x2EZqK4+PsYmPtB4czZmerMaqFk/RXGWR/pYa/EZL/CfJRdZE+qA3fJHjKWSKpBV5mfb2s8GaEHQl6kBUgIaDiCj7MCIu25b00zHe5agcZBUydD3T+V7ABtMhZTkQvUA+4OZjzQhdas0n6F/hDWBlhWQAzEY2Hj4KRY9r7thyuWYK2m3a4dCXepry2WAGG/sYn4qtD487NOQCNJ9qedg1eI0o0zbL45ErrLLJ3kIhC0upyEMekF85fVgUVu9uTRGvRzdibk55VgqrJPEZwxn3DHvqrJp2e2NYnY6Vo2dpa1avCwdORxA/pK5m4NjetTlD78N2+rJujuAODdglQQXEbe5Kscp0CChI5+/cVPD/gFsnt6YHiEohkMHYT7w+Aa+NWpikcL+QH7pQfKoW+LQKTM89eeVhcgDHsYavqk2oIL/X8+j2I3nFGJPiqkrAgbV95u7SO0tTycfq9m8+hs2yUodxAd2wUxOKk516COTYQ6WzpPXKTCje92m/XqtshoqF4PfH1Fxdf3bE6DS+X8XFOL3z+nR/8neaPbX0VC4kTDks3I2slc+uGRr7YZM+Wv9shsQzJXVgbbJYZgXJS0bwuvUhnc+BMUixPKMx772TjYDvXzsi/xubYyAsIT6KOyzJ+AyT+M7f+754vdrM6uinNnLpSbYN6J1MJ4WNhWluJidpYuBwhFCYg5V/71T6P62lgepaqPkwwHggwHwfVkRQfc7KqWimcfbS4HSFQ=
username: AgAOrTTBYkYzZqSHaj03zUnpkSB8daMP0CoGGfw9dWwDK/BNgRzEdkTEGpCKwtJN5ojKTjZTm9q014clw8gXTz2sX5k/NFFF3L9UxeRksrixJUtzLe9deOah+XvurxalhDXHClBIWCipqUGmqdShPOYY8vLHVuXzmVPstLiW58eUmnG/Wafo95weUpFr078rZUKu3+f7DkFP9j+3HqJAsOZsEOjhfCtvmhaOjwHVBldXBR964R1H52w/Jbr3WILNEKSso9/fZD279cvbdBESd7NPuj6UM60cZE8NB5rV6cOkHT9yncFBRq2co13lCo34Xl/4fpqRfF1Dq41DiO8Cm1n5i7EJeY5XyfmKl+Eq6FEGuDxQBVdxFtQfSERICk4g+GJkzJdDhaESzTMeANqLM5alTnIrn/MX+dhCnmD3N6VTYwN8Xiso4CTRPUEwwQqf5CY+prgWQLTUoqomfS4m81C9JCDqKKwKj0mc9df+rGzfIQXNR8W0ETcgitr/9TxTtGMVcU8wanNgNQbV8vu+AKmJZPgXCs7ghqLarzXuTPPPrF/12gtvG77+Gpa/Qh3RsUq3v35fbZZHyy2Evg4D18Rt7nUmvf3ao5QVgG/vI60WjM2TNmNtBmCjrbzGGntZuL3om4LZAV9EIhDDWL6ISdaUtgubrRpGSW7lNgfemi8NIkTq5hd/o/vK8j3ozOsLacvsRaMDnrK3LP8=
template:
metadata:
annotations:
sealedsecrets.bitnami.com/managed: "true"
labels:
argocd.argoproj.io/secret-type: repository
name: gitea-rusty-solitare
namespace: argocd
type: Opaque
@@ -0,0 +1,16 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: cloudflare-api-token
namespace: cert-manager
spec:
encryptedData:
api-token: AgDB+icQdE9haTjqxdeXMFJJkDteMpN+jIjgsbT0RQo55rx8e4U6y4FgBcadOovNDF9g4slD4xqalcJVLBeJWvEZ80wOBhzY+9iP+m8HUPzmm67XWCJgnADtWoBjXqZT6NgUoxNb8MMDrFXdRPatM74o3cbVOcp7dIumOEgo+IML0+TdyIXIkSkjMaPTBQZwkxJ5SQs7WQfnRQw1rTxc9lti49LtH3tELWWLK8MdCOtj23OU4YkbzdbZsqymsiHTYJTK80YyL8bP+TjEMp6ZFstkvEvk8Xk6JNiuPqymiQLTK3y9MOfxK6Picux4Q5KGvWoi3N3D3Ol3I5mwsCXGlZA2WfDIyl86VcrYEphKqouxD/dMNJRJW0vzxbtNiFHBBQDulKfmWs9QVYvA2lD+fdPC34CbvyzyVCBVgg9bp/4OUHWkq0Koo5PDVGgIO7ylHP3YHu271koLNmcIKLBKnMlVnhX5RDVjAk48sFK9+rla6mDiQ19xJpd4P5ep1NxRe9NV0DtFrsRVLsbvPe27P0pwICevG/9OPzfDHQyd6ScGzKM+ltzpi6qgyLudu2bb+0rQ83r6EEvCW654nTGatlGaIbyG1n3cs7V2TlMYRyUYD8IjVXpRoVbYqnxMK2PoGL9ZgKGBoYTKabGYZnl9ngUBHwipEPXZ1ShxmvMn3/l1Mjr1thUlO2ktRMo19gGGAUulnFPoHycY7DxFwyxFa9vcHH//oejC20Lut3kq6cJCYt98WtOtXWqaY45t0gsEcnX89nvkyA==
template:
metadata:
annotations:
sealedsecrets.bitnami.com/managed: "true"
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
@@ -0,0 +1,16 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: gitea-registry
namespace: solitaire
spec:
encryptedData:
.dockerconfigjson: AgC4qgegUdhckd0IVypEjGhMEshxPaZ1Xk6JYbmxZqFyzdIQs72Uv/HxIYTwnfMKcKPd75mkFz6wi4I5yAFRnZDs3JFMujHejIXXAxNYB+mp9jTCDGGS+V4V84zEbdNNuXFBZ034LrnOMeOhn14+ieq22FZWX4w/pnjPpuMGpWL2aunG4nCwoRikXMcvZfVptGjfrYUDEYksCGPcoIlGaV9xuMRQcdt2gWeecs0UdZtTDVBUOVNX4CnnZX2zrTdEX6MCooPqUsv4ZMjDUhjSeLUDgNeAZde0jhbc3wSdjedeeUOqlLTfYgeVRaABUaaslVsBAqTp7MTDjhBYpXl9IRJkiV/RTaC+6vlL1Gku8rva9B2RjORTWL2+IGxiFWmyKnkAR1OxJIkH4VpuiimpWV5eVTNNBSH80k+rquYA8KOjn8aJQ6ZxXN8kOSz3xicNOjZpSp+6eCPwSFUEJePSA/z0JaKWtVYk+rB24D2FHqUkVwOV/X1AG65zsHG1gTUNdr/2k42jB7SOHsBr/0nCgvuJoXftxXnLLinKmBDKa3hHhG7Wjqq5p3eu1MWAADvn0p4UiM/mhomCPHP9oPiuAiiYPMU6Z3nL2eAT2vA0i9gikv9vrk0e6HEtCW2FUlk7JtPZjJCJwKjqlAPu5dceS+YD/eETLX1h1AwugzDbYjtasfiRoQ+AiOqyWitNrds6Dcqk8SlMsWsGf/SRfODR1Dh/JFX6du+dyNzcyJM6L2s0dSCCRuBcUPi2NrjNXbwcIVOHepPLRXyz4KuGNH6pF6ePLpdlxH5DuatTvcaeTKdEtLjwW2YUmK/L66DldrFs5OhGpD02L+LPWYHjrARa/yCl7NWTU7lfzvhGLukVZ2nJG7INYRvPy9qnHIYLRdwpdK+t0UaMCWOgeeThHF4PWn/+BZEjpSk0jDsqj4ETCjtf0NonqhONceeVdg==
template:
metadata:
annotations:
sealedsecrets.bitnami.com/managed: "true"
name: gitea-registry
namespace: solitaire
type: kubernetes.io/dockerconfigjson
@@ -0,0 +1,21 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: matomo-secret
namespace: solitaire
spec:
encryptedData:
MATOMO_ADMIN_PASSWORD: AgCfXf0iM1z1Z3RFkWV1De+u5hCwR5nJyH8OJJy95vboJReTx+pKph4nkHSX1dCOSkm1rDBXu/hAyzq2bytXG7qa7xLMtQLpBl0X/Q/b1tI+G+hwDolOQSzKUSuKghf9n0d7wDBHho1n5JPptTYuyC1jhRzugw9v3lXXzUHT/vS4sypVJ9QGj8fBoyRrld0LLTTooRIV5l0+R3daGrk8EJyV+cx300aFQ1xYBCMgjhexW5lVsbTtYNSZNx0aejPt0pPeOBqZbDHTBKXQA4mXP0VvCHyB6LanZ2oJImbJXyUVKM4uxKrICSVdCPdyOWk22rh4sm1DalmPSAYOS5aEKWOlcX87YCOByamiFF3J/vbtolscqcpWQOE9dv1fBNW8hHEHRmneNyH5UEn7q3Bz3ruqotl1b4wq9tmr5cyYCAZPRXWXKLr6oXr+eGjU0ZliAS/fIJ/VF7MvQpqelS10x+MSSnTV8u+ju9sZuQkRMWVdy/OPFj6S4pxZTfuqwF/Bsptg+TL+kXoAf5zKJjBwEUlEDGkoevGwryADLsITd10kc86zDhrjdLrh7Y5RcKIuXe8H51mzCTHuThB3oxEVQEsgftRudRwvubRmYg3/9HC/k7TSFVX2fmBi+fk3YVTdvxldrRj0+UaRPm+48Ohuxvqccj5N1dFgZXL9Z3TRP6EnuxY5Ehm/dVJx+NXQEH9mGpWJ0lVJAk54i2B+PzbEDYsW1qRaEnXXNkw=
MYSQL_DATABASE: AgAH48u2q6zQrN5PaWFRZWssUzStVmIvydUQMZSMqEkrCUK91RzOla0v6Yx9pV1lxlPrqW5NJQ3vQxD7VI4YYiD3FL3DlLFru2wgAT5emvU/UvZnUC6wh5zAxQNjaDPMbjinzVHflKHTztKSyXnnn8j7E2uTAAiRbFpjE94uckBVAxbrUohk62yTFThJ7Y08MV48RE2v5NugWumyisKoMjpo7Wovrj76a17yLO80QQ4Af5Fnk2sAw9/hk4SFrCl5OXdOGq71wtCw8jNAsqHZRlM6cXkcRiGYoFKWTgsW+h/VnSEbuWuEAyF71u/hnre6mSk8Ci5k+VGkTh37afaIcjNdihH8EcWeubOIIxVm7f3LCQ4S9dXMalqG3Frc0QX2zH5eZL0qEqy1zEJ8weXZkgMy6WspCWegnVvfubVZzvrUlRu+oZDpt4SpiKiEWh9g3R5GImWXPrmVm5feet0A7Ff9QhhLdLTl2K/mqTiK5kj0mfbmXnnBvGhh8YNaa5CBTlE3C/5TH/Cop2Qno4/xsgvAqMr5qSd+xUy65S94pOdlc38ngNNsWe71/jfP49u/szO2v/OHnXE2JbhFArAeMRYvcLKIxf73Us/IiliKwIRdFWO2cYknD6Z2FplM9IoC9BmTSgZZMagz0+SnsZBVcVP+K7yizKFVtj5rTjcsHSvqRaAFrOxyiqPAdI7yQTOa/QYHIpHD4f8=
MYSQL_PASSWORD: AgC2K1DumViJ6BO8+ACM7ayaE6R46mEiPcFmftjF1wHGaSLYejk0sqJWdh1DHWo2Xa1jk7CnCZBmhZmbbbteDR/E2hKEDfDa3YTQIBVIn+G2blEsSmG/tgnbhW/ZZUSwF/Z/i2I+gMbQDUeKsnSSZ2I+dOo/jVnCfps3xz/9s+ChhrM7mqc9J1Qumh4rtIAd8X+cXPq20rHUMfKm8jt4m/w7UY0DGg5CuoHswrfteo40LtW8KcP4Pc4N/b7uDCdZY54iEzg4e2EL/QlayX8PFiqgRzOHr/fMZ6Z7wWCEiP13V+gPdHaajdqJzlz0QgwvxI5EUOv6Pl2efIfoitOnCvQz7cw7cpT6iapCi9SY65kWRlrDYmybpfP06Jvk39r3HtUzLNEn9cadagbVg1zsjTm/EtMvNPfwY5MQdra0rbQy2b/WI0U8V5Olf9LvrW+y6kRs+YGFKY+FO1CNrWD2N9i/0K5jQVHPx5p3LZPyILKmDZN8ndPDNAGfjRmIC7WHPHRm3sY55P0xc5fI9hb9qss+2BfH70cRu+rdylO70pIcrV/px6V4xTI76MXVp90j4lxbTmwTLggJY6aZoh8NOCU3oaHGwzCXwTDmVpAzgx5amLuG8l4VM8dqBOke7FB0x8Q+2VTK0OEqFiYUVRY0KIwc8i7fXziR0Y/MMbzMyY0EjpoE+kOjNSKgrCxnWxoBCO/0rmlPqeWP1rrbh4h4EfaDIaZ/YG0XsiA=
MYSQL_ROOT_PASSWORD: AgCiCZzjb0xHh/ZttsDcnWp6jauP5wBYEoFNc5yRJ+WbSiEBB/ayTHP23XJUH6tBDs9xTGjvoXsMJi5yAX2hxMT/fQ0tlTTzoGlwq2fNdWGkqAL1qkOxLRRyoJhnDA0E98sb6v4LBxWCWlCCBgBrZNi2TMpRI+M5htrEfWXcdUa4pPqGDh5Wd1P7z/DLdmtyAGSIIq3BS6T5dnNaobJj4ya/X9PamzZP9Ck3A98qKJnQpHgUKOgm9zWGx/tpxXpru4oNmYvGxDC6Og7XxfVvBp+eBRcWYMuE6ds6cH5iqvrsJJJJBR1vjVTa3UJ8ByP1tyUngJxiz3YQY/zX+UtewkO93AkBA4eU4oZvUxi6+HzRBBHqK3AEjWbuGKRRbmyfjLAvp/nNmTo3MKKPRDILkNG2yA9d/WDama9L9wTiS48SGZdIo8JesPfOJ86ag6XnLdC7BoO6s3W7zuTyXJ8Lre3e65oo9zDHqIX1OgAJBSXq9yuYK7aZG0gTgZauKtktJJFRexwmaCGkrUP9c19jrdDBj87A1gr5+dNYPqt0yuZqIw/rNLjXdLUqh9HqBev1abYyfJ4Z3BkgKUyFfyicTUMma8WfGFWlEoKFbwnaL2Z2/d2GhqP876Tu+YpFDC5GcqszBpfy0Gg83nhIsWlaWWSRtkIr0NTL3LewtvZKbIqjb/VM0xeiYf5iBO30Ma9NtiFzYU4D1Z5A5cQzdGlqfF5bN8toL5iCkVs=
MYSQL_USER: AgAr+wwear3WFeXjqEuB2bY5SF5KpkGQvEeLZlxKwaMSH+PeVxylHUxlPmg2EQAFezfoR0EzOJtTLttNUDi2+7vLIeS0i6GRoHIHul1oInzMiOQWognohU868OqUwuNJedgwGhskx3TgKspRsUUykhcyQTmPP5GbbfH16i11pYQzCVcZ0H8/rRdey3UDwVZl3nY0Ei6d2kXXEKLwBwRsuQ3q0ZfCYCIrnpSuK00tPYubo16ESYdvy50oP4Ssn2NO2j2Httk6EAUtH1HpAdwmjleEIuVXeXIAdK6Ne5N3JXVTqt/JoI1J0PvPgmIaGSMFTyo59IfKfatD7TqFW/JbDp/Ui3pByqEjNljrrs+RqzJ+6tl77U9/hFO2kN61VP1zLpkK6uNYTnGH1ENjjdmCy3Ih/+j9CJ2DVdQ0pp24cBG1cMi2gm66j/w6389OqAjOQoi7ta8GwVdmltAFij3tvrS+aCV2MSS5BgtqJXk8zfLAPW43MKar2N5aH1N1ODiZ99EIfJM9UsTPRDWDuRPiNzuINF2XxGiG6VQwAVVzmlK6s4R65zU9ZpNVi4gFZZlYhppM2IVlM+02w/7pE1c5Am+tMnpWnIM2Z32YcKASdZT/K1MpL/7CSerfcr+gYh4hepWUFKfirVGoZ4LerIEG0OEDaLM/hxZH3hxh8s/kHRQWovgXqGIkcCLJRlwn/zEyy2WeCnSmcQk=
template:
metadata:
annotations:
argocd.argoproj.io/sync-options: Prune=false
sealedsecrets.bitnami.com/managed: "true"
name: matomo-secret
namespace: solitaire
type: Opaque
@@ -0,0 +1,16 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: solitaire-secrets
namespace: solitaire
spec:
encryptedData:
jwt-secret: AgCDylu761COJ1pbjk1QZbZ9tbwYVX0k2I0T5U/UXhaNPJsNaekiOz0+1063VphPYILey16I+ijc1OplzMgUNaLCdYRCeFjfq1XR2+ntfv3EhtZG19CFlyoZWrQ0guliV3fD0pqMpstClZQ7LmvdeseQ7tknDYrOKg5A19k9eP4kO3SQXW2nuPesDTwddVOs1hU33noIeFAM89cI9TdPC5s9RYjB4sbWFbbtgJPmebku1Mcc/PaTepx0GjaS52OK7cSkcsp+JRlZ6oENfv7EpZSu4V3qW3ajFXLHUSGvxP8VlQGCrLRS2RkNY7b15X3HctYxEdkq1Co4kjArFQ9Ut7qqUl26ANNcfSPnrN3xKA+xBGREr3x8VhQAvWmm/CKAedxmTnIImfpmrerbVELLl3gGz2OztFFzt0l9kcXnlpOBVjmoYvn9O6WStp4YecUy70FrMKmI9Q40qsy10E2hQR5wK+y2ptd6RZ6DCLxS2yRIldqflQPjBdhFjRZl+5cQIwVAaWrqrHd7/J98E1mTd0Pn5cb1iboIdET0Hc/98hF+VMiXQ3GRyih5+brazTePIEpfgDVwmpBodaiqwC2c3qpqWFudt1WQsUupud/koosb3YOWy9sRXTBVaS7hA+X+Pq9woAQeXaJs1CWcPb5amsG7DGpOT8O7DswjODfS53g0yX0SK+6VoZgU0poT68i7PkDyHQ3S96Boz1LxKV8RmLTQbxrREXwXkyCCRof6ZhJfYclKNfncoc8TDRm7rcGfi4lRB2lMq5W1gALJhOl2BADA
template:
metadata:
annotations:
sealedsecrets.bitnami.com/managed: "true"
name: solitaire-secrets
namespace: solitaire
type: Opaque
+1
View File
@@ -4,6 +4,7 @@ kind: Kustomization
resources:
- namespace.yaml
- nightscout-configmap.yaml
- nightscout-sealedsecret.yaml
- mongo-pvc.yaml
- mongo-deployment.yaml
- mongo-service.yaml
@@ -0,0 +1,18 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: nightscout-secret
namespace: nightscout
spec:
encryptedData:
API_SECRET: AgC0frB9Yx9u8pzIE1//OVLdEFaFZSAguSlosSF6ZcQg2sJrunqlxygNujo8LxPHv9YsT1Wwjcl7B0+4SVpqygkIAIN15dQiQX8QEYAytGkRv2nnG1q7jaW/m9S5D5EAIyFgzLHtO8zFCw6lO+e5Yt+qgEE7ZRN/eWEOK9LIYmIhVa7Pbc/xQoueqS2XxtHORhyLU3b74e4GEzb0HJDSDYq1LZfTqi1dnuxKTx0zNcFhQL/QM2W+kUhuZEM/eWpdQt4GBgRvNRT1CPUCeHw1Hf5veh38CxiByU3jYy1SA5q/GN5skgq7fEReKGpBLNIadgjt9516Mognt/f+C60E/VHxKyIWb9F6Lf0tJT9lRv2mU9bX3YykPHLzrVIZ/TdFQ/SDqZ6I2Lz5MOSIJWV5pTqPOvMBLNdzGLOx56kdolxBbwNYRAsELKwwlZnW27Kodc1PFgtFbfkwpoVGdnh3xdyFPQTBcUTyZHAMcproZAhMrcALdyT0rAvnNICFetvGsacT0gv+2c88UWvb1UZ0FYKcnQmUWKD5ou69Gspm2mr+izVt1dQPNa5wxLbRFF5pzong4Q/6hVA+VX1i8rR7/D0adaqdvPYOV36Wl80JLmGbgpilFnDTZP5IQV8kaIZqb94Y1YqOlkciAcO6eICQuXf923kigELB9CYPEUoQCOYWCGBFTFxLbIwq3dvQEjgDoNyR0P3D1mPhR6mCEt7n
MONGODB_URI: AgA9Wj9k2Tdq3UiZVSK8QBhxqxch4pMWDPzWVG/gRcSsAs0KNGiDfd3MINencnSPF5Qf6ChOdc1XXJVZg83H/no068nNev+qT11T/xs7cFNhFgagYzp5bd7i5vsxIC9f69GfGbtOie32phQtCQgjkjwv9seYFywbR6u6cuaBl0wcU/RJRfLEr+goGW3EEtV0yvqxMUeg574D/uL9TSs2uZ9/XGBWGrEzbwIQkyWpUKSn7BWlCxPLQn1s7yOVQsjCKiehzb5EyEkqXhhv2QKObHyIduETFcx+8Pw40QCLVL4IXaAIV//gxDxPJPRbJ+3kL09sWW1ZdnLyvu6Tf7S228MA7uswq3Qe67q/s3vm31+ujnjGugmFtABRVAwvvmC44r79PtrRatMzeRaqMWAJSb+aGv0vxCBifA2qK/mGMIkniSFK0ciV19+Bv6m4L8IB4B4Vn1ookYjcyp0YiSqTJo2c5gMaCHqfwIS6F53+RBod0iQHByLpELgYx9h7qwoNsbFqFTDQLPk8qH1tBoJO3b1vp6FWIFLlkHbuJLfA8hBnWDEEvKoSbVjiDwyusW6duJ3CF4F0WugtfUtg8YVffR1RllUczzPgie1MCl1eZaY9ro86W1+vMC9SRyewIPTUxi64Xpmv4LtXdqdcBctSw3SxWz0ITjVk2pOgDFDOxYYRpngAiLk+8FGRzKiWM175L1E/LyjQsdsNrwwfhWjkaYQbHyelBBh/R5EJTIx2aqLL7hY6
template:
metadata:
annotations:
argocd.argoproj.io/sync-options: Prune=false
sealedsecrets.bitnami.com/managed: "true"
name: nightscout-secret
namespace: nightscout
type: Opaque
+23
View File
@@ -0,0 +1,23 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: notesnook-attachments
namespace: notesnook
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: attachments.notes.aleshym.co
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: notesnook-s3
port: { number: 9000 }
tls:
- hosts: ["attachments.notes.aleshym.co"]
secretName: notesnook-attachments-tls
+27
View File
@@ -0,0 +1,27 @@
apiVersion: batch/v1
kind: Job
metadata:
name: notesnook-db-init
namespace: notesnook
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
backoffLimit: 20
template:
spec:
restartPolicy: OnFailure
containers:
- name: rs-init
image: mongo:7.0.12
command: ["bash", "-c"]
args:
- |
until mongosh mongodb://notesnook-db:27017 --quiet --eval 'db.runCommand("ping").ok' ; do
echo "waiting for mongod..."; sleep 3;
done
mongosh mongodb://notesnook-db:27017 --quiet --eval '
try { rs.status() }
catch (e) { rs.initiate({_id:"rs0", members:[{_id:0, host:"notesnook-db:27017"}]}) }
'
echo "replica set ready"
+13
View File
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: notesnook-db
namespace: notesnook
spec:
clusterIP: None
selector:
app: notesnook-db
ports:
- name: mongo
port: 27017
targetPort: 27017
+42
View File
@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: notesnook-db
namespace: notesnook
spec:
serviceName: notesnook-db
replicas: 1
selector:
matchLabels:
app: notesnook-db
template:
metadata:
labels:
app: notesnook-db
spec:
containers:
- name: mongod
image: mongo:7.0.12
args: ["--replSet", "rs0", "--bind_ip_all"]
ports:
- containerPort: 27017
name: mongo
volumeMounts:
- name: dbdata
mountPath: /data/db
readinessProbe:
exec:
command: ["mongosh", "--quiet", "--eval", "db.runCommand('ping').ok"]
initialDelaySeconds: 10
periodSeconds: 15
resources:
requests: { cpu: "100m", memory: "256Mi" }
limits: { cpu: "1000m", memory: "1Gi" }
volumeClaimTemplates:
- metadata:
name: dbdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
+39
View File
@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: notesnook-identity
namespace: notesnook
spec:
replicas: 1
selector:
matchLabels:
app: notesnook-identity
template:
metadata:
labels:
app: notesnook-identity
spec:
containers:
- name: identity
image: streetwriters/identity:latest
envFrom:
- configMapRef: { name: notesnook-config }
- secretRef: { name: notesnook-secret }
env:
- name: MONGODB_CONNECTION_STRING
value: "mongodb://notesnook-db:27017/identity?replSet=rs0"
- name: MONGODB_DATABASE_NAME
value: "identity"
ports:
- { containerPort: 8264, name: http }
readinessProbe:
httpGet: { path: /health, port: 8264 }
initialDelaySeconds: 20
periodSeconds: 15
livenessProbe:
httpGet: { path: /health, port: 8264 }
initialDelaySeconds: 60
periodSeconds: 30
resources:
requests: { cpu: "50m", memory: "128Mi" }
limits: { cpu: "500m", memory: "512Mi" }
+23
View File
@@ -0,0 +1,23 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: notesnook-auth
namespace: notesnook
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: auth.notes.aleshym.co
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: notesnook-identity
port: { name: http }
tls:
- hosts: ["auth.notes.aleshym.co"]
secretName: notesnook-auth-tls
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: notesnook-identity
namespace: notesnook
spec:
selector:
app: notesnook-identity
ports:
- { name: http, port: 8264, targetPort: 8264 }
+27
View File
@@ -0,0 +1,27 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: notesnook
resources:
- namespace.yaml
- notesnook-config.yaml
- notesnook-sealedsecret.yaml
- db-service.yaml
- db-statefulset.yaml
- db-init-job.yaml
- minio-pvc.yaml
- minio-deployment.yaml
- minio-service.yaml
- minio-bucket-job.yaml
- identity-deployment.yaml
- identity-service.yaml
- identity-ingress.yaml
- sync-deployment.yaml
- sync-service.yaml
- sync-ingress.yaml
- sse-deployment.yaml
- sse-service.yaml
- sse-ingress.yaml
- monograph-deployment.yaml
- monograph-service.yaml
- monograph-ingress.yaml
- attachments-ingress.yaml
+26
View File
@@ -0,0 +1,26 @@
apiVersion: batch/v1
kind: Job
metadata:
name: notesnook-s3-setup
namespace: notesnook
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
backoffLimit: 20
template:
spec:
restartPolicy: OnFailure
containers:
- name: mc
image: minio/mc:RELEASE.2024-07-26T13-08-44Z
envFrom:
- secretRef: { name: notesnook-secret }
command: ["/bin/bash", "-c"]
args:
- |
until mc alias set minio http://notesnook-s3:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"; do
sleep 2;
done
mc mb --ignore-existing minio/attachments
echo "bucket ready"
+42
View File
@@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: notesnook-s3
namespace: notesnook
spec:
replicas: 1
strategy: { type: Recreate }
selector:
matchLabels:
app: notesnook-s3
template:
metadata:
labels:
app: notesnook-s3
spec:
containers:
- name: minio
image: minio/minio:RELEASE.2024-07-29T22-14-52Z
args: ["server", "/data/s3", "--console-address", ":9090"]
envFrom:
- secretRef: { name: notesnook-secret }
env:
- name: MINIO_BROWSER
value: "on"
ports:
- { containerPort: 9000, name: api }
- { containerPort: 9090, name: console }
volumeMounts:
- name: s3data
mountPath: /data/s3
readinessProbe:
tcpSocket: { port: 9000 }
initialDelaySeconds: 10
periodSeconds: 15
resources:
requests: { cpu: "50m", memory: "128Mi" }
limits: { cpu: "500m", memory: "512Mi" }
volumes:
- name: s3data
persistentVolumeClaim:
claimName: notesnook-s3-data
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: notesnook-s3-data
namespace: notesnook
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: notesnook-s3
namespace: notesnook
spec:
selector:
app: notesnook-s3
ports:
- { name: api, port: 9000, targetPort: 9000 }
- { name: console, port: 9090, targetPort: 9090 }
+37
View File
@@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: notesnook-monograph
namespace: notesnook
spec:
replicas: 1
selector:
matchLabels:
app: notesnook-monograph
template:
metadata:
labels:
app: notesnook-monograph
spec:
containers:
- name: monograph
image: streetwriters/monograph:latest
envFrom:
- configMapRef: { name: notesnook-config }
- secretRef: { name: notesnook-secret }
env:
- name: HOST
value: "0.0.0.0"
- name: API_HOST
value: "http://notesnook-sync:5264"
- name: PUBLIC_URL
value: "https://monograph.notes.aleshym.co"
ports:
- { containerPort: 3000, name: http }
readinessProbe:
httpGet: { path: /, port: 3000 }
initialDelaySeconds: 20
periodSeconds: 15
resources:
requests: { cpu: "50m", memory: "128Mi" }
limits: { cpu: "500m", memory: "512Mi" }
+21
View File
@@ -0,0 +1,21 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: notesnook-monograph
namespace: notesnook
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: monograph.notes.aleshym.co
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: notesnook-monograph, port: { name: http } }
tls:
- hosts: ["monograph.notes.aleshym.co"]
secretName: notesnook-monograph-tls
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: notesnook-monograph
namespace: notesnook
spec:
selector:
app: notesnook-monograph
ports:
- { name: http, port: 3000, targetPort: 3000 }
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: notesnook
+29
View File
@@ -0,0 +1,29 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: notesnook-config
namespace: notesnook
data:
INSTANCE_NAME: "aleshym-notesnook"
DISABLE_SIGNUPS: "true"
NOTESNOOK_APP_PUBLIC_URL: "https://app.notesnook.com"
AUTH_SERVER_PUBLIC_URL: "https://auth.notes.aleshym.co"
MONOGRAPH_PUBLIC_URL: "https://monograph.notes.aleshym.co"
ATTACHMENTS_SERVER_PUBLIC_URL: "https://attachments.notes.aleshym.co"
NOTESNOOK_CORS_ORIGINS: "https://app.notesnook.com,https://notes.aleshym.co"
KNOWN_PROXIES: ""
SELF_HOSTED: "1"
NOTESNOOK_SERVER_HOST: "notesnook-sync"
NOTESNOOK_SERVER_PORT: "5264"
IDENTITY_SERVER_HOST: "notesnook-identity"
IDENTITY_SERVER_PORT: "8264"
SSE_SERVER_HOST: "notesnook-sse"
SSE_SERVER_PORT: "7264"
IDENTITY_SERVER_URL: "https://auth.notes.aleshym.co"
NOTESNOOK_APP_HOST: "https://app.notesnook.com"
S3_INTERNAL_SERVICE_URL: "http://notesnook-s3:9000"
S3_INTERNAL_BUCKET_NAME: "attachments"
S3_SERVICE_URL: "https://attachments.notes.aleshym.co"
S3_BUCKET_NAME: "attachments"
S3_REGION: "us-east-1"
MINIO_BROWSER: "on"
@@ -0,0 +1,24 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: notesnook-secret
namespace: notesnook
spec:
encryptedData:
MINIO_ROOT_PASSWORD: AgDAj0bx9aNQHIXAcNB3YfA4+P5qIhVNwEDBDDTEcJ9OQ3qrmTrZ0HtYIWswMU1CYuCGzfCT9sdoJ94L4VFWCdqavHdPn3/zDtXXR7WAaMOoYygiKIPTVO1rmRNnk0gzirl1aWSm+fWsc61+SYCvx08C/RnSyTeocfhpY0rvm0r9eiIxmdjc0u2VKksh1ARSkSqJkLniPXBH6PCFH/tUOH3FZqYycCW+FEhj8KE9D/FIVKy3pCFhfg9KfH7XHqQx5wnQ0j2aL9ljqA6DPI6tDB5rcR8lW7+3iC8X3YZwaExFfWq+3ksZj5LLnESBlqwvV1IY8B7k1VoiL8f5r/5VAE/EJ6SFvIueoOnKxUWC+NJroWeBSnk0isDpNPluVkrGJOVTHv0bx3bAhUVjr3lQsimvyi1ZZXKvr/b/Hpg++RLTF3wBGLjM7tsJ0RHnzysvIyfg8euU2OYitjfkQKbvDIREkU5A0AQ35oapgNnVAB2gWlm4SAkkfOQuT/oowTOLEYKg4zzk64lQiOWFtD5q82Dr6PU6s/snIDixkY/5ICgcV5/Y1Oq0wtvhblDBroWpEnohoQAqlYkRNN4jh+oSEJwJORv6mF370XxBxY9k3h/IyCdJeC9xv9DocUjUP+FU9HQE7Qvd3OcNag1DAyhmtLv5wDgZEea3fPX5XMhnvE5U+YUxZDXkN1MchItTTMMim5fySIPg5KZ+Hr2Ticpxfjw47RMFNH3y2qfDqb8hBfB9SA==
MINIO_ROOT_USER: AgAyLNT8gHmThqLjQBBcmoZsxtEG/ZR/hjUICEZKAyF8+FV6A3WFizA8tYBzjNyfaGFSdS2yLmZGbwtEKmHSApBmXoY4xgnyZpEjh2whsgeBjMFdx2jWt+odvEaPGA+xEbuODJbkJMQiXhi/ctyiMD1/y6WIWZ5Ya8Ahz469di0fT1znMk9B050FKFgVEK6Sfzb8MQuZBj2wukTgZ2rlKMQ+7EQi6pb6cEATLDjheXvbgx4g9LTPwVN+1uZcC5GU7jg/IGJcEg/QJfmOCgRmz9iDx3/d9lFnDnJBOv8Plt6PmWEMR9QOvdQjUhWiN2BwrdICHqFxFTnV+z9JbMk2kT2Ovw6W239BT+KbabEaiwVE5+3ubLLymF3bXwRO7fqPO3xy6/aVsv6k9995jPY7/DNIr5kozo/uFf2DeP8sbldJp2ymR2RBoa8TCiGHT5pgWr36qHxMt1J7fU1mWIRPeUmkFJchCjNc8qDr1bwY9hjrdXxC5fUkFsG/gyb5FsmzvQaAPAy45Wl1DRpp4xBiE/SUc+xBcA1y3Bo6lJ2w7w8tB2bMZUqZkgOyPDaPfzLViMtPUlnCnKRGzvi8HoGvzLS4gN1jjRUK33YtLpLqa6B9Wj1/fkgu9H1cIz1ffS3baL4ZIyA3K7/4LrAS2xzUcJ8HdezxjuZQKqtwDgJSysZJ0blXG6GLpx8JonZh4gx8na1c9kKt07BNYao=
NOTESNOOK_API_SECRET: AgC3JoH/PCMgUPPvUCkIsRWGEIJtwt+E+wXvfBTi2V60nN19RiJLpXF860UrKnFZxvloFalJLIxA1mtLUAfO2wZuzGpid8IIrAUIyhXCZkZWkZr9UbB/TjzmorC5NEAwjo08zhBKIlME+Emdb7BrpHGbj2ykQlyKqwCSTPKoitkypdgXQsbPWM6j03o/F9hSmKVFCTb6ugBY72rXBlVBqBGdeoPLkwSnuk7kk6y+lOfufeqGw2MXoNCZJ1+rbZQ2ihGFSGLKd955I3wnidcu8oCQO50u4V1IHhj0/iJHh/bXu6ipXEw4ZX886SFQgqqb36sFt8b2PIk+h2GtqFC+Ixew+ay790B/wXIMWhze680oAwq/6ZvWe0PSbJgDXLCYga7ZIw8RYFphk3+VblYfc9EObGWvuLDoGzSs/qgi/+euxOkM1P8lGUGuSETz4Bm7VEg4udEneuCnfHqQ19/qkhu/isMwN7KpOaymKwpw0Yb2oefUljjPOGHq+ni08TUU1V+fmKMmnxG3Uq05mLDjTpN6lkpm5yF5xCA4WRflL1kqLFsCPm4JF62NiLDXuVb7c0uz9l85YaJg06RaYVHOf5Km7B3wnBHHrk8sJ2NtkUa2vvGbkZ64JFP9PrD7Ubvh62i4QoXn83nv67rihtzB8BNzkA7sQVLH2OpBqFkbJaTquBWXo3zg8AWcxpuXvNXv6ba4kZJEbBbCDpQDUdQ//MSJttHEgPKvZgmqtqCQ7W3Mypr6CWWGhSfcEmgAzcSfYlYNAbGAGxFErDkXelhbsgNr
S3_ACCESS_KEY: AgC9kcAVZEpanlIjfkpJjDDJe6nikUTorye4l03NhgOoMrU6ivxVXBboiXAYyRYYRidp60r041YPs5lZNCYOwwpuf5iquAlAuD5mecR2xFJIsv3Mxi5wEdoFvPtCIFv8Xm0MTMvR1ch4J4X1gds4S3HQmYIxEKSg/qU+/4ww1CccbDexGOhwOwMMor2KZwfGTrDIZO7/3SwMQs6h8XVRUR5BhuLGftV6tUFEOQBQ6pjijurkSW/faG6YJbE2hpmWNdot6Qau7matCcJhRZI0NAeZhAPeCvIlyrEyJpqjMxNEMVJszj//F7dlPxTW8AkWyw2bmzzgNPhAYvpRTxrDDr70huRUWIo5mAlBbJypT3haVjKYFoRKAGXWC/8dhLSp3NKWQ/smBzpvUOQ30N4EskIVT+V8GArkrLN35yi2yP1aFaQpvLCA1m8pp5hUTgyDm1dqcOnQg3DRJgiMN7/Jt+P9TQGs2RiKoHCPp6lIfDGRKDLYeG5uuiUouridf1HnkrKGXlSmnd4xYWYlym6ZAC7Uzd9GlwTeZwZT8YJCQHqnZFPGq6MAVOPR5B+kQldAH81qosXjRdQyn8RT3DefdAjbduh4/DzuLJTTu8OeSmyPx7H5uqDIrkn1DLuBca2+lJPu4xMv6bthGRzwBLA1aU+nmniFBznkg+YlI5XxJLrA5Ra/PZsglhCHRz1hSiajZ5g1jUzMk2XDjti/q3Xbqj1AkmLmKjZbR05e7VRELMIj4g==
S3_ACCESS_KEY_ID: AgAjLq/HHmD30h2/2mHU/iQKS6l4PRTRhquB4UgCdJjkr/a4eGfxxPZWRJJrj/AbFaWQrAq/ADqa9cAxwXsJInwVLkmv42sB9hODGxRtKbQgwubJaa84uOqdNvce/GvlxXrVIh5H7GUTTKuIXSuS5U/AtBniPudJ5/p8QulYXyqY3K34uePIMEqBP5m7CJ8kKB7EPTLvb29pjr/VdKCpSMwnhXiiiwU/Y1NDdf4ZiIc1gsp79kRd1RhFsLfJky/tBntv4waHHfYKZ7jkCJOGp189ZSqNFlAkP2TPg+jpygpznPvTTggQB45DlYKupUI1bMGlkWcfYLM9+/jmvHngGuzPvSUlGFukaPHkhJ8YwLz+Jc+K6xxdbX9WjzqqGYs3CW41ccxHjxgrZnUW62NbCYIR7xj1K2vCVmmyjwC++N9ArDvS/VreXAq2QJsxKQjL7YElsQrFlMzKrRw98kUd4vpQPQxK+nkLe9HVj0VJprDK22ZrfCcyA8rl64Zx8huwbKEmNscZou49oDx3LJRI8vGFS0lQJFLBrjJKjF9xT+7R59SIo+7JxFAWDvmEaJrdPSq4EDEturHhzvKcv5y1Gy1ul4nwIGvxDZAK9kwp5oiGxfARncoVzgFINqwlVmVfPRb4RZl5mzu8IToMebwvJ52Rpf+IhkW1yEnCYxppBNmUyPWewTc3UUvtViOOawaARaXii6O9sR0uhlE=
SMTP_HOST: AgBZiWzF45lbMOrRXlGi2k1jq2bA+hQWF1DVLJemKgn9et0uBfcIyICHS/7E4cukDI+MOInEQEYem5M4FoQ2mM2ylcIm+LrUI+p5azDKRYkkbcwKD2F0+J9G5YDXxYth11NzS3fLhrVxAuqmbqy3LxOmSkjFvFdJxLx0Su78B2CSDBXmy+gu4soY7bdQV0L3T/zfG6s1WuLzR8WPnji3EmBM5dexG7Smhziz0dymOuhdGfSwaI2MWrYzw9J3Vshc+mRXzw1JvI5mVbawhQOLrnW57NvXlJtk19LLr/kXWOt1ppJcJOYGOxWrFs4VA4Bg4QSN2piARWZ6FbL1/a8ynamUhw+R2yDlT/J8oQIVBzhIWCU5EwfHgDu+QRKzmd2iAhfYZjrUkJNojUjqaQvb7RXrG4DVL9UJxGjushebgc2EG/PykdzoRL2d1KMfJRK6O5ouM42UvOCqRzbBbWuTiA6fj8hGTIZYFXX3vWIM368uDvrEVxj2C+skrRzHEzAQgsJEaWtFvZm6WGkZZOLTuava9+JxMvnpftWMIE8FrYCAYwn5xa88eftgw+GW65EWVSm8IXFtGoNd4DKYj5OGF2DcN//OxteoVd3OxsmuJtY0Qx0REb9nqg9Qdw2bP6emmpmgtyw8opUS/VubGghdCnhLUES+CA08ooK4a8n8rZSSFiujvjMjoDdOnVktM/D3R6ECftJRakhMes4INANM4w==
SMTP_PASSWORD: AgAdjxnT2uC0LRUmlQ/BOlLs6l1PCP13tMmgmSnqqNWSuucYkWwTWtz4qBnG3dauhC0ufS5eqABJqZq0r+QpyhxU0Otqzk7oVcea0Mq9ai159IMCx/rZL8HXIkUF76muv8GOGDYlaf3A+CbrNTsaRXDbjZ7LjzD8OwsdfaeIzUejyNXgs5EMjSOHLx4YhmVfVEZLAtoP9zcNlDSNFaat79wnwjwUifHuY3usvQXhJS+vLVtLLtWwKF0rCsfkbCRwJtJCCrOvOLuid8aZ3cO9Y7n0+TOAlkSe4dSSq0D2psg3njEZ2l3RNLW2YbM2LkcXK+MjIpCivqSCXLufq1dUuhDxCirxSd0VgTt5TPgeTCi+kuNv/UAqydQWDQlh6izUDdnTtMlRqtk4ZFhoBvIo1NVdpObmbFlSlBURwrmNqmdyO1/MNZT40gi1UsuEtktDCohoq6zQrahiGqcXr1VVscE+u5VoV7PRTICMSJQexFV9fx8YsZxDycng7zaf6xbK0JWVSuU1IXwb1ZOReQs0MtZj45o/c9JtYS1HPoSZRCCSCAd6qPhnHoUc6jYYFznfKTqvrpSdXqoBF61I1Sw+CKuVA/aut9mgCzWIwGjb38Hjh/VYufrgkFMcvnqbpkX4F8sMvzI3zsvFT2S0RwNGHOay6f+otg2KJdPlE2HSLVGLTFxS1Ku2F3ZPL79/Br8eCW3PPnq4c6w4f0XBFtuGSXtt
SMTP_PORT: AgATegLz9D+74mUBak9tv+7L/LeRqcJXcE58T1fOh1shsz3KjSzuXb4HxytqghT0LqplAEemCPoG823wPFKYnxZtvedQ2CyZGCxEXPoV6iUvBAVC3Pj6H92k9CIEZRPHLvR6IR+mXzu6HLWwJsOGrxn28waSx374uVizylptkpjIHvyseY3chfH0N/x2tK0yZn9rKOqnzfjKVGh4DF+i7wU/uuFPhcMbqRPg70J0pxhesTKHBAerGnvfnoCxFRoSl0ZPgC0kK5NfIEp3zWW45NWshPNsbb0yix76Rnr+8dEn/Rbopa8jZtfdK260ZPclLRO6z04ks69Zp5u9kE1g883Q25I+M9C8B27QDC+KHxgSwq0w8a5CwlCYDRu/e9UBHtsJobRaARgghyyAVsS4FsDS45kJom3U7n0Ip/27hg2VMm+jwNRoB5cmlyhDDlFng49U36DRsd0z2d2wBFAG4NM4xgbWlK5zUDmKCveC7zJfFAuZGsMbY5U8/lDv1WvGIG8xmQsg5CgriVL6gG3ReRjk3BmK93b8OkUhfquFHKBoyqJZWIIz+W5dcP2fKdjZc0Ec2vPqszHaNTN+dEcGzm9MGFpeus6rA6Egxzq1esVbEPuixcI6eXc87SOG0Ntn2wbPJQKpNZq+JkzYSXBUZcemPz5M2nybLr/lwuX7//Qg5b95nwzXMRRm9kagwWubDPNPpd8=
SMTP_USERNAME: AgAxdxvMjUjvj8vYu8upKMfpAIi00dBizXD83F5f6TCSlAFlo3kuVFbfJ8tCQZNRSdU7vP6n3zQfW2IhYUQSnO44+Wb8q5V4sfs1cClbPyGpQdEyFeLk1KwW3LeFxsi9cym3H7Qtu9e/S3dOmD9IxK8AuOJkq5t7Xepum7WvuOuvBVFm2nVetZlTs1BmHoxA8dHlAhk+qwGMAuo/Xltut67uDsPzZTcA4fEAYe+We7zqfD0hpN+k1TffZHE7x+GpA3tyc6yUf/uSsWRq9HKyASc0UM5uoK/WoxYoUA7RF5j5FXFrA1jY/Y1pE3oLjQmpB59q5vOasd77uXE/1b+rs6l8L1zngWw+8MeKA/julrmWBwte+vs68yqDFOL7QNfwpYmYoaAcuvezeXZwF4nnwzXyE8ZEO7xtbp/r9DATeGypjRnm1bDAMTARGLgHoxV18UELCiksNNCbVO7twXJs+HUE/sbfI3tQmk6O5zbWD/GbcR0lTsTjM+hcM5r9ZYfrq7Ad3Oyb3ANU184Rm4fiR9v+t9OyJf9jIASF9p8oFcTr2dupdv3r8wzPq/ayfvN44nyOr2xVfIv+ubMqjjLixg0jUDeFoR/GST6fpvWBmd2HVMG1tHvZxphUb0V/M946HxKBMpsFn/WWyLgUzJG64bIICDAetCferbaa4UFEHP23G/R5tLBWM2GNZpT6yfkBQYppc6e1/KAjGNVMrTpKQeNkD/xs
template:
metadata:
annotations:
sealedsecrets.bitnami.com/managed: "true"
name: notesnook-secret
namespace: notesnook
type: Opaque
@@ -0,0 +1,25 @@
# NOTE: This app's Secret is now managed via SealedSecrets (notesnook-sealedsecret.yaml),
# which IS committed (encrypted). You should not need to hand-apply a plain Secret.
#
# This file remains only as documentation of the required keys.
#
# To change a secret value:
# 1. Build a plain Secret locally (NEVER commit it):
# kubectl -n notesnook create secret generic notesnook-secret \
# --from-literal=KEY=value ... --dry-run=client -o yaml > /tmp/s.yaml
# 2. Seal it:
# kubeseal --format yaml --scope strict < /tmp/s.yaml \
# > apps/notesnook/notesnook-sealedsecret.yaml
# 3. Commit the SealedSecret; ArgoCD applies it, controller unseals it.
#
# DISASTER RECOVERY: the controller's private key is backed up GPG-encrypted at
# /mnt/bulk/backups/sealed-secrets/master-key-YYYYMMDD.gpg (passphrase in password manager)
# Restore it BEFORE applying SealedSecrets to a rebuilt cluster:
# gpg -d master-key-YYYYMMDD.gpg | kubectl apply -f -
# kubectl -n kube-system rollout restart deploy/sealed-secrets-controller
#
# Required keys:
# NOTESNOOK_API_SECRET (reuse existing — do NOT regenerate)
# SMTP_USERNAME, SMTP_PASSWORD, SMTP_HOST, SMTP_PORT
# MINIO_ROOT_USER, MINIO_ROOT_PASSWORD
# S3_ACCESS_KEY_ID (= MINIO_ROOT_USER), S3_ACCESS_KEY (= MINIO_ROOT_PASSWORD)
+34
View File
@@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: notesnook-sse
namespace: notesnook
spec:
replicas: 1
selector:
matchLabels:
app: notesnook-sse
template:
metadata:
labels:
app: notesnook-sse
spec:
containers:
- name: sse
image: streetwriters/sse:latest
envFrom:
- configMapRef: { name: notesnook-config }
- secretRef: { name: notesnook-secret }
ports:
- { containerPort: 7264, name: http }
readinessProbe:
httpGet: { path: /health, port: 7264 }
initialDelaySeconds: 20
periodSeconds: 15
livenessProbe:
httpGet: { path: /health, port: 7264 }
initialDelaySeconds: 60
periodSeconds: 30
resources:
requests: { cpu: "50m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
+21
View File
@@ -0,0 +1,21 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: notesnook-sse
namespace: notesnook
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: events.notes.aleshym.co
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: notesnook-sse, port: { name: http } }
tls:
- hosts: ["events.notes.aleshym.co"]
secretName: notesnook-sse-tls
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: notesnook-sse
namespace: notesnook
spec:
selector:
app: notesnook-sse
ports:
- { name: http, port: 7264, targetPort: 7264 }
+39
View File
@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: notesnook-sync
namespace: notesnook
spec:
replicas: 1
selector:
matchLabels:
app: notesnook-sync
template:
metadata:
labels:
app: notesnook-sync
spec:
containers:
- name: sync
image: streetwriters/notesnook-sync:latest
envFrom:
- configMapRef: { name: notesnook-config }
- secretRef: { name: notesnook-secret }
env:
- name: MONGODB_CONNECTION_STRING
value: "mongodb://notesnook-db:27017/?replSet=rs0"
- name: MONGODB_DATABASE_NAME
value: "notesnook"
ports:
- { containerPort: 5264, name: http }
readinessProbe:
httpGet: { path: /health, port: 5264 }
initialDelaySeconds: 20
periodSeconds: 15
livenessProbe:
httpGet: { path: /health, port: 5264 }
initialDelaySeconds: 60
periodSeconds: 30
resources:
requests: { cpu: "50m", memory: "128Mi" }
limits: { cpu: "1000m", memory: "512Mi" }
+28
View File
@@ -0,0 +1,28 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: notesnook-sync
namespace: notesnook
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: sync.notes.aleshym.co
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: notesnook-sync, port: { name: http } }
- host: notes.aleshym.co
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: notesnook-sync, port: { name: http } }
tls:
- hosts: ["sync.notes.aleshym.co", "notes.aleshym.co"]
secretName: notesnook-sync-tls
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: notesnook-sync
namespace: notesnook
spec:
selector:
app: notesnook-sync
ports:
- { name: http, port: 5264, targetPort: 5264 }
+26
View File
@@ -0,0 +1,26 @@
# sealed-secrets
Controller v0.38.4, applied directly (not via ArgoCD — matches how cert-manager is handled;
ArgoCD apps here are workloads, not cluster infra):
kubectl apply -f apps/sealed-secrets/controller.yaml
`controller.yaml` is the upstream release manifest plus ONE local change:
`--key-renew-period=0`. Upstream defaults to rotating the sealing key every 30 days;
kubeseal always seals with the newest key, so a rotation would silently invalidate the
off-cluster key backup for anything sealed afterwards. Pinning to one key keeps the
backup permanently valid. Rotation buys little here — it never re-seals existing secrets.
## Disaster recovery (order matters)
# 1. install controller
kubectl apply -f apps/sealed-secrets/controller.yaml
# 2. restore the private key BEFORE applying any SealedSecret,
# or a fresh key is generated and nothing decrypts
gpg -d /mnt/bulk/backups/sealed-secrets/master-key-YYYYMMDD.gpg | kubectl apply -f -
kubectl -n kube-system rollout restart deploy/sealed-secrets-controller
# 3. apply the SealedSecrets
kubectl apply -f apps/cluster-secrets/ # cert-manager, argocd, solitaire
# (apps/notesnook + apps/nightscout SealedSecrets come via ArgoCD)
Passphrase for the .gpg is in the password manager, and nowhere else.
+415
View File
@@ -0,0 +1,415 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
spec:
minReadySeconds: 30
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
name: sealed-secrets-controller
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
spec:
containers:
- args: []
command:
- controller
env: []
args:
- --key-renew-period=0 # renewal disabled: keeps ONE key so the GPG key backup stays valid forever
image: docker.io/bitnami/sealed-secrets-controller:0.38.4
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /healthz
port: http
name: sealed-secrets-controller
ports:
- containerPort: 8080
name: http
- containerPort: 8081
name: metrics
readinessProbe:
httpGet:
path: /healthz
port: http
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
stdin: false
tty: false
volumeMounts:
- mountPath: /tmp
name: tmp
imagePullSecrets: []
initContainers: []
securityContext:
fsGroup: 65534
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
serviceAccountName: sealed-secrets-controller
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: tmp
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: sealedsecrets.bitnami.com
spec:
group: bitnami.com
names:
kind: SealedSecret
listKind: SealedSecretList
plural: sealedsecrets
singular: sealedsecret
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: |-
SealedSecret is the K8s representation of a "sealed Secret" - a
regular k8s Secret that has been sealed (encrypted) using the
controller's key.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: SealedSecretSpec is the specification of a SealedSecret.
properties:
data:
description: Data is deprecated and will be removed eventually. Use
per-value EncryptedData instead.
format: byte
type: string
encryptedData:
additionalProperties:
type: string
type: object
x-kubernetes-preserve-unknown-fields: true
template:
description: |-
Template defines the structure of the Secret that will be
created from this sealed secret.
properties:
data:
additionalProperties:
type: string
description: Keys that should be templated using decrypted data.
nullable: true
type: object
immutable:
description: |-
Immutable, if set to true, ensures that data stored in the Secret cannot
be updated (only object metadata can be modified).
If not set to true, the field can be modified at any time.
Defaulted to nil.
type: boolean
metadata:
description: |-
Standard object's metadata.
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
nullable: true
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
x-kubernetes-preserve-unknown-fields: true
type:
description: Used to facilitate programmatic handling of secret
data.
type: string
type: object
required:
- encryptedData
type: object
status:
description: SealedSecretStatus is the most recently observed status of
the SealedSecret.
properties:
conditions:
description: Represents the latest available observations of a sealed
secret's current state.
items:
description: SealedSecretCondition describes the state of a sealed
secret at a certain point.
properties:
lastTransitionTime:
description: Last time the condition transitioned from one status
to another.
format: date-time
type: string
lastUpdateTime:
description: The last time this condition was updated.
format: date-time
type: string
message:
description: A human readable message indicating details about
the transition.
type: string
reason:
description: The reason for the condition's last transition.
type: string
status:
description: |-
Status of the condition for a sealed secret.
Valid values for "Synced": "True", "False", or "Unknown".
type: string
type:
description: |-
Type of condition for a sealed secret.
Valid value: "Synced"
type: string
required:
- status
- type
type: object
type: array
observedGeneration:
description: ObservedGeneration reflects the generation most recently
observed by the sealed-secrets controller.
format: int64
type: integer
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
spec:
ports:
- port: 8080
targetPort: 8080
selector:
name: sealed-secrets-controller
type: ClusterIP
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations: {}
labels:
name: sealed-secrets-service-proxier
name: sealed-secrets-service-proxier
namespace: kube-system
rules:
- apiGroups:
- ""
resourceNames:
- sealed-secrets-controller
resources:
- services
verbs:
- get
- apiGroups:
- ""
resourceNames:
- 'http:sealed-secrets-controller:'
- http:sealed-secrets-controller:http
- sealed-secrets-controller
resources:
- services/proxy
verbs:
- create
- get
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
name: sealed-secrets-controller-metrics
name: sealed-secrets-controller-metrics
namespace: kube-system
spec:
ports:
- port: 8081
targetPort: 8081
selector:
name: sealed-secrets-controller
type: ClusterIP
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-service-proxier
name: sealed-secrets-service-proxier
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: sealed-secrets-service-proxier
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:authenticated
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: sealed-secrets-key-admin
subjects:
- kind: ServiceAccount
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations: {}
labels:
name: sealed-secrets-key-admin
name: sealed-secrets-key-admin
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secrets-unsealer
subjects:
- kind: ServiceAccount
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations: {}
labels:
name: secrets-unsealer
name: secrets-unsealer
rules:
- apiGroups:
- bitnami.com
resources:
- sealedsecrets
verbs:
- get
- list
- watch
- apiGroups:
- bitnami.com
resources:
- sealedsecrets/status
verbs:
- update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- create
- update
- delete
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
+20
View File
@@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: notesnook
namespace: argocd
spec:
project: default
source:
repoURL: https://git.aleshym.co/funman300/k3s-homelab.git
targetRevision: main
path: apps/notesnook
destination:
server: https://kubernetes.default.svc
namespace: notesnook
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,222 @@
# Notesnook self-host → k3s migration — design
**Date:** 2026-07-15
**Status:** Approved design, pending spec review → implementation plan
## Goal
Move the self-hosted Notesnook stack from the docker-compose deployment on host
`10.10.0.5` (LXC on Proxmox `pve`) into the k3s cluster (CT 114, node IP
`10.10.0.100`), managed by ArgoCD via the `k3s-homelab` GitOps repo. The existing
account, notes, encryption keys, and attachments must be preserved.
## Current state (source)
- **Host:** `manage@10.10.0.5`, docker-compose stack in `~/notesnook`.
- **Services:** MongoDB 7.0.12 (replica set `rs0`), MinIO (S3), identity/auth,
sync, sse/events, monograph, plus bootstrap helpers (`validate`, `setup-s3`)
and `autoheal`.
- **Public hosts** (Traefik on the compose box, cert via Cloudflare resolver):
`auth`, `sync` (+ apex `notes`), `events`, `monograph`, `attachments`,
`minio` — all under `notes.aleshym.co`.
- **Data lives in:** Mongo databases `identity` and `notesnook`; MinIO bucket
`attachments`.
- **Root cause history:** login-code email was failing because SMTP was left at
`CHANGE_ME`; now fixed to Gmail SMTP. That corrected config carries into the
migration.
## Target cluster conventions (observed)
- **GitOps:** ArgoCD (no app-of-apps). Monorepo
`https://git.aleshym.co/funman300/k3s-homelab.git`, branch `main`. App
manifests under `apps/<name>/` (kustomize); the `Application` CR committed at
`argocd/<name>.yaml` with `automated {prune, selfHeal}` + `CreateNamespace=true`.
- **Ingress:** Traefik (`ingressClassName: traefik`), entrypoint `websecure`.
- **TLS:** cert-manager clusterissuer `letsencrypt-prod` via **Cloudflare DNS-01**.
Per-host TLS secret named `<something>-tls`.
- **Storage:** single-node `local-path` (default), `ReadWriteOnce`, `Delete`
reclaim. **Implication:** deleting a PVC deletes its data — treat Mongo/MinIO
PVCs as precious.
- **Secrets:** no sealed-secrets / external-secrets controller. Convention is a
committed `*-secret.yaml.example`; the real `*-secret.yaml` is **gitignored**
(`apps/**/*-secret.yaml`) and applied once with `kubectl apply`. The
`Application` CR uses `ignoreDifferences` on the Secret's `/data` so ArgoCD
won't fight the out-of-band secret.
- **Internal DNS:** resolver `10.10.0.8` holds split-horizon `*.aleshym.co` → LAN
IP records (`ns.aleshym.co → 10.10.0.100`, `auth.notes.aleshym.co → 10.10.0.5`
today). This is where the cutover repoint happens. Public authority for
`aleshym.co` is Cloudflare (used only for the ACME DNS-01 challenge).
## Architecture
Namespace: `notesnook`. Repo path: `apps/notesnook/`. ArgoCD app:
`argocd/notesnook.yaml`.
| Component | Image | k8s workload | Service (in-cluster) | Public ingress host → port |
|---|---|---|---|---|
| MongoDB `rs0` | `mongo:7.0.12` | StatefulSet (1) + headless Service + init Job | `notesnook-db:27017` | none |
| MinIO | `minio/minio:RELEASE.2024-07-29T22-14-52Z` | Deployment + PVC | `notesnook-s3:9000` | `attachments.notes.aleshym.co` → 9000 |
| Identity/auth | `streetwriters/identity:latest` | Deployment | `notesnook-identity:8264` | `auth.notes.aleshym.co` → 8264 |
| Sync | `streetwriters/notesnook-sync:latest` | Deployment | `notesnook-sync:5264` | `sync.notes.aleshym.co` + `notes.aleshym.co` → 5264 |
| SSE/events | `streetwriters/sse:latest` | Deployment | `notesnook-sse:7264` | `events.notes.aleshym.co` → 7264 |
| Monograph | `streetwriters/monograph:latest` | Deployment | `notesnook-monograph:3000` | `monograph.notes.aleshym.co` → 3000 |
**Dropped from compose:** `autoheal` (replaced by k8s liveness/readiness probes),
`validate` (env presence enforced by manifests), `setup-s3` (folded into a MinIO
bucket-create init Job / or `mc` init container).
### MongoDB replica set (the one real deviation from the nightscout template)
Notesnook's connection strings use `?replSet=rs0` and the sync server relies on
change streams/transactions, which require a replica set. Plan:
- **StatefulSet** (1 replica) `notesnook-db`, `command: [mongod, --replSet, rs0, --bind_ip_all]`.
- **Headless Service** `notesnook-db` for stable DNS.
- **One-time init Job** (or `postStart`) runs
`rs.initiate({_id:"rs0", members:[{_id:0, host:"notesnook-db:27017"}]})`,
idempotent (skip if already initialized). The RS member host must be the stable
Service name `notesnook-db:27017` so clients using `?replSet=rs0` resolve the
primary correctly.
- No Mongo auth (matches current compose: `mongodb://notesnook-db:27017`, no
credentials). Access is namespace-internal only.
### Config vs secrets
**ConfigMap `notesnook-config`** (committed, non-secret):
```
INSTANCE_NAME=aleshym-notesnook
DISABLE_SIGNUPS=true
NOTESNOOK_APP_PUBLIC_URL=https://app.notesnook.com
AUTH_SERVER_PUBLIC_URL=https://auth.notes.aleshym.co
MONOGRAPH_PUBLIC_URL=https://monograph.notes.aleshym.co
ATTACHMENTS_SERVER_PUBLIC_URL=https://attachments.notes.aleshym.co
NOTESNOOK_CORS_ORIGINS=https://app.notesnook.com,https://notes.aleshym.co
KNOWN_PROXIES= # may need the Traefik pod CIDR; see open items
# service-discovery (in-cluster names replace compose hostnames)
NOTESNOOK_SERVER_HOST=notesnook-sync
NOTESNOOK_SERVER_PORT=5264
IDENTITY_SERVER_HOST=notesnook-identity
IDENTITY_SERVER_PORT=8264
SSE_SERVER_HOST=notesnook-sse
SSE_SERVER_PORT=7264
IDENTITY_SERVER_URL=https://auth.notes.aleshym.co
NOTESNOOK_APP_HOST=https://app.notesnook.com
# S3/MinIO non-secret
S3_INTERNAL_SERVICE_URL=http://notesnook-s3:9000
S3_INTERNAL_BUCKET_NAME=attachments
S3_SERVICE_URL=https://attachments.notes.aleshym.co
S3_BUCKET_NAME=attachments
S3_REGION=us-east-1
MONGODB_CONNECTION_STRING (identity)=mongodb://notesnook-db:27017/identity?replSet=rs0
MONGODB_CONNECTION_STRING (sync)=mongodb://notesnook-db:27017/?replSet=rs0
```
**Secret `notesnook-secret`** (out-of-band, `*.example` committed, real gitignored):
```
NOTESNOOK_API_SECRET=... # reuse existing value from old .env
SMTP_USERNAME=funman300@gmail.com
SMTP_PASSWORD=... # Gmail app password
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
MINIO_ROOT_USER=notesnook
MINIO_ROOT_PASSWORD=... # reuse existing value
S3_ACCESS_KEY_ID=notesnook # = MINIO_ROOT_USER
S3_ACCESS_KEY=... # = MINIO_ROOT_PASSWORD
# TWILIO_* : present in old .env but SMS 2FA unused; omit unless in use
```
Reusing `NOTESNOOK_API_SECRET`, MinIO creds, and the Mongo data verbatim is what
keeps the migrated account valid — do **not** regenerate the API secret.
## Repo layout to add
```
apps/notesnook/
kustomization.yaml
namespace.yaml
notesnook-config.yaml # ConfigMap
notesnook-secret.yaml.example # secret template (real applied out-of-band)
db-statefulset.yaml # mongo rs0
db-service.yaml # headless
db-init-job.yaml # rs.initiate (idempotent)
minio-pvc.yaml
minio-deployment.yaml
minio-service.yaml
minio-bucket-job.yaml # create 'attachments' bucket
identity-deployment.yaml + identity-service.yaml + identity-ingress.yaml
sync-deployment.yaml + sync-service.yaml + sync-ingress.yaml
sse-deployment.yaml + sse-service.yaml + sse-ingress.yaml
monograph-deployment.yaml+ monograph-service.yaml+ monograph-ingress.yaml
attachments-ingress.yaml # attachments.notes.aleshym.co -> notesnook-s3:9000
argocd/notesnook.yaml
```
`.gitignore` already covers `apps/**/*-secret.yaml`.
## Data migration procedure
Runs during the cutover window; both stacks reachable from the migration host.
1. **Mongo:** `mongodump` the `identity` and `notesnook` DBs from the compose
Mongo (`docker exec notesnook-db mongodump --archive`), stream to the k8s Mongo
via `kubectl exec` + `mongorestore --archive --nsInclude 'identity.*'
--nsInclude 'notesnook.*'`. Verify collection counts match on both sides.
2. **MinIO:** `mc mirror` the `attachments` bucket from the compose MinIO to the
k8s MinIO (mirror via a temporary `mc` pod/job with both aliases, or
port-forward). Verify object count/size.
3. **Sanity:** confirm identity DB user document and notesnook sync docs present.
## Cutover procedure
1. Deploy the empty k8s stack (commit → ArgoCD sync). Verify all pods healthy,
ingress created, **TLS certs issued** for all 5 hosts (temporary test hostnames
or `/etc/hosts` override so we can validate before flipping real DNS).
2. Announce brief downtime. Stop client sync / freeze writes on old stack
(`docker compose stop` the app services, keep DB+MinIO up for the dump).
3. Run the data migration (above).
4. **DNS cutover on `10.10.0.8`:** repoint
`auth`/`sync`/`events`/`monograph`/`attachments`.notes.aleshym.co **and** apex
`notes.aleshym.co` from `10.10.0.5``10.10.0.100`. Lower TTL beforehand if
possible; flush caches after.
5. Verify end-to-end against k8s: request a login code (email arrives), log in,
sync a note, open an attachment, load a monograph link.
6. `docker compose down` the old stack (data retained) — rollback = flip DNS back
and `docker compose up -d`.
## Rollback
Until decommission, rollback is: repoint the 6 records on `10.10.0.8` back to
`10.10.0.5` and `docker compose up -d` on the old host. Because writes were frozen
during cutover, no data diverges.
## Decommission (≈1 week after stable cutover)
Remove the compose stack and its data on `10.10.0.5`
(`docker compose down -v`, remove `~/notesnook`), and reclaim the LXC if no longer
needed. Update memory notes to point at the k8s deployment.
## Verification / success criteria
- All Notesnook pods `Running`/healthy; Mongo RS `PRIMARY`; MinIO bucket present.
- All 5 ingress hosts serve valid Let's Encrypt certs.
- Existing account logs in (email code delivered) — **no re-signup**.
- Pre-existing notes + attachments visible in the client after sync.
- Monograph public link resolves.
## Open items / access still needed
- **Git push access** to `git.aleshym.co/funman300/k3s-homelab` (clone was
anonymous/read-only). Decide branch vs PR workflow.
- **Edit access to internal DNS `10.10.0.8`** for the cutover (mechanism/creds TBD
— AdGuard/Pi-hole/unbound?).
- **`KNOWN_PROXIES`:** old value empty; behind Traefik the sync/identity servers
may need the Traefik pod source CIDR here for correct client-IP/proxy handling.
Confirm during implementation.
- **Public access path:** are the `notes.*` hosts reachable externally (Cloudflare
tunnel / netbird), or LAN/VPN-only? Affects whether any Cloudflare records also
need updating, or just internal DNS.
- **Twilio SMS 2FA:** present in old `.env`; assumed unused (email 2FA works). Omit
unless confirmed in use.
```