Published: Feb 20, 2025 by Isaac Johnson
I was reading XDA the other day when I came across this guide for self-hosting Vikunja Task Management.
The guide Ayush Pande wrote focused on using Proxmox to create a VM to run their local install. However, when I checked Vikunja’s Gitea, I saw they have a helm chart we can use.
Vikunja is an OS To-Do app made and hosted in the EU. This is probably something we should highlight here on out. There is a cloud option for 4 Euros/mon (or 40/year) for personal, but the stack is entirely open-source and easy to host on a VM, Docker or Kubernetes.
Today we’ll start with Kubernetes and also look at Docker. In all honesty, I had planned to dig into a few Open-Source apps but really just kind of fell in love with this one and wanted to dive deep.
Vicuña, the animal
I was curious about the logo and name. I searched only to find Vikunja is the German word for the “Lama vicugna”, or just “Vicuna” in English.
Vicuña/Vikunja is wild relative of the Llama that lives high in the Andes. They produce a fine wool which is quite expensive due to the fact they can only be shorn once every three years. And that’s not a throw away comment - I checked - a Vicuña baseball cap is US$2100 and sweaters are US$2000-4000. Wow.
These are amazing animals high in the Andes and have been protected since 1974 when they were down to only 6000 left (today their numbers are above 350k). Their fur has the finest fibers in the word - at 12 micrometers (to cashmere’s 14 to 19). Read more on this amazing animal here.
But let’s get back to the Task Manager named after this fine species…
Vikunja Install
We can start with the official helm chart
(spoiler: I do ultimately move on to using Docker, forwarded through k8s, as I encountered a bug i couldn’t get past. It still functioned, but had a persistent typescript error.)
I can pull down their helm chart repo
builder@LuiGi:~/Workspaces$ git clone https://kolaente.dev/vikunja/helm-chart.git
Cloning into 'helm-chart'...
remote: Enumerating objects: 169, done.
remote: Counting objects: 100% (57/57), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 169 (delta 47), reused 30 (delta 30), pack-reused 112 (from 1)
Receiving objects: 100% (169/169), 82.63 KiB | 528.00 KiB/s, done.
Resolving deltas: 100% (86/86), done.
builder@LuiGi:~/Workspaces$ cd helm-chart/
I had an initial error with the chart lock
builder@LuiGi:~/Workspaces/helm-chart$ helm dependency build
Error: the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies
But I just removed it and moved on
$ helm dependency build
Getting updates for unmanaged Helm repositories...
...Successfully got an update from the "https://bjw-s.github.io/helm-charts" chart repository
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "librenms" chart repository
...Successfully got an update from the "backstage" chart repository
...Successfully got an update from the "datadog" chart repository
...Successfully got an update from the "onedev" chart repository
...Successfully got an update from the "nicholaswilde" chart repository
...Successfully got an update from the "grafana" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 3 charts
Downloading redis from repo https://charts.bitnami.com/bitnami
Downloading postgresql from repo https://charts.bitnami.com/bitnami
Pulled: registry-1.docker.io/bitnamicharts/postgresql:16.4.5
Digest: sha256:7e2bd8ed9d2ac7673a5730141301d038fa7b7cf130503c8dd5dcbc6ddfe0e377
Downloading common from repo https://bjw-s.github.io/helm-charts
Deleting outdated charts
I can now install the stack with default values
$ helm install vikunja -n vikuna --create-namespace ./
NAME: vikunja
LAST DEPLOYED: Thu Feb 6 17:34:37 2025
NAMESPACE: vikuna
STATUS: deployed
REVISION: 1
TEST SUITE: None
After a moment, i checked the pods and saw the main app was in a crash loop
$ kubectl get po -n vikuna
NAME READY STATUS RESTARTS AGE
vikunja-typesense-b4f5c477f-lv6qc 1/1 Running 0 99s
vikunja-postgresql-0 1/1 Running 0 99s
vikunja-7776696b7-bfksv 0/1 CrashLoopBackOff 4 (4s ago) 99s
Seems like it didnt connect to the database (perhaps it was ready yet)
$ kubectl logs vikunja-7776696b7-bfksv -n vikuna
2025-02-06T23:36:18Z: INFO ▶ 001 Using config file: /etc/vikunja/config.yml
2025-02-06T23:36:18Z: INFO ▶ 002 Running migrations…
2025-02-06T23:36:18Z: CRITICAL ▶ 004 Migration failed: dial tcp [::1]:5432: connect: connection refused
I’ll bounce the pod and check again
builder@LuiGi:~/Workspaces/helm-chart$ kubectl delete po vikunja-7776696b7-bfksv -n vikuna
pod "vikunja-7776696b7-bfksv" deleted
builder@LuiGi:~/Workspaces/helm-chart$ kubectl get po -n vikuna
NAME READY STATUS RESTARTS AGE
vikunja-typesense-b4f5c477f-lv6qc 1/1 Running 0 3m29s
vikunja-postgresql-0 1/1 Running 0 3m29s
vikunja-7776696b7-f2rnz 0/1 CrashLoopBackOff 1 (4s ago) 7s
builder@LuiGi:~/Workspaces/helm-chart$ kubectl logs vikunja-7776696b7-f2rnz -n vikuna
2025-02-06T23:38:31Z: INFO ▶ 001 Using config file: /etc/vikunja/config.yml
2025-02-06T23:38:31Z: INFO ▶ 002 Running migrations…
2025-02-06T23:38:31Z: CRITICAL ▶ 004 Migration failed: dial tcp [::1]:5432: connect: connection refused
Still no go.
I suspect it need the DB Host name set
$ helm upgrade vikunja -n vikuna --set vikunja.env.VIKUNJA_DATABASE_HOST=vikunja-postgresql ./
Release "vikunja" has been upgraded. Happy Helming!
NAME: vikunja
LAST DEPLOYED: Thu Feb 6 17:47:26 2025
NAMESPACE: vikuna
STATUS: deployed
REVISION: 2
TEST SUITE: None
Looking better
$ kubectl get po -n vikuna
NAME READY STATUS RESTARTS AGE
vikunja-typesense-b4f5c477f-lv6qc 1/1 Running 0 13m
vikunja-postgresql-0 1/1 Running 0 13m
vikunja-85d5577974-9vsc8 1/1 Running 0 52s
I’ll try port-forwarding to the service
builder@LuiGi:~/Workspaces/helm-chart$ kubectl get svc -n vikuna
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
vikunja-postgresql-hl ClusterIP None <none> 5432/TCP 14m
vikunja ClusterIP 10.43.117.87 <none> 3456/TCP 14m
vikunja-typesense ClusterIP 10.43.138.202 <none> 8108/TCP 14m
vikunja-postgresql ClusterIP 10.43.47.186 <none> 5432/TCP 14m
builder@LuiGi:~/Workspaces/helm-chart$ kubectl port-forward svc/vikunja -n vikuna 3456:3456
Forwarding from 127.0.0.1:3456 -> 3456
Forwarding from [::1]:3456 -> 3456
I’m met with the login page
I’ll go ahead and create an account
I think there might be a funny redirect or something as when I saved, it gave an error and a retry suggested I already existed
I can login but I do see the odd “error” pop up from time to time
I’ll try creating a project
But I keep seeing errors
A refresh of the page showed it clearly was creating them - again, must be a redirect issue
As I explored, I brought up the console to see what was vomiting. Seems like a bug in a typescript promise
I’m going to pause and make a proper ingress to see if this is sorted by way of NGinx Ingress controller.
I first need a DNS name
$ gcloud dns --project=myanthosproject2 record-sets create vikunja.steeped.space --zone="steepedspace" --type="A" --ttl="300" --rrdatas="75.73.224.240"
NAME TYPE TTL DATA
vikunja.steeped.space. A 300 75.73.224.240
Then apply
$ kubectl apply -n vikuna -f ./ingress.yaml
ingress.networking.k8s.io/vikunja configured
I can now see the Ingress created but the Cert still coming up
builder@LuiGi:~/Workspaces/helm-chart$ kubectl get ingress -n vikuna
NAME CLASS HOSTS ADDRESS PORTS AGE
vikunja <none> vikunja.steeped.space 80, 443 29m
builder@LuiGi:~/Workspaces/helm-chart$ kubectl get cert -n vikuna
NAME READY SECRET AGE
vikunja-tls False vikunja-tls 39s
I’m not sure what happened. I did typo the namespace initially. But the order shows error but Order details shows good
vikuna vikunja-tls-1-4080831633 errored 5m4s
builder@LuiGi:~/Workspaces/helm-chart$ kubectl describe order vikunja-tls-1-4080831633 -n vikuna | tail -n5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Created 5m7s cert-manager-orders Created Challenge resource "vikunja-tls-1-4080831633-1069710835" for domain "vikunja.steeped.space"
Normal Complete 4m1s cert-manager-orders Order completed successfully
I’ll clean and try again
builder@LuiGi:~/Workspaces/helm-chart$ kubectl delete -n vikuna -f ./ingress.yaml
ingress.networking.k8s.io "vikunja" deleted
builder@LuiGi:~/Workspaces/helm-chart$ kubectl delete order vikunja-tls-1-4080831633 -n vikuna
Error from server (NotFound): orders.acme.cert-manager.io "vikunja-tls-1-4080831633" not found
builder@LuiGi:~/Workspaces/helm-chart$ kubectl apply -n vikuna -f ./ingress.yaml
ingress.networking.k8s.io/vikunja created
This time it was fine - I must have screwed up the cert-manager with two similar rapid fire requests
$ kubectl get cert -n vikuna
NAME READY SECRET AGE
vikunja-tls True vikunja-tls 20s
But even in logging in to the website i do see that error persist
Let me try a newer typesense than the 0.25.1
specified in the helm chart
helm upgrade vikunja -n vikuna --set vikunja.env.VIKUNJA_DATABASE_HOST=vikunja-postgresql --set typesense.image.tag=28.0.rc35 ./
Something I’m noticing - the helm upgrade is replacing my ingress, but more importantly, i see it is using “Prefix” for the path type
- host: vikunja.local
http:
paths:
- backend:
service:
name: vikunja
port:
number: 3456
path: /
pathType: Prefix
I’ll try the same
builder@LuiGi:~/Workspaces/helm-chart$ kubectl delete ingress vikunja -n vikuna
ingress.networking.k8s.io "vikunja" deleted
builder@LuiGi:~/Workspaces/helm-chart$ vi ingress.yaml
builder@LuiGi:~/Workspaces/helm-chart$ kubectl apply -f ./ingress.yaml -n vikuna
ingress.networking.k8s.io/vikunja created
builder@LuiGi:~/Workspaces/helm-chart$ cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: gcpleprod2
ingress.kubernetes.io/proxy-body-size: "0"
ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.org/client-max-body-size: "0"
nginx.org/proxy-connect-timeout: "3600"
nginx.org/proxy-read-timeout: "3600"
nginx.org/websocket-services: vikunja
name: vikunja
spec:
rules:
- host: vikunja.steeped.space
http:
paths:
- backend:
service:
name: vikunja
port:
number: 3456
path: /
pathType: Prefix
tls:
- hosts:
- vikunja.steeped.space
secretName: vikunja-tls
My last attempt was to set a JWT directly because some of the errors looked like they might be token related based on the console log
$ helm upgrade vikunja -n vikuna --set vikunja.env.VIKUNJA_DATABASE_HOST=vikunja-postgresql --set vikunja.env.VIKUNJA_SERVICE_JWTSECRET=201bdeee0e137adc6641d039da293c92ad01325b86a5ba9865ada4cc75d40ecf01b35c3fdfbd6cee767db591365434ae3dbec31b96af7164c2c602fd1defa1e2f9f9b9a6e2cc06ebe09d11a60e1ae72d6131b42def7be210f648f6ca57a5d1f8054cdaa11ab5a4a4579b8557a7fca5c2b791471e2f3ace0c19a5929f42fbaa2039a69663dd86cf8c6a3ebd1ae8eb5b49fe10184f9bb2d2a7a950d337928c45d2f62030aa63f22eb5e5e24b6253d00ef5302ec47f675d59598626d99afde2e7f840bbf17a4f2dca6be2c43d96495dbe3422c0b828da5970ab0f6f377bc77e2feea2b1909023293b4301788798a1ed70d4eb63ed5b21988c9dcf147ed4a78e428d --set typesense.image.tag=28.0.rc35 --set vikunja.ingress.main.enabled=fal
se ./
Release "vikunja" has been upgraded. Happy Helming!
NAME: vikunja
LAST DEPLOYED: Thu Feb 6 18:35:27 2025
NAMESPACE: vikuna
STATUS: deployed
REVISION: 5
TEST SUITE: None
But still errors persisted.
I uninstalled
builder@LuiGi:~/Workspaces/helm-chart$ helm delete vikunja -n vikuna
release "vikunja" uninstalled
builder@LuiGi:~/Workspaces/helm-chart$ kubectl get pvc -n vikuna
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-vikunja-postgresql-0 Bound pvc-1b01c82f-e6eb-4db8-8d3f-0a6e547e3537 8Gi RWO local-path 68m
builder@LuiGi:~/Workspaces/helm-chart$ kubectl delete pvc data-vikunja-postgresql-0 -n vikuna
persistentvolumeclaim "data-vikunja-postgresql-0" deleted
builder@LuiGi:~/Workspaces/helm-chart$ kubectl get pvc -n vikuna
No resources found in vikuna namespace.
Then tried the last release (0.24.5)
$ helm install vikunja -n vikuna --set vikunja.env.VIKUNJA_DATABASE_HOST=vikunja-postgresql --set vikunja.env.VIKUNJA_SERVICE_JWTSECRET=201bdeee0e137adc6641d039da293c92ad01325b86a5ba9865ada4cc75d40ecf01b35c3fdfbd6cee767db591365434ae3dbec31b96af7164c2c602fd1defa1e2f9f9b9a6e2cc06ebe09d11a60e1ae72d6131b42def7be210f648f6ca57a5d1f8054cdaa11ab5a4a4579b8557a7fca5c2b791471e2f3ace0c19a5929f42fbaa2039a69663dd86cf8c6a3ebd1ae8eb5b49fe10184f9bb2d2a7a950d337928c45d2f62030aa63f22eb5e5e24b6253d00ef5302ec47f675d59598626d99afde2e7f840bbf17a4f2dca6be2c43d96495dbe3422c0b828da5970ab0f6f377bc77e2feea2b1909023293b4301788798a1ed70d4eb63ed5b21988c9dcf147ed4a78e428d --set typesense.image.tag=28.0.rc35 --set vikunja.ingress.main.enabled=false --set vikunja.image.tag=0.24.5 ./
NAME: vikunja
LAST DEPLOYED: Thu Feb 6 18:40:12 2025
NAMESPACE: vikuna
STATUS: deployed
REVISION: 1
TEST SUITE: None
That errors as well.
Docker
Let’s take a break from the Kubernetes helm install and try using Docker instead
builder@builder-T100:~$ cd vikunja/
builder@builder-T100:~/vikunja$ mkdir $PWD/files $PWD/db
builder@builder-T100:~/vikunja$ chown 1000 $PWD/files $PWD/db
builder@builder-T100:~/vikunja$ docker run -d -p 3456:3456 -v $PWD/files:/app/vikunja/files -v $PWD/db:/db vikunja/vikunja
Unable to find image 'vikunja/vikunja:latest' locally
latest: Pulling from vikunja/vikunja
a968a69f52dd: Pull complete
5e115961be39: Pull complete
6285d5f3f8bb: Pull complete
Digest: sha256:ed1f3ed467fecec0b57e9de7bc6607f8bbcbb23ffced6a81f5dfefc794cdbe3b
Status: Downloaded newer image for vikunja/vikunja:latest
194d8e03f394047f8f310438a66c890e0fcceba4b3107fb7ecd99922eb7f8af4
which comes up without issue
I created an account and logged in without issue
I then pivotted to creating a forwarding proxy through Kubernetes
$ cat vikunja.ext.yaml
apiVersion: v1
kind: Service
metadata:
name: vikunja-external-ip
spec:
clusterIP: None
internalTrafficPolicy: Cluster
ports:
- name: vikunjap
port: 3456
protocol: TCP
targetPort: 3456
sessionAffinity: None
type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
name: vikunja-external-ip
subsets:
- addresses:
- ip: 192.168.1.100
ports:
- name: vikunjap
port: 3456
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: gcpleprod2
ingress.kubernetes.io/proxy-body-size: "0"
ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.org/client-max-body-size: "0"
nginx.org/proxy-connect-timeout: "3600"
nginx.org/proxy-read-timeout: "3600"
nginx.org/websocket-services: vikunja-external-ip
labels:
app.kubernetes.io/instance: vikunjaingress
name: vikunjaingress
spec:
rules:
- host: vikunja.freshbrewed.science
http:
paths:
- backend:
service:
name: vikunja-external-ip
port:
number: 3456
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- vikunja.freshbrewed.science
secretName: vikunja-tls
Then applied it
$ kubectl apply -f vikunja.ext.yaml
service/vikunja-external-ip created
endpoints/vikunja-external-ip created
ingress.networking.k8s.io/vikunjaingress created
Once the cert is created
$ kubectl get cert vikunja-tls
NAME READY SECRET AGE
vikunja-tls True vikunja-tls 20s
I can use the mobile app without issue
and see a dashboard
Let’s create a project and a task:
REST API
Let’s check out the REST API
curl I can get a REST token
$ curl -X POST -H "Content-Type: application/json" -d '{"long_token":true,"password":"MyPassowrd!","username":"idjohnson"}' https://vikunja.steeped.space/api/v1/login
{"token":"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfsadfasdfasdfasdfasdfasdf.asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfsadfasdfasdfasdfasdfasdf.OawCjdQpkAIn2h00ZbAit_6jwanCP-5rswr8Qy75d5A"}
Then use the token for getting things like projects
curl -H "Accept: application/json" -H "Authorization: Bearer asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfsadfasdfasdfasdfasdfasdf.OawCjdQpkAIn2h00ZbAit_6jwanCP-5rswr8Qy75d5A" https://vikunja.steeped.space/api/v1/projects
[{"id":1,"title":"Inbox","description":"","identifier":"","hex_color":"","parent_project_id":0,"owner":{"id":1,"name":"","username":"idjohnson","created":"2025-02-08T01:04:15Z","updated":"2025-02-08T01:04:15Z"},"is_archived":false,"background_information":null,"background_blur_hash":"","is_favorite":false,"position":65536,"views":[{"id":1,"title":"List","project_id":1,"view_kind":"list","filter":"done = false","position":100,"bucket_configuration_mode":"none","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:04:15Z","created":"2025-02-08T01:04:15Z"},{"id":2,"title":"Gantt","project_id":1,"view_kind":"gantt","filter":"","position":200,"bucket_configuration_mode":"none","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:04:15Z","created":"2025-02-08T01:04:15Z"},{"id":3,"title":"Table","project_id":1,"view_kind":"table","filter":"","position":300,"bucket_configuration_mode":"none","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:04:15Z","created":"2025-02-08T01:04:15Z"},{"id":4,"title":"Kanban","project_id":1,"view_kind":"kanban","filter":"","position":400,"bucket_configuration_mode":"manual","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:04:15Z","created":"2025-02-08T01:04:15Z"}],"created":"2025-02-08T01:04:15Z","updated":"2025-02-08T01:04:15Z"},{"id":2,"title":"FreshBrewed","description":"","identifier":"","hex_color":"ff4136","parent_project_id":0,"owner":{"id":1,"name":"","username":"idjohnson","created":"2025-02-08T01:04:15Z","updated":"2025-02-08T01:04:15Z"},"is_archived":false,"background_information":null,"background_blur_hash":"","is_favorite":false,"position":131072,"views":[{"id":5,"title":"List","project_id":2,"view_kind":"list","filter":"done = false","position":100,"bucket_configuration_mode":"none","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:49:52Z","created":"2025-02-08T01:49:52Z"},{"id":6,"title":"Gantt","project_id":2,"view_kind":"gantt","filter":"","position":200,"bucket_configuration_mode":"none","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:49:52Z","created":"2025-02-08T01:49:52Z"},{"id":7,"title":"Table","project_id":2,"view_kind":"table","filter":"","position":300,"bucket_configuration_mode":"none","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:49:52Z","created":"2025-02-08T01:49:52Z"},{"id":8,"title":"Kanban","project_id":2,"view_kind":"kanban","filter":"","position":400,"bucket_configuration_mode":"manual","bucket_configuration":null,"default_bucket_id":0,"done_bucket_id":0,"updated":"2025-02-08T01:49:52Z","created":"2025-02-08T01:49:52Z"}],"created":"2025-02-08T01:49:52Z","updated":"2025-02-08T01:51:00Z"}]
I can fetch the tasks
$ curl -H "Accept: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfsadfasdfasdfasdfasdfasdf.OawCjdQpkAIn2h00ZbAit_6jwanCP-5rswr8Qy75d5A" https://vikunja.steeped.space/api/v1/tasks/all | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 877 100 877 0 0 17094 0 --:--:-- --:--:-- --:--:-- 17196
[
{
"id": 1,
"title": "Record Vikunja",
"description": "<pre><code>Hi there</code></pre>",
"done": false,
"done_at": "0001-01-01T00:00:00Z",
"due_date": "0001-01-01T00:00:00Z",
"reminders": null,
"project_id": 2,
"repeat_after": 0,
"repeat_mode": 0,
"priority": 2,
"start_date": "0001-01-01T00:00:00Z",
"end_date": "0001-01-01T00:00:00Z",
"assignees": null,
"labels": null,
"hex_color": "",
"percent_done": 0,
"identifier": "#1",
"index": 1,
"related_tasks": {},
"attachments": null,
"cover_image_attachment_id": 0,
"is_favorite": false,
"created": "2025-02-08T01:50:08Z",
"updated": "2025-02-08T01:51:11Z",
"bucket_id": 0,
"position": 0,
"reactions": {
"🍋": [
{
"id": 1,
"name": "",
"username": "idjohnson",
"created": "2025-02-08T01:04:15Z",
"updated": "2025-02-08T01:04:15Z"
}
]
},
"created_by": {
"id": 1,
"name": "",
"username": "idjohnson",
"created": "2025-02-08T01:04:15Z",
"updated": "2025-02-08T01:04:15Z"
}
}
]
I can use the REST API to PUT a new task too
$ curl -X PUT -H "Content-Type: application/json" -H "Accept: application/json" -H "Authorization: Bearer asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfsadfasdfasdfasdfasdfasdf" https://vikunja.steeped.space/api/v1/
projects/2/tasks -d '{"title": "a test task"}'
{"id":2,"title":"a test task","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":2,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[],"labels":null,"hex_color":"","percent_done":0,"identifier":"#2","index":2,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2025-02-08T02:38:29.169901125Z","updated":"2025-02-08T02:38:29.169906748Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"idjohnson","created":"2025-02-08T01:04:15Z","updated":"2025-02-08T01:04:15Z"}}
We can see it worked:
Adding to Issues Webform
There is a Feedback Form which uses a narrowly scoped GH Pat to trigger a workflow that updates a handful of Issue Trackers like Github and Plane.so.
So we can see that, provided a PAT hasn’t expired, feedback entries are created in Github (completely unplanned that we got the issue number shown)
as well as Plane.so
To make maintenance easier, I use AKV as a storage medium for notification tokens. So as with Discord, I’ll want to put a Token into wldemokv
for my Vikunja instance.
However, let’s create a user just for FB user requests (again, to minimize blast radius).
I realized I started Vikunja in default mode meaning it was wide open for registration. Leaving things open burned me with Gitea so I’ll want to close that door real quick.
builder@builder-T100:~/vikunja$ docker stop clever_bartik
clever_bartik
builder@builder-T100:~/vikunja$ docker rm clever_bartik
clever_bartik
builder@builder-T100:~/vikunja$ docker run -d -p 3456:3456 -v $PWD/files:/app/vikunja/files -v $PWD/db:/db -e VIKUNJA_SERVICE_ENABLEREGISTRATION=false vikunja/vikunja
c22d59c2ed93914147c9ebb50ec48c138216d7bae2e28a59bd90cd7aa55321cd
I can attest that not only worked, but kept my current configuration data. To create a user now, we use the docker container name and the user create
command
$ docker exec priceless_benz /app/vikunja/vikunja user create -e isaac@freshbrewed.science -p 'NotMyPassword!' -u fbformuser
2025-02-08T22:20:10Z: INFO ▶ 001 No config file found, using default or config from environment variables.
2025-02-08T22:20:10Z: INFO ▶ 002 Running migrations…
2025-02-08T22:20:10Z: INFO ▶ 06a Ran all migrations successfully.
2025-02-08T22:20:10Z: INFO ▶ 06b Mailer is disabled, not sending reminders per mail
2025-02-08T22:20:10Z: INFO ▶ 06c Mailer is disabled, not sending overdue per mail
User was created successfully.
I can now test locally
$ curl -X POST -H "Content-Type: application/json" -d '{"long_token":true,"password":"NotMyPassword!","username":"fbformuser"}' https://vikunja.steeped.space/api/v1/login > fbfu.token
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 352 100 281 100 71 1384 349 --:--:-- --:--:-- --:--:-- 1733
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ cat fbfu.token
{"token":"asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf"}
I tried posting with the user’s token, but got the message
{"message":"Forbidden"}
I need to first share the project
Then indicate “Read and write”
Now my test script to post worked:
$ cat t.sh
#!/bin/bash
set -x
echo '{"long_token":true,"password":"' > login.json
az keyvault secret show --vault-name wldemokv --name vikunja-fbformuser-password -o json | jq -r .value | tr -d '\n' >> login.json
echo '","username":"fbformuser"}' >> login.json
cat login.json
curl -X POST -H "Content-Type: application/json" -d @login.json https://vikunja.steeped.space/api/v1/login > fbfu.token
sed -i 's/"body"/"description"/g' ./emailJson2.json
curl -X PUT -H "Content-Type: application/json" -H "Accept: application/json" -H "Authorization: Bearer `cat fbfu.token | jq -r .token | tr -d '\n'`" https://vikunja.steeped.space/api/v1/projects/2/tasks -d @emailJson2.json
Where emailJson2.json is a copy of the test file used for Gh last time
{"title":"Test Part 3","body":"2025 part 3 :: Requested by anonymous@dontemailme.com"}
{"id":3,"title":"Test Part 3","description":"2025 part 3 :: Requested by anonymous@dontemailme.com","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":2,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[],"labels":null,"hex_color":"","percent_done":0,"identifier":"#3","index":3,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2025-02-08T22:39:59.950549844Z","updated":"2025-02-08T22:39:59.950552972Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":2,"name":"","username":"fbformuser","created":"2025-02-08T22:20:11Z","updated":"2025-02-08T22:20:48Z"}}
And I can see the entry there
We can see my addition to the Github Workflow
Let’s put it all together now and test a form submission, tickets, workflow and Issue creation
And not shown above, but I also get notices on Keybase
And slack
I could add Telegram and Matrix, but I think we are good for now.
Usage
Here we see our Kanban board just has a backlog
Here I clicked in the “Create Bucket” and added Kanban columns for “InProgress”, “Staged” and “Done”
Let’s change our incoming requests to go to “Inbox” instead of the Blog Project
DialogFlow, and when AI Fails Us
I had the brilliant Idea of tying my Google Assistant to create tasks. I wanted some way to say “Add a task” and make it happen for when I’m driving.
I searched things like GrogCloud’s hosted 70b Deepseek R1
And followed the guide to create an Agent
Giving it a name
Then following the setup steps best I could.
I contrasted that prior suggestion with DeepSeek 14b hosted locally
I reviewed docs on actions and intents
And worked through Diagflow’s steps to hit a REST endpoint verifying I could translate an Assistant flow
Then I hit the wall. It seems they ended the integration back in 2023:
It seems they decided to pivot to “App Actions” instead which means I would have to build an Android App to receive (and process) the content which does nothing for me at home when I’m most often talking to a Google Home Mini
I am not sure how others do this. I know at home I can say “Play Foil by Weird Al on Spotify” and it does what I ask and there is no Android app there.
I decided to put a pin in it and consider it just a few wasted hours trying to build and launch a Diagflow option.
Summary
Today we setup Vikunja on Kubernetes and Docker. I tested on both and settled on Docker with TLS via Kubernetes.
Since I wiped the Docker instance and fired it back up, I know the DB file with SQLite works for replacements and I could back it up on occasion.
I showed how to use the REST API to engage with Vikunja. We created a web form user and I walked through how to add to my static feedback form to create an issue on user feedback.
Lastly, in an attempt to extend it to work with Google Home devices I went down a rabbit how with Diagflow only to find that solution was terminated in 2023 for connecting Diagflow/Google Home.
I will say I’m using the webapp pinned to my phone screen regularly now and will be comparing it to Github Issues which has been my go-to for Blog Ideas for some time.