OS Apps: Jellyfin and Lauti

Published: Sep 2, 2025 by Isaac Johnson

Today I want to look for an alternative to Plex. For a while, Jellyfin has been on my list as that seems to be the most popular drop-in replacement. I’ll try setting it up and using and how one could expose it externally.

I’ll take a moment to checkout another open-source movie app before moving on to Lauti which is a shared calendar tool. This can best be compared to Gancio. We’ll set it up (and troubleshoot some install issues) then compare an event in both tools so we can see some side-by-side comparisons.

Let’s start with Jellyfin

Jellyfin Installation

I can easily install with a curl one-liner

builder@bosgamerz9:~$ curl -s https://repo.jellyfin.org/install-debuntu.sh | sudo bash
> Determining optimal repository settings.

Found the following details from '/etc/os-release':
  Real OS:            ubuntu
  Repository OS:      ubuntu
  Repository Release: noble
  CPU Architecture:   amd64
If this looks correct, press <Enter> now to continue installing Jellyfin.

> Fetching repository signing key.

> Installing Jellyfin repository into APT.
Types: deb
URIs: https://repo.jellyfin.org/ubuntu
Suites: noble
Components: main
Architectures: amd64
Signed-By: /etc/apt/keyrings/jellyfin.gpg

> Updating APT repositories.
Hit:1 https://download.docker.com/linux/ubuntu noble InRelease
Get:2 https://repo.jellyfin.org/ubuntu noble InRelease [10.6 kB]
Hit:3 http://us.archive.ubuntu.com/ubuntu noble InRelease
Hit:4 http://security.ubuntu.com/ubuntu noble-security InRelease
Hit:5 https://packages.microsoft.com/repos/code stable InRelease
Hit:6 http://us.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:7 http://us.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:9 https://ppa.launchpadcontent.net/obsproject/obs-studio/ubuntu noble InRelease
Get:8 https://tor1.mirror.jellyfin.org/files/ubuntu noble/main amd64 Packages [5,019 B]
Fetched 15.6 kB in 1s (20.5 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
180 packages can be upgraded. Run 'apt list --upgradable' to see them.

> Installing Jellyfin.
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages were automatically installed and are no longer required:
  libllvm17t64 python3-netifaces
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  jellyfin-ffmpeg7 jellyfin-server jellyfin-web
Suggested packages:
  intel-opencl-icd intel-opencl-icd-legacy
The following NEW packages will be installed:
  jellyfin jellyfin-ffmpeg7 jellyfin-server jellyfin-web
0 upgraded, 4 newly installed, 0 to remove and 180 not upgraded.
Need to get 134 MB of archives.
After this operation, 428 MB of additional disk space will be used.
Get:4 https://repo.jellyfin.org/ubuntu noble/main amd64 jellyfin all 10.10.7+ubu2404 [2,298 B]
Get:1 https://repo.jellyfin.org/ubuntu noble/main amd64 jellyfin-server amd64 10.10.7+ubu2404 [51.2 MB]
Get:2 https://repo.jellyfin.org/ubuntu noble/main amd64 jellyfin-web all 10.10.7+ubu2404 [32.8 MB]
Get:3 https://nyc1.mirror.jellyfin.org/files/ubuntu noble/main amd64 jellyfin-ffmpeg7 amd64 7.1.1-7-noble [49.8 MB]
Fetched 134 MB in 6s (22.2 MB/s)
Selecting previously unselected package jellyfin-server.
(Reading database ... 207209 files and directories currently installed.)
Preparing to unpack .../jellyfin-server_10.10.7+ubu2404_amd64.deb ...
Unpacking jellyfin-server (10.10.7+ubu2404) ...
Selecting previously unselected package jellyfin-web.
Preparing to unpack .../jellyfin-web_10.10.7+ubu2404_all.deb ...
Unpacking jellyfin-web (10.10.7+ubu2404) ...
Selecting previously unselected package jellyfin-ffmpeg7.
Preparing to unpack .../jellyfin-ffmpeg7_7.1.1-7-noble_amd64.deb ...
Unpacking jellyfin-ffmpeg7 (7.1.1-7-noble) ...
Selecting previously unselected package jellyfin.
Preparing to unpack .../jellyfin_10.10.7+ubu2404_all.deb ...
Unpacking jellyfin (10.10.7+ubu2404) ...
Setting up jellyfin-ffmpeg7 (7.1.1-7-noble) ...
Setting up jellyfin-web (10.10.7+ubu2404) ...
Setting up jellyfin-server (10.10.7+ubu2404) ...
Created symlink /etc/systemd/system/multi-user.target.wants/jellyfin.service → /usr/lib/systemd/system/jellyfin.service.
Setting up jellyfin (10.10.7+ubu2404) ...
Processing triggers for libc-bin (2.39-0ubuntu8.5) ...

> Waiting 15 seconds for Jellyfin to fully start up.

-------------------------------------------------------------------------------
● jellyfin.service - Jellyfin Media Server
     Loaded: loaded (/usr/lib/systemd/system/jellyfin.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/jellyfin.service.d
             └─jellyfin.service.conf
     Active: active (running) since Tue 2025-08-26 14:54:56 CDT; 16s ago
   Main PID: 192517 (jellyfin)
      Tasks: 22 (limit: 15015)
     Memory: 122.9M (peak: 125.9M)
        CPU: 7.926s
     CGroup: /system.slice/jellyfin.service
             └─192517 /usr/bin/jellyfin --webdir=/usr/share/jellyfin/web --ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg

Aug 26 14:54:58 bosgamerz9 jellyfin[192517]: [14:54:58] [INF] Available encoders: ["libsvtav1", "av1_nvenc", "av1_qsv", "av1_a…
Aug 26 14:54:58 bosgamerz9 jellyfin[192517]: [14:54:58] [INF] Available filters: ["bwdif_cuda", "deinterlace_qsv", "deinterlac…
Aug 26 14:54:58 bosgamerz9 jellyfin[192517]: [14:54:58] [INF] Available hwaccel types: ["cuda", "vaapi", "qsv", "drm",…vulkan"]
Aug 26 14:55:00 bosgamerz9 jellyfin[192517]: [14:55:00] [INF] FFmpeg: /usr/lib/jellyfin-ffmpeg/ffmpeg
Aug 26 14:55:00 bosgamerz9 jellyfin[192517]: [14:55:00] [INF] ServerId: 8361e94a8ac646b5b341b52b5dda6da4
Aug 26 14:55:00 bosgamerz9 jellyfin[192517]: [14:55:00] [INF] Core startup complete
Aug 26 14:55:00 bosgamerz9 jellyfin[192517]: [14:55:00] [INF] Startup complete 0:00:03.8468285
Aug 26 14:55:01 bosgamerz9 jellyfin[192517]: [14:55:01] [INF] Clean Transcode Directory Completed after 0 minute(s) an… seconds
Aug 26 14:55:01 bosgamerz9 jellyfin[192517]: [14:55:01] [INF] Clean up collections and playlists Completed after 0 min… seconds
Aug 26 14:55:01 bosgamerz9 jellyfin[192517]: [14:55:01] [INF] Update Plugins Completed after 0 minute(s) and 0 seconds
Hint: Some lines were ellipsized, use -l to show in full.
-------------------------------------------------------------------------------

You should see the service as 'active (running)' above. If not, use https://jellyfin.org/contact to find us for troubleshooting.

You can access your new instance now at http://192.168.1.142:8096 in your web browser to finish setting up Jellyfin.

Thank you for installing Jellyfin, and happy watching!

I can now fire up the UI in a browser

/content/images/2025/09/jellyfin-01.png

I next create a user

/content/images/2025/09/jellyfin-02.png

I mounted some folders to my NAS and added them here

/content/images/2025/09/jellyfin-03.png

I’m not comfortable with it updating my router so I’ll leave that unchecked for now

/content/images/2025/09/jellyfin-04.png

So items worked and some failed

/content/images/2025/09/jellyfin-05.png

I found it funny that my old DVR recordings were there (including the ads)

/content/images/2025/09/jellyfin-06.png

Exposing

I had an idea of using my OpenVPN profile to connect back in to my network externally, but I think Xfinity is blocking traffic now (because they are terrible). I don’t really want to expose this publicly

I could, however, do something like fire off an A record in Azure DNS

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n jelly
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "c3768c40-959a-4baf-a15f-628393d06f08",
  "fqdn": "jelly.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/jelly",
  "name": "jelly",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

The I can use a forwarding service

$ cat jelly-fwd.yaml
---
apiVersion: v1
kind: Endpoints
metadata:
  name: jelly-external-ip
subsets:
- addresses:
  - ip: 192.168.1.142
  ports:
  - name: jellyweb
    port: 8096
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: jelly-external-ip
spec:
  clusterIP: None
  clusterIPs:
  - None
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  - IPv6
  ipFamilyPolicy: RequireDualStack
  ports:
  - name: jellyweb
    port: 8096
    protocol: TCP
    targetPort: 8096
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    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: jelly-external-ip
  name: jelly-ingress
spec:
  rules:
  - host: jelly.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: jelly-external-ip
            port:
              number: 8096
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - jelly.tpk.pw
    secretName: jelly-tls

And applied

$ kubectl apply -f ./jelly-fwd.yaml
endpoints/jelly-external-ip created
service/jelly-external-ip created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/jelly-ingress created

And it works without issue

/content/images/2025/09/jellyfin-07.png

This includes working on mobile as well. I found some DVR footage was a bit dated (like Jared Subway commercials).

However, I feel like I have the upper hand now, a real advantage…

/content/images/2025/09/jellyfin-08.jpg

Movie Web

I was browsing Github and found movie-web.

It was four years old and the URL for a demo wasn’t working anymore.

I cloned and installed (using NodeJS 20.x) with npm i:

builder@DESKTOP-QADGF36:~/Workspaces$ git clone https://github.com/TGlide/movie-web.git
Cloning into 'movie-web'...
remote: Enumerating objects: 321, done.
remote: Total 321 (delta 0), reused 0 (delta 0), pack-reused 321 (from 1)
Receiving objects: 100% (321/321), 1.81 MiB | 7.71 MiB/s, done.
Resolving deltas: 100% (153/153), done.
builder@DESKTOP-QADGF36:~/Workspaces$ cd movie-web/
builder@DESKTOP-QADGF36:~/Workspaces/movie-web$ npm i
npm warn deprecated is-data-descriptor@1.0.0: Please upgrade to v1.0.1
npm warn deprecated is-accessor-descriptor@1.0.0: Please upgrade to v1.0.1
npm warn deprecated @npmcli/move-file@1.1.2: This functionality has been moved to @npmcli/fs
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated is-accessor-descriptor@0.1.6: Please upgrade to v0.1.7
npm warn deprecated is-data-descriptor@0.1.4: Please upgrade to v0.1.5
npm warn deprecated is-data-descriptor@0.1.4: Please upgrade to v0.1.5
npm warn deprecated is-accessor-descriptor@0.1.6: Please upgrade to v0.1.7
npm warn deprecated is-data-descriptor@0.1.4: Please upgrade to v0.1.5
npm warn deprecated is-accessor-descriptor@0.1.6: Please upgrade to v0.1.7
npm warn deprecated is-data-descriptor@0.1.4: Please upgrade to v0.1.5
npm warn deprecated is-accessor-descriptor@0.1.6: Please upgrade to v0.1.7
npm warn deprecated is-accessor-descriptor@0.1.6: Please upgrade to v0.1.7
npm warn deprecated is-data-descriptor@0.1.4: Please upgrade to v0.1.5
npm warn deprecated move-concurrently@1.0.1: This package is no longer supported.
npm warn deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm warn deprecated stable@0.1.8: Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility
npm warn deprecated flatten@1.0.3: flatten is deprecated in favor of utility frameworks such as lodash.
npm warn deprecated figgy-pudding@3.5.2: This module is no longer supported.
npm warn deprecated rimraf@2.7.1: Rimraf versions prior to v4 are no longer supported
npm warn deprecated rimraf@2.7.1: Rimraf versions prior to v4 are no longer supported
npm warn deprecated rimraf@2.7.1: Rimraf versions prior to v4 are no longer supported
npm warn deprecated rimraf@2.7.1: Rimraf versions prior to v4 are no longer supported
npm warn deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm warn deprecated source-map-resolve@0.6.0: See https://github.com/lydell/source-map-resolve#deprecated
npm warn deprecated copy-concurrently@1.0.5: This package is no longer supported.
npm warn deprecated lodash.template@4.5.0: This package is deprecated. Use https://socket.dev/npm/package/eta instead.
npm warn deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
npm warn deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm warn deprecated domexception@2.0.1: Use your platform's native DOMException instead
npm warn deprecated glob@7.1.7: Glob versions prior to v9 are no longer supported
npm warn deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm warn deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm warn deprecated @hapi/topo@3.1.6: This version has been deprecated and is no longer supported or maintained
npm warn deprecated @hapi/bourne@1.3.2: This version has been deprecated and is no longer supported or maintained
npm warn deprecated w3c-hr-time@1.0.2: Use your platform's native performance.now() and performance.timeOrigin.
npm warn deprecated q@1.5.1: You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
npm warn deprecated
npm warn deprecated (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
npm warn deprecated fs-write-stream-atomic@1.0.10: This package is no longer supported.
npm warn deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead
npm warn deprecated rollup-plugin-terser@5.3.1: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser
npm warn deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm warn deprecated @hapi/address@2.1.4: Moved to 'npm install @sideway/address'
npm warn deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm warn deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm warn deprecated rollup-plugin-babel@4.4.0: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-babel.
npm warn deprecated querystring@0.2.1: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm warn deprecated abab@2.0.5: Use your platform's native atob() and btoa() methods instead
npm warn deprecated sane@4.1.0: some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added
npm warn deprecated @hapi/hoek@8.5.1: This version has been deprecated and is no longer supported or maintained
npm warn deprecated @humanwhocodes/config-array@0.5.0: Use @eslint/config-array instead
npm warn deprecated @humanwhocodes/object-schema@1.2.0: Use @eslint/object-schema instead
npm warn deprecated workbox-google-analytics@5.1.4: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
npm warn deprecated @babel/plugin-proposal-unicode-property-regex@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.
npm warn deprecated @babel/plugin-proposal-optional-catch-binding@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.
npm warn deprecated @babel/plugin-proposal-numeric-separator@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
npm warn deprecated @babel/plugin-proposal-object-rest-spread@7.14.7: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
npm warn deprecated @babel/plugin-proposal-private-methods@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
npm warn deprecated @babel/plugin-proposal-optional-chaining@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
npm warn deprecated @babel/plugin-proposal-logical-assignment-operators@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.
npm warn deprecated @babel/plugin-proposal-export-namespace-from@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.
npm warn deprecated @babel/plugin-proposal-class-properties@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
npm warn deprecated @babel/plugin-proposal-class-static-block@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.
npm warn deprecated @babel/plugin-proposal-async-generator-functions@7.14.7: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.
npm warn deprecated @babel/plugin-proposal-private-property-in-object@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.
npm warn deprecated @babel/plugin-proposal-nullish-coalescing-operator@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
npm warn deprecated @babel/plugin-proposal-dynamic-import@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.
npm warn deprecated @babel/plugin-proposal-json-strings@7.14.5: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.
npm warn deprecated babel-eslint@10.1.0: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.
npm warn deprecated @babel/plugin-proposal-optional-chaining@7.12.1: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
npm warn deprecated @babel/plugin-proposal-class-properties@7.12.1: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
npm warn deprecated @babel/plugin-proposal-numeric-separator@7.12.1: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.
npm warn deprecated @babel/plugin-proposal-nullish-coalescing-operator@7.12.1: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
npm warn deprecated @hapi/joi@15.1.1: Switch to 'npm install joi'
npm warn deprecated svgo@1.3.2: This SVGO version is no longer supported. Upgrade to v2.x.x.
npm warn deprecated eslint@7.30.0: This version is no longer supported. Please see https://eslint.org/version-support for other options.
npm warn deprecated core-js@2.6.12: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
npm warn deprecated core-js-pure@3.15.2: core-js-pure@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js-pure.
npm warn deprecated core-js@3.15.2: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.

added 1985 packages, and audited 1986 packages in 1m

147 packages are looking for funding
  run `npm fund` for details

174 vulnerabilities (7 low, 105 moderate, 47 high, 15 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

But the build failed

builder@DESKTOP-QADGF36:~/Workspaces/movie-web$ npm run build

> movie-web@0.1.0 build
> react-scripts build

Creating an optimized production build...
Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:79:19)
    at Object.createHash (node:crypto:139:10)
    at module.exports (/home/builder/Workspaces/movie-web/node_modules/webpack/lib/util/createHash.js:135:53)
    at NormalModule._initBuildHash (/home/builder/Workspaces/movie-web/node_modules/webpack/lib/NormalModule.js:417:16)
    at handleParseError (/home/builder/Workspaces/movie-web/node_modules/webpack/lib/NormalModule.js:471:10)
    at /home/builder/Workspaces/movie-web/node_modules/webpack/lib/NormalModule.js:503:5
    at /home/builder/Workspaces/movie-web/node_modules/webpack/lib/NormalModule.js:358:12
    at /home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:373:3
    at iterateNormalLoaders (/home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:214:10)
    at iterateNormalLoaders (/home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:221:10)
    at /home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:236:3
    at runSyncOrAsync (/home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:130:11)
    at iterateNormalLoaders (/home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:232:2)
    at Array.<anonymous> (/home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:205:4)
    at Storage.finished (/home/builder/Workspaces/movie-web/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:55:16)
    at /home/builder/Workspaces/movie-web/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:91:9
/home/builder/Workspaces/movie-web/node_modules/react-scripts/scripts/build.js:19
  throw err;
  ^

Error: error:0308010C:digital envelope routines::unsupported
    at new Hash (node:internal/crypto/hash:79:19)
    at Object.createHash (node:crypto:139:10)
    at module.exports (/home/builder/Workspaces/movie-web/node_modules/webpack/lib/util/createHash.js:135:53)
    at NormalModule._initBuildHash (/home/builder/Workspaces/movie-web/node_modules/webpack/lib/NormalModule.js:417:16)
    at /home/builder/Workspaces/movie-web/node_modules/webpack/lib/NormalModule.js:452:10
    at /home/builder/Workspaces/movie-web/node_modules/webpack/lib/NormalModule.js:323:13
    at /home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:367:11
    at /home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:233:18
    at context.callback (/home/builder/Workspaces/movie-web/node_modules/loader-runner/lib/LoaderRunner.js:111:13)
    at /home/builder/Workspaces/movie-web/node_modules/babel-loader/lib/index.js:59:103 {
  opensslErrorStack: [
    'error:03000086:digital envelope routines::initialization error',
    'error:0308010C:digital envelope routines::unsupported'
  ],
  library: 'digital envelope routines',
  reason: 'unsupported',
  code: 'ERR_OSSL_EVP_UNSUPPORTED'
}

Node.js v20.19.4

I asked Gemini for some help (I mean, I would google the errors anyhow)

/content/images/2025/09/movieweb-01.png

And it found a legacy SSL option was needed

/content/images/2025/09/movieweb-02.png

Though I am not sure why it thought it was correct to revert the changes after build

/content/images/2025/09/movieweb-03.png

I made an identical correction on the start package.json script and fired up a server with npm run start

/content/images/2025/09/movieweb-04.png

It now asks what I want to watch

/content/images/2025/09/movieweb-05.png

Though, anything I searched for failed

/content/images/2025/09/movieweb-06.png

Looking at the code (lib/lookMovie.js), it would seem it used some kind of Herokuapp to get URLs

function getCorsUrl(url) {
    return `https://hidden-inlet-27205.herokuapp.com/${url}`;
}

Then try and fetch them from “lookmovie.so”

   if (config.type === 'movie') {
        url = getCorsUrl(`https://lookmovie.io/manifests/movies/json/${config.id}/${now}/${accessToken}/master.m3u8`);
    } else if (config.type === 'show') {
        url = getCorsUrl(`https://lookmovie.io/manifests/shows/json/${accessToken}/${now}/${config.id}/master.m3u8`);
    }

which now just links to things like YT and Hulu so I’m guessing this was more of an easy pirate portal (and those never last).

We’ll call this a fail and move on, though it was a good example of using Gemini CLI to help with compile issues

Lauti

I decided to head over to Codeberg to explore what they might have. I found an interesting calendaring app named Lauti with a demo instance at eintopf.info.

Looking at their repo it looks like a simple docker image we can run locally based on the deployment docs

$ docker run --rm -p 3333:3333 -e LAUTI_ADMIN_EMAIL=admin@example.com -e LAUTI_ADMIN_PASSWORD=admin codeberg.org/klasse-methode/lauti:latest
Unable to find image 'codeberg.org/klasse-methode/lauti:latest' locally
latest: Pulling from klasse-methode/lauti
9824c27679d3: Pull complete
e003de853579: Pull complete
78440ed976d3: Pull complete
649907a908fa: Pull complete
f6836a6e2689: Pull complete
e28c31153375: Pull complete
f8e29ab2be06: Pull complete
98bd0168c7c2: Pull complete
Digest: sha256:693b7c5174b4e6c80ed7c54b2d469d9a0185c59ec76bbf9faee042ec0dd8c7c0
Status: Downloaded newer image for codeberg.org/klasse-methode/lauti:latest
time=2025-08-27T11:11:48.339Z level=INFO msg="Chosen Language based on deprecated env var LAUTI_LANGUAGE" language=en
time=2025-08-27T11:11:48.339Z level=INFO msg="Default locale used as LAUTI_LOCALE is undefined" locale=en_US
time=2025-08-27T11:11:48.339Z level=INFO msg="Please replace LAUTI_LANGUAGE with LAUTI_LOCALE!"
time=2025-08-27T11:11:48.908Z level=INFO msg="loaded parent" theme=lauti
time=2025-08-27T11:11:49.032Z level=INFO msg="listening and serving" address=:3333

This looks pretty nice right off the bat

/content/images/2025/09/lauti-01.png

Clicking on “Backstage” didn’t take me to a backstage server, rather it is their word for the admin/setup sections. We can login with the admin user/pass we provided to docker as environment variables

/content/images/2025/09/lauti-02.png

I’ll do the “first steps” wizard

/content/images/2025/09/lauti-03.png

However, it seems to error each time I try and save a group

/content/images/2025/09/lauti-04.png

Using another browser does not let me login now

/content/images/2025/09/lauti-05.png

Docker was giving me the unhealthy indicator

4b3b6b598769   codeberg.org/klasse-methode/lauti:latest                                           "/lauti/docker-entry…"   7 minutes ago   Up 7 minutes (unhealthy)   0.0.0.0:3333->3333/tcp   boring_pare

I decided to try using a different host to just rule out local machine issues

builder@bosgamerz7:~$ docker run -d -p 3333:3333 -e LAUTI_ADMIN_EMAIL=admin@example.com -e LAUTI_ADMIN_PASSWORD=admin codeberg.org/klasse-methode/lauti:latest
Unable to find image 'codeberg.org/klasse-methode/lauti:latest' locally
latest: Pulling from klasse-methode/lauti
9824c27679d3: Pull complete
e003de853579: Pull complete
78440ed976d3: Pull complete
649907a908fa: Pull complete
f6836a6e2689: Pull complete
e28c31153375: Pull complete
f8e29ab2be06: Pull complete
98bd0168c7c2: Pull complete
Digest: sha256:693b7c5174b4e6c80ed7c54b2d469d9a0185c59ec76bbf9faee042ec0dd8c7c0
Status: Downloaded newer image for codeberg.org/klasse-methode/lauti:latest
29c14fb1b1facb20e8650d564d0aeb85270f664abcb008e5190401106fac7187

However, that too errored when I tried to create a group

/content/images/2025/09/lauti-06.png

There is nothing in the logs

builder@bosgamerz7:~$ docker logs hardcore_booth
time=2025-08-27T11:21:08.749Z level=INFO msg="Chosen Language based on deprecated env var LAUTI_LANGUAGE" language=en
time=2025-08-27T11:21:08.749Z level=INFO msg="Default locale used as LAUTI_LOCALE is undefined" locale=en_US
time=2025-08-27T11:21:08.749Z level=INFO msg="Please replace LAUTI_LANGUAGE with LAUTI_LOCALE!"
time=2025-08-27T11:21:09.119Z level=INFO msg="loaded parent" theme=lauti
time=2025-08-27T11:21:09.223Z level=INFO msg="listening and serving" address=:3333

I checked the container repository on Codeberg and saw that “latest” is just 2 days old. Let’s use the last release (3 mo ago)

builder@bosgamerz7:~$ docker run -d -p 3333:3333 -e LAUTI_ADMIN_EMAIL=admin@example.com -e LAUTI_ADMIN_PASSWORD=admin codeberg.org/klasse-methode/lauti:1.0.0
Unable to find image 'codeberg.org/klasse-methode/lauti:1.0.0' locally
1.0.0: Pulling from klasse-methode/lauti
f18232174bc9: Pull complete
932930a83388: Pull complete
f2f39eae3292: Pull complete
79648e5a8c9a: Pull complete
09fdd5f3cb38: Pull complete
9915542f3a00: Pull complete
976369225c62: Pull complete
39b024e3ac27: Pull complete
Digest: sha256:88b6a5a57371452c74f9c99b3075919f9f0fb64877da01a7c0f5244cf62116a0
Status: Downloaded newer image for codeberg.org/klasse-methode/lauti:1.0.0
77dcbbc760ead3e64030c027a06ea19d10e61f9800e2ef4a63cdd48f89358dc7

Still fails to create a group

/content/images/2025/09/lauti-07.png

And nothing in the logs

builder@bosgamerz7:~$ docker logs brave_wu
time=2025-08-27T11:27:46.072Z level=INFO msg="loaded parent" theme=lauti
time=2025-08-27T11:27:46.178Z level=INFO msg="listening and serving" address=:3333

Creating places also fails (and nothing in the docker logs)

/content/images/2025/09/lauti-08.png

In all the errors, they are “method not allowed” which makes me thing they expect some kind of header or auth token not being passed

/content/images/2025/09/lauti-09.png

Hitting this with Gemini CLI just wants to add debug code

/content/images/2025/09/lauti-10.png

Getting it working

/content/images/2025/09/lauti-11.png

The issue was so simple (though I challenge them for not having documented it). We just need to pass the BASE URL, otherwise it’s confused on where to send backend traffic , breaking everything

Once I set the BASE_URL in the docker invokation:

builder@bosgamerz7:~$ docker run -d -p 3333:3333 \
  -e LAUTI_BASE_URL=http://192.168.1.121:3333 \
  -e LAUTI_ADMIN_EMAIL=admin@example.com \
  -e LAUTI_ADMIN_PASSWORD=admin \
  codeberg.org/klasse-methode/lauti:latest
2841d7c7b08bb73c8bd1fb050188fd39955505eb015f01a5e32a9f928c25525f

Then it worked!

Here we can see the created group:

/content/images/2025/09/lauti-12.png

If we do not have SMTP setup, we can create Invite links for users, though note (green arrow) it will insert “https” where this host is “http”

/content/images/2025/09/lauti-13.png

Let’s pivot to K8s now.

I’ll make an A record

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n lauti
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "bc68b578-899a-4436-bbca-287f7e28c43a",
  "fqdn": "lauti.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/lauti",
  "name": "lauti",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

Then use a YAML manifest that will deploy with a proper ingress

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: lauti-db-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lauti
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lauti
  template:
    metadata:
      labels:
        app: lauti
    spec:
      containers:
        - name: lauti
          image: codeberg.org/klasse-methode/lauti:latest
          ports:
            - containerPort: 3333
          env:
            - name: LAUTI_BASE_URL
              value: "https://lauti.tpk.pw"
            - name: LAUTI_SQLITE_DB
              value: "/mnt/db/lauti.db"
            - name: LAUTI_ADMIN_EMAIL
              value: "admin@example.com"
            - name: LAUTI_ADMIN_PASSWORD
              value: "admin"
          volumeMounts:
            - name: db
              mountPath: /mnt/db
      volumes:
        - name: db
          persistentVolumeClaim:
            claimName: lauti-db-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: lauti
spec:
  selector:
    app: lauti
  ports:
    - protocol: TCP
      port: 3333
      targetPort: 3333
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
  name: lauti-ingress
spec:
  rules:
  - host: lauti.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: lauti
            port:
              number: 3333
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - lauti.tpk.pw
    secretName: lauti-tls

Now apply

$ kubectl apply -f ./manifest.yaml
persistentvolumeclaim/lauti-db-pvc created
deployment.apps/lauti created
service/lauti created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/lauti-ingress created

(of course, as this is production, I used something other than “admin” for user/pass)

When I see the cert is satisfied

$ kubectl get cert lauti-tls
NAME        READY   SECRET      AGE
lauti-tls   False   lauti-tls   71s
$ kubectl get cert lauti-tls
NAME        READY   SECRET      AGE
lauti-tls   True    lauti-tls   16m

I can give it a try

/content/images/2025/09/lauti-14.png

Looks like the admin page is working

/content/images/2025/09/lauti-15.png

I had to fill out a lot of required parts in order to actually log an event. For instance, you cannot create an event till you have created at least one place, group, topic and category.

/content/images/2025/09/lauti-16.png

I can now see the event listed

/content/images/2025/09/lauti-17.png

The details look fine, but it is clear the OpenStreetMaps integration isn’t loading maps

/content/images/2025/09/lauti-18.png

A quick check in Google though shows those coordinates are correct

/content/images/2025/09/lauti-19.png

Because I detailed out a place, we can see that place has an address which would work for most people

/content/images/2025/09/lauti-20.png

Let’s compare that to Gancio, what i use today

/content/images/2025/09/lauti-21.png

At least to my eyes, I think I like the Gancio interface a bit more

/content/images/2025/09/lauti-22.png

However, it could be argued that for having a lot of events, Lauti might be better with it’s stacked list interface.

Summary

Today we looked at a few Open-source tools: Jellyfin, Movieweb and Lauti. MovieWeb was clearly some pirate software that was abandoned so no need to go further with it.

Jellyfin is nice. I used to really like Plex but then my NAS stopped handling it, the codec support got worse and they started to charge for more and more.

Lauti is quite nice - the problems I had at the start were mostly self-inflicted. That said, their docker quick start made no mention of the required BASE environment variable so it’s not entirely on me. Once I got it sorted, firing it up in K8s was easy.

In comparing with Gancio, which I coveredat the start of August, I think I might like Gancio better in so far as it’s a very light clean interface. However, because it just shows a few events at a time, it could be a challenge for a larger group or project. I might suggest smaller teams or solo practitioners use Gancio and larger groups or shared projects use Lauti. I’ll keep both going for a while regardless.

opensource docker kubernetes Lauti Gancio Jellyfin

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes