Tracking progress with Ryot
Sometimes I wish for a centralized, automatically updated and moderately fancy-looking application to keep track of multiple activities; mostly around digital media.
- Audiobookshelf is pretty good but separates podcasts from books and only shows yearly summary at the end of the year. Audible does not offer even that, and no export options.
- Jellyfin (and previously Plex) don't go beyond marking things as "done". Besides, movies and TV shows are not the kind of videos I'm intersted in tracking progress with; video lectures are (where was I with this Inkscape course?).
- Paper books are very nearly not even a thing anymore, but it would still be nice to be able to track progress on them, as well as reading e-Books in Komga.
- Video games are absurdly difficult to track progress for. Naturally grown from need,
a spreadsheet is works well enough to collect data across multiple platforms, but
it is limited, ugly and increasing slow as the library grows.
- Steam shows only total and recent (last 2 weeks) gameplay, and probress is tracked in terms of achievements, not how close you are to finish the main story. At least there is the option to query the Steam Web API to periodically fetch gameplay stats, so they can be kept at a higher resolution (daily, hourly, etc.).
- Nintendo Switch Parental Control (Android app). shows only gameplay time per game (and per user) in the current month, after that it shows only montly summaries. There is no option to export any of this.
- GOG requires installing their own (Windows-only) Galaxy 2.0 client and the possiblity of exporting or even seeing your personal gameplay stats appears to be not even a question.
Looking around for tracking applications in the awesome directory of awesome-selfhosted, two applications look promising and worth a try: Ryot and Yamtrack.
Ryot
Ryot is a self hosted platform for tracking various facets of your life - media, fitness, etc. which seems to include everything that Yamtrack can track, plus other activities outside of media; it is focused in fitness but it could be used for other activities like studying, music practice, workshop time, other hobbies, sleep, etc.
It supports Integration with Jellyfin, Plex, Audiobookshelf and many more, OpenID Connect, sending notifications to Discord and Ntfy. While it's not yet ready to easily track everything, it recently added a webhook endpoint that supports generic json which can be used to build integrations with other services that Ryot does not support natively by importing Generic Json generated from any non-supported systems (e.g. Steam API).
Ryot deployment
Note
This deployment has been updated to version 10 of Ryot; migration from v8 to v9
failed persistently. Version 10 requires the MOVIES_AND_SHOWS_TMDB_ACCESS_TOKEN to
be a personal API Read Access Token from https://www.themoviedb.org/settings/api
Ryot documentation starts off with an Installation based on
docker-compose, on which the following basic deployment is based:
Basic Ryot deployment: ryot.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | |
Ryot can be deployed with debug logs and full backtrace when needed.
To enable debug log with full backtrace, re-deploy with these environment variables:
Before deploying the application, create a dedicated system user to own the local storage for the Postgres database:
root@octavo ~ # groupadd ryot -g 120
root@octavo ~ # useradd ryot -u 120 -g 120 -s /usr/sbin/nologin
root@octavo ~ # mkdir -p /home/k8s/ryot/postgres
root@octavo ~ # chown -R ryot:ryot /home/k8s/ryot
root@octavo ~ # ls -hal /home/k8s/ryot
total 0
drwxr-xr-x 1 ryot ryot 16 May 17 14:13 .
drwxr-xr-x 1 root root 330 May 17 14:13 ..
drwxr-xr-x 1 ryot ryot 0 May 17 14:13 postgres
Then deploy the application:
$ kubectl apply -f ryot.yaml
namespace/ryot created
persistentvolume/postgres-pv created
persistentvolumeclaim/postgres-pv-claim created
deployment.apps/postgres created
service/postgres-svc created
deployment.apps/ryot created
service/ryot-svc created
ingress.networking.k8s.io/ryot-ingress created
$ kubectl get all -n ryot
NAME READY STATUS RESTARTS AGE
pod/postgres-5f649d875c-82fh4 1/1 Running 0 3s
pod/ryot-55c5845667-gplxd 1/1 Running 0 3s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/postgres-svc NodePort 10.96.122.54 <none> 5432:30543/TCP 3s
service/ryot-svc NodePort 10.98.120.72 <none> 8000:30380/TCP 3s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/postgres 1/1 1 1 3s
deployment.apps/ryot 1/1 1 1 3s
NAME DESIRED CURRENT READY AGE
replicaset.apps/postgres-5f649d875c 1 1 1 3s
replicaset.apps/ryot-55c5845667 1 1 1 3s
$ kubectl get ingress -n ryot
NAME CLASS HOSTS ADDRESS PORTS AGE
ryot-ingress nginx ryot.very-very-dark-gray.top 192.168.0.171 80 59s
After a couple of minutes the application will be available at https://ryot.very-very-dark-gray.top/
Warning
By default, Ryot allows anyone to create a new account. Gating access behind Cloudflare Access is advisable, at least initially, to prevent strangers from creating accounts if they accidentally find the application.
Ryot setup
First, create a user to gain admin access to the application. Additional users can be created but will not have the Admin role, only the first user can add it.
Import
The first step each user should probaly do is to import their progress so far in the supported systems. This is done under Settings > Imports and Exports > Import data using the relevant importers.
Audiobookshelf
The Audiobookshelf importer supports importing all media that have a valid Audible ID or iTunes ID or ISBN. Unfortunately this means those audiobooks not available in Audible or iTunes, e.g. those from LibriVox, won't be imported.
This will only import media that are already finished, neither books in progress nor those that have not been started yet. Those will be added later, as progress is made, via the Audiobookshelf integration.
- Select a source: Audiobookshelf
- Instance URL: https://audiobookshelf.very-very-dark-gray.top or whatever the ingress is for Audiobookshelf
- API Key: Create an API Key to act on behalf of the user to track progress for. This is no longer the same as the API token for the user, as described in the Audiobookshelf authentication docs, which is now used only in the Audiobookshelf Integration.
The import will take a little while; in my case it took about 7 minutes to import 212 books.
Ryot will not import audiobooks that don't have ISBN or ASIN, a list of such titles can be found checking the result from the importer:
This can be mitigated by adding ISBN or ASIN to those titles in Audiobookshelf, e.g. libro.fm and other stores may list ISBN and/or ASIN for audiobooks purchased offline. Audiobooks from librivox.org appear to have no ISBN or ASIN.
Jellyfin
The Jellyfin importer can import watched (i.e. not the entire) Movies and Shows libraries from Jellyfin, and it's very simple to setup:
- Select a source: Jellyfin
- Instance URL: https://jellyfin.very-very-dark-gray.top (without trailing
/) - or whatever the ingress is for Jellyfin
- Username: the user to import progress for
- Password: their password
Do not add a trailing / to the Instance URL.
Doing so, the import always fails immediately and seems to stay in the still running state, so the UI won't allow even looking at the logs. Inspecing the pod's logs in Kubernetes won't show much:
$ klogs ryot ryot
[frontend] POST /settings/imports-and-exports.data?intent=deployImport 200 - - 14.458 ms
[frontend] GET /settings/imports-and-exports.data 200 - - 36.602 ms
[backend]
[backend] thread 'main' panicked at /home/runner/work/ryot/ryot/crates/utils/external/src/lib.rs:93:14:
[backend] called `Result::unwrap()` on an `Err` value: reqwest::Error { kind: Decode, source: Error("EOF while parsing a value", line: 1, column: 0) }
The Jellyfin API does allow fetching entirely libraries.
The Jellyfin API is actually reachable and does return 611 items, of which ~90% do have ProviderIds, which are required by Ryot in order to import items:
$ kubectl exec -it ryot-59cdcdc56d-k7bvn -n ryot -- \
curl -H "X-Emby-Token: a7____________________________ee" \
"http://jellyfin.very-very-dark-gray.top/Items?IncludeItemTypes=Movie,Series&Recursive=true&Fields=ProviderIds" \
> /tmp/items.json
$ jq -r ".TotalRecordCount" /tmp/items.json
611
$ jq -r ".Items[].ProviderIds | length" /tmp/items.json | sort | uniq -c
60 0
6 1
196 2
306 3
43 4
Plex
The Plex importer can import watched movies and shows from Plex but it may actually require having an active self-hosted Plex server, which is not running in the new server (octavo).
No longer having my own self-hosted Plex Media Server running, it seems the process to
obtain a Plex-Token as described
here
no longer works, and at this point I'm past done with Plex anyway.
Integrations
Audiobookshelf
The Audiobookshelf integration must be setup to keep syncing progress made on audiobooks. The setup is essentially the same as for the Audiobookshelf importer:
- Instance URL: https://audiobookshelf.very-very-dark-gray.top or whatever the ingress is for Audiobookshelf
- API Key: the API token for the user, as described in the Audiobookshelf authentication docs
Jellyfin
The Jellyfin Sink integration automatically adds Jellyfin movies and shows (when they are played in Jellyfin). It only works for media that has a valid TMDb ID attached to their metadata. Create the integration and click on its eye icon to reveal its URL; this will be used when creating the webhook in Jellyfin.
This integration requires the unofficial webhook plugin to be installed and active in Jellyfin, whiich a few steps in Jellyfin:
- Under Dashboard > Plugins go to Manage Repositories
- Use the + New Repository button to add a new repository pointing to https://raw.githubusercontent.com/shemanaev/jellyfin-plugin-repo/master/manifest.json
- Go back to Plugins and select the Available filter, then Search for
web - Install the unofficial Webhooks plugin (not the official Webhook)
- Restart Jellyfin by restarting the deployment:
- Enable the Webhooks plugin once Jellyfin has restarted.
- Go to the newly available section Dashboard > Webhooks and click on Add, use the URL created by Ryot for the Jellyfin Sink integration and set the other values as explained here
The Jellyfin Push integration does not seem as interested, since it only marks items as watched in Jellyfin after they are marked as watched in Ryot, but the import won't import items that have not been watched in Jellyfin in the first place.
Komga
There is no importer option for Komga so instead the Komga integration can be setup with
- Base URL: https://jellyfin.very-very-dark-gray.top/ or whatever the ingress is for Jellyfin
- Username: the user to import progress for
- Password: their password
- Provider: Anlist
This integration will not have anything visible effect until books are interacted with in Komga; only then will the integration add these under Book.
Generic JSON
For everything else, "Generic Json" can be used to
import and
sync progress from any
other sources that Ryot. The format of the JSON file should be CompleteExport
as described in the documentation for
exporting.
This should made it possible to import and sync video games, at least from Steam. However, having to implement this from scratch is not particularly appealing for now.
Ryot impressions
Without spending many hours on it, all I seem to get out of Ryot is but a limited subset of what Audiobookshelf shows. While the app looks promising and pleasant to navigate, the funcionality achieved is too limited to justify regular use.
Yamtrack
Yamtrack is a self hosted media tracker for movies, tv shows, anime and manga but, more interestingly to me, also books and video games. It even has integration with Jellyfin, to automatically track new media watched, which is nice, but what I'd really love to see is integration with Audiobookshelf and Komga, for books, and Steam for games.
There is perhaps enough information available about the Yamtrack CSV import format that it may be not too hard to hack something together to import listening history and play time history using the available APIs:
- Audiobookshelf API exposes listening sessions and libraries.
- Komga REST API exposes progression and metadata for each book.
- Steam Web API exposes recent (2-weeks) and total (forever) play time, and the list of owned games. These can be used to track progress and purchases of games.
This may be worth the investment if Ryot turns out not to be the best option
in the end. For now, lets just say Yamtrack is even easier to deploy, based on its own
docker-compose.yml
Basic Jellyfin deployment: yamtrack.yaml
apiVersion: v1
kind: Namespace
metadata:
name: yamtrack
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis-pv
labels:
type: local
namespace: yamtrack
spec:
storageClassName: manual
capacity:
storage: 3Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /home/k8s/yamtrack/redis
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-pv-claim
namespace: yamtrack
spec:
storageClassName: manual
volumeName: redis-pv
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: yamtrack
labels:
app: redis
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
hostname: redis
containers:
- image: redis:7-alpine
name: redis
volumeMounts:
- mountPath: /data
name: redis-data
volumes:
- name: redis-data
persistentVolumeClaim:
claimName: redis-pv-claim
---
apiVersion: v1
kind: Service
metadata:
labels:
app: redis
name: redis-svc
namespace: yamtrack
spec:
ports:
- port: 6379
protocol: TCP
targetPort: 6379
nodePort: 30379
selector:
app: redis
type: NodePort
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: yamtrack-pv
labels:
type: local
namespace: yamtrack
spec:
storageClassName: manual
capacity:
storage: 3Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /home/k8s/yamtrack/db
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: yamtrack-pv-claim
namespace: yamtrack
spec:
storageClassName: manual
volumeName: yamtrack-pv
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: yamtrack
labels:
app: yamtrack
name: yamtrack
spec:
replicas: 1
selector:
matchLabels:
app: yamtrack
template:
metadata:
labels:
app: yamtrack
spec:
containers:
- image: ghcr.io/fuzzygrim/yamtrack
env:
- name: "SECRET"
value: "____________________"
- name: "REDIS_URL"
value: "redis://redis-svc:6379"
name: yamtrack
volumeMounts:
- name: yamtrack-data
mountPath: /yamtrack/db
volumes:
- name: yamtrack-data
persistentVolumeClaim:
claimName: yamtrack-pv-claim
---
apiVersion: v1
kind: Service
metadata:
labels:
app: yamtrack
name: yamtrack-svc
namespace: yamtrack
spec:
ports:
- port: 8000
protocol: TCP
targetPort: 8000
nodePort: 30380
selector:
app: yamtrack
type: NodePort
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: yamtrack-ingress
namespace: yamtrack
annotations:
acme.cert-manager.io/http01-edit-in-place: "true"
cert-manager.io/issue-temporary-certificate: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
rules:
- host: yamtrack.very-very-dark-gray.top
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: yamtrack-svc
port:
number: 8000
tls:
- secretName: tls-secret-cloudflare
hosts:
- yamtrack.very-very-dark-gray.top
Prepare a local user and directory in the similar to the one for Ryot:
root@octavo ~ # groupadd yamtrack -g 120
root@octavo ~ # useradd yamtrack -u 120 -g 120 -s /usr/sbin/nologin
root@octavo ~ # mkdir /home/k8s/yamtrack/redis /home/k8s/yamtrack/db
root@octavo ~ # chown -R yamtrack:yamtrack /home/k8s/yamtrack
root@octavo ~ # ls -hal /home/k8s/yamtrack
total 0
drwxr-xr-x 1 yamtrack yamtrack 14 May 15 23:06 .
drwxr-xr-x 1 root root 322 May 15 23:01 ..
drwxr-xr-x 1 yamtrack yamtrack 0 May 15 23:06 db
drwxr-xr-x 1 yamtrack yamtrack 0 May 15 23:06 redis
Then deploy the application:
$ kubectl apply -f yamtrack.yaml
namespace/yamtrack created
persistentvolume/redis-pv created
persistentvolumeclaim/redis-pv-claim created
deployment.apps/redis created
service/redis-svc created
persistentvolume/yamtrack-pv created
persistentvolumeclaim/yamtrack-pv-claim created
deployment.apps/yamtrack created
service/yamtrack-svc created
ingress.networking.k8s.io/yamtrack-ingress created
$ kubectl get all -n yamtrack
NAME READY STATUS RESTARTS AGE
pod/redis-549b9c9b6f-2xq94 1/1 Running 0 98s
pod/yamtrack-668755cf84-xbt9v 1/1 Running 0 97s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/redis-svc NodePort 10.96.239.219 <none> 6379:30379/TCP 98s
service/yamtrack-svc NodePort 10.111.29.62 <none> 8000:30380/TCP 98s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/redis 1/1 1 1 98s
deployment.apps/yamtrack 1/1 1 1 98s
NAME DESIRED CURRENT READY AGE
replicaset.apps/redis-549b9c9b6f 1 1 1 98s
replicaset.apps/yamtrack-668755cf84 1 1 1 98s
$ kubectl get ingress -n yamtrack
NAME CLASS HOSTS ADDRESS PORTS AGE
yamtrack-ingress nginx yamtrack.very-very-dark-gray.top 192.168.0.171 80 36s
After a couple of minutes the application will be available at https://yamtrack.very-very-dark-gray.top; otherwise inspect the logs to debug.
$ klogs yamtrack yamtrack
$ klogs yamtrack yamtrack
Operations to perform:
Apply all migrations: account, admin, app, auth, contenttypes, db, django_celery_beat, django_celery_results, events, lists, mfa, sessions, socialaccount, users
Running migrations:
Applying contenttypes.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0001_initial... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying users.0001_squashed_0008_user_game_layout_alter_user_last_search_type... OK
Applying account.0001_initial... OK
Applying account.0002_email_max_length... OK
Applying account.0003_alter_emailaddress_create_unique_verified_email... OK
Applying account.0004_alter_emailaddress_drop_unique_email... OK
Applying account.0005_emailaddress_idx_upper_email... OK
Applying account.0006_emailaddress_lower... OK
Applying account.0007_emailaddress_idx_email... OK
Applying account.0008_emailaddress_unique_primary_email_fixup... OK
Applying account.0009_emailaddress_unique_primary_email... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying app.0001_squashed_0014_historicalanime_historicalepisode_historicalgame_and_more... OK
Applying app.0015_customlist_listitem_squashed_0021_alter_item_media_type... OK
Applying app.0022_item_source_squashed_0037_alter_item_media_type... OK
Applying app.0038_comic_historicalcomic_and_more... OK
Applying app.0039_anime_progress_changed_basicmedia_progress_changed_and_more... OK
Applying db.0001_initial... OK
Applying django_celery_beat.0001_initial... OK
Applying django_celery_beat.0002_auto_20161118_0346... OK
Applying django_celery_beat.0003_auto_20161209_0049... OK
Applying django_celery_beat.0004_auto_20170221_0000... OK
Applying django_celery_beat.0005_add_solarschedule_events_choices... OK
Applying django_celery_beat.0006_auto_20180322_0932... OK
Applying django_celery_beat.0007_auto_20180521_0826... OK
Applying django_celery_beat.0008_auto_20180914_1922... OK
Applying django_celery_beat.0006_auto_20180210_1226... OK
Applying django_celery_beat.0006_periodictask_priority... OK
Applying django_celery_beat.0009_periodictask_headers... OK
Applying django_celery_beat.0010_auto_20190429_0326... OK
Applying django_celery_beat.0011_auto_20190508_0153... OK
Applying django_celery_beat.0012_periodictask_expire_seconds... OK
Applying django_celery_beat.0013_auto_20200609_0727... OK
Applying django_celery_beat.0014_remove_clockedschedule_enabled... OK
Applying django_celery_beat.0015_edit_solarschedule_events_choices... OK
Applying django_celery_beat.0016_alter_crontabschedule_timezone... OK
Applying django_celery_beat.0017_alter_crontabschedule_month_of_year... OK
Applying django_celery_beat.0018_improve_crontab_helptext... OK
Applying django_celery_beat.0019_alter_periodictasks_options... OK
Applying django_celery_results.0001_initial... OK
Applying django_celery_results.0002_add_task_name_args_kwargs... OK
Applying django_celery_results.0003_auto_20181106_1101... OK
Applying django_celery_results.0004_auto_20190516_0412... OK
Applying django_celery_results.0005_taskresult_worker... OK
Applying django_celery_results.0006_taskresult_date_created... OK
Applying django_celery_results.0007_remove_taskresult_hidden... OK
Applying django_celery_results.0008_chordcounter... OK
Applying django_celery_results.0009_groupresult... OK
Applying django_celery_results.0010_remove_duplicate_indices... OK
Applying django_celery_results.0011_taskresult_periodic_task_name... OK
Applying django_celery_results.0012_taskresult_date_started... OK
Applying django_celery_results.0013_taskresult_django_cele_periodi_1993cf_idx... OK
Applying django_celery_results.0014_alter_taskresult_status... OK
Applying events.0001_initial... OK
Applying events.0002_alter_event_unique_together... OK
Applying events.0003_alter_event_unique_together_and_more... OK
Applying events.0004_fix_anime_episode_numbers... OK
Applying events.0005_alter_event_date... OK
Applying events.0006_alter_event_options_rename_date_event_datetime... OK
Applying events.0007_event_notification_sent... OK
Applying events.0008_delete_tv_events... OK
Applying events.0009_fix_movie_episode_number... OK
Applying events.0010_alter_event_options... OK
Applying events.0011_remove_event_unique_item_episode_and_more... OK
Applying events.0012_remove_event_unique_item_episode_and_more... OK
Applying events.0013_delete_single_anime_events... OK
Applying lists.0001_initial... OK
Applying lists.0002_alter_customlistitem_item_alter_customlist_items_and_more... OK
Applying lists.0003_alter_customlist_unique_together_and_more... OK
Applying mfa.0001_initial... OK
Applying mfa.0002_authenticator_timestamps... OK
Applying mfa.0003_authenticator_type_uniq... OK
Applying sessions.0001_initial... OK
Applying socialaccount.0001_initial... OK
Applying socialaccount.0002_token_max_lengths... OK
Applying socialaccount.0003_extra_data_default_dict... OK
Applying socialaccount.0004_app_provider_id_settings... OK
Applying socialaccount.0005_socialtoken_nullable_app... OK
Applying socialaccount.0006_alter_socialaccount_extra_data... OK
Applying users.0009_alter_user_options_squashed_0022_alter_user_last_search_type... OK
Applying users.0023_user_home_sort_user_home_sort_valid... OK
Applying users.0024_remove_user_lists_sort_valid_alter_user_lists_sort_and_more... OK
Applying users.0025_remove_user_last_search_type_valid_and_more... OK
Applying users.0026_user_anime_sort_user_book_sort_user_game_sort_and_more... OK
Applying users.0027_user_list_detail_sort_user_list_detail_sort_valid... OK
Applying users.0028_user_anime_status_user_book_status_user_game_status_and_more... OK
Applying users.0029_user_notification_urls... OK
Applying users.0030_user_notification_excluded_items... OK
Applying users.0031_remove_user_last_search_type_valid_and_more... OK
Applying users.0032_alter_user_token_and_generate_missing_user_tokens... OK
Applying users.0033_user_daily_digest_enabled_and_more... OK
usermod: no changes
2025-05-15 21:13:58,156 INFO Set uid to user 0 succeeded
2025-05-15 21:13:58,168 INFO supervisord started with pid 1
2025-05-15 21:13:59,170 INFO spawned: 'nginx' with pid 16
2025-05-15 21:13:59,172 INFO spawned: 'gunicorn' with pid 17
2025-05-15 21:13:59,175 INFO spawned: 'celery' with pid 18
2025-05-15 21:13:59,176 INFO spawned: 'celery-beat' with pid 19
celery beat v5.5.2 (immunity) is starting.
[2025-05-15 21:13:59 +0000] [17] [INFO] Starting gunicorn 23.0.0
[2025-05-15 21:13:59 +0000] [17] [INFO] Listening at: http://127.0.0.1:8001 (17)
[2025-05-15 21:13:59 +0000] [17] [INFO] Using worker: sync
[2025-05-15 21:13:59 +0000] [37] [INFO] Booting worker with pid: 37
__ - ... __ - _
LocalTime -> 2025-05-15 21:14:00
Configuration ->
. broker -> redis://redis-svc:6379//
. loader -> celery.loaders.app.AppLoader
. scheduler -> django_celery_beat.schedulers.DatabaseScheduler
. logfile -> [stderr]@%INFO
. maxinterval -> 5.00 seconds (5s)
2025-05-15 21:14:00,475 INFO success: nginx entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2025-05-15 21:14:00,475 INFO success: gunicorn entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2025-05-15 21:14:00,475 INFO success: celery entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2025-05-15 21:14:00,475 INFO success: celery-beat entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
[2025-05-15 21:14:00 +0000] [19] [INFO] beat: Starting...
[2025-05-15 21:14:00 +0000] [19] [INFO] DatabaseScheduler: Schedule changed.
-------------- celery@yamtrack-668755cf84-xbt9v v5.5.2 (immunity)
--- ***** -----
-- ******* ---- Linux-6.8.0-58-generic-x86_64-with 2025-05-15 21:14:00
- *** --- * ---
- ** ---------- [config]
- ** ---------- .> app: yamtrack:0x7aefc7145df0
- ** ---------- .> transport: redis://redis-svc:6379//
- ** ---------- .> results:
- *** --- * --- .> concurrency: 1 (prefork)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** -----
-------------- [queues]
.> celery exchange=celery(direct) key=celery
[tasks]
. Import from AniList
. Import from Kitsu
. Import from MyAnimeList
. Import from SIMKL
. Import from Trakt
. Import from Yamtrack
. Reload calendar
. Send daily digest
. Send release notifications
[2025-05-15 21:14:01 +0000] [18] [INFO] Connected to redis://redis-svc:6379//
[2025-05-15 21:14:01 +0000] [18] [INFO] celery@yamtrack-668755cf84-xbt9v ready.
Deploy Yamtrack without securityContext or it will fail:
$ kubectl get all -n yamtrack
NAME READY STATUS RESTARTS AGE
pod/redis-6768c658df-ncnrk 1/1 Running 0 19s
pod/yamtrack-6d955f556b-hq5df 0/1 Error 1 (4s ago) 19s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/redis-svc NodePort 10.111.45.57 <none> 6379:30379/TCP 20s
service/yamtrack-svc NodePort 10.105.251.86 <none> 8000:30380/TCP 20s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/redis 1/1 1 1 20s
deployment.apps/yamtrack 0/1 1 0 20s
NAME DESIRED CURRENT READY AGE
replicaset.apps/redis-6768c658df 1 1 1 20s
replicaset.apps/yamtrack-6d955f556b 1 1 0 20s
$ klogs yamtrack yamtrack
Operations to perform:
Apply all migrations: account, admin, app, auth, contenttypes, db, django_celery_beat, django_celery_results, events, lists, mfa, sessions, socialaccount, users
Running migrations:
No migrations to apply.
groupmod: /etc/group.9: Permission denied
groupmod: cannot lock /etc/group; try again later.
Videogame progress trackers
Importing and tracking progress in video games can only be done manually, even though some gaming platforms provide APIs that some Home Assistant integrations use. [FEATURE REQUEST] track video games process on game platforms mentions of a few of those, and there is also the Steam API that can used to import libraries and track progress.
There doesn't seem to be any self-hosted option to keep track of multi-platform video game libraries and game progress. The closest thing to it is the open-source project Playnite, sadly only available for Windows and not possible to run using Wine, PlayOnLinux or similar tools. The good news is, Playnite Will Eventually Make Its Way to Linux; the bad news is, not any time soon.
In the meantime, there are several hosted services to do the above, although at most they can importing games from Steam or GOG and few other platforms, but not including itch.io, Nintendo or most other platforms. A quick review with the following show that at beast very basic functionality may be used without some sort of paid subscription:
- HowLongToBeat can be used for free. It is kinda possible to track games in different states but maintaining a wishlist is not a native, well-supported feature.
- Backloggd can be used for free (with ads) and support keeping a wishlist alongside the collection, but the latter can only be seen in 3 different sections (Played, Playing, Backlog) and sorting options are not remembered.
- INFINITE BACKLOG does support more detailled progress tracking, plus importing games and progress from Steam, GOG and a few more. Both the collection and wishlist can be sorted by release date and filtered by multiple fields. Access to some features is gated behind Patreon, but the tiers are low enough that it could justify a $1/mo. subscription.
- Keep Track of My Games gates most features behind their own Fair Use Price.
- GG seems unfinished and inactive, with elite features gated behind a $5/mo. subscriptions (apparently on their own platform) and no updates since September 2022.
Of all the above, INFINITE BACKLOG seems like the only potential winner.