175 lines
5.7 KiB
Markdown
175 lines
5.7 KiB
Markdown
# Vikunja
|
|
|
|
Self-hosted to-do / task management (Vikunja `2.6.0`), served on the internal network.
|
|
|
|
## URL
|
|
|
|
- Web: `https://vikunja.aleshym.co`
|
|
|
|
## Internal DNS
|
|
|
|
Pi-hole local DNS record (create it manually if not present):
|
|
|
|
```
|
|
vikunja.aleshym.co A 10.10.0.100
|
|
```
|
|
|
|
`10.10.0.100` is the k3s node and the `traefik` LoadBalancer's external IP — the same
|
|
destination Vaultwarden uses for `vault.aleshym.co`.
|
|
|
|
## Architecture
|
|
|
|
This app is **private** (LAN/VPN only, no public WAN exposure), exactly like
|
|
Vaultwarden. It is deliberately absent from the k3s `catchall-to-docker-apps` public
|
|
host list and from the Docker Traefik `wan` entrypoint. Pi-hole resolves the hostname to
|
|
the node IP, and Traefik terminates TLS for it.
|
|
|
|
```
|
|
Pi-hole DNS
|
|
|
|
|
v
|
|
10.10.0.100 (k3s node / Traefik LoadBalancer external IP)
|
|
|
|
|
Traefik (ingressClassName traefik, websecure entrypoint, letsencrypt-prod)
|
|
|
|
|
Vikunja Service (ClusterIP, port 80 -> pod http)
|
|
|
|
|
Vikunja Pod :3456
|
|
|
|
|
+-- SQLite PVC (/db/vikunja.db)
|
|
|
|
|
+-- Files PVC (/app/vikunja/files)
|
|
```
|
|
|
|
No NodePort / hostNetwork / separate LoadBalancer is used for Vikunja; Traefik remains the
|
|
single ingress point.
|
|
|
|
## Secret generation
|
|
|
|
The service secret (`VIKUNJA_SERVICE_SECRET`) must come from a Kubernetes Secret named
|
|
`vikunja-secret` in the `vikunja` namespace, which is generated from a **SealedSecret**.
|
|
The encrypted sealed secret is *not* committed in plaintext form.
|
|
|
|
Generate the real `apps/vikunja/vikunja-sealedsecret.yaml` from the repo root:
|
|
|
|
```bash
|
|
apps/vikunja/generate-secret.sh
|
|
```
|
|
|
|
This script:
|
|
|
|
1. generates a strong random secret (`openssl rand -hex 64`),
|
|
2. builds the Secret with `kubectl create ... --dry-run=client` (nothing touches the cluster),
|
|
3. pipes it through `kubeseal` using this cluster's controller
|
|
(`--controller-name sealed-secrets-controller --controller-namespace kube-system`),
|
|
4. writes the encrypted manifest to `apps/vikunja/vikunja-sealedsecret.yaml`.
|
|
|
|
It never prints the secret. Commit the generated file before/alongside the Argo CD sync so
|
|
Vikunja gets a stable JWT-signing secret across restarts. Equivalent manual one-liner:
|
|
|
|
```bash
|
|
SECRET="$(openssl rand -hex 64)"
|
|
kubectl create secret generic vikunja-secret \
|
|
--namespace vikunja \
|
|
--from-literal=VIKUNJA_SERVICE_SECRET="$SECRET" \
|
|
--dry-run=client -o yaml \
|
|
| kubeseal \
|
|
--controller-name sealed-secrets-controller \
|
|
--controller-namespace kube-system \
|
|
--format yaml \
|
|
--namespace vikunja \
|
|
--name vikunja-secret \
|
|
> apps/vikunja/vikunja-sealedsecret.yaml
|
|
unset SECRET
|
|
```
|
|
|
|
The controller's private key must be restored on the cluster before unsealing works (see
|
|
`apps/sealed-secrets/README.md`).
|
|
|
|
## Deployment
|
|
|
|
`argocd/vikunja.yaml` registers an Argo CD `Application` pointing at:
|
|
|
|
```
|
|
repoURL: https://git.aleshym.co/funman300/k3s-homelab.git
|
|
targetRevision: main
|
|
path: apps/vikunja
|
|
destination: https://kubernetes.default.svc / namespace vikunja
|
|
automated: prune: true, selfHeal: true
|
|
syncOptions: CreateNamespace=true
|
|
```
|
|
|
|
Flow:
|
|
|
|
1. commit `apps/vikunja/*` and `argocd/vikunja.yaml` to `main`,
|
|
2. push,
|
|
3. apply the Application once: `kubectl apply -f argocd/vikunja.yaml`,
|
|
4. Argo CD syncs the app, creates the `vikunja` namespace, deploys everything, and
|
|
continuously self-heals it back to the Git state (prunes removed resources too).
|
|
|
|
## Verification
|
|
|
|
```bash
|
|
kubectl -n vikunja get all
|
|
kubectl -n vikunja get pvc
|
|
kubectl -n vikunja get ingress
|
|
kubectl -n vikunja describe deployment vikunja
|
|
kubectl -n vikunja logs deployment/vikunja
|
|
curl -k https://vikunja.aleshym.co/health
|
|
```
|
|
|
|
## Registration
|
|
|
|
Registration is **enabled** initially so the first account can be created at
|
|
`https://vikunja.aleshym.co`. It is controlled by:
|
|
|
|
- Config key: `service.enableregistration`
|
|
- Environment variable: `VIKUNJA_SERVICE_ENABLEREGISTRATION`
|
|
|
|
It is set to `"true"` in `apps/vikunja/vikunja-deployment.yaml`. **After creating your
|
|
account**, disable new registrations:
|
|
|
|
1. Edit `apps/vikunja/vikunja-deployment.yaml`
|
|
2. Set `VIKUNJA_SERVICE_ENABLEREGISTRATION` to `"false"`
|
|
3. Commit and push
|
|
4. Argo CD (selfHeal) syncs the new Deployment and the Recreate strategy rolls the pod
|
|
|
|
Once disabled, additional accounts can still be created via the CLI
|
|
(`vikunja user create`), e.g. in a temporary pod mounting the same PVC.
|
|
|
|
## Troubleshooting
|
|
|
|
```bash
|
|
# pod status / events / logs
|
|
kubectl -n vikunja get pods -o wide
|
|
kubectl -n vikunja get events --sort-by=.lastTimestamp
|
|
kubectl -n vikunja logs deployment/vikunja
|
|
|
|
# PVC status (bound? local-path volume created?)
|
|
kubectl -n vikunja get pvc
|
|
kubectl -n vikunja describe pvc vikunja-db vikunja-files
|
|
|
|
# ingress + certificate (TLS via letsencrypt-prod)
|
|
kubectl -n vikunja describe ingress
|
|
kubectl -n vikunja get certificate
|
|
kubectl -n vikunja describe certificate
|
|
|
|
# sealed secret + generated secret
|
|
kubectl -n vikunja get sealedsecret
|
|
kubectl -n vikunja get sealedsecret vikunja-secret -o yaml
|
|
kubectl -n vikunja get secret vikunja-secret
|
|
```
|
|
|
|
### SQLite / volume permissions
|
|
|
|
- Runtime user is UID/GID `1000` (the official image sets `USER 1000`); the pod sets
|
|
`runAsUser/runAsGroup/fsGroup: 1000` so the `local-path`-provisioned volumes are handed
|
|
to group 1000 and remain writable. If you ever see "permission denied" writing
|
|
`/db/vikunja.db`, verify the PVCs are `Bound` and the fsGroup is applied
|
|
(`kubectl -n vikunja describe pod`).
|
|
- SQLite + `Recreate` strategy: only one pod runs at a time, so the old pod never writes
|
|
the database while the new one migrates/launches.
|
|
- The `local-path` storage class has `reclaimPolicy: Delete` — deleting a PVC destroys the
|
|
database/files. Back up `/db` and `/app/vikunja/files` (see `apps/sealed-secrets` backups
|
|
for the general DR posture; adapt for vikunja volumes).
|