<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Home on FarisZR</title><link>https://fariszr.com/</link><description>Recent content in Home on FarisZR</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 01 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://fariszr.com/index.xml" rel="self" type="application/rss+xml"/><item><title>How I Made My Homelab Fix Itself Using Komodo and OpenClaw</title><link>https://fariszr.com/homelab-fixes-itself-komodo-openclaw/</link><pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate><guid>https://fariszr.com/homelab-fixes-itself-komodo-openclaw/</guid><description>&lt;img src="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/thumbnail.jpeg" alt="Featured image of post How I Made My Homelab Fix Itself Using Komodo and OpenClaw" />&lt;p>The most annoying thing about homelabbing is the upkeep. Keeping things running, handling breaking updates, fixing stuff as it breaks instead of letting everything slowly decay.
It&amp;rsquo;s kinda embarrassing telling people how cool your Homelab is, only for things to break constantly.&lt;/p>
&lt;p>Luckily AI agents have gotten good enough that you can use them for the upkeep of your homelab.&lt;/p>
&lt;p>Until now though, you still had to sit down at your computer, start an agent, copy the logs, show it the error, and unless it&amp;rsquo;s running on the server itself, apply the fix manually.&lt;/p>
&lt;p>You could install the agent directly on the server and let it manage things, but what if you have multiple servers? Like a whole homelab network?&lt;/p>
&lt;p>Do you install an agent on every server? And what happens when services depend on each other?&lt;/p>
&lt;h2 id="komodo">Komodo
&lt;/h2>&lt;p>&lt;img src="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/komodo.png"
width="1699"
height="941"
srcset="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/komodo_hu3604480265148128085.png 480w, https://fariszr.com/homelab-fixes-itself-komodo-openclaw/komodo_hu3283292176384866200.png 1024w"
loading="lazy"
alt="komodo ui"
class="gallery-image"
data-flex-grow="180"
data-flex-basis="433px"
>&lt;/p>
&lt;p>Here comes &lt;a class="link" href="https://komo.do/" target="_blank" rel="noopener"
>Komodo&lt;/a>.
Komodo is an open-source GitOps platform that lets you manage your Docker Compose stacks through a clean UI with full API access to logs, container status, and even a bash shell on the server.&lt;/p>
&lt;p>It&amp;rsquo;s a central hub for all your servers.
What makes it special is that any change you make to your stacks, resources, or Procedures in Komodo can be committed back to git, so things never drift from the repo.
Everything around your compose stacks is described as code under the Resources folder: servers, procedures.
That means your agent can also adjust how Komodo itself works on the fly.&lt;/p>
&lt;h3 id="my-secret-sauce-the-komodo-km-cli-agentic-edition">My secret sauce: The Komodo KM CLI, agentic edition.
&lt;/h3>&lt;p>Komodo has a powerful API, but the official CLI doesn&amp;rsquo;t expose all of it.
So I built my own version, &lt;a class="link" href="https://github.com/FarisZR/komodo-agentic-cli/tree/agentic-cli/bin/cli" target="_blank" rel="noopener"
>the &amp;ldquo;agentic&amp;rdquo; edition&lt;/a>.
It adds support for:&lt;/p>
&lt;ul>
&lt;li>&lt;code>stack&lt;/code> - manage stacks and see their status&lt;/li>
&lt;li>&lt;code>procedure&lt;/code> - trigger procedures like auto-updates&lt;/li>
&lt;li>&lt;code>sync&lt;/code> - manage resource sync (GitOps repos)&lt;/li>
&lt;li>&lt;code>variable&lt;/code> - manage variables and secrets without exposing values to the agent (e.g. setting secrets as command outputs)&lt;/li>
&lt;/ul>
&lt;p>It also adjusts the &lt;code>exec&lt;/code> and &lt;code>ssh&lt;/code> commands so agents can run commands on the host or inside a container non-interactively. (&lt;code>ssh&lt;/code> for interactive sessions, &lt;code>exec&lt;/code> for non-interactive commands.)&lt;/p>
&lt;p>Running the KM CLI within a GitOps repo lets your agent debug running homelab services, fix issues, plan and push updates, or even tackle that migration we&amp;rsquo;ve all been putting off for months.&lt;/p>
&lt;p>You can also create a service user so you have an idea what the agent did on komodo.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/komodo-logs.png"
width="426"
height="205"
srcset="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/komodo-logs_hu11125456043377363013.png 480w, https://fariszr.com/homelab-fixes-itself-komodo-openclaw/komodo-logs_hu5496957812307465962.png 1024w"
loading="lazy"
alt="komodo-logs"
class="gallery-image"
data-flex-grow="207"
data-flex-basis="498px"
>&lt;/p>
&lt;p>And if you don&amp;rsquo;t want your agent running arbitrary commands on your servers, you can disable that at the container or server level in the Komodo &amp;ldquo;periphery&amp;rdquo; config. Just install periphery and edit the config to disable terminal access for the host or all containers.&lt;/p>
&lt;h2 id="ai-assistants-openclaw-or-hermes-agent">AI Assistants (OpenClaw or Hermes Agent)
&lt;/h2>&lt;p>Komodo solves the stack access and management problem, but you still have to sit down, open your computer, and prompt the agent to fix things.&lt;/p>
&lt;p>What if you could just message your agent and say &amp;ldquo;Jellyfin is down, fix it&amp;rdquo;?
That&amp;rsquo;s the idea behind async personal assistants like OpenClaw and the newer Hermes. Fix things as you notice them, wherever you are.&lt;/p>
&lt;p>Just clone the git repo, install the agentic KM CLI skill, and your agent can debug and fix any issue across your homelab, even a multi-server network.&lt;/p>
&lt;p>Homelabbing becomes genuinely fun. You say what you want and it appears. You can see what changed, revert it easily thanks to git, and even set up backups to guard against catastrophic failures, though most models are smart enough to not do the classic &lt;code>rm -rf&lt;/code> mistake anymore. Still nice to have.&lt;/p>
&lt;h3 id="openclaw-in-action">Openclaw in action
&lt;/h3>&lt;p>So how does this look in reality? What can I use this for? Well, I quickly found a use for this.&lt;/p>
&lt;p>I self-host my own language tool server and it breaks often. So why not let OpenClaw figure out a solution for it?&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/openclaw-1.png"
width="1049"
height="423"
srcset="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/openclaw-1_hu16749338968004068411.png 480w, https://fariszr.com/homelab-fixes-itself-komodo-openclaw/openclaw-1_hu5079639590609920361.png 1024w"
loading="lazy"
alt="my openclaw assistant Dax on Matrix (gpt 5.3-codex)"
class="gallery-image"
data-flex-grow="247"
data-flex-basis="595px"
>&lt;/p>
&lt;p>After telling it to investigate and actually solve the issue, it deployed a new modified docker stack that added a memory limit to the container
&lt;img src="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/openclaw-2.png"
width="905"
height="1259"
srcset="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/openclaw-2_hu8592652574171081884.png 480w, https://fariszr.com/homelab-fixes-itself-komodo-openclaw/openclaw-2_hu15293945345280530701.png 1024w"
loading="lazy"
alt="the fix it found out"
class="gallery-image"
data-flex-grow="71"
data-flex-basis="172px"
>&lt;/p>
&lt;p>I legit did this while i was cooking in the kitchen, didn&amp;rsquo;t need to do anything manually.&lt;/p>
&lt;p>I also used it to see if any of my servers had LiteLLM
installed after the CVE was made public.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/litellm-example.webp"
width="1141"
height="1123"
srcset="https://fariszr.com/homelab-fixes-itself-komodo-openclaw/litellm-example_hu15711609569472859966.webp 480w, https://fariszr.com/homelab-fixes-itself-komodo-openclaw/litellm-example_hu17191363043675715496.webp 1024w"
loading="lazy"
alt="OpenClaw using komodo to check if litellm is installed anywhere"
class="gallery-image"
data-flex-grow="101"
data-flex-basis="243px"
>&lt;/p>
&lt;h2 id="the-next-step-full-automation">The next step: full automation.
&lt;/h2>&lt;p>Deploying new services and fixing containers on demand is great. But what if things never broke in the first place? What if your agent fixed issues as they happened, not as you noticed them?
AKA what if things just Self healed?&lt;/p>
&lt;p>it&amp;rsquo;s actually doable now. All you need is Komodo&amp;rsquo;s alert system calling a webhook connected to your OpenClaw or Hermes agent. The agent sees the logs and makes whatever changes are needed to bring the service back up. If it&amp;rsquo;s a server-level issue, you could even hook into your hoster&amp;rsquo;s API and trigger a force restart.
It doesn&amp;rsquo;t even have to be fully autonomous. You could build a way for the agent to reach you and ask you for approval.&lt;/p>
&lt;p>It&amp;rsquo;s proactive. The agent already has the fix and you are the one to allow it or not. You are actually the manager now.&lt;/p>
&lt;p>Honestly, thinking about it feels a bit wild. We&amp;rsquo;re on our way to self-healing, self-aware systems that can change themselves to stay up no matter what. Give your agent enough access and resources, and it could restore a backup on another server and bring the service back up without you ever noticing. This is an exciting time for sure.&lt;/p></description></item><item><title>FYI: 2025 Huawei Matepads can't play 4k youtube videos</title><link>https://fariszr.com/huawei-matepad-pro-4k-youtube-vp9/</link><pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate><guid>https://fariszr.com/huawei-matepad-pro-4k-youtube-vp9/</guid><description>&lt;img src="https://fariszr.com/huawei-matepad-pro-4k-youtube-vp9/thumbnail.jpg" alt="Featured image of post FYI: 2025 Huawei Matepads can't play 4k youtube videos" />&lt;p>I got my hands on a 2025 Huawei Matepad pro 12.2, with a gorgeous display.
I was kinda stocked to use it as my media power house, that&amp;rsquo;s where the weirdness started.
The device can&amp;rsquo;t play 4k videos on YouTube for some reason, and to make it worse, it&amp;rsquo;s a known issue, but it isn&amp;rsquo;t something that gets mentioned in reviews.
It can play 1080p vp9 videos just fine, and you can play 4k video on YouTube through the browser, so what&amp;rsquo;s going on here?&lt;/p>
&lt;h2 id="the-reason">The reason
&lt;/h2>&lt;p>Huawei in their ultimate wisdom chose to support the paid h264 and HEVC codecs, but cheeped out on supporting the royalty-free VP9 codec used by the world&amp;rsquo;s most popular streaming Platform, YouTube.
That&amp;rsquo;s why streaming apps like Netflix can play 4k just fine, because they use HEVC.&lt;/p>
&lt;p>YouTube uses VP9, and the Kirin chipset has &lt;strong>no hardware VP9 decoder whatsoever&lt;/strong>. it can only play 1080p vp9 videos because the software decoder is limited to 2048x2048, aka 1920x1080.&lt;/p>
&lt;p>Browsers can include their own software decoder, and that&amp;rsquo;s what happening here, when playing a 4k video, the browser is using the CPU to do the decoding, and that&amp;rsquo;s why it drops frames on 4k 60fps videos.&lt;/p>
&lt;p>What an absolutely asinine decision, what makes it crazier is that VP9 is supported by devices going back to &lt;strong>2013&lt;/strong>!&lt;/p>
&lt;h2 id="confirmed-by-huawei-support">Confirmed by Huawei support
&lt;/h2>&lt;p>I contacted the official Huawei support, and they confirmed that this is a known issue with the workaround being playing videos through the browser.&lt;/p>
&lt;p>They also said this issue is known for the 2025 13.3 and 12.2 Matepad Pros and the 2025 Matepad 12X, aka all of their 2025 releases. Great!&lt;/p>
&lt;p>A flagship tablet that can&amp;rsquo;t play 4k videos in 2025, what a flagship!&lt;/p>
&lt;h2 id="how-i-found-out">How I found out
&lt;/h2>&lt;p>I let OpenCode loose with ADB shell access to the device and &amp;ldquo;we&amp;rdquo; systematically checked:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Huawei Browser (4K VP9)&lt;/strong>: &lt;code>dumpsys media.resource_manager&lt;/code> showed only software VP9 decoders available (&lt;code>c2.android.vp9.decoder&lt;/code>, &lt;code>OMX.google.vp9.decoder&lt;/code>). Hardware decoders exist only for H.264 and HEVC. CPU monitoring revealed &lt;code>vpx tile worker&lt;/code> threads maxing out cores - clear software decoding.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Codec inventory&lt;/strong>: Full decoder list from &lt;code>dumpsys media.player&lt;/code> confirmed hardware support limited to &lt;code>OMX.hisi.video.decoder.avc&lt;/code> (H.264) and &lt;code>OMX.hisi.video.decoder.hevc&lt;/code> (HEVC, 4K capable). VP9 is software-only.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Grayjay (1080p AVC)&lt;/strong>: Confirmed hardware acceleration with &lt;code>OMX.hisi.video.decoder.avc&lt;/code> allocation and efficient MediaCodec threads.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Grayjay (1080p VP9)&lt;/strong>: Switched to VP9 stream and confirmed degradation to &lt;code>c2.android.vp9.decoder&lt;/code> software path. Even at 1080p, no hardware acceleration exists for VP9.&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>Knocker: Skip the VPN for your homelab, Just Knock</title><link>https://fariszr.com/knocker-access-your-homelab-without-vpn/</link><pubDate>Sun, 14 Dec 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/knocker-access-your-homelab-without-vpn/</guid><description>&lt;img src="https://fariszr.com/knocker-access-your-homelab-without-vpn/thumbnail.jpeg" alt="Featured image of post Knocker: Skip the VPN for your homelab, Just Knock" />&lt;p>I have a homelab, but accessing it from outside my home network was always a pain. I didn&amp;rsquo;t want to deal with the hassle of setting up VPNs on every device just to watch something on Jellyfin, so I built Knocker!&lt;/p>
&lt;p>It&amp;rsquo;s a knock-based access control service for your homelab that actually works with mobile apps, with clients on most platforms used by homelabbers.&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;div class="video-wrapper">
&lt;video
controls
src="knocker-video.webm"
poster="./.png"
autoplay
>
&lt;p>
Your browser doesn't support HTML5 video. Here is a
&lt;a href="knocker-video.webm">link to the video&lt;/a> instead.
&lt;/p>
&lt;/video>
&lt;/div>
&lt;!-- raw HTML omitted -->
&lt;h2 id="how-does-it-work">How does it work?
&lt;/h2>&lt;div class="mermaid" data-mermaid="pending">
&lt;script type="text/plain" data-mermaid-source>sequenceDiagram
participant User
participant Caddy as Reverse Proxy
participant Knocker
participant Service
Note over User,Service: 1. Access Denied
User->>Caddy: Request Service
Caddy->>Knocker: Check Access?
Knocker-->>Caddy: Denied
Caddy-->>User: 401 Unauthorized
Note over User,Knocker: 2. The "Knock"
User->>Knocker: Send Knock (Token)
Knocker->>Knocker: Whitelist User IP
Note over User,Service: 3. Access Granted
User->>Caddy: Request Service
Caddy->>Knocker: Check Access?
Knocker-->>Caddy: Allowed
Caddy->>Service: Forward Request
Service-->>User: Response&lt;/script>
&lt;/div>&lt;p>By being completely transparent for whitelisted IPs, Knocker doesn&amp;rsquo;t break API clients like mobile apps. Once you whitelist your IP, you are ready to go. You can even whitelist an IP other than your own if needed.&lt;/p>
&lt;h3 id="knocker-tokens">Knocker Tokens
&lt;/h3>&lt;p>Knocker works by using tokens as passwords. Each token has specific permissions, like a customizable max TTL (capped server-side) and an option to allow whitelisting IPs other than your own.&lt;/p>
&lt;p>By connecting Knocker to your reverse proxy, Knocker will analyze the request IP. Depending on the local whitelists, it will either return a &lt;code>200 OK&lt;/code> or a &lt;code>401 Unauthorized&lt;/code> status, which is then forwarded to the end client.&lt;/p>
&lt;h2 id="why-not-just-use-a-vpn">Why Not Just Use a VPN?
&lt;/h2>&lt;p>I already use Tailscale. In fact, the IP of my homelab is the Tailscale IP because I added custom routes for it.&lt;/p>
&lt;p>But using a VPN for everything is annoying. You have to install it on every single device, which is a nightmare on things like Smart TVs. Plus, keeping the Tailscale app running on Android drains my battery like crazy.&lt;/p>
&lt;p>With Knocker, you just need one device to knock, and thanks to NAT, your whole network gets access.&lt;/p>
&lt;h3 id="is-this-as-secure-as-a-vpn">Is This as Secure as a VPN?
&lt;/h3>&lt;p>No.&lt;/p>
&lt;p>Knocker is a compromise. It trades some security for convenience. You can&amp;rsquo;t whitelist specific devices, only source IPs (which might be shared CGNAT IPs).&lt;/p>
&lt;p>You&amp;rsquo;re basically making a bet that within that short whitelist window, the likelihood of a hacker scanning your specific port and attacking it is pretty slim.&lt;/p>
&lt;p>That&amp;rsquo;s why you should use short TTLs on public networks. In general, Knocker should be used in front of services that already have their own authentication.&lt;/p>
&lt;h2 id="setup">Setup
&lt;/h2>&lt;p>Knocker is distributed as a Docker container that optionally uses the system DBus socket to interact with the FirewallD daemon.&lt;/p>
&lt;p>&lt;code>docker-compose.yml&lt;/code>: &lt;a class="link" href="https://github.com/FarisZR/knocker/blob/main/docker-compose.yml" target="_blank" rel="noopener"
>github.com/FarisZR/knocker&lt;/a>&lt;/p>
&lt;h3 id="knockeryaml">knocker.yaml
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">server&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">8000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">trusted_proxies&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Trust the IPv4 and IPv6 subnets of the caddy_net docker network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Adjust these to match your specific Docker network configuration.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;172.16.238.0/24&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;fd00:dead:beef::/64&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">cors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">allowed_origin&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;https://knocker.fariszr.com&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># SECURITY: Set to your Knocker webapp&amp;#39;s origin if you are hosting your own.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">security&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">always_allowed_ips&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># These IPs/CIDRs are always allowed to pass through the /verify endpoint&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># without needing to be dynamically whitelisted.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># It&amp;#39;s recommended to include your reverse proxy&amp;#39;s IP here.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;172.16.238.0/24&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;fd00:dead:beef::/64&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Also your home&amp;#39;s local network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;192.168.1.0/24&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">excluded_paths&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Request paths that start with any of these values will bypass&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># the IP whitelist check entirely.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Example:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - &amp;#34;/api/v1/status&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - &amp;#34;/metrics&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;/knock&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Maximum number of entries in the whitelist (default: 10000)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">max_whitelist_entries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">10000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">whitelist&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># The path where the whitelist file will be stored.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># This path is relative to the container&amp;#39;s file system.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># The docker-compose.yml file will mount a volume to this location.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storage_path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/data/whitelist.json&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">api_keys&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;admin-key-for-remote-whitelisting&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;CHANGE_ME_SUPER_SECRET_ADMIN_KEY&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># SECURITY: Change this to a strong, random key&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">max_ttl&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">3600&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Maximum TTL in seconds (1 hour)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">allow_remote_whitelist&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Can whitelist other IP/CIDR specified in request body&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="connecting-your-reverse-proxy-to-knocker">Connecting Your Reverse Proxy to Knocker
&lt;/h3>&lt;p>I use Caddy, but Knocker should work with any reverse proxy that supports using an external auth endpoint.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-caddy" data-lang="caddy">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Define a reusable snippet for the knock-knock check.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># It points to the knocker service using Docker&amp;#39;s internal DNS.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">(knocker_auth)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">forward_auth&lt;/span> &lt;span class="n">knocker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8000&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">uri&lt;/span> &lt;span class="nd">/verify?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">copy_headers&lt;/span> &lt;span class="s">X-Forwarded-For&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># The public endpoint for performing the knock.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Make sure this domain points to your Caddy server&amp;#39;s IP.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="gh">knock.your-domain.com&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">reverse_proxy&lt;/span> &lt;span class="n">knocker&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8000&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># An example protected service.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="gh">jellyfin.your-domain.com&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">import&lt;/span> knocker_auth&lt;span class="c1"> # Apply the forward_auth check
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">reverse_proxy&lt;/span> &lt;span class="s">jellyfin_service_name:8096&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now your service will respond with a &lt;code>401 Unauthorized&lt;/code> for every non-whitelisted IP.&lt;/p>
&lt;h2 id="firewalld-integration">FirewallD Integration
&lt;/h2>&lt;p>Knocker has another trick up its sleeve: it can integrate with FirewallD to operate on the firewall level, allowing you to use it for non-HTTP services like a game server!&lt;/p>
&lt;div class="mermaid" data-mermaid="pending">
&lt;script type="text/plain" data-mermaid-source>sequenceDiagram
participant User
participant Firewall
participant Knocker
participant Service
Note over User,Service: 1. Port Blocked
User->>Firewall: Connect to Service
Firewall--xUser: Drop Connection
Note over User,Knocker: 2. The "Knock"
User->>Knocker: Send Knock (Token)
Knocker->>Firewall: Open Port for User IP
Note over User,Service: 3. Access Granted
User->>Firewall: Connect to Service
Firewall->>Service: Allow Traffic
Service-->>User: Connected&lt;/script>
&lt;/div>&lt;!-- raw HTML omitted -->
&lt;div class="video-wrapper">
&lt;video
controls
src="firewall-demo.webm"
poster="./.png"
autoplay
>
&lt;p>
Your browser doesn't support HTML5 video. Here is a
&lt;a href="firewall-demo.webm">link to the video&lt;/a> instead.
&lt;/p>
&lt;/video>
&lt;/div>
&lt;!-- raw HTML omitted -->
&lt;p>There is a caveat, though: &lt;strong>exposed bridge Docker ports will still bypass these rules.&lt;/strong>
Unfortunately, there isn&amp;rsquo;t any firewall that deals with Docker port forwarding rules well.
You have to use host networking mode for it to work.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">firewalld&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">zone_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;knocker&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># WARNING: Zone won&amp;#39;t be cleaned up automatically on shutdown&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">zone_priority&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>-&lt;span class="m">100&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Zone priority (negative numbers = higher priority)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># zone_target: &amp;#34;default&amp;#34; # Optional: Set the zone target (default: not set)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Options: &amp;#34;default&amp;#34;, &amp;#34;ACCEPT&amp;#34;, &amp;#34;REJECT&amp;#34;, &amp;#34;DROP&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># &amp;#34;default&amp;#34; = practically matches reject behavior&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># &amp;#34;ACCEPT&amp;#34; = Accept all packets not matched by rules set by knocker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># &amp;#34;REJECT&amp;#34; = Reject all packets not matched by rules set by knocker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># &amp;#34;DROP&amp;#34; = Silently drop all packets not matched by rules set by knocker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default_action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;drop&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Action for blocked traffic on monitored ports:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">monitored_ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Only these ports will be protected by knocker firewall rules&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">80&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tcp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tcp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">22&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tcp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">monitored_ips&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># WARNING: Using 0.0.0.0/0 or ::/0 applies DROP/REJECT rules to ALL traffic&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;203.0.113.0/24&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Example WAN IPv4 range - CHANGE THIS&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;2001:db8:1234::/48&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Example WAN IPv6 range - CHANGE THIS&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="clients">Clients
&lt;/h2>&lt;p>The strong point of Knocker, and where I actually spent most of my time, is the clients.
I designed them to get out of the way as much as possible.&lt;/p>
&lt;h3 id="knocker-web">Knocker-Web
&lt;/h3>&lt;p>&lt;a class="link" href="https://github.com/FarisZR/Knocker-Web" target="_blank" rel="noopener"
>GitHub.com/FarisZR/Knocker-Web&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-web.webp"
width="705"
height="858"
srcset="https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-web_hu565177608104904794.webp 480w, https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-web_hu10262828763814643767.webp 1024w"
loading="lazy"
alt="Knocker-Web"
class="gallery-image"
data-flex-grow="82"
data-flex-basis="197px"
>&lt;/p>
&lt;p>Knocker Web is a web client built with Vite.
It&amp;rsquo;s fully static and can be installed as a PWA. It&amp;rsquo;s meant to be the primary client, as it works basically everywhere.&lt;/p>
&lt;p>What makes it really convenient is the &amp;ldquo;knock on reload&amp;rdquo; feature.
You just open the site and that&amp;rsquo;s it; you don&amp;rsquo;t care what happens after that.
It&amp;rsquo;s especially useful when used as a PWA, as it means it will perform a knock when opened.&lt;/p>
&lt;h3 id="knocker-cli">Knocker-CLI
&lt;/h3>&lt;p>&lt;a class="link" href="https://github.com/FarisZR/knocker-CLI" target="_blank" rel="noopener"
>GitHub.com/FarisZR/knocker-CLI&lt;/a>&lt;/p>
&lt;p>A CLI client written in Go with support for creating a background service for periodic knocks using Systemd/LaunchAgent.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&amp;gt;&amp;gt; knocker --help
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">A reliable, cross-platform service that keeps your external IP address whitelisted.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">It runs in the background, detects IP changes, and ensures you always have access.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Usage:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> knocker &lt;span class="o">[&lt;/span>flags&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> knocker &lt;span class="o">[&lt;/span>command&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Available Commands:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> completion Generate the autocompletion script &lt;span class="k">for&lt;/span> the specified shell
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">help&lt;/span> Help about any &lt;span class="nb">command&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> install Install the Knocker service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> knock Manually trigger a whitelist request
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> run Run the Knocker service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> start Start the Knocker service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> status Get the status of the Knocker service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> stop Stop the Knocker service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> uninstall Uninstall the Knocker service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Flags:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --check_interval int Interval in minutes to poll &lt;span class="k">for&lt;/span> IP changes &lt;span class="o">(&lt;/span>only used when ip_check_url is &lt;span class="nb">set&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">(&lt;/span>default 5&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --config string config file &lt;span class="o">(&lt;/span>default is &lt;span class="nv">$HOME&lt;/span>/.knocker.yaml&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -h, --help &lt;span class="nb">help&lt;/span> &lt;span class="k">for&lt;/span> knocker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --ip_check_url string URL of the external IP checker service
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> --ttl int Time to live in seconds &lt;span class="k">for&lt;/span> the knock request &lt;span class="o">(&lt;/span>&lt;span class="m">0&lt;/span> &lt;span class="k">for&lt;/span> server default&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> -v, --version version &lt;span class="k">for&lt;/span> knocker
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Use &lt;span class="s2">&amp;#34;knocker [command] --help&amp;#34;&lt;/span> &lt;span class="k">for&lt;/span> more information about a command.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="knocker-gnome">Knocker-Gnome
&lt;/h4>&lt;p>&lt;a class="link" href="https://github.com/FarisZR/knocker-Gnome" target="_blank" rel="noopener"
>GitHub.com/FarisZR/knocker-Gnome&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-gnome.webp"
width="461"
height="376"
srcset="https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-gnome_hu1290299578857773620.webp 480w, https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-gnome_hu5103673797175496364.webp 1024w"
loading="lazy"
alt="Knocker Gnome Extension"
class="gallery-image"
data-flex-grow="122"
data-flex-basis="294px"
>&lt;/p>
&lt;p>An experimental Gnome extension that interprets the JSON logs output by Knocker-CLI, and allows you to manually trigger knocks, disable the service, and see the timeout for the current knock.&lt;/p>
&lt;h3 id="knocker-expo">Knocker-EXPO
&lt;/h3>&lt;p>&lt;a class="link" href="https://github.com/FarisZR/knocker-expo" target="_blank" rel="noopener"
>GitHub.com/FarisZR/knocker-expo&lt;/a>&lt;/p>
&lt;figure class="gallery-image" style="flex-grow: 47; flex-basis: 1080px; --img-max-height: 70vh;">
&lt;a href="https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-expo.webp" style="display: block;">
&lt;img
src="https://fariszr.com/knocker-access-your-homelab-without-vpn/knocker-expo.webp"
width="1080"
height="2286"
loading="lazy" alt="Knocker EXPO" style="max-height: 70vh; width: auto; height: auto;"
>
&lt;/a>
&lt;figcaption>Knocker EXPO&lt;/figcaption>
&lt;/figure>
&lt;p>An experimental Android App built using React EXPO.
It does the same thing as the PWA, with support for creating a background service to regularly send knocks.&lt;/p>
&lt;p>However, its reliability depends on the manufacturer, as not all of them actually allow Android background processes to run reliably.&lt;/p>
&lt;h2 id="vibe-coded-with-ai">Vibe Coded with AI
&lt;/h2>&lt;p>Thanks to the Roo Code hackathon (sponsored by Requestly and Google), I finally had the excuse to build this.
I started with Gemini 2.5 Pro, burned through over $1700 in tokens, and eventually moved to GPT-5-CODEX and GitHub Coding Agent (Sonnet 4, later Sonnet 4.5) to build out features.&lt;/p>
&lt;p>I used CodeRabbit for reviews and set up a full integration test environment for the agents to iterate against that&amp;rsquo;s why this thing actually works at all.&lt;/p>
&lt;p>I ran static analysis on the backend and found zero vulnerabilities, so there&amp;rsquo;s that.&lt;/p>
&lt;p>What I&amp;rsquo;m trying to say is, this isn&amp;rsquo;t your average &amp;ldquo;I told Replit to code it&amp;rdquo; project, but still, if you&amp;rsquo;re anti-AI, don&amp;rsquo;t use this please.&lt;/p>
&lt;p>I may go into more details about my workflow for implementing Knocker using Roo Code in a separate blog post, because it wasn&amp;rsquo;t easy to go this far with AI.&lt;/p></description></item><item><title>How to use the Proxy Protocol with Caddy (Get the Source IP from a TCP proxy)</title><link>https://fariszr.com/use-proxy-protocol-with-caddy/</link><pubDate>Thu, 16 Oct 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/use-proxy-protocol-with-caddy/</guid><description>&lt;img src="https://fariszr.com/use-proxy-protocol-with-caddy/thumbnail.jpg" alt="Featured image of post How to use the Proxy Protocol with Caddy (Get the Source IP from a TCP proxy)" />&lt;p>My Homelab often has peering issues when connecting from another ISP.
The solution to this is just to pass the connection through a proxy, which has better speed than a direct connection.
But if I want to do this without MITMing the connection, it can&amp;rsquo;t be a classic reverse proxy sandwich.&lt;/p>
&lt;p>That&amp;rsquo;s where tools like &lt;a class="link" href="https://fariszr.com/home-server-access-without-port-forwarding/" target="_blank" rel="noopener"
>rathole&lt;/a> come in, they are TCP proxies that allow you to forward any TCP connection to your homelab.
However, there&amp;rsquo;s always the issue of the source IP.
Due to how these proxies work, the source will always be the IP of the rathole client on the homelab (AKA the proxy itself) and not the actual source IP of the connection.&lt;/p>
&lt;p>Proxy protocol fixes this by appending headers to the TCP proxied connections that give details about the actual source IP.&lt;/p>
&lt;h2 id="using-the-proxy-protocol-with-caddy">Using the Proxy Protocol with Caddy
&lt;/h2>&lt;p>Assuming your TCP proxy supports the proxy protocol (example, &lt;a class="link" href="https://fariszr.com/home-server-access-without-port-forwarding/#forward-source-ip-with-proxy-protocol-in-rathole" target="_blank" rel="noopener"
>modified version of rathole&lt;/a>)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-caddyfile" data-lang="caddyfile">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">servers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">listener_wrappers&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">proxy_protocol&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">allow&lt;/span> &lt;span class="mi">172&lt;/span>&lt;span class="s">.20.0.0/16&lt;/span>&lt;span class="c1"> #ip with netmask of the proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">fallback_policy&lt;/span> &lt;span class="s">reject&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">tls&lt;/span>&lt;span class="c1"> # the proxied packets are HTTPS, so we still need the tls wrapper https://caddyserver.com/docs/caddyfile/options#proxy-protocol
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">example.fariszr.com&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">reverse_proxy&lt;/span> &lt;span class="n">service&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8000&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">header_up&lt;/span> &lt;span class="s">X-Forwarded-For&lt;/span> &lt;span class="se">{client_ip}&lt;/span>&lt;span class="c1"> # {client_ip} is the source ip in the proxy
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">header_up&lt;/span> &lt;span class="s">X-Real-IP&lt;/span> &lt;span class="se">{client_ip}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">header_up&lt;/span> &lt;span class="s">X-Forwarded-Proto&lt;/span> &lt;span class="se">{scheme}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">header_up&lt;/span> &lt;span class="s">X-Forwarded-Host&lt;/span> &lt;span class="se">{host}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now your app should get the source IP correctly without actually being aware of the proxy protocol and the connection being proxied through a TCP proxy.&lt;/p></description></item><item><title>Gitea Actions, a buggy mess</title><link>https://fariszr.com/gitea-actions-a-buggy-mess/</link><pubDate>Wed, 03 Sep 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/gitea-actions-a-buggy-mess/</guid><description>&lt;img src="https://fariszr.com/gitea-actions-a-buggy-mess/thumbnail.jpg" alt="Featured image of post Gitea Actions, a buggy mess" />&lt;p>My team uses Jenkins for CI/CD, and we want to migrate away from it due to its insanely complex and hard to understand scripts.&lt;/p>
&lt;p>We use Gitea as our git hosting platform, the direct CI/CD offer for it is Gitea Actions, it&amp;rsquo;s already there, integrated into Gitea and gives us access to the huge community Ecosystem around GitHub Actions.&lt;/p>
&lt;p>Gitea says everything works apart from a few limitations, then everything else should work, right?&lt;/p>
&lt;p>&lt;a class="link" href="https://docs.gitea.com/usage/actions/comparison" target="_blank" rel="noopener"
>https://docs.gitea.com/usage/actions/comparison&lt;/a>&lt;/p>
&lt;p>WRONG!!!!&lt;/p>
&lt;h2 id="private-reusable-workflows">Private reusable workflows
&lt;/h2>&lt;p>In GitHub Actions you can use reusable workflows from private repos, as long as the GitHub token has access to the repo.&lt;/p>
&lt;p>Gitea Actions don&amp;rsquo;t have such a token, but you can just add a PAT to the actions URL for this to work, right?&lt;/p>
&lt;p>Wrong, it just doesn&amp;rsquo;t work no matter what, as the YAML parser doesn&amp;rsquo;t allow anything other than a specific syntax.&lt;/p>
&lt;p>This also affects limited Orgs (Organizations that are only visible to authenticated users). You would think the runner is authenticated, but apparently not.&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/go-gitea/gitea/issues/25929" target="_blank" rel="noopener"
>https://github.com/go-gitea/gitea/issues/25929&lt;/a>&lt;/p>
&lt;p>Reported in 2023, still broken.&lt;/p>
&lt;p>Sure no biggie, we can just create a dedicated public organization for the workflows.&lt;/p>
&lt;h2 id="reusable-workflow-steps-are-all-shown-as-one-step">Reusable workflow steps are all shown as one step
&lt;/h2>&lt;p>Gitea parses the steps through the workflow file, and then sorts the logs under each step.
When you are using a reusable workflow, Gitea only parses one step, and all logs are shown under it.&lt;/p>
&lt;p>This makes debugging a nightmare when workflows fail.&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/go-gitea/gitea/issues/26187" target="_blank" rel="noopener"
>https://github.com/go-gitea/gitea/issues/26187&lt;/a>
Reported in 2023, still broken.&lt;/p>
&lt;p>Well, we just have to get used to the mangled logs, maybe just add an emoji to each step, and then search for it in the browser to find the step logs.&lt;/p>
&lt;h2 id="actions-are-only-pulled-once">Actions are only pulled once
&lt;/h2>&lt;p>When using a major tag for an action like v4, the latest release belonging to that version will be pulled, including patches and minor releases.&lt;/p>
&lt;p>Guess what? That doesn&amp;rsquo;t work in the Gitea Actions act_runner, since the act_runner has a runtime cache, and the runner won&amp;rsquo;t check for updates after the first pull.&lt;/p>
&lt;p>This is an even bigger problem for reusable workflows, where you might prefer to use a branch instead of tags, and just assume the runner will use the latest commit on each run.&lt;/p>
&lt;p>&lt;a class="link" href="https://gitea.com/gitea/act_runner/issues/726" target="_blank" rel="noopener"
>https://gitea.com/gitea/act_runner/issues/726&lt;/a>&lt;/p>
&lt;p>Simple fix, just tag the actions to the last patch release, and run Renovate to automatically update the tag used, that surely gotta work?&lt;/p>
&lt;p>Only for actions hosted on GitHub, not on Gitea.&lt;/p>
&lt;h3 id="renovate-cant-update-workflow-tags-in-gitea">Renovate can&amp;rsquo;t update workflow tags in Gitea
&lt;/h3>&lt;p>&lt;a class="link" href="https://github.com/renovatebot/renovate/discussions/27734" target="_blank" rel="noopener"
>https://github.com/renovatebot/renovate/discussions/27734&lt;/a>&lt;/p>
&lt;p>Renovate still looks for packages in GitHub and not Gitea for reusable workflows and actions.&lt;/p>
&lt;p>Meaning auto updates for Gitea-hosted actions/workflows aren&amp;rsquo;t possible.&lt;/p>
&lt;p>Yeah, I&amp;rsquo;ve had enough, Gitea ain&amp;rsquo;t it then.&lt;/p>
&lt;h2 id="conclusion">Conclusion
&lt;/h2>&lt;p>Gitea Actions might work for simple workflows that are only used in one repo, don&amp;rsquo;t need concurrency limits, don&amp;rsquo;t need to get updated regularly, and don&amp;rsquo;t rely on anything non-public.&lt;/p>
&lt;p>In addition to these crazy bugs, dealing with HTTP proxies in Gitea Actions is just insane. The NOPROXY proxy exception env variable is very finicky where it&amp;rsquo;s just ignored most of the time, but I think it&amp;rsquo;s more of an Actions problem than a Gitea Actions problem.&lt;/p>
&lt;p>After hitting so many bugs that were reported years ago! I just can&amp;rsquo;t continue to invest time in Gitea Actions. Who knows what issues will show up when these workflows become part of the team&amp;rsquo;s development process? It seems like GitLab is really the way to go.&lt;/p></description></item><item><title>Vibecoding with GitHub Copilot: Lessons Learned</title><link>https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/</link><pubDate>Thu, 31 Jul 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/</guid><description>&lt;img src="https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/thumbnail.jpg" alt="Featured image of post Vibecoding with GitHub Copilot: Lessons Learned" />&lt;p>Over the past few weeks, I&amp;rsquo;ve been vibecoding a new app with GitHub Copilot.&lt;/p>
&lt;p>I know it&amp;rsquo;s not meant for that, but here&amp;rsquo;s what I learned anyway:&lt;/p>
&lt;h2 id="200-requests-are-actually-a-lot">200 requests are actually a lot
&lt;/h2>&lt;p>The GitHub Copilot pro sub includes 200 premium requests and unlimited gpt4.1 and gpt-4o requests.&lt;/p>
&lt;p>It&amp;rsquo;s genuinely a lot, especially considering o4-mini&amp;rsquo;s 0.33x multiplier. It&amp;rsquo;s great for debugging but not ideal for designing.&lt;/p>
&lt;p>Don&amp;rsquo;t forget that the Copilot coding agent on GitHub uses only one premium request for the entire session.&lt;/p>
&lt;h3 id="not-enough-with-third-party-extensions">Not enough with third party extensions
&lt;/h3>&lt;p>Cline and its forks added support for using copilot as a provider through the VS Code LM API.&lt;/p>
&lt;p>However, the way these requests are handled differs significantly. A request in the official plugin is an interaction, every action done by LLM till it stops generating is included in that request.&lt;/p>
&lt;p>However, when third party extensions do this each API request counts as a premium request on its own, meaning instead of using one premium request to do the entire generation workflow, it&amp;rsquo;s using 5 to 10.&lt;/p>
&lt;p>That&amp;rsquo;s at least the case with Cline based tools.&lt;/p>
&lt;h2 id="limited-context-window">Limited context window
&lt;/h2>&lt;p>All Copilot models are capped at a &lt;a class="link" href="https://github.com/microsoft/vscode-copilot-release/issues/8303#issuecomment-2835038819" target="_blank" rel="noopener"
>64k context window&lt;/a>, which is disappointing, especially for Gemini models that shine with their 1m context window.&lt;/p>
&lt;p>(maybe that&amp;rsquo;s not the case for the async coding agent?)&lt;/p>
&lt;h2 id="the-continue-to-iterate-prompt-is-a-disaster">The Continue to iterate prompt is a disaster
&lt;/h2>&lt;p>&lt;img src="https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/continue-to-iterate.png"
width="796"
height="1874"
srcset="https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/continue-to-iterate_hu3855612166667711327.png 480w, https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/continue-to-iterate_hu11657535787081642525.png 1024w"
loading="lazy"
alt="GitHub Copilot Continue to Iterate Prompt"
class="gallery-image"
data-flex-grow="42"
data-flex-basis="101px"
>&lt;/p>
&lt;p>This is the worst thing in GitHub Copilot, it shows up when it&amp;rsquo;s actually doing work and stops the agent.&lt;/p>
&lt;p>This wouldn&amp;rsquo;t be a problem if the LLM actually just continued iterating, but due to the limited context window it seems to get only a summary of the chat and just starts anew.&lt;/p>
&lt;p>It&amp;rsquo;s very annoying, but luckily there&amp;rsquo;s a fix, you can increase the request limit, so that it practically never shows up&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/continue-to-iterate-setting.png"
width="1196"
height="235"
srcset="https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/continue-to-iterate-setting_hu12519701475077990308.png 480w, https://fariszr.com/vibe-coding-with-github-copilot-lessons-learned/continue-to-iterate-setting_hu17717783893429209245.png 1024w"
loading="lazy"
alt="GitHub Copilot Continue to Iterate Setting"
class="gallery-image"
data-flex-grow="508"
data-flex-basis="1221px"
>&lt;/p>
&lt;h2 id="context7-mcp-is-op">Context7 MCP is OP
&lt;/h2>&lt;p>Context7 is an MCP that enables the LLM to fetch up-to-date documentation in Markdown for almost any library out there. Adding it is crucial as it prevents the model from hallucinating or using outdated functions.&lt;/p>
&lt;p>Sonnet 4 especially excelled at this, it always used context7 before doing anything, or while debugging.&lt;/p>
&lt;p>Other models like o4-mini needed to be reminded explicitly for them to actually use it, either by text or by adding it to the context window.&lt;/p>
&lt;h2 id="stagewise-mcp-is-superb-for-editing-ui-elements">Stagewise MCP is superb for editing UI elements
&lt;/h2>&lt;p>&lt;a class="link" href="https://stagewise.io/" target="_blank" rel="noopener"
>Stagewise&lt;/a> is an MCP client that adds an npm dev module that allows you to select elements and forward them to Copilot.&lt;/p>
&lt;p>This makes adjusting frontend elements much easier.&lt;/p>
&lt;h2 id="generating-copilotmd-instructions-is-actually-useful">Generating copilot.md instructions is actually useful
&lt;/h2>&lt;p>Copilot can now generate a &lt;code>copilot-instructions.md&lt;/code> file for itself.&lt;/p>
&lt;p>It might sound odd, but it works by providing the LLM with a general project overview, which you can build on with additional instructions.&lt;/p>
&lt;h2 id="one-long-detailed-prompt-is-way-better-than-refining-things-later-especially-for-the-copilot-coding-agent">One long, detailed prompt is way better than refining things later, especially for the Copilot coding agent
&lt;/h2>&lt;p>This could be a side effect of the small context window, but there&amp;rsquo;s a huge difference when actually starting with a fully detailed prompt.&lt;/p>
&lt;p>The LLM generates based on its assumptions. The more detailed you are, the better these assumptions align with your expectations.&lt;/p>
&lt;p>It&amp;rsquo;s the reason why I think Kiro.dev will excel in this, because it unpacks these assumptions into separate markdown files, which then you can refine.&lt;/p>
&lt;p>if you want to use the GitHub Copilot async coding agent, you can trigger it from the chat which will do a search and then write a really detailed prompt.
The Results are way better than usual with this.&lt;/p>
&lt;p>In general, it&amp;rsquo;s been decent, but most of the issues really stem from the limited 64k context window, it makes Gemini models almost useless.&lt;/p>
&lt;p>However, Copilot&amp;rsquo;s access to GitHub has been a strong point.&lt;/p></description></item><item><title>Automatically sync files to Mega.io with dockerized mega-cmd</title><link>https://fariszr.com/dockerized-mega-cmd-automatic-file-sync/</link><pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/dockerized-mega-cmd-automatic-file-sync/</guid><description>&lt;img src="https://fariszr.com/dockerized-mega-cmd-automatic-file-sync/thumbnail.webp" alt="Featured image of post Automatically sync files to Mega.io with dockerized mega-cmd" />&lt;p>I self-host my own private cloud, but sometimes I have to make files available on external clouds like Mega.&lt;/p>
&lt;p>In this post I will explain how to create a dockerized mega-cmd with an entrypoint script that will automatically sync files and watch for changes.&lt;/p>
&lt;h2 id="dockerfile">dockerfile
&lt;/h2>&lt;p>To make it easier to build the image, i embedded the entrypoint script inside the dockerfile.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;span class="lnt">87
&lt;/span>&lt;span class="lnt">88
&lt;/span>&lt;span class="lnt">89
&lt;/span>&lt;span class="lnt">90
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="k">FROM&lt;/span>&lt;span class="s"> debian:12&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Install dependencies and MegaCMD&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apt-get update &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get install -y &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> wget &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> gnupg &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> apt-transport-https &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> mkdir -p /etc/apt/keyrings &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> curl -fsSL https://mega.nz/keys/MEGA_signing.key &lt;span class="p">|&lt;/span> gpg --dearmor -o /etc/apt/keyrings/mega.nz.gpg &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;deb [arch=amd64,arm64 signed-by=/etc/apt/keyrings/mega.nz.gpg] https://mega.nz/linux/repo/Debian_12/ ./&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> tee /etc/apt/sources.list.d/mega.nz.list &amp;gt; /dev/null &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get update &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get install -y megacmd &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get clean &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> rm -rf /var/lib/apt/lists/*&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Create directories for configuration and data&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> mkdir -p /root/.megaCmd /data&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Create startup script&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> &lt;span class="nb">echo&lt;/span> &lt;span class="s1">&amp;#39;#!/bin/bash\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">set -e\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Function to check if already logged in\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">check_login() {\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> mega-whoami &amp;gt; /dev/null 2&amp;gt;&amp;amp;1\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> return $?\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">}\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Check if already logged in\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">if check_login; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> echo &amp;#34;Existing session found. Skipping login.&amp;#34;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">else\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> # Check if credentials are provided\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> if [ -z &amp;#34;$MEGA_EMAIL&amp;#34; ] || [ -z &amp;#34;$MEGA_PASSWORD&amp;#34; ]; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> echo &amp;#34;Error: MEGA_EMAIL and MEGA_PASSWORD environment variables must be set&amp;#34;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> exit 1\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> # Login to MEGA\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> if [ -n &amp;#34;$MEGA_2FA&amp;#34; ]; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> mega-login $MEGA_EMAIL $MEGA_PASSWORD --auth-code=$MEGA_2FA\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> else\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> mega-login $MEGA_EMAIL $MEGA_PASSWORD\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Show account info to confirm login\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">echo &amp;#34;Logged in as:&amp;#34;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">mega-whoami\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Set up sync if MEGA_SYNC_LOCAL_PATH and MEGA_SYNC_REMOTE_PATH are provided\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">if [ ! -z &amp;#34;$MEGA_SYNC_LOCAL_PATH&amp;#34; ] &amp;amp;&amp;amp; [ ! -z &amp;#34;$MEGA_SYNC_REMOTE_PATH&amp;#34; ]; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> # Check if sync already exists\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> if mega-sync | grep -q &amp;#34;$MEGA_SYNC_LOCAL_PATH&amp;#34;; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> echo &amp;#34;Sync already exists for $MEGA_SYNC_LOCAL_PATH. Skipping sync setup.&amp;#34;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> else\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> echo &amp;#34;Setting up sync from $MEGA_SYNC_LOCAL_PATH to $MEGA_SYNC_REMOTE_PATH&amp;#34;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> mega-sync $MEGA_SYNC_LOCAL_PATH $MEGA_SYNC_REMOTE_PATH\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Keep the container running and show logs\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">echo &amp;#34;MegaCMD is now running and syncing...&amp;#34;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">echo &amp;#34;Streaming MegaCMD logs...&amp;#34;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Wait a moment for log files to be created\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">sleep 2\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Tail the mega-cmd log files to show activity in docker logs\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># MegaCMD creates log files in ~/.megaCmd/\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">if [ -f /root/.megaCmd/megacmdserver.log ]; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> tail -f /root/.megaCmd/megacmdserver.log &amp;amp;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">if [ -f /root/.megaCmd/megacmd.log ]; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> tail -f /root/.megaCmd/megacmd.log &amp;amp;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Also monitor any sync-related logs\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">if [ -d /root/.megaCmd/logs ]; then\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"> tail -f /root/.megaCmd/logs/*.log 2&amp;gt;/dev/null &amp;amp;\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">fi\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1"># Keep the main process alive\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">wait\n\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s1">&amp;#39;&lt;/span> &amp;gt; /entrypoint.sh &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> chmod +x /entrypoint.sh&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Set the entrypoint&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENTRYPOINT&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;/entrypoint.sh&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="docker-composeyml">docker-compose.yml
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">megacmd&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">MEGA_EMAIL=your@email.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;MEGA_PASSWORD=pass&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">MEGA_2FA=10101&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">MEGA_SYNC_LOCAL_PATH=/data&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># directory to sync inside the container&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">MEGA_SYNC_REMOTE_PATH=/remote-dir&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># directory on mega&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">MEGA_DEVICE_ID=dockerized-mega&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c">#device name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">~/files:/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./megacmd-config:/root/.megaCmd&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/etc/machine-id:/etc/machine-id:ro&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>the authentication variables are only needed for the first run, after that the token is stored in the .megaCmd folder.&lt;/p>
&lt;p>And now your files should show up on mega!&lt;/p>
&lt;p>Keep in mind mega support only two-way sync, mounting the data as read only isn&amp;rsquo;t an option due to megacmd checking for write-access on startup.&lt;/p></description></item><item><title>How to add support for Element Call with Docker Compose &amp; Caddy</title><link>https://fariszr.com/matrix-rtc-setup-with-docker-caddy/</link><pubDate>Tue, 27 May 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/matrix-rtc-setup-with-docker-caddy/</guid><description>&lt;img src="https://fariszr.com/matrix-rtc-setup-with-docker-caddy/thumbnail.jpg" alt="Featured image of post How to add support for Element Call with Docker Compose &amp; Caddy" />&lt;p>If you have been getting &lt;code>MISSING_MATRIX_RTC_FOCUS&lt;/code> on Element X recently when starting calls, the reason is that Element has stopped their free relay for Matrix RTC, AKA Element Call.
It was there just to speed up initial Element X adoption.&lt;/p>
&lt;p>Luckily, adding it isn&amp;rsquo;t that hard. The tricky part is the reverse proxy and updating the well-known files for the clients, but don&amp;rsquo;t worry, I already did the hard work for you 😅&lt;/p>
&lt;p>We need two domains: one for LiveKit (the RTC service) and one for the JWT auth service for Element X.&lt;/p>
&lt;h2 id="docker-compose-additions">Docker Compose additions
&lt;/h2>&lt;p>(livekit_api_key) = api key name&lt;/p>
&lt;p>(livekit_api_secret) = random 20+ character string.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># JWT Authentication Service for Element Call&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lk-jwt-service&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ghcr.io/element-hq/lk-jwt-service:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">unless-stopped&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Port the JWT service listens on internally&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">LK_JWT_PORT=8080&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Publicly accessible URL for the LiveKit SFU WebSocket endpoint (updated domain)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">LIVEKIT_URL=wss://matrixrtc.fariszr.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># API Key and Secret (must match LiveKit service and secrets)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">LIVEKIT_KEY=(livekit_api_key)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">LIVEKIT_SECRET=(livekit_api_secret)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Optional: Restrict call creation to users from specific homeservers&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">LIVEKIT_LOCAL_HOMESERVERS=fariszr.com&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># server name&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Needs to be reachable by Caddy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">livekit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">livekit/livekit-server:v1.8&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">livekit&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Use config file instead of just environment variables&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>--&lt;span class="l">config /etc/livekit.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">unless-stopped&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># LIVEKIT_KEYS is still needed here for the server itself&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">LIVEKIT_KEYS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;(livekit_api_key): (livekit_api_secret)&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Remove other env vars now handled by config.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - LIVEKIT_WS_URL=wss://livekit.fariszr.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - LIVEKIT_PORT=7880&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Port 7880 is now only needed internally for Caddy proxying to /livekit/sfu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - &amp;#34;7880:7880&amp;#34; # Client API and Webhook callback - proxied via Caddy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;7881:7881&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># TURN/TLS (LiveKit&amp;#39;s own TURN, though disabled in config)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;50100-50200:50100-50200/udp&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># UDP port range for media over WebRTC&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Mount the new config file&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./livekit-config.yaml:/etc/livekit.yaml:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Needs to be reachable by Caddy&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="configs">Configs
&lt;/h2>&lt;p>Additions to the Synapse config:&lt;/p>
&lt;h3 id="homeserveryaml">homeserver.yaml
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">experimental_features&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># MSC3266: Room summary API. Used for knocking over federation.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">msc3266_enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># MSC4222: Needed for syncv2 state_after. Allows clients to correctly track room state.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">msc4222_enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># MSC4140: Delayed events are required for proper call participation signalling.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># If disabled, you might get stuck calls in Matrix rooms.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">msc4140_enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># The maximum allowed duration by which sent events can be delayed, as per MSC4140.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">max_event_delay_duration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">24h&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Adjust rate limiting for call-related events&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">rc_message&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Needs to match at least E2EE key sharing frequency plus headroom.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Key sharing events are bursty.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">per_second&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">burst_count&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">30&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">rc_delayed_event_mgmt&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># Needs to match at least the heartbeat frequency (5s = 0.2/s) plus headroom.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">per_second&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1.0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Increased from example&amp;#39;s 1 to be safer&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">burst_count&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">20&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="livekit-configyaml">livekit-config.yaml:
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Basic LiveKit configuration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">7880&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Port LiveKit listens on internally&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">bind_addresses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Listen on all interfaces within the container&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">rtc&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tcp_port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">7881&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port_range_start&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">50100&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Match docker-compose port range&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">port_range_end&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">50200&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Match docker-compose port range&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">use_external_ip&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Rely on reverse proxy/NAT&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">logging&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">level&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">info&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Disable LiveKit&amp;#39;s internal TURN server, since you probably have something already set up for the classic P2P calls.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">turn&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># domain: matrixrtc.fariszr.com # Not needed if disabled, updated comment for consistency&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># cert_file: &amp;#34;&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># key_file: &amp;#34;&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># tls_port: 5349 # Default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># udp_port: 443 # Default&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># external_tls: true # If using external certs, but disabled anyway&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Keys are provided via environment variable LIVEKIT_KEYS in docker-compose.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># keys:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># (livekit_api_key): (livekit_api_secret)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="caddy-reverse-proxy">Caddy reverse proxy
&lt;/h2>&lt;p>I had to spend a lot of time figuring out the best reverse proxy setup. It seems that proxying the JWT service and LiveKit on different domains is the easiest solution, because I couldn&amp;rsquo;t get it to work with a subdirectory override for JWT on the same domain.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-caddy" data-lang="caddy">&lt;span class="line">&lt;span class="cl">&lt;span class="gh">matrix.fariszr.com&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">reverse_proxy&lt;/span> &lt;span class="n">synapse&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8008&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">encode&lt;/span> &lt;span class="s">zstd&lt;/span> &lt;span class="s">gzip&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # Serves the Matrix client .well-known JSON configuration file
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">respond&lt;/span> &lt;span class="nd">/.well-known/matrix/client&lt;/span> &lt;span class="sb">`&lt;/span>&lt;span class="s">{&amp;#34;m.homeserver&amp;#34;:{&amp;#34;base_url&amp;#34;:&amp;#34;https://matrix.fariszr.com&lt;/span>&lt;span class="sb">&amp;#34;},&amp;#34;org.matrix.msc4143.rtc_foci&amp;#34;:[&lt;/span>&lt;span class="s">{&amp;#34;type&amp;#34;:&amp;#34;livekit&amp;#34;,&amp;#34;livekit_service_url&amp;#34;:&amp;#34;https://jwt.matrixrtc.fariszr.com&lt;/span>&lt;span class="sb">&amp;#34;}]`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">jwt.matrixrtc.fariszr.com&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">encode&lt;/span> &lt;span class="s">zstd&lt;/span> &lt;span class="s">gzip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">reverse_proxy&lt;/span> &lt;span class="n">lk-jwt-service&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gh">matrixrtc.fariszr.com&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">encode&lt;/span> &lt;span class="s">zstd&lt;/span> &lt;span class="s">gzip&lt;/span>&lt;span class="c1">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"> # Route SFU WebSocket requests to the livekit container on its internal port 7880
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">reverse_proxy&lt;/span> &lt;span class="n">livekit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">7880&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>IMPORTANT:&lt;/strong> If you are using your delegated domain on the client (i.e., fariszr.com instead of matrix.fariszr.com when logging in), then you &lt;strong>will&lt;/strong> have to update the client well-known file served on the delegated domain too.&lt;/p>
&lt;p>And now you should have a Matrix RTC-capable home server!&lt;/p>
&lt;p>You can take a look at &lt;a class="link" href="https://github.com/FarisZR/Server/tree/main/matrix" target="_blank" rel="noopener"
>my server&amp;rsquo;s setup&lt;/a> anytime on my GitOps repo, run with &lt;a class="link" href="https://fariszr.com/docker-compose-gitops-github/" target="_blank" rel="noopener"
>docker-compose-gitops-action&lt;/a>.&lt;/p>
&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md" target="_blank" rel="noopener"
>https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://willlewis.co.uk/blog/posts/deploy-element-call-backend-with-synapse-and-docker-compose" target="_blank" rel="noopener"
>https://willlewis.co.uk/blog/posts/deploy-element-call-backend-with-synapse-and-docker-compose&lt;/a>&lt;/p></description></item><item><title>Selfhosted grammar autocorrect with languagetool and docker compose.</title><link>https://fariszr.com/host-language-tool-with-docker/</link><pubDate>Mon, 31 Mar 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/host-language-tool-with-docker/</guid><description>&lt;img src="https://fariszr.com/host-language-tool-with-docker/thumbnail.jpeg" alt="Featured image of post Selfhosted grammar autocorrect with languagetool and docker compose." />&lt;p>Self-hosted grammar autocorrect with LanguageTool and Docker Compose.&lt;/p>
&lt;p>LanguageTool is a great extension for correcting typos and grammar errors, it&amp;rsquo;s basically a more advanced and useful version of autocorrect.&lt;/p>
&lt;p>However, LanguageTool watches everything you type in real time, so it&amp;rsquo;s a real privacy risk.&lt;/p>
&lt;p>Fortunately, LanguageTool is open source, at least the core of it, and you can easily host it yourself using Docker.&lt;/p>
&lt;h2 id="setup">Setup
&lt;/h2>&lt;h3 id="download-the-n-gram-language-weights">Download the N-gram language weights
&lt;/h3>&lt;p>Ngrams are large datasets that help Languagetool improve context-dependent corrections, such as yours and there.&lt;/p>
&lt;p>Languagetool offers free n-gram datasets for a few languages, but AFAIK they are not the same as those used in the cloud version, and certainly not in the premium version.&lt;/p>
&lt;p>&lt;a class="link" href="https://languagetool.org/download/ngram-data/" target="_blank" rel="noopener"
>https://languagetool.org/download/ngram-data/&lt;/a>&lt;/p>
&lt;p>Download and extract the N-gram datasets into a language-specific folder under the N-grams directory, etc. &lt;code>./ngrams/en, ./ngrams/de&lt;/code>.&lt;/p>
&lt;h3 id="docker-composeyml">Docker-compose.yml
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">languagetool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">erikvl87/languagetool:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tmpfs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/tmp:exec&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cap_drop&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">ALL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cap_add&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CAP_SETUID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CAP_SETGID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CAP_CHOWN&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">security_opt&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="kc">no&lt;/span>-&lt;span class="l">new-privileges&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">100.81.157.127&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">8010&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">8010&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">langtool_languageModel=/ngrams # OPTIONAL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Using ngrams data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">Java_Xms=512m # OPTIONAL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Setting a minimal Java heap size of 512 mib&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">Java_Xmx=2g&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Max memory &lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ngrams:/ngrams&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="add-fasttext">Add Fasttext
&lt;/h2>&lt;p>Contrary to its name, Fasttext is not about improving correction speed, it&amp;rsquo;s an algorithm to improve autocorrect accuracy using an open model from Facebook.&lt;/p>
&lt;h3 id="dockerfile">Dockerfile
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="k">FROM&lt;/span>&lt;span class="s"> erikvl87/languagetool:latest&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="s"> root&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apk add fasttext&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="s"> languagetool&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="download-the-fasttext-model">download the fasttext Model
&lt;/h3>&lt;p>fasttext also needs a model to work:
&lt;a class="link" href="https://fasttext.cc/docs/en/language-identification.html" target="_blank" rel="noopener"
>https://fasttext.cc/docs/en/language-identification.html&lt;/a>&lt;/p>
&lt;p>Save it in the same LanguageTool directory and rename it to fasttext-model.bin.&lt;/p>
&lt;h3 id="fully-featured-docker-compose">Fully featured Docker-compose
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">languagetool&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># image: erikvl87/languagetool:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">languagetool&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tmpfs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/tmp:exec&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cap_drop&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">ALL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cap_add&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CAP_SETUID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CAP_SETGID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CAP_CHOWN&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">security_opt&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="kc">no&lt;/span>-&lt;span class="l">new-privileges&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">8010&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">8010&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">langtool_languageModel=/ngrams # OPTIONAL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Using ngrams data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">Java_Xms=512m # OPTIONAL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Setting a minimal Java heap size of 512 mib&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">Java_Xmx=3g&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">langtool_fasttextBinary=/usr/bin/fasttext&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">langtool_fasttextModel=/fasttext-model.bin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ngrams:/ngrams&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./fasttext-model.bin:/fasttext-model.bin&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="make-the-web-extensions-use-your-selfhosted-instance">Make the web extensions use your selfhosted instance
&lt;/h2>&lt;p>Go to languagetool settings, scroll all the way down to pro settings, and then enter the URL of your instance with &lt;code>/v2&lt;/code> appended at the end. If you are hosting it on the same machine, then it is https://127.0.0.1:8010/v2.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/host-language-tool-with-docker/language-tool-settings.jpg"
width="940"
height="416"
srcset="https://fariszr.com/host-language-tool-with-docker/language-tool-settings_hu18226818864362390922.jpg 480w, https://fariszr.com/host-language-tool-with-docker/language-tool-settings_hu3767461070019257393.jpg 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="225"
data-flex-basis="542px"
>&lt;/p>
&lt;p>BTW you can use languagetool in other applications like &lt;a class="link" href="https://github.com/ltex-plus/vscode-ltex-plus" target="_blank" rel="noopener"
>Vscode/Vscodium&lt;/a> and Libreoffice.&lt;/p>
&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/Erikvl87/docker-languagetool/issues/70" target="_blank" rel="noopener"
>https://github.com/Erikvl87/docker-languagetool/issues/70&lt;/a>
&lt;a class="link" href="https://github.com/Erikvl87/docker-languagetool" target="_blank" rel="noopener"
>https://github.com/Erikvl87/docker-languagetool&lt;/a>&lt;/p></description></item><item><title>Go Paperless with Paperless-ngx and Ai + Nextcloud Intergration.</title><link>https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/</link><pubDate>Fri, 28 Feb 2025 00:00:00 +0000</pubDate><guid>https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/</guid><description>&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/thumbnail.jpeg" alt="Featured image of post Go Paperless with Paperless-ngx and Ai + Nextcloud Intergration." />&lt;p>Having a scanner is the first step of going paperless, however with time you realize that keeping stuff in order and finding documents quickly becomes a challenge, even when using full text search in the cloud.&lt;/p>
&lt;p>Thats why document management Systems like Paperless-ngx exist.
And in this post I&amp;rsquo;m going to setup paperless-ngx with Nextcloud Integration, automated AI tagging and more accurate LLM-powered OCR.&lt;/p>
&lt;h2 id="docker-compose">Docker-compose
&lt;/h2>&lt;p>For the docker compose setup i don&amp;rsquo;t have to explain much, Paperless-ngx has template docker files ready to use in their Git repos and a quick guide on how to use them:
&lt;a class="link" href="https://docs.paperless-ngx.com/setup/#docker" target="_blank" rel="noopener"
>https://docs.paperless-ngx.com/setup/#docker&lt;/a>&lt;/p>
&lt;p>The needed changes will be mentioned in the next steps.&lt;/p>
&lt;h2 id="setting-up-separate-directories-for-each-user">setting up separate directories for each user
&lt;/h2>&lt;p>unless you want every user to be able to see each others documents, you need to make sure paperless separates the resulting files into a per-user dir.&lt;/p>
&lt;p>You can do this with workflows and storage Paths, or make it the default and just use workflows to assign documents to users when importing(&amp;ldquo;consuming&amp;rdquo;) docs.&lt;/p>
&lt;h3 id="make-paperless-ngx-use-a-separate-directory-for-each-user">Make Paperless-ngx use a separate directory for each user.
&lt;/h3>&lt;p>Using the placeholder details in the Paperless &lt;a class="link" href="https://docs.paperless-ngx.com/advanced_usage/#filename-format-variables" target="_blank" rel="noopener"
>docs&lt;/a>, you can set the default file format in Paperless using an environment variable.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> app:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ....
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> .....
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ PAPERLESS_FILENAME_FORMAT=&amp;#39;{{ owner_username }}/{{ title }}&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This will keep the filename and store the file in an owner-specific directory in the paperless archive folder&lt;/p>
&lt;h3 id="assign-ownership-based-on-subdirectory-in-consumption-directory">Assign ownership based on subdirectory in consumption directory
&lt;/h3>&lt;p>By default, when importing from the consumption directory, the file is available to all users, but with workflows we can tell Paperless to assign ownership based on the file dir when importing.&lt;/p>
&lt;p>If you have a scanner/printer that supports Samba, you can have it scan directly to a user-specified SMB share.
A guide to setting up an SMB share with Docker can be found &lt;a class="link" href="https://fariszr.com/docker-smb-network-discovery/" target="_blank" rel="noopener"
>here&lt;/a>.&lt;/p>
&lt;h4 id="workflow-trigger">Workflow trigger
&lt;/h4>&lt;p>&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/workflow-trigger.png"
width="1154"
height="888"
srcset="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/workflow-trigger_hu18120534033271159999.png 480w, https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/workflow-trigger_hu12432305136788749219.png 1024w"
loading="lazy"
alt="workflow trigger"
class="gallery-image"
data-flex-grow="129"
data-flex-basis="311px"
>&lt;/p>
&lt;p>Trigger type: Consumption started
Filter Sources: Consumption folder&lt;/p>
&lt;p>Filter path: &lt;code>/usr/src/paperless/consume/$USERNAME&lt;/code>
This matches any file imported from a specific directory&lt;/p>
&lt;p>you can also use wildcards to match any file with the username in its directory by just writing &lt;code>*$USERNAME*&lt;/code>&lt;/p>
&lt;h4 id="workflow-action">Workflow action
&lt;/h4>&lt;p>&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/workflow-action.png"
width="1149"
height="911"
srcset="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/workflow-action_hu11512487003268988479.png 480w, https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/workflow-action_hu13389472495105232926.png 1024w"
loading="lazy"
alt="workflow action"
class="gallery-image"
data-flex-grow="126"
data-flex-basis="302px"
>&lt;/p>
&lt;p>the action should be to assign the ownership and rights to the user.&lt;/p>
&lt;h2 id="ai-with-paperless-gpt">AI with Paperless-GPT
&lt;/h2>&lt;p>It&amp;rsquo;s 2025, we need to have AI in everything right?&lt;/p>
&lt;p>It makes sense here though, with Paperless-GPT you can use it to OCR documents using vision LLM models, using cloud vision models like GPT-4o, custom services from Google and Azure or even using local open source LLMs!&lt;/p>
&lt;h3 id="install-paperless-gpt">Install paperless-GPT
&lt;/h3>&lt;p>&lt;a class="link" href="https://github.com/icereed/paperless-gpt?tab=readme-ov-file#installation" target="_blank" rel="noopener"
>https://github.com/icereed/paperless-gpt?tab=readme-ov-file#installation&lt;/a>&lt;/p>
&lt;p>Assuming you want to use ollama, make sure you adjust the &lt;code>LLM_PROVIDER&lt;/code> and &lt;code>LLM_MODEL&lt;/code> variables accordingly.
Also comment out the &lt;code>OPENAI_API_KEY&lt;/code> environment variable.&lt;/p>
&lt;p>For &lt;code>PAPERLESS_API_TOKEN&lt;/code>, you can generate a token by going to the django admin dashboard, which you can find in the settings page under your profile icon in the top right.&lt;/p>
&lt;p>(You can also use vLLM, in which case you should probably stick with openai as your provider, while setting the &lt;code>OPENAI_BASE_URL&lt;/code> to point to vLLM).&lt;/p>
&lt;p>You can also customize the tags used to trigger processing by paperless-gpt, whether just for metadata or for OCR.&lt;/p>
&lt;p>Paperless-gpt has a webui exposed on port 8080 by default, consider setting up a reverse proxy in front of it.
Example for caddy:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-caddyfile" data-lang="caddyfile">&lt;span class="line">&lt;span class="cl">&lt;span class="gh">paperless-ai.yourdomain.test&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">reverse_proxy&lt;/span> &lt;span class="n">paperless-gpt&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="mi">8080&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="ollama-setup">Ollama setup
&lt;/h3>&lt;p>Ollama is the easiest way to run LLMs locally, but its support for Vision LLMs is &lt;strong>very limited&lt;/strong>, with the latest model being &lt;code>minicpm-v&lt;/code> 2.6, which doesn&amp;rsquo;t work well with non-Latin languages.
If you want to run better models like Qwen2.5-VL-7B-Instruct, you will have to use &lt;strong>vLLM&lt;/strong> for that. (if you figure out how to make it run on consumer AMD GPUs [&lt;em>not the 7900xt/x&lt;/em>], hit me up)&lt;/p>
&lt;h4 id="rocm-amd">ROCm (AMD)
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ollama&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ollama/ollama:rocm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">devices&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/dev/kfd:/dev/kfd&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/dev/dri:/dev/dri&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># environment:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - HSA_OVERRIDE_GFX_VERSION=10.3.0 # for unsupported gpus such as the 6700xt, 7800xt etc.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ollama:/root/.ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;11434:11434&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="cuda-nvidia">CUDA (Nvidia)
&lt;/h4>&lt;p>(If you have an Nvidia GPU, You should probably be using vLLM for this)
You will need to install the nvidia container toolkit and enable it for docker, see the &lt;a class="link" href="https://hub.docker.com/r/ollama/ollama" target="_blank" rel="noopener"
>Ollama&amp;rsquo;s docker hub page&lt;/a> for details.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ollama&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">reservations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">devices&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nvidia&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">count&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">all&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">gpu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">ollama:/root/.ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">11434&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">11434&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ollama/ollama&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="pulling-the-llm-model">pulling the llm model
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it ollama bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~# ollama pull minicpm-v&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="testing-out-ocr-and-tag-suggestions-using-minicpm-v">Testing out OCR and Tag suggestions using minicpm-v
&lt;/h3>&lt;p>Open the paperless-gpt dashboard (exposed on port 8080 by default) and check that everything is working correctly.&lt;/p>
&lt;p>To test OCR with a Vision LLM, go to the OCR tab and enter the ID of a document you want to test on.&lt;/p>
&lt;p>You can find the ID in the URL of each document, &lt;code>https://paperless.yourdomain.test/documents/XXX/details&lt;/code>, XXX being the document id.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/LLM-OCR-ui.png"
width="1118"
height="852"
srcset="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/LLM-OCR-ui_hu8662153027210579218.png 480w, https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/LLM-OCR-ui_hu12075313229513319455.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="131"
data-flex-basis="314px"
>&lt;/p>
&lt;p>To test the metadata suggestions (based on the text extracted by teserract), you have to add one of the paperless-gpt processing tags to the document, by default &lt;code>paperless-gpt&lt;/code> for manual processing (you have to trigger it in the UI) and &lt;code>paperless-gpt-auto&lt;/code> for automatic processing (for OCR its &lt;code>paperless-gpt-ocr-auto&lt;/code> by default).
You can customize the tag names using environment variables in docker compose.&lt;/p>
&lt;p>When documents are tagged with &lt;code>paperless-gpt&lt;/code>, they are displayed in the web interface.
&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/tagged-docs.png"
width="931"
height="580"
srcset="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/tagged-docs_hu7889251094212098572.png 480w, https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/tagged-docs_hu4621918309254161030.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="160"
data-flex-basis="385px"
>&lt;/p>
&lt;p>You can now create better metadata based on the OCR text (from Paperless-NGX).&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/llm-suggested-metadata.png"
width="931"
height="580"
srcset="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/llm-suggested-metadata_hu1540566210849222458.png 480w, https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/llm-suggested-metadata_hu6131716776095983752.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="160"
data-flex-basis="385px"
>&lt;/p>
&lt;h3 id="process-all-documents-with-paperless-gpt">Process all documents with Paperless-gpt
&lt;/h3>&lt;p>If you want all documents to be processed by AI, you can create a workflow to add the &lt;code>paperless-gpt-auto' and &lt;/code>paperless-gpt-ocr-auto&amp;rsquo; tags to new documents.
They will then be automatically processed by paperless-gpt and updated accordingly.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/paperless-gpt-tags-workflow.png"
width="912"
height="996"
srcset="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/paperless-gpt-tags-workflow_hu10154136427285643635.png 480w, https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/paperless-gpt-tags-workflow_hu17368616390714974341.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="91"
data-flex-basis="219px"
>&lt;/p>
&lt;h2 id="general-tips-on-using-paperless">General Tips on using Paperless
&lt;/h2>&lt;p>I found this thread on reddit to be useful on how to get started with Paperless:&lt;/p>
&lt;p>&lt;a class="link" href="https://reddit.com/r/selfhosted/comments/sdv0rr/paperless_ng_which_tags_document_types/" target="_blank" rel="noopener"
>https://reddit.com/r/selfhosted/comments/sdv0rr/paperless_ng_which_tags_document_types/&lt;/a>&lt;/p>
&lt;p>And now you have an (AI-assisted) Document management System!&lt;/p>
&lt;h2 id="nextcloud-integration">Nextcloud Integration
&lt;/h2>&lt;p>Now if you are setting up paperless, chances are, you are using a cloud solution like nextcloud.&lt;/p>
&lt;p>You can integrate Nextcloud with Paperless through external mounts, you can mount the user-specfic consumption directory for each user, and then the archive directory to be able to access processed pdfs.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/nextcloud-external-mounts.png"
width="1343"
height="290"
srcset="https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/nextcloud-external-mounts_hu323987336632972940.png 480w, https://fariszr.com/go-paperless-with-ai-and-nextcloud-integration/nextcloud-external-mounts_hu15943693618867647437.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="463"
data-flex-basis="1111px"
>&lt;/p>
&lt;p>/mnt/archive being a mounted directory pointing to the &lt;code>{YOUR_DOCKER_DIR}/paperless-ngx/media/documents/archive/$USER$:/mnt/archive:ro&lt;/code> and scans pointing to a directory mounted as &lt;code>/usr/src/paperless/consume/$USER&lt;/code> in paperless.&lt;/p>
&lt;p>Make sure to set per-user paths as the default or to create an active workflow to assign storage paths depending on the import-path.&lt;/p>
&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://www.madebyagents.com/blog/paperless-ngx-nextcloud-integration" target="_blank" rel="noopener"
>https://www.madebyagents.com/blog/paperless-ngx-nextcloud-integration&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/icereed/paperless-gpt" target="_blank" rel="noopener"
>https://github.com/icereed/paperless-gpt&lt;/a>&lt;/p></description></item><item><title>Setup Contact and Calendar (dav)sync using Radicale and Docker compose.</title><link>https://fariszr.com/contact-calendar-dav-sync-radicale-docker/</link><pubDate>Sat, 30 Nov 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/contact-calendar-dav-sync-radicale-docker/</guid><description>&lt;img src="https://fariszr.com/contact-calendar-dav-sync-radicale-docker/thumbnail.jpg" alt="Featured image of post Setup Contact and Calendar (dav)sync using Radicale and Docker compose." />&lt;p>I have been looking for a solution to sync my contacts across devices.
Etesync seemed great for this, but it seems the IOS app is no longer maintained.&lt;/p>
&lt;p>And then I found Radicale, a lightweight and easy to setup server for Caldav/Cardav syncing, and in true FarisZR fashion, I&amp;rsquo;m going to install it with docker compose.&lt;/p>
&lt;p>Now radicale doesn&amp;rsquo;t seem to have an official docker image, but luckily for us &lt;a class="link" href="https://github.com/tomsquest/docker-radicale" target="_blank" rel="noopener"
>tomesquest&lt;/a> did the hard part for us and created a docker image for Radiacle.&lt;/p>
&lt;h2 id="docker-composeyml">docker-compose.yml
&lt;/h2>&lt;p>*Note that this uses an external network called &lt;code>web&lt;/code> to connect the service to the reverse proxy, you could just expose the ports directly.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">external&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">radicale&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tomsquest/docker-radicale:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">radicale&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ports:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - 127.0.0.1:5232:5232&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">init&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">read_only&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">security_opt&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="kc">no&lt;/span>-&lt;span class="l">new-privileges:true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cap_drop&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">ALL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cap_add&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">SETUID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">SETGID&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">CHOWN&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">KILL&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">limits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">256M&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">pids&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">50&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">curl -f http://127.0.0.1:5232 || exit 1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">30s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">unless-stopped&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./data:/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./config:/config&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">web&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="config">config
&lt;/h2>&lt;p>There&amp;rsquo;s a template config file for the docker image, we still need to change something to be able to set up htpasswd auth.&lt;/p>
&lt;p>Create a file named config inside the mounted config directory with the raw content of &lt;a class="link" href="https://github.com/tomsquest/docker-radicale/blob/master/config" target="_blank" rel="noopener"
>the config file from github&lt;/a>&lt;/p>
&lt;p>and modify it to your liking, if you want to change the data directory, make sure to adjust the mount points in docker compose accordingly.&lt;/p>
&lt;h2 id="auth">Auth
&lt;/h2>&lt;p>We need to adjust the auth section to enable authentication with a password, here&amp;rsquo;s an example:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[auth]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">htpasswd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">htpasswd_filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">/config/users&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">htpasswd_encryption&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">bcrypt&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="hashing-your-password-with-bcrypt">Hashing your password with bcrypt
&lt;/h3>&lt;p>Now, as you probably noticed, htpasswd encryption is set to bcrypt, which means we need to hash the password in bcrypt format.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">echo&lt;/span> -n &lt;span class="s2">&amp;#34;your password&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span> mkpasswd --method&lt;span class="o">=&lt;/span>bcrypt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Then create a file called &lt;code>users&lt;/code> in the mounted &lt;code>config&lt;/code> directory.
It should be formatted like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">john:$2a$10$l1Se4qIaRlfOnaC1pGt32uNe/Dr61r4JrZQCNnY.kTx2KgJ70GPSm&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and now it should be ready to run!&lt;/p>
&lt;h2 id="caddy-reverse-proxy">Caddy reverse proxy
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="err">sync.example.com&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">reverse_proxy&lt;/span> &lt;span class="err">radicale:5232&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="err">encode&lt;/span> &lt;span class="err">zstd&lt;/span> &lt;span class="err">gzip&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="importing-data">Importing data
&lt;/h2>&lt;p>For me, importing data from the web UI didn&amp;rsquo;t work, I just re-imported my contacts on my phone and it worked.&lt;/p></description></item><item><title>Quick Fixes for Flatpak Dark Mode Detection Issues</title><link>https://fariszr.com/flatpak-dark-mode-fix/</link><pubDate>Mon, 28 Oct 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/flatpak-dark-mode-fix/</guid><description>&lt;img src="https://fariszr.com/flatpak-dark-mode-fix/thumbnail.png" alt="Featured image of post Quick Fixes for Flatpak Dark Mode Detection Issues" />&lt;p>Flatpak is the most popular universal distribution format on Linux, but I still face issues with dark mode detection in some apps.&lt;/p>
&lt;p>Luckily I found some workarounds to fix this.&lt;/p>
&lt;h2 id="make-sure-dark-mode-themes-are-installed-for-flatpak-updates">Make sure dark mode themes are installed for flatpak updates
&lt;/h2>&lt;p>Technically, flatpak should automatically detect the theme you&amp;rsquo;re using and install it, but if for some reason it doesn&amp;rsquo;t, you can manually install the gtk3 themes you need.&lt;/p>
&lt;p>Adwaita:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">flatpak install org.gtk.Gtk3theme.Adwaita-dark/x86_64/3.22&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Generally for Ubuntu:
dark&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">flatpak install flathub org.gtk.Gtk3theme.Yaru-dark&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>light&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">flatpak install flathub org.gtk.Gtk3theme.Yaru-light&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>But these are the general themes, if you&amp;rsquo;ve changed the accent colour then you&amp;rsquo;re using a different theme, like Yaru-Red-dark for the red accent theme.&lt;/p>
&lt;p>You can find all the available gtk3themes on Flathub using the command&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">flatpak search gtk3theme&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="switch-to-the-stock-adwaita-theme">Switch to the stock Adwaita theme
&lt;/h2>&lt;p>&lt;img src="https://fariszr.com/flatpak-dark-mode-fix/stock-adwaita.png"
width="1901"
height="1049"
srcset="https://fariszr.com/flatpak-dark-mode-fix/stock-adwaita_hu3458769970658291019.png 480w, https://fariszr.com/flatpak-dark-mode-fix/stock-adwaita_hu5103860060390400287.png 1024w"
loading="lazy"
alt="Testing applications with the stock Adwaita theme"
class="gallery-image"
data-flex-grow="181"
data-flex-basis="434px"
>&lt;/p>
&lt;p>Open the Gnome Tweaks application and make sure you are using the stock Adwaita theme for Gnome.
The default Adwaita theme provides the best compatibility, Some applications like Proton VPN seem to only follow dark mode when using Adwaita.&lt;/p>
&lt;h2 id="manual-overrides">Manual overrides
&lt;/h2>&lt;p>Some applications don&amp;rsquo;t implement the necessary XDG standards to detect dark mode, at least not completely.
These fixes will manually override the default theme, which means that dynamic switching between dark and light themes from GNOME&amp;rsquo;s quick settings won&amp;rsquo;t work anymore when using these overrides.&lt;/p>
&lt;h2 id="use-adw-gtk3">Use adw-gtk3
&lt;/h2>&lt;p>&lt;img src="https://fariszr.com/flatpak-dark-mode-fix/adw-gtk3-dark.png"
width="1901"
height="1049"
srcset="https://fariszr.com/flatpak-dark-mode-fix/adw-gtk3-dark_hu5221030524894079898.png 480w, https://fariszr.com/flatpak-dark-mode-fix/adw-gtk3-dark_hu6422087505931445161.png 1024w"
loading="lazy"
alt="Testing apps with the adw-gtk3-dark theme"
class="gallery-image"
data-flex-grow="181"
data-flex-basis="434px"
>
Adw-gtk3 is a gtk3 theme that makes applications look like gtk4.
It&amp;rsquo;s also the theme that fixes all the problems for me.
Apps like Xournal++ won&amp;rsquo;t follow the dark mode with Adwaita, even when I manually select adwaita-dark in gnome-tweaks, but with adw-gtk3-dark it works fine.&lt;/p>
&lt;p>Even applications like Motrix, which use an old version of electron that doesn&amp;rsquo;t support automatic dark mode detection, detected dark mode when using adw-gtk3-dark.&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/lassekongo83/adw-gtk3?tab=readme-ov-file#how-to-install" target="_blank" rel="noopener"
>Adw-gtk3 Installaion instructions&lt;/a>&lt;/p>
&lt;h2 id="manual-override-with-flatseal">Manual override with Flatseal
&lt;/h2>&lt;p>If nothing works, you can manually override the theme used by the applications using the &lt;code>GTK_THEME&lt;/code> environment variable.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/flatpak-dark-mode-fix/Itsfoss-flatseal.png"
width="1000"
height="642"
srcset="https://fariszr.com/flatpak-dark-mode-fix/Itsfoss-flatseal_hu3295254884043916080.png 480w, https://fariszr.com/flatpak-dark-mode-fix/Itsfoss-flatseal_hu3019096727169478077.png 1024w"
loading="lazy"
alt="Photo Courtesy of itsfoss.com"
class="gallery-image"
data-flex-grow="155"
data-flex-basis="373px"
>&lt;/p>
&lt;p>You can also do this at the application level.&lt;/p>
&lt;p>These are the workarounds I found, for me adw-gtk3 fixed everything and I don&amp;rsquo;t have to worry about this anymore, however, because it is explicitly selected in gnome-tweaks, &lt;strong>the light/dark switch in the quick settings will not work.&lt;/strong>.
&lt;img src="https://fariszr.com/flatpak-dark-mode-fix/no-dynamic-dark-mode.png"
width="1901"
height="1049"
srcset="https://fariszr.com/flatpak-dark-mode-fix/no-dynamic-dark-mode_hu6303500083864709518.png 480w, https://fariszr.com/flatpak-dark-mode-fix/no-dynamic-dark-mode_hu3387458476960459463.png 1024w"
loading="lazy"
alt="The quick settings toggle has no effect with adw-gtk3-dark"
class="gallery-image"
data-flex-grow="181"
data-flex-basis="434px"
>&lt;/p>
&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://ubuntuhandbook.org/index.php/2021/10/enable-dark-flatpak-apps-ubuntu-linux-mint/" target="_blank" rel="noopener"
>https://ubuntuhandbook.org/index.php/2021/10/enable-dark-flatpak-apps-ubuntu-linux-mint/&lt;/a>
&lt;a class="link" href="https://itsfoss.com/flatpak-app-apply-theme/" target="_blank" rel="noopener"
>https://itsfoss.com/flatpak-app-apply-theme/&lt;/a>
&lt;a class="link" href="https://github.com/lassekongo83/adw-gtk3" target="_blank" rel="noopener"
>https://github.com/lassekongo83/adw-gtk3&lt;/a>&lt;/p></description></item><item><title>Using Custom providers like Groq with Zed Ai</title><link>https://fariszr.com/use-custom-providers-with-zed-groq/</link><pubDate>Sun, 08 Sep 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/use-custom-providers-with-zed-groq/</guid><description>&lt;img src="https://fariszr.com/use-custom-providers-with-zed-groq/thumbnail.jpg" alt="Featured image of post Using Custom providers like Groq with Zed Ai" />&lt;p>&lt;a class="link" href="https://zed.dev/" target="_blank" rel="noopener"
>Zed&lt;/a> is a new open source code editor on the block, which has a top-notch Ai integration, aiming to compete with Cursor.&lt;/p>
&lt;p>You can use &lt;a class="link" href="https://zed.dev/blog/zed-ai" target="_blank" rel="noopener"
>Zed Ai for free&lt;/a>, it will use Claude 3.5 sonnet with strict rate limits, and it also may use your Inputs for training.&lt;/p>
&lt;p>However you can also run Zed AI using openAI LLMs or even any other LLM running on an openai api-compatible provider like groq!&lt;/p>
&lt;h2 id="settingsjson">settings.json
&lt;/h2>&lt;p>Zed&amp;rsquo;s settings are mainly managed through the settings.json file.
Open the file using the shortcut &lt;code>ctrl +,&lt;/code>&lt;/p>
&lt;p>And paste the following:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;language_models&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;openai&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;version&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;api_url&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;https://api.groq.com/openai/v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;available_models&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;llama-3.1-70b-versatile&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;max_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">131072&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;max_output_tokens&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">null&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>
&lt;p>&lt;strong>api_url&lt;/strong>: openai compatible API url, for groq it&amp;rsquo;s &lt;code>https://api.groq.com/openai/v1&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>available_models&lt;/strong>: this is a json array of all models from this provider, the best available model on groq is LLama 3.1 70b&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="storing-your-api-token-and-enabling-choosing-custom-llm-model">storing your API token and enabling choosing custom LLM model
&lt;/h2>&lt;p>&lt;img src="https://fariszr.com/use-custom-providers-with-zed-groq/llm-config.png"
width="641"
height="1018"
srcset="https://fariszr.com/use-custom-providers-with-zed-groq/llm-config_hu8815928345108587400.png 480w, https://fariszr.com/use-custom-providers-with-zed-groq/llm-config_hu7510324150377452327.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="62"
data-flex-basis="151px"
>&lt;/p>
&lt;p>Enter your API token for Groq or your preferred OpenAI-compatible platform in the OpenAI modal at the end of the zed ai configure page.&lt;/p>
&lt;p>You can generate your &lt;a class="link" href="https://console.groq.com/keys" target="_blank" rel="noopener"
>Groqcloud API token&lt;/a> by going to the groq console page and then to api keys&lt;/p>
&lt;p>then choose your custom LLM model (it&amp;rsquo;s going to have an OpenAI logo next to it, sorry!) and now you should have a Llama-powered Zed AI assistant!&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/use-custom-providers-with-zed-groq/choosing-the-custom-model.png"
width="638"
height="473"
srcset="https://fariszr.com/use-custom-providers-with-zed-groq/choosing-the-custom-model_hu16936243595129718737.png 480w, https://fariszr.com/use-custom-providers-with-zed-groq/choosing-the-custom-model_hu2023860921985564701.png 1024w"
loading="lazy"
alt="Choose your custom model to make Zed use it for all ai related tasks"
class="gallery-image"
data-flex-grow="134"
data-flex-basis="323px"
>&lt;/p>
&lt;h2 id="sources">sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/zed-industries/zed/pull/13276" target="_blank" rel="noopener"
>https://github.com/zed-industries/zed/pull/13276&lt;/a>&lt;/p></description></item><item><title>How to run Brave Leo LLM locally with ollama and Docker compose</title><link>https://fariszr.com/brave-leo-with-ollama-using-docker-compose/</link><pubDate>Sat, 31 Aug 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/brave-leo-with-ollama-using-docker-compose/</guid><description>&lt;img src="https://fariszr.com/brave-leo-with-ollama-using-docker-compose/thumbnail.jpg" alt="Featured image of post How to run Brave Leo LLM locally with ollama and Docker compose" />&lt;p>Brave recently introduced &lt;a class="link" href="https://brave.com/blog/byom-nightly/" target="_blank" rel="noopener"
>the ability to bring your own model&lt;/a> to use with LEO using other third party providers or a local model using Ollama!&lt;/p>
&lt;p>This is a quick guide to hosting Ollama with docker and integrating it with Leo.
I&amp;rsquo;m doing this with docker because it&amp;rsquo;s much easier for AMD GPUs, because you don&amp;rsquo;t need any drivers to make it work.&lt;/p>
&lt;h2 id="ollama-setup">Ollama setup
&lt;/h2>&lt;h3 id="cpu">CPU
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ollama&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ollama/ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ollama:/root/.ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;11434:11434&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="rocm">ROCm
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ollama&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ollama/ollama:rocm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">devices&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/dev/kfd:/dev/kfd&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/dev/dri:/dev/dri&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># environment:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - HSA_OVERRIDE_GFX_VERSION=10.3.0 # for unsupported gpus such as 6700xt, 7800xt etc.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ollama:/root/.ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;11434:11434&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="cuda">CUDA
&lt;/h3>&lt;p>You will need to install the nvidia container toolkit and enable it for docker, see the &lt;a class="link" href="https://hub.docker.com/r/ollama/ollama" target="_blank" rel="noopener"
>docker hub page&lt;/a> for Ollama for details.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ollama&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">reservations&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">devices&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">nvidia&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">count&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">all&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">gpu&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">ollama:/root/.ollama&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">11434&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">11434&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ollama/ollama&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="downloading-an-llm">Downloading an LLM
&lt;/h2>&lt;p>You need to choose a model that fits into your GPU&amp;rsquo;s VRAM.
Remember that there are smaller &amp;ldquo;quantized&amp;rdquo; versions of most LLMs, you could just scroll down the tag picker on Ollama&amp;rsquo;s site until you see something that might fit your GPU.&lt;/p>
&lt;p>You can find the state-of-the-art models using the Lmsys&amp;rsquo;s chatbot arena Leaderboard, which ranks most Ai models depending on how users like them.
&lt;a class="link" href="https://lmarena.ai/" target="_blank" rel="noopener"
>https://lmarena.ai/&lt;/a>&lt;/p>
&lt;p>Meta AI&amp;rsquo;s LLama 3 models are currently by far the most popular models on Ollama.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker &lt;span class="nb">exec&lt;/span> -it ollama bash
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">~# ollama pull llama3:YOUR_TAG&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This will download the model to the mounted &lt;code>./ollama&lt;/code> directory.&lt;/p>
&lt;h2 id="using-leo-with-ollama">Using Leo with Ollama
&lt;/h2>&lt;p>Go to settings and then to the Leo page
&lt;img src="https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image.png"
width="1999"
height="1250"
srcset="https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image_hu5960846875194798597.png 480w, https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image_hu17309402144558154161.png 1024w"
loading="lazy"
alt="Leo setting page"
class="gallery-image"
data-flex-grow="159"
data-flex-basis="383px"
>
(pictures courtesy of brave)&lt;/p>
&lt;p>The model request name is the same as the one in Ollama, so &lt;code>llama3:YOUR_TAG&lt;/code>&lt;/p>
&lt;p>the server endpoint will always be: &lt;code>http://localhost:11434/v1/chat/completions&lt;/code> unless you host Ollama somewhere else.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image-1.png"
width="1999"
height="1251"
srcset="https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image-1_hu6685546932042350335.png 480w, https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image-1_hu15863083940895120754.png 1024w"
loading="lazy"
alt="local llm settings"
class="gallery-image"
data-flex-grow="159"
data-flex-basis="383px"
>&lt;/p>
&lt;p>And then select the model when using leo,&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image-2.png"
width="1999"
height="1250"
srcset="https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image-2_hu12997017938205712974.png 480w, https://fariszr.com/brave-leo-with-ollama-using-docker-compose/image-2_hu853866906602134135.png 1024w"
loading="lazy"
alt="select model model"
class="gallery-image"
data-flex-grow="159"
data-flex-basis="383px"
>&lt;/p>
&lt;p>And now you have your own local AI assistant built into your browser!&lt;/p></description></item><item><title>Setup Owncloud infinite scale with docker compose</title><link>https://fariszr.com/owncloud-infinite-scale-docker-setup/</link><pubDate>Wed, 31 Jul 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/owncloud-infinite-scale-docker-setup/</guid><description>&lt;img src="https://fariszr.com/owncloud-infinite-scale-docker-setup/thumbnail.jpg" alt="Featured image of post Setup Owncloud infinite scale with docker compose" />&lt;p>ownCloud infinite scale is a complete rewrite of the classic ownCloud/Nextcloud server in go, it&amp;rsquo;s much faster and much more efficient, to the point it doesn&amp;rsquo;t even need a database!&lt;/p>
&lt;p>I found their docs to be a bit confusing, but I managed to figure it out and wrote this guide to save you the hassle!&lt;/p>
&lt;h2 id="setup-plan">Setup plan
&lt;/h2>&lt;p>ownCloud infinite scale is, as the name says, very scalable, you could deploy multiple instances, with services separated from the main program to be able to scale depending on usage and so on.&lt;/p>
&lt;p>This guide is focused on the most common setup for Homelabs and small scale NAS&amp;rsquo;s, local UNIX storage with one instance using the embedded supervisor.
It&amp;rsquo;s also a great starting point if you are planning a bigger deployment.&lt;/p>
&lt;h2 id="initial-owncloud-setup">Initial ownCloud setup
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ocis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">owncloud/ocis:5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">user&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">127.0.0.1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9200&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9200&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">entrypoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/bin/sh&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># run ocis init to initialize a configuration file with random secrets&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># it will fail on subsequent runs, because the config file already exists&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># therefore we ignore the error and then start the ocis server&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;-c&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ocis init || true; ocis server&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">OCIS_URL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://owncloud.yourdomain.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">OCIS_LOG_LEVEL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">error&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># make oCIS less verbose&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PROXY_TLS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># do not use SSL between reverse proxy and oCIS&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">OCIS_INSECURE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># basic auth (not recommended, but needed for eg. WebDav clients that do not support OpenID Connect)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PROXY_ENABLE_BASIC_AUTH&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># admin user password&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">IDM_ADMIN_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;admin&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># this overrides the admin password from the configuration file&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># make settings service available to oCIS Hello&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SETTINGS_GRPC_ADDR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.0.0.0&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9191&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">GATEWAY_GRPC_ADDR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.0.0.0&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9142&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># make the REVA gateway accessible to the app drivers&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># email server (if configured)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NOTIFICATIONS_SMTP_HOST&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;xxxxxx&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NOTIFICATIONS_SMTP_PORT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;xxxx&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NOTIFICATIONS_SMTP_SENDER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;xxxxx&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NOTIFICATIONS_SMTP_USERNAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;xxxxxxxx&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NOTIFICATIONS_SMTP_INSECURE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;xxxxxxx&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># PROXY_TLS is set to &amp;#34;false&amp;#34;, the download url has no https&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">STORAGE_USERS_DATA_GATEWAY_URL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://ocis:9200/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># separate directory for thumbnails&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">THUMBNAILS_FILESYSTEMSTORAGE_ROOT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/var/lib/ocis-thumbnails&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ocis-config:/etc/ocis&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ocis-data:/var/lib/ocis&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./thumbnails:/var/lib/ocis-thumbnails&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">logging&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This is the basic setup for OCIS running on port 9200, with a separate directory for thumbnails (useful for speeding up thumbnail loading by storing them on an SSD) and override for the admin password.&lt;/p>
&lt;p>You need a reverse proxy for this to work properly, you could use Caddy or Nginx for this.&lt;/p>
&lt;p>This includes all the basic ownCloud services, you could just continue to use it like this if you feel it&amp;rsquo;s adequate for your use case.&lt;/p>
&lt;p>But you could add a few bells and whistles to your instance by adding a few optional services:&lt;/p>
&lt;h2 id="enable-full-text-search">Enable full text search
&lt;/h2>&lt;p>By default the text search in OCIS only indexes file and folder names and the content of raw and Markdown text files.
You can enable Full text search by adding Apache Tika, which will use OCR to read PDF file content and auto-detect its language (&lt;a class="link" href="https://www.javatpoint.com/tika-language-detection" target="_blank" rel="noopener"
>list of supported languages&lt;/a>) to build a search index of file contents.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ocis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c">#add this to your yaml file!&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># better fulltext search&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SEARCH_EXTRACTOR_TYPE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tika&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">SEARCH_EXTRACTOR_TIKA_TIKA_URL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://tika:9998&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">FRONTEND_FULL_TEXT_SEARCH_ENABLED&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;true&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># better full text search&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tika&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">apache/tika:latest-full&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># command: apt update &amp;amp;&amp;amp; apt install tesseract-ocr-xxx -y add additional languages (only works if Tika supports auto-detection for that language)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">logging&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">local&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This is a partial compose file, you should add the environment variables to the OCIS template at the start of the article and the Tika container.&lt;/p>
&lt;h2 id="automatic-anti-virus-scanning">Automatic anti virus scanning
&lt;/h2>&lt;p>If you don&amp;rsquo;t like viruses casually hanging up with your files on your server, you can enable the antivirus service using ClamAV as an antivirus scanner.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ocis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enviroment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ClamAV setup&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ANTIVIRUS_SCANNER_TYPE&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clamav&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ANTIVIRUS_INFECTED_FILE_HANDLING&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">delete&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ANTIVIRUS_CLAMAV_SOCKET&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/var/run/clamav/clamd.sock&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># enable the antivirus service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">OCIS_ADD_RUN_SERVICES&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;antivirus&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># configure the antivirus service&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTPROCESSING_STEPS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;virusscan&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">clamav-socket:/var/run/clamav&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clamav&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clamav/clamav:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">clamav&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># restart: always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ports:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># - &amp;#39;127.0.0.1:3310:3310&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./clamav:/var/lib/clamav&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">clamav-socket:/tmp&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">clamav-socket:&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="online-office">Online office
&lt;/h2>&lt;p>One of the latest features in OCIS 6 is the addition of support for online office suites like Collabora (Libreoffice) and OnlyOffice.&lt;/p>
&lt;p>However, OCIS 6 is a rolling release, which doesn&amp;rsquo;t enjoy full production support and is intended for &amp;ldquo;Early adopters&amp;rdquo;.&lt;/p>
&lt;p>So you will have to switch to the rolling release for this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ocis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">owncloud/ocis-rolling:latest&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># use OCIS-rolling releases&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enviroment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NATS_NATS_HOST&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.0.0.0&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># make NATS accessible to ocis-collaboration&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">NATS_NATS_PORT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">9233&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># make collabora the secure view app&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">FRONTEND_APP_HANDLER_SECURE_VIEW_APP_ADDR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">com.owncloud.api.collaboration.Collabora&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># fix CSP for collabora&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">PROXY_CSP_CONFIG_FILE_LOCATION&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/etc/ocis/csp.yaml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./csp.yaml:/etc/ocis/csp.yaml&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># modifiy CSP policy to allow loading of Collabora in OCIS web ui&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># the collaboration service isn&amp;#39;t included in the embedded supervisor and needs to be run separately&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ocis-collaboration&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">owncloud/ocis-rolling:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">user&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">127.0.0.1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9300&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9300&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c">#woopi server port&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ocis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_started&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">collabora&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">entrypoint&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/bin/sh&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;-c&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ocis collaboration server&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">COLLABORATION_GRPC_ADDR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.0.0.0&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9301&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">COLLABORATION_HTTP_ADDR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.0.0.0&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9300&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MICRO_REGISTRY&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;nats-js-kv&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">MICRO_REGISTRY_ADDRESS&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;ocis:9233&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">COLLABORATION_WOPI_SRC&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://wopiserver.owncloud.yourdomain.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">COLLABORATION_APP_NAME&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Collabora&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">COLLABORATION_APP_ADDR&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://collabora.owncloud.yourdomain.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">COLLABORATION_APP_ICON&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://collabora.owncloud.yourdomain.com/favicon.ico&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># COLLABORATION_APP_INSECURE: true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># COLLABORATION_CS3API_DATAGATEWAY_INSECURE: true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">COLLABORATION_LOG_LEVEL&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${LOG_LEVEL:-info}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./ocis-config:/etc/ocis&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">logging&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">collabora&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">collabora/code:24.04.5.1.1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">127.0.0.1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9980&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">9980&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">aliasgroup1&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">https://wopiserver.owncloud.yourdomain.com:443&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">DONT_GEN_SSL_CERT&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;YES&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">extra_params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>--&lt;span class="l">o:ssl.enable=false --o:ssl.termination=true --o:welcome.enable=false --o:net.frame_ancestors=owncloud.yourdomain.com&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">username&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">password&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">admin&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cap_add&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">MKNOD&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">logging&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">local&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;bash&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;-c&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;coolconfig generate-proof-key ; /start-collabora-online.sh&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;CMD&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;curl&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;-f&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;http://localhost:9980/hosting/discovery&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">]&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This adds two services which need to be reverse proxied, the OCIS-collaboration service which exposes port 9300 for wopiserver and Collabora which uses port 9980.&lt;/p>
&lt;p>&lt;strong>However, this is not all&lt;/strong>, we have to also modify the content security policy of ownCloud to allow Libreoffice to load inside ownCloud&amp;rsquo;s web interface.&lt;/p>
&lt;p>Create a csp.yaml file and use the &lt;a class="link" href="https://github.com/owncloud/ocis/blob/master/deployments/examples/ocis_full/config/ocis/csp.yaml" target="_blank" rel="noopener"
>template&lt;/a> prepared from ownCloud to modify the CSP policy accordingly, make sure to replace &lt;code>https://${COLLABORA_DOMAIN|collabora.owncloud.test}/'&lt;/code> with the public URL of your Collabora server.&lt;/p>
&lt;p>That should be it! You now have an OCIS server ready for small production use in your Homelab!&lt;/p>
&lt;p>There are other services I didn&amp;rsquo;t cover like the import service to make it easier to import files from other cloud providers.
You can find more templates under the &lt;a class="link" href="https://github.com/owncloud/ocis/tree/master/deployments/examples/ocis_full" target="_blank" rel="noopener"
>ocis_full&lt;/a> directory in ownCloud&amp;rsquo;s OCIS repo.&lt;/p>
&lt;p>It&amp;rsquo;s a bit oddly organized, but each yml file is a docker-compose template, named under the service it adds.
You start with OCIS.yml and the other files are the things you have to add to enable the service.
Under the config directory you will find all the additional files used in the docker compose templates.&lt;/p>
&lt;h2 id="sources">sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/owncloud/ocis/tree/master/deployments/examples/ocis_full" target="_blank" rel="noopener"
>https://github.com/owncloud/ocis/tree/master/deployments/examples/ocis_full&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://doc.owncloud.com/ocis/next/deployment/services/services.html" target="_blank" rel="noopener"
>https://doc.owncloud.com/ocis/next/deployment/services/services.html&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://doc.owncloud.com/ocis/next/deployment/general/general-info.html#infinite-scale-supervised-services" target="_blank" rel="noopener"
>https://doc.owncloud.com/ocis/next/deployment/general/general-info.html#infinite-scale-supervised-services&lt;/a>&lt;/p></description></item><item><title>How to Make Your Linux Home Server Accessible on the Internet Without Port Forwarding</title><link>https://fariszr.com/home-server-access-without-port-forwarding/</link><pubDate>Tue, 28 May 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/home-server-access-without-port-forwarding/</guid><description>&lt;img src="https://fariszr.com/home-server-access-without-port-forwarding/thumbnail.jpg" alt="Featured image of post How to Make Your Linux Home Server Accessible on the Internet Without Port Forwarding" />&lt;p>If you want to make your home server accessible on the Internet but can&amp;rsquo;t use port forwarding (if you are behind CGNAT for example) or just aren&amp;rsquo;t comfortable having your home IP public, there are a few ways to do it.&lt;/p>
&lt;p>You could just use Cloudflared, it is free, easy to set up and gives you some security protection through Cloudflare&amp;rsquo;s WAF.
However, Cloudflare is a reverse proxy, meaning that your connection is decrypted, analyzed and then re-encrypted back to its origin, it also has specific T&amp;amp;S limits.&lt;/p>
&lt;p>Herein comes &lt;a class="link" href="https://github.com/rapiz1/rathole" target="_blank" rel="noopener"
>Rathole&lt;/a>, it&amp;rsquo;s a small standalone program written in Rust, it&amp;rsquo;s basically a simple TCP proxy, it doesn&amp;rsquo;t decrypt anything, it just forwards TCP traffic from one host to another using NAT traversal to avoid the need to port-forward anything.&lt;/p>
&lt;p>You just need a server with a public IP to tunnel TCP traffic back to your home server.&lt;/p>
&lt;p>I am going to use it to proxy Caddy, the reverse proxy I use on my home server, without MITMing any connections.&lt;/p>
&lt;h2 id="installing-rathole">Installing Rathole
&lt;/h2>&lt;p>Rathole is a self-contained package, just download the one that&amp;rsquo;s compiled for your platform and run it from &lt;a class="link" href="https://github.com/rapiz1/rathole/releases" target="_blank" rel="noopener"
>GitHub&lt;/a>.
You can also run it in a &lt;a class="link" href="https://hub.docker.com/r/rapiz1/rathole" target="_blank" rel="noopener"
>container&lt;/a>.&lt;/p>
&lt;p>This will be split into two parts, as Rathole needs to run on your home server and on the server with a static IP.&lt;/p>
&lt;h3 id="public-server-docker-composeyml">Public server docker-compose.yml
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rathole&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">rapiz1/rathole&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stdin_open&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tty&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">8080&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">8080&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># api port&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="m">443&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">443&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># forwarded port&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./rathole/config.toml:/app/config.toml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>--&lt;span class="l">server /app/config.toml&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="rathole-sever-config">Rathole sever Config
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># server.toml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">bind_addr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;0.0.0.0:8080&amp;#34;&lt;/span> &lt;span class="c"># `8080` specifies the port that rathole listens for clients&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">server&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">home-proxy&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">token&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;your_very_secure_shared_token&amp;#34;&lt;/span> &lt;span class="c"># Shared value between the client(your home server) and the server(the VPS)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">bind_addr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;0.0.0.0:443&amp;#34;&lt;/span> &lt;span class="c"># port on the vps routing traffic to your home server &amp;#34;the client&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="home-server-docker-setup">Home server docker setup
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># assuming your webserver is connected to a shared docker network called web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">external&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">rathole&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">stdin_open&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tty&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/home/lammah/rathole/config.toml:/app/config.toml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">rapiz1/rathole&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>--&lt;span class="l">client /app/config.toml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">web&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="home-server-rathole-config">Home server Rathole Config
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># client.toml&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">client&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">remote_addr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;yourservers-ip-or-domain:8080&amp;#34;&lt;/span> &lt;span class="c"># The address of the server. The port must be the same with the port in `server.bind_addr`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">client&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">services&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">home-proxy&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">token&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;your_very_secure_shared_token&amp;#34;&lt;/span> &lt;span class="c"># Must be the same one on the server to pass the validation&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">local_addr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;caddy:443&amp;#34;&lt;/span> &lt;span class="c"># The address of the service that needs to be forwarded&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Just start them both and you should be up and running!
You will also need to make sure that the domains are pointing to the VPS correctly so that HTTPS and applications work as expected.&lt;/p>
&lt;h2 id="source-ip">Source IP
&lt;/h2>&lt;p>There is one caveat though, and it&amp;rsquo;s kind of a big one, the source IP for all connections will be set to 127.0.0.1.
That&amp;rsquo;s a big problem if you want to protect your applications from DOS or endless login attempts.&lt;/p>
&lt;p>There is a solution, however, the PROXY protocol&lt;/p>
&lt;h2 id="forward-source-ip-with-proxy-protocol-in-rathole">Forward source IP with PROXY protocol in Rathole
&lt;/h2>&lt;p>The proxy protocol is a protocol that allows you to forward the source IP without needing too much computation.&lt;/p>
&lt;p>Both the TCP proxy and the upstream application must support the PROXY protocol for this to work.
Caddy supports the PROXY protocol, as a &lt;a class="link" href="https://caddyserver.com/docs/json/apps/http/servers/listener_wrappers/proxy_protocol/" target="_blank" rel="noopener"
>client&lt;/a> and as a &lt;a class="link" href="https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#the-http-transport" target="_blank" rel="noopener"
>proxy&lt;/a>.&lt;/p>
&lt;p>There is an open &lt;a class="link" href="https://github.com/rapiz1/rathole/pull/352" target="_blank" rel="noopener"
>PR&lt;/a> for Rathole which adds support for the PROXY protocol.
You can use my &lt;a class="link" href="https://github.com/FZR-forks/rathole-proxy_protocol/pkgs/container/rathole-proxy_protocol" target="_blank" rel="noopener"
>docker image&lt;/a> to use the version with PROXY protocol support, or just build it locally.&lt;/p>
&lt;p>Just use the same Configs as before, while adding &lt;code>enable_proxy_protocol = true&lt;/code> under the &lt;code>[server.services.home-proxy]&lt;/code> block.
Make sure that the web server/app you&amp;rsquo;re tunneling to supports the PROXY protocol and that it&amp;rsquo;s set up correctly.&lt;/p>
&lt;p>Now you should be able to get the correct source IP even with a TCP tunnel in between!&lt;/p></description></item><item><title>Redirect URL paths on Cloudflare pages for free</title><link>https://fariszr.com/redirect-url-path-free-cloudflare-pages/</link><pubDate>Sat, 27 Apr 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/redirect-url-path-free-cloudflare-pages/</guid><description>&lt;img src="https://fariszr.com/redirect-url-path-free-cloudflare-pages/thumbnail.jpg" alt="Featured image of post Redirect URL paths on Cloudflare pages for free" />&lt;p>If you need to do an HTTP redirect for anything while using Cloudflare the first idea that comes to mind will be just to use Redirect Rules, however, if your goal is to redirect a path, like &lt;code>/old/*&lt;/code> to &lt;code>/new/*&lt;/code>, you actually can&amp;rsquo;t do that in the free plan, as &lt;code>regex_replace&lt;/code> requires at least a PRO subscription.&lt;/p>
&lt;p>Luckily this blog is hosted on Cloudflare pages, which has its own &lt;a class="link" href="https://developers.cloudflare.com/pages/configuration/redirects/" target="_blank" rel="noopener"
>redirect mechanism&lt;/a>, and you can do such redirects without needing a pro subscription.&lt;/p>
&lt;h2 id="using-the-_redirects-file">using the _redirects file
&lt;/h2>&lt;p>you just need to create file called _redirects,
which needs to be included in the final directory uploaded to Cloudflare pages.
There are multiple site generator dependent ways to do this, I just add it manually in GitHub Actions after the site generation step.&lt;/p>
&lt;h3 id="using-splats">Using Splats
&lt;/h3>&lt;p>The easiest way to do this is just using splats, a Splat is everything that is matched when using &lt;code>*&lt;/code> after a path.&lt;/p>
&lt;p>_redirects&lt;/p>
&lt;pre>&lt;code>/old/* /new/:splat 301&lt;/code>&lt;/pre>&lt;h3 id="using-placeholders">using Placeholders
&lt;/h3>&lt;p>Placeholders are another option, they do the same thing, but look cleaner I guess.&lt;/p>
&lt;p>However, they have some specific &lt;a class="link" href="https://developers.cloudflare.com/pages/configuration/redirects/#placeholders" target="_blank" rel="noopener"
>limitations&lt;/a>, so stick to splats when possible.&lt;/p>
&lt;p>_redirects&lt;/p>
&lt;pre>&lt;code>/movies/:title /media/:title 302&lt;/code>&lt;/pre>&lt;h2 id="sources">sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://developers.cloudflare.com/pages/configuration/redirects/" target="_blank" rel="noopener"
>https://developers.cloudflare.com/pages/configuration/redirects/&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://community.cloudflare.com/t/transform-rule-replace-part-of-url/437813" target="_blank" rel="noopener"
>https://community.cloudflare.com/t/transform-rule-replace-part-of-url/437813&lt;/a>&lt;/p></description></item><item><title>Deploy Projects Securely Using Tailscale SSH and GitHub Actions</title><link>https://fariszr.com/deploy-with-tailscale-ssh-github-action/</link><pubDate>Sun, 31 Mar 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/deploy-with-tailscale-ssh-github-action/</guid><description>&lt;img src="https://fariszr.com/deploy-with-tailscale-ssh-github-action/thumbnail.jpg" alt="Featured image of post Deploy Projects Securely Using Tailscale SSH and GitHub Actions" />&lt;p>If you want to deploy your project to a server without exposing it or manually managing ssh keys, you can use Tailscale SSH!&lt;/p>
&lt;p>Here is a quick guide to using my Github action &lt;a class="link" href="https://github.com/FarisZR/tailscale-ssh-deploy" target="_blank" rel="noopener"
>tailscale-ssh-deploy&lt;/a> to deploy files to a server using Tailscale SSH (not normal ssh).&lt;/p>
&lt;h3 id="deploying-a-static-site">Deploying a static site
&lt;/h3>&lt;p>For example, we will deploy a Hugo static site using this action&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">push&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build-and-deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">build and deploy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">checkout&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fetch-depth&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Setup Hugo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">peaceiris/actions-hugo@v2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hugo-version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;latest&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">extended&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Build&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">run&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">hugo --minify -v --gc --destination generated&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Setup Tailscale&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tailscale/github-action@v2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hostname&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Github-actions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">oauth-client-id&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.TS_OAUTH_CLIENT_ID }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">oauth-secret&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.TS_OAUTH_SECRET }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tags&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tag:ci&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Start Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">FarisZR/tailscale-ssh-deploy@v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">remote_host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">USER@LOCAL-IPV4-ADDRESS-TAILSCALE&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">generated&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">remote_destination&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/var/www/html&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">post_upload_command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">systemctl restart nginx&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The relevant part for us are the last two actions.
First, we set up Tailscale using an Oauth client ID and secret that you generate in the settings page of your Tailnet &lt;a class="link" href="https://tailscale.com/kb/1215/oauth-clients" target="_blank" rel="noopener"
>more details&lt;/a>.&lt;/p>
&lt;p>Now make sure you specify the user and use the Tailscale IP address of the server, as I didn&amp;rsquo;t find using MagicDNS hostnames to be very reliable.&lt;/p>
&lt;p>Then you just define the directory to upload to in &lt;code>directory&lt;/code> and where it should be uploaded into &lt;code>remote_destination&lt;/code>&lt;/p>
&lt;p>You can use &lt;code>post_upload_command&lt;/code> to specify a command to run after a successful upload, as an example I added a command to restart nginx.&lt;/p>
&lt;p>That should do it! Now you have a workflow that automatically deploys your project to a server without exposing an ssh service or managing ssh keys!&lt;/p></description></item><item><title>Add Missing Linux Packages into WordPress Docker Images Without Rebuilding</title><link>https://fariszr.com/add-missing-packages-wordpress-docker/</link><pubDate>Thu, 29 Feb 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/add-missing-packages-wordpress-docker/</guid><description>&lt;img src="https://fariszr.com/add-missing-packages-wordpress-docker/thumbnail.jpg" alt="Featured image of post Add Missing Linux Packages into WordPress Docker Images Without Rebuilding" />&lt;p>If you use WordPress in docker, sometimes you may have some plugins complaining about missing libraries / packages, like for example EWWW image optimizer, that was complaining about missing image libraries.&lt;/p>
&lt;p>You could of course just rebuild the image, but if you want to avoid that, you could modify the command that runs at start to install the packages you need, and then start the WordPress process:&lt;/p>
&lt;h2 id="wordpressapache-latest">Wordpress:apache (latest)
&lt;/h2>&lt;p>the default image uses Debian as a base, so we modify the command to update packages lists and then install the packages we need, and after that WordPress Entrypoint should get triggered.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> wordpress:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: wordpress:apache
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ command: sh -c &amp;#34;apt update &amp;amp;&amp;amp; apt install -y --no-install-recommends xxxxxxx; docker-entrypoint.sh apache2-foreground&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_HOST: mariadb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_USER: xxxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_NAME: wordpress
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_PASSWORD: xxxxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - html:/var/www/html:rw
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="wordpressfpm">Wordpress:fpm
&lt;/h2>&lt;p>Basically the same as the default Apache image, with just a different entrypoint command.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> wordpress:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: wordpress:fpm-alpine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ command: sh -c &amp;#34;apt update &amp;amp;&amp;amp; apt install -y --no-install-recommends xxxxxxx; docker-entrypoint.sh php-fpm&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_HOST: mariadb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_USER: xxxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_NAME: wordpress
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_PASSWORD: xxxxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - html:/var/www/html:rw
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="wordpressfpm-alpine">Wordpress:fpm-alpine
&lt;/h2>&lt;p>As the tag implies, this image uses Alpine as a base, here we have to use apk, the package manager for alpine.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-diff" data-lang="diff">&lt;span class="line">&lt;span class="cl">services:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> wordpress:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> image: wordpress:fpm-alpine
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> restart: always
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">+ command: sh -c &amp;#34;apk add xx xx xx; docker-entrypoint.sh php-fpm&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="gi">&lt;/span> environment:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_HOST: mariadb
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_USER: xxxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_NAME: wordpress
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> WORDPRESS_DB_PASSWORD: xxxxx
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> volumes:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> - html:/var/www/html:rw
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>And now you should have the packages you need without needing to rebuild the image manually on every update!&lt;/p></description></item><item><title>Set up scheduled Debian/Ubuntu unattended upgrades</title><link>https://fariszr.com/setup-scheduled-debian-ubuntu-unattended-upgrades/</link><pubDate>Wed, 31 Jan 2024 00:00:00 +0000</pubDate><guid>https://fariszr.com/setup-scheduled-debian-ubuntu-unattended-upgrades/</guid><description>&lt;img src="https://fariszr.com/setup-scheduled-debian-ubuntu-unattended-upgrades/thumbnail.jpg" alt="Featured image of post Set up scheduled Debian/Ubuntu unattended upgrades" />&lt;p>When you run a server that relies on containers for almost everything, keeping the system up to date has almost zero chance of breaking anything.&lt;/p>
&lt;p>So here is a quick guide to setting up automatic updates on a fixed schedule + automatic reboots for kernel updates (optional)&lt;/p>
&lt;h2 id="installing-unattended-upgrades">installing unattended-upgrades
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo apt update
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt install unattended-upgrades&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="configuring-unattended-upgrades">Configuring Unattended-upgrades
&lt;/h2>&lt;p>Depending on what kind of updates you feel comfortable applying, you can customize the source repository as you like.&lt;/p>
&lt;h3 id="allow-security-updates-only">allow security updates only
&lt;/h3>&lt;p>This is actually the default configuration for unattended-upgrades&lt;/p>
&lt;p>/etc/apt/apt.conf.d/50unattended-upgrades&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">// &amp;#34;origin=Debian,codename=${distro_codename}-updates&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// &amp;#34;origin=Debian,codename=${distro_codename}-proposed-updates&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;origin=Debian,codename=${distro_codename},label=Debian&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;origin=Debian,codename=${distro_codename},label=Debian-Security&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;origin=Debian,codename=${distro_codename}-security,label=Debian-Security&amp;#34;;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="allow-all-updates">allow all updates
&lt;/h3>&lt;p>/etc/apt/apt.conf.d/50unattended-upgrades&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl"> &amp;#34;origin=Debian,codename=${distro_codename}-updates&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">// &amp;#34;origin=Debian,codename=${distro_codename}-proposed-updates&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;origin=Debian,codename=${distro_codename},label=Debian&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;origin=Debian,codename=${distro_codename},label=Debian-Security&amp;#34;;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;origin=Debian,codename=${distro_codename}-security,label=Debian-Security&amp;#34;;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="additional-repositories">Additional repositories
&lt;/h3>&lt;p>You can also automate the update for additional repositories such as Docker&amp;rsquo;s official repositories.&lt;/p>
&lt;p>First, go to &lt;code>/var/lib/apt/lists/&lt;/code> and find the Docker repos filename.
In this file you should find something like&lt;/p>
&lt;pre>&lt;code>label: Docker CE
Origin: Docker
Suite: bookworm&lt;/code>&lt;/pre>&lt;p>For us, the most important details here are &lt;strong>origin&lt;/strong> and &lt;strong>suite&lt;/strong>, as suite is the archive that unattended-upgrades needs to specify which version to use.&lt;/p>
&lt;p>To add the docker repository, create a new line under the Distro&amp;rsquo;s repos in &lt;code>/etc/apt/apt.conf.d/50unattended-upgrades' with the following format &lt;/code>&amp;quot;&lt;!-- raw HTML omitted -->:&lt;!-- raw HTML omitted -->&amp;quot;;`&lt;/p>
&lt;p>so in our case it would be &lt;code>&amp;quot;Docker:bookworm&amp;quot;;&lt;/code>, but what if we don&amp;rsquo;t want to update this every time the system is upgraded? then you use the &lt;code>${distro_codename}&lt;/code> variable.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">&amp;#34;Docker:${distro_codename}&amp;#34;;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and now unattended upgrades should automatically update packages from the docker repos!&lt;/p>
&lt;h2 id="enable-automatic-reboot-for-kernel-upgrades">Enable automatic reboot for kernel upgrades
&lt;/h2>&lt;p>If you can tolerate a little downtime, you can also automate system reboots for kernel upgrades&lt;/p>
&lt;p>by changing the following settings&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">// Unattended-Upgrade::Automatic-Reboot &amp;#34;false&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>to&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Unattended-Upgrade::Automatic-Reboot &amp;#34;true&amp;#34;;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>And you can change the reboot time by changing the default `02:00&amp;rsquo; to any time you like (in the local server timezone).
&lt;strong>REMEMBER TO UNCOMMENT THE SETTING (remove the // and the spaces)&lt;/strong>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Unattended-Upgrade::Automatic-Reboot-Time &amp;#34;22:00&amp;#34;;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="setting-up-timers">Setting up timers
&lt;/h2>&lt;p>We will set two timers, one to update apt&amp;rsquo;s local repo cache and one to apply the updates.&lt;/p>
&lt;h3 id="apt-update-timer">Apt update timer
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl edit apt-daily.timer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>then add the following in the space between systemd comments (assuming you want to update apt lists at 00:55 server time every day)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[Timer]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OnCalendar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OnCalendar=00:55
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RandomizedDelaySec=0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and then you should restart the service and check the status of the timer, it should show the next trigger time&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl restart apt-daily.timer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo systemctl status apt-daily.timer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="apt-upgrade-timer">Apt upgrade timer
&lt;/h3>&lt;p>This is very similar to setting the update timer, we just need to make sure it&amp;rsquo;s &lt;strong>after&lt;/strong> the update job so we can apply the latest updates as they are released.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl edit apt-daily-upgrade.timer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Again, in the blank space, add the following timer settings (to apply updates at 01:00 server time every day)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[Timer]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OnCalendar
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">OnCalendar=01:00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RandomizedDelaySec=0&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and then you should restart the service and check the status of the timer, it should show the next trigger time&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl restart apt-daily-upgrade.timer
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo systemctl status apt-daily-upgrade.timer&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and now you have fully automated updates for your entire system!&lt;/p>
&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://askubuntu.com/questions/87849/how-to-enable-silent-automatic-updates-for-any-repository" target="_blank" rel="noopener"
>https://askubuntu.com/questions/87849/how-to-enable-silent-automatic-updates-for-any-repository&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://wiki.debian.org/StableProposedUpdates" target="_blank" rel="noopener"
>https://wiki.debian.org/StableProposedUpdates&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://linuxiac.com/how-to-set-up-automatic-updates-on-debian/" target="_blank" rel="noopener"
>https://linuxiac.com/how-to-set-up-automatic-updates-on-debian/&lt;/a>&lt;/p></description></item><item><title>Scheduling Docker jobs without cron</title><link>https://fariszr.com/scheduling-docker-jobs-without-cron/</link><pubDate>Wed, 27 Dec 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/scheduling-docker-jobs-without-cron/</guid><description>&lt;img src="https://fariszr.com/scheduling-docker-jobs-without-cron/thumbnail.jpg" alt="Featured image of post Scheduling Docker jobs without cron" />&lt;p>Sometimes you need to automate some things in Docker containers, run a script or check a result online, and I honestly dread dealing with cron to do it.&lt;/p>
&lt;p>That&amp;rsquo;s where &lt;a class="link" href="https://github.com/mcuadros/ofelia" target="_blank" rel="noopener"
>Ofelia&lt;/a> comes in! It&amp;rsquo;s a modern scheduler built from the ground up for Docker, and it interacts directly with the Docker socket to run a container, run a script in a specific container, or even on the host!&lt;/p>
&lt;h2 id="usage-with-labels">Usage with labels
&lt;/h2>&lt;p>I wanted to run a script to update data in a JSON file.
You can configure Ofelia in several ways, but I think by far the easiest is to just use docker labels with compose.&lt;/p>
&lt;p>Labels are structured like this:
&lt;code>ofelia.&amp;lt;JOB_TYPE&amp;gt;.&amp;lt;JOB_NAME&amp;gt;.&amp;lt;JOB_PARAMETER&amp;gt;=&amp;lt;PARAMETER_VALUE&amp;gt;&lt;/code>
&amp;lt;JOB_TYPE&amp;gt;:&lt;/p>
&lt;blockquote>
&lt;ul>
&lt;li>job-exec: this job is executed inside of a running container.&lt;/li>
&lt;li>job-run: runs a command inside of a new container, using a specific image.&lt;/li>
&lt;li>job-local: runs the command inside of the host running ofelia.&lt;/li>
&lt;li>job-service-run: runs the command inside a new &amp;ldquo;run-once&amp;rdquo; service, for running inside a swarm&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>Available parameters for each job type &lt;a class="link" href="https://github.com/mcuadros/ofelia/blob/master/docs/jobs.md" target="_blank" rel="noopener"
>here&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ofelia&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">mcuadros/ofelia:latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">daemon --docker&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">file.json:/src/file.json&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">update_json_file.sh:/update_json_file.sh:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ofelia.job-local.update_json_file.schedule&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;@every 24h&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ofelia.job-local.update_json_file.command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;/bin/sh -c &amp;#34;apk add curl jq &amp;amp;&amp;amp; sh /update_json_file.sh /src/file.json&amp;#34;&amp;#39;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Let me explain the setup here.
First we have &lt;code>daemon --docker&lt;/code> as command, because adding &lt;code>--docker&lt;/code> allows us to use labels as configuration, you don&amp;rsquo;t have to do that, you can also use an &lt;a class="link" href="https://github.com/mcuadros/ofelia?tab=readme-ov-file#ini-style-config" target="_blank" rel="noopener"
>INI structured&lt;/a> file as config.&lt;/p>
&lt;p>And since it&amp;rsquo;s running inside a container, and it needs access to docker to start containers and run commands, in addition to reading container labels, we also mount the docker socket at &lt;code>/var/run/docker.sock&lt;/code>.&lt;/p>
&lt;p>Then in the labels I defined a job to run every 24h, and the command is &lt;code>/bin/sh -c &amp;quot;apk add curl jq &amp;amp;&amp;amp; sh /update_json_file.sh /src/file.json&amp;quot;&lt;/code> I had to manually open a shell with &lt;code>/bin/sh -c&lt;/code> to use &lt;code>&amp;amp;&amp;amp;&lt;/code> and run multiple commands.&lt;/p>
&lt;p>And that&amp;rsquo;s it! You now have an automated Docker container without having to fiddle with cron!&lt;/p></description></item><item><title>Purge remote media cache in Matrix Synapse</title><link>https://fariszr.com/clear-remote-media-cache-matrix-synapse/</link><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/clear-remote-media-cache-matrix-synapse/</guid><description>&lt;img src="https://fariszr.com/clear-remote-media-cache-matrix-synapse/thumbnail.jpg" alt="Featured image of post Purge remote media cache in Matrix Synapse" />&lt;p>Using the Purge Remote Media API, we can send a POST request to delete all cached remote media before a certain time:&lt;/p>
&lt;p>&lt;strong>Your account must be a server admin for this command to work&lt;/strong>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -X POST -H &lt;span class="s2">&amp;#34;Authorization: Bearer &lt;/span>&lt;span class="nv">$ADMIN_ACCESS_TOKEN&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;https://matrix.org/_synapse/admin/v1/purge_media_cache?before_ts=1672527600000&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>before_ts&lt;/code> takes time in UNIX epoch milliseconds, the one in the template is at 2023-01-01 00:00.
You can generate one [here] (&lt;a class="link" href="https://currentmillis.com/%29z" target="_blank" rel="noopener"
>https://currentmillis.com/)z&lt;/a>&lt;/p>
&lt;h2 id="generating-an-access-token">Generating an access token
&lt;/h2>&lt;p>If you don&amp;rsquo;t have an access token, you can also generate one using curl:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -XPOST &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="s1">&amp;#39;{&amp;#34;type&amp;#34;:&amp;#34;m.login.password&amp;#34;, &amp;#34;user&amp;#34;:&amp;#34;&amp;lt;userid&amp;gt;&amp;#34;, &amp;#34;password&amp;#34;:&amp;#34;&amp;lt;password&amp;gt;&amp;#34;}&amp;#39;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="s2">&amp;#34;https://matrix.org/_matrix/client/r0/login&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://matrix-org.github.io/synapse/latest/admin_api/media_admin_api.html#purge-remote-media-api" target="_blank" rel="noopener"
>https://matrix-org.github.io/synapse/latest/admin_api/media_admin_api.html#purge-remote-media-api&lt;/a>
Bing AI.&lt;/p></description></item><item><title>Setup IPv6 properly on docker (Fix Source IPv6 propagation)</title><link>https://fariszr.com/docker-ipv6-setup-with-propagation/</link><pubDate>Tue, 26 Sep 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/docker-ipv6-setup-with-propagation/</guid><description>&lt;img src="https://fariszr.com/docker-ipv6-setup-with-propagation/thumbnail.jpeg" alt="Featured image of post Setup IPv6 properly on docker (Fix Source IPv6 propagation)" />&lt;p>If you&amp;rsquo;re wondering why there are a lot of connections coming from your docker gateway right after you enabled IPv6 for your services, you&amp;rsquo;ve come to the right place.&lt;/p>
&lt;p>Unfortunately, setting up IPv6 support for Docker isn&amp;rsquo;t as simple as setting up a DNS record and exposing the container to all interfaces.&lt;/p>
&lt;p>If you just expose the container to an IPv6 connection without any additional setup, it will work, but without any IPv6 source propagation, you will get a lot of connections seemingly coming from the Docker gateway (usually starting with 172).&lt;/p>
&lt;p>The fix for this is to make sure all networks from the bridge network to the internal docker compose networks are IPv6 ready, one network that is IPv4 only will bring the problem back.&lt;/p>
&lt;h2 id="enable-ipv6-support">Enable IPv6 support
&lt;/h2>&lt;p>/etc/docker/daemon.json&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;experimental&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ip6tables&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This will enable ipv6tables support for Docker, which allows port mapping and network isolation, but requires the experimental flag.&lt;/p>
&lt;h2 id="enabling-ipv6-on-the-default-bridge-network">Enabling IPv6 on the default bridge network
&lt;/h2>&lt;p>Enabling ipv6 in the config should be enough now, but to make sure all networks are IPv6 enabled, we add IPv6 to the default bridge network.&lt;/p>
&lt;p>/etc/docker/daemon.json&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ipv6&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fixed-cidr-v6&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2001:db8:1::/64&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;experimental&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ip6tables&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="dynamic-ipv6-subnet-allocation">Dynamic IPv6 Subnet Allocation
&lt;/h2>&lt;p>Now with the previous settings you should be ready to go.
However, you will need to manually set a subnet for each network, either within Compose or with &lt;code>docker network create --ipv6 --subnet=xxx $NETWORK_NAME&lt;/code>.&lt;/p>
&lt;p>If you want Docker to automatically allocate subnets, as it does with IPv4, you will need to provide it with a pool.&lt;/p>
&lt;p>/etc/docker/daemon.json&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ipv6&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;fixed-cidr-v6&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2001:db8:1::/64&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;experimental&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;ip6tables&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;default-address-pools&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;172.17.0.0/16&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;172.18.0.0/16&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;172.19.0.0/16&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;172.20.0.0/14&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;172.24.0.0/14&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;172.28.0.0/14&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">16&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;192.168.0.0/16&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">20&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span> &lt;span class="nt">&amp;#34;base&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2001:db8::/104&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nt">&amp;#34;size&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">112&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>2001:db8::/104&lt;/code> should not be used as a real subnet, it&amp;rsquo;s for documentation purposes only.
If you want a local pool, go to &lt;a class="link" href="https://simpledns.plus/private-ipv6" target="_blank" rel="noopener"
>simpledns.plus&lt;/a> and replace the first two address blocks with those generated by the site.&lt;/p>
&lt;p>Now restart the docker daemon:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo systemctl restart docker&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="migrating-existing-containers--docker-compose-projects">Migrating Existing Containers / Docker Compose Projects
&lt;/h2>&lt;p>Restarting the daemon alone won&amp;rsquo;t be enough if you have containers running, especially those inside a Docker Compose project using an internal network.&lt;/p>
&lt;p>You will need to remove all containers connected to a network other than bridge.&lt;/p>
&lt;p>If you are using Compose, first delete all containers and their networks (this should&amp;rsquo;t delete volumes):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker compose down&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Add &lt;code>enable_ipv6: true&lt;/code> to all networks, if you didn&amp;rsquo;t define any networks, then you are using the default network, and you should define it and add the option to it:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enable_ipv6&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Classic docker networks need to be removed and rebuilt with the &lt;code>--IPv6&lt;/code> option:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker network rm &lt;span class="nv">$NETWORK&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> docker network create --ipv6 &lt;span class="nv">$NETWORK&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now you should have full IPv6 support with proper IPv6 source IP propagation.&lt;/p>
&lt;h2 id="testing-the-changes">Testing the changes
&lt;/h2>&lt;p>To make sure it actually works, run a Python container connected to a network you&amp;rsquo;ve created and see if it propagates the address or not:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -p 8080:8080 --rm -it --network &lt;span class="nv">$NETWORK&lt;/span> python:latest python3 -m http.server &lt;span class="m">8080&lt;/span> --bind ::&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now just connect to it on port 8080 and see if the IP is displayed correctly.&lt;/p>
&lt;p>If the source still shows up as a local IPv4 address, just test it with the bridge network:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker run -p 8080:8080 --rm -it python:latest python3 -m http.server &lt;span class="m">8080&lt;/span> --bind ::&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If it works, then the problem is that the network you are using doesn&amp;rsquo;t have IPv6 enabled, if it still doesn&amp;rsquo;t work, make sure the config changes have been applied!&lt;/p>
&lt;h2 id="sources">sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://docs.docker.com/config/daemon/ipv6/" target="_blank" rel="noopener"
>https://docs.docker.com/config/daemon/ipv6/&lt;/a>&lt;/p></description></item><item><title>setup Squid proxy with docker</title><link>https://fariszr.com/squid-proxy-docker-setup/</link><pubDate>Tue, 29 Aug 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/squid-proxy-docker-setup/</guid><description>&lt;img src="https://fariszr.com/squid-proxy-docker-setup/thumbnail.jpeg" alt="Featured image of post setup Squid proxy with docker" />&lt;p>I use proxies very often as a way to do split tunneling on Linux, as easy split-tunneling with WireGuard or OpenVPN isn&amp;rsquo;t there yet.&lt;/p>
&lt;p>The quickest way to set up anything IMO is with docker, and here is a quick guide for squid proxy setup using the official Ubuntu-squid image, which seems to be the only up-to-date docker image on Docker hub.&lt;/p>
&lt;h2 id="docker-composeyml">docker-compose.yml
&lt;/h2>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;3&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">proxy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu/squid&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;3128:3128&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">TZ=UTC&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./squid.conf:/etc/squid/squid.conf&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">configs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">source&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">squid&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/etc/squid/squid.conf&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">configs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">squid&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">file&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">./squid.conf&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I used configs to prevent any permission issues popping up.&lt;/p>
&lt;h2 id="squidconf">squid.conf
&lt;/h2>&lt;pre>&lt;code>http_port 3128
http_access allow all&lt;/code>&lt;/pre>&lt;p>This is a simple setup for a local transparent proxy, it allows all connections, and it doesn&amp;rsquo;t cache or log anything.&lt;/p>
&lt;h2 id="sources">sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://medium.com/setup-a-web-proxy-server-with-docker-d5c6942b5575" target="_blank" rel="noopener"
>https://medium.com/setup-a-web-proxy-server-with-docker-d5c6942b5575&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://hub.docker.com/r/ubuntu/squid" target="_blank" rel="noopener"
>https://hub.docker.com/r/ubuntu/squid&lt;/a>&lt;/p></description></item><item><title>How to send a matrix message using CURL and an access token</title><link>https://fariszr.com/send-matrix-message-curl/</link><pubDate>Sun, 23 Jul 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/send-matrix-message-curl/</guid><description>&lt;img src="https://fariszr.com/send-matrix-message-curl/thumbnail.png" alt="Featured image of post How to send a matrix message using CURL and an access token" />&lt;p>Here is a how you can send a simple matrix message with CURL:&lt;/p>
&lt;pre>&lt;code>curl 'https://matrix.org/_matrix/client/r0/rooms/!xxxxxx:example.com/send/m.room.message/?access_token=xxxxxxxx' -X PUT --data '{"msgtype":"m.text","body":"hello world"}'&lt;/code>&lt;/pre>&lt;h2 id="generate-an-access-token">generate an access token
&lt;/h2>&lt;p>if you don&amp;rsquo;t have an access token, you can generate one using curl too:&lt;/p>
&lt;pre>&lt;code>curl -XPOST \
-d '{"type":"m.login.password", "user":"&lt;userid>", "password":"&lt;password>"}' \
"https://matrix.org/_matrix/client/r0/login"&lt;/code>&lt;/pre>&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://news.ycombinator.com/item?id=19227859" target="_blank" rel="noopener"
>https://news.ycombinator.com/item?id=19227859&lt;/a>
&lt;a class="link" href="https://webapps.stackexchange.com/questions/131056/how-to-get-an-access-token-for-element-riot-matrix" target="_blank" rel="noopener"
>https://webapps.stackexchange.com/questions/131056/how-to-get-an-access-token-for-element-riot-matrix&lt;/a>&lt;/p></description></item><item><title>setup full text search for Nextcloud on docker</title><link>https://fariszr.com/nextcloud-fulltextsearch-elasticsearch-docker-setup/</link><pubDate>Thu, 08 Jun 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/nextcloud-fulltextsearch-elasticsearch-docker-setup/</guid><description>&lt;img src="https://fariszr.com/nextcloud-fulltextsearch-elasticsearch-docker-setup/thumbnail.jpeg" alt="Featured image of post setup full text search for Nextcloud on docker" />&lt;p>Setting up Nextcloud &lt;a class="link" href="https://apps.nextcloud.com/apps/fulltextsearch" target="_blank" rel="noopener"
>full-text search&lt;/a> isn&amp;rsquo;t very clear, especially with Docker, so in this post I&amp;rsquo;ll explain how to set up full-text search with Elasticsearch and Tesseract using Nextcloud&amp;rsquo;s official Docker images.&lt;/p>
&lt;h2 id="add-new-tesseract-to-your-nextcloud-container">add new tesseract to your Nextcloud container
&lt;/h2>&lt;p>Normally we would need to create a custom image for this, but fortunately &lt;a class="link" href="https://github.com/nextcloud/docker/issues/1414#issuecomment-884711124" target="_blank" rel="noopener"
>modzilla99&lt;/a> and &lt;a class="link" href="https://github.com/nextcloud/docker/issues/1414#issuecomment-1008915705" target="_blank" rel="noopener"
>Schw3pps&lt;/a> have found a way to add additional packages without needing a custom image, by adding a custom command to the container.&lt;/p>
&lt;h3 id="latestapache-image">latest/apache image
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sh -c &amp;#34;apt update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends tesseract-ocr tesseract-ocr-eng tesseract-ocr-$(YOUR_THREE_LETTER_LANGUAGE_CODE) &amp;amp;&amp;amp; /entrypoint.sh apache2-foreground&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;strong>TIP:&lt;/strong> if you want to add Cron, you don&amp;rsquo;t need a separate container, just add supervisor&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sh -c &amp;#34;apt update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends tesseract-ocr tesseract-ocr-eng tesseract-ocr-$(YOUR_THREE_LETTER_LANGUAGE_CODE) &amp;amp;&amp;amp; mkdir -p /var/log/supervisord &amp;amp;&amp;amp; mkdir -p /var/run/supervisord supervisor &amp;amp;&amp;amp; supervisord -c /supervisord.conf&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>make sure to mount &lt;a class="link" href="https://github.com/nextcloud/docker/blob/master/.examples/dockerfiles/full/apache/supervisord.conf" target="_blank" rel="noopener"
>this file&lt;/a> at &lt;code>/supervisord.conf&lt;/code>&lt;/p>
&lt;h3 id="fpm-alpine-image">fpm-alpine image
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>-&lt;span class="l">c &amp;#34;apk add --no-cache tesseract-ocr tesseract-ocr-data-eng tesseract-ocr-data-$(YOUR_THREE_LETTER_LANGUAGE_CODE); /entrypoint.sh php-fpm&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>and if you have a separate Cron container, add this to it:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>-&lt;span class="l">c &amp;#34;apk add --no-cache tesseract-ocr tesseract-ocr-data-eng tesseract-ocr-data-$(YOUR_THREE_LETTER_LANGUAGE_CODE); /cron.sh&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="elasticsearch-setup">Elasticsearch setup
&lt;/h2>&lt;h3 id="docker-composeyml">docker-compose.yml
&lt;/h3>&lt;p>Add this to your Nextcloud compose file&lt;/p>
&lt;p>&lt;strong>To avoid any problems, make the Nextcloud container depends on Elasticsearch, to make sure it&amp;rsquo;s started before Nextcloud&lt;/strong>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">elasticsearch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">docker.elastic.co/elasticsearch/elasticsearch:7.17.10&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">default&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ports:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># 127.0.0.1:9200:9200 #only needed if you are connecting through a docker network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sh -c &amp;#34;bin/elasticsearch-plugin install --batch ingest-attachment; /bin/tini -s /usr/local/bin/docker-entrypoint.sh eswrapper&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">discovery.type=single-node&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">bootstrap.memory_lock=true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;ES_JAVA_OPTS=-Xms512m -Xmx2048m&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">user&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="m">1000&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ulimits&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">memlock&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">soft&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>-&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hard&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>-&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">elasticsearch:/usr/share/elasticsearch/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="l">elasticsearch:&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Using the same trick as before, we can install the &lt;code>ingest-attachment&lt;/code> plugin without the need for a custom image.
There will be an error when the container is restarted as it tries to install the plugin again, but it should continue to work fine.&lt;/p>
&lt;h2 id="nextcloud-setup">Nextcloud setup
&lt;/h2>&lt;p>Select Elasticsearch as the search platform, then add the server address.
If your language requires a custom tokenizer, feel free to change it.
&lt;img src="https://fariszr.com/nextcloud-fulltextsearch-elasticsearch-docker-setup/nextcloud-settings.png"
width="735"
height="268"
srcset="https://fariszr.com/nextcloud-fulltextsearch-elasticsearch-docker-setup/nextcloud-settings_hu5523383922916709850.png 480w, https://fariszr.com/nextcloud-fulltextsearch-elasticsearch-docker-setup/nextcloud-settings_hu18045335696046996515.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="274"
data-flex-basis="658px"
>&lt;/p>
&lt;p>The other options below aren&amp;rsquo;t very important and can be configured to your liking.&lt;/p>
&lt;h2 id="start-indexing">Start indexing!
&lt;/h2>&lt;p>Test setup&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./occ fulltextsearch:test&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>index all files&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./occ fulltextsearch:index&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="automatic-indexing">automatic indexing
&lt;/h2>&lt;p>The official wiki has a section on using cron for this:
&lt;a class="link" href="https://github.com/nextcloud/fulltextsearch/wiki/Basic-Installation#live-index-service" target="_blank" rel="noopener"
>https://github.com/nextcloud/fulltextsearch/wiki/Basic-Installation#live-index-service&lt;/a>&lt;/p>
&lt;p>But since we are using docker, probably with supervisord added, &lt;a class="link" href="https://github.com/nextcloud/fulltextsearch/issues/671" target="_blank" rel="noopener"
>robeatoz&lt;/a> has found a way to add &lt;code>fulltextsearch:live&lt;/code> to the &lt;a class="link" href="https://github.com/nextcloud/docker/blob/master/.examples/dockerfiles/cron/apache/supervisord.conf" target="_blank" rel="noopener"
>supervisord.conf&lt;/a> file.&lt;/p>
&lt;h3 id="fulltextsearchsh">fulltextsearch.sh
&lt;/h3>&lt;p>create a new file called &lt;code>fulltextsearch.sh&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="c1"># Stop all running indexes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">php /var/www/html/occ fulltextsearch:stop
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Start live index&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">php /var/www/html/occ fulltextsearch:live
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># More information: https://github.com/nextcloud/fulltextsearch/wiki/Commands&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="supervisordconf">supervisord.conf
&lt;/h3>&lt;p>Add this to the end of the file&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">[program:fulltextsearch_index_live]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">stdout_logfile=/dev/stdout
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">stdout_logfile_maxbytes=0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">stderr_logfile=/dev/stderr
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">stderr_logfile_maxbytes=0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">user=www-data
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">command=/bin/sh /fulltextsearch.sh&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="docker-composeyml-1">docker-compose.yml
&lt;/h3>&lt;p>mount &lt;code>fulltextsearch.sh&lt;/code> into the container&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">....&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./fulltextsearch.sh:/fulltextsearch:ro&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>And you are done!&lt;/p>
&lt;h2 id="sources">sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/nextcloud/docker/issues/1724" target="_blank" rel="noopener"
>https://github.com/nextcloud/docker/issues/1724&lt;/a>&lt;/p></description></item><item><title>Did the EU ban JSdelivr and other free CDNs?</title><link>https://fariszr.com/eu-gdpr-jsdelivr-free-cdn-ban/</link><pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/eu-gdpr-jsdelivr-free-cdn-ban/</guid><description>&lt;img src="https://fariszr.com/eu-gdpr-jsdelivr-free-cdn-ban/thumbnail.jpeg" alt="Featured image of post Did the EU ban JSdelivr and other free CDNs?" />&lt;p>So while I was updating my website, I was thinking about using JSdelivr for my blogs images, the slowest part of my website since it&amp;rsquo;s hosted on a VPS and not on Cloudflare Pages to avoid further internet centralization.&lt;/p>
&lt;p>But I remembered that a court in Germany recently ruled that using Google Fonts is illegal, does this affect JSdelivr and other public CDNs? Apparently it does.&lt;/p>
&lt;p>&lt;strong>I&amp;rsquo;m not a lawyer, these are just my observations.&lt;/strong>&lt;/p>
&lt;h2 id="data-processing-agreements-with-free-cdns">Data processing agreements with free CDNs
&lt;/h2>&lt;p>According to &lt;a class="link" href="https://legalweb.io/en/news-en/cdns-with-gdpr-in-mind/" target="_blank" rel="noopener"
>legalweb.io&lt;/a>, in order to use a CDN you need to have an data processing agreement, which you don&amp;rsquo;t have with free CDNs, and the data needs to be processed within the EU or a country that is deemed to follow GDPR standards, which is certainly not the U.S.&lt;/p>
&lt;p>So basically any CDN provider that you don&amp;rsquo;t have an Article 28 GDPR agreement with is illegal, any of the free CDNs like Jsdelivr are banned, that should include &lt;a class="link" href="https://fonts.bunny.net/" target="_blank" rel="noopener"
>Bunny fonts&lt;/a>!, which was created as a GDPR-friendly alternative to Google Fonts, but since you don&amp;rsquo;t have a DPA with them, it&amp;rsquo;s still probably illegal.&lt;/p>
&lt;h2 id="legitimate-interest-and-cdns">Legitimate interest and CDNs
&lt;/h2>&lt;p>In that &lt;a class="link" href="https://rewis.io/urteile/urteil/lhm-20-01-2022-3-o-1749320/" target="_blank" rel="noopener"
>LG München ruling on Google fonts&lt;/a>, it was also mentioned that the website had no legitimate interest in using X fonts (presumably Google fonts), since the website admin could easily host the fonts themselves.&lt;/p>
&lt;blockquote>
&lt;ol start="3">
&lt;li>Es liegt auch kein Rechtfertigungsgrund für den Eingriff in das allgemeine Persönlichkeitsrecht vor. Ein berechtigtes Interesse der Beklagten i.S.d. Art. 6 Abs. 1 f) DS-GVO, wie von ihr behauptet, liegt nicht vor, denn X. Fonts kann durch die Beklagte auch genutzt werden, ohne dass beim Aufruf der Webseite eine Verbindung zu einem X.-Server hergestellt wird und eine Übertragung der IP-Adresse der Webseitennutzer an X. stattfindet.&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;p>What about other use cases like video hosting, which is much more bandwidth intensive and benefits a lot from CDNs? the answer won&amp;rsquo;t really matter as all your favourite streaming providers will have their own DPAs with their CDN providers, nobody&amp;rsquo;s giving you a free video CDN!&lt;/p>
&lt;p>Looks like I&amp;rsquo;ll just stick to hosting my blog images on my free VPS (shout out to Oracle Free Tier), and if I really want to improve the speed of my blog, I should just move to a CDN host like Cloudflare pages.&lt;/p>
&lt;h2 id="edit-jsdelivr-doesnt-seem-to-agree">EDIT: JSdelivr doesn&amp;rsquo;t seem to agree
&lt;/h2>&lt;p>JSdelivr hired a Law firm to look into this, they don&amp;rsquo;t seem to agree, but that doesn&amp;rsquo;t invalidate other opinions.&lt;/p>
&lt;blockquote>
&lt;p>In conclusion, the ruling that has been so controversial recently does not seem to fully address the factual and technical circumstances of how jsDelivr works, and at this point as a single ruling should not lead to any real concerns about using CDN&amp;rsquo;s services. The arguments for extending to other online services a single ruling strongly emphasizing Google&amp;rsquo;s failure to adequately protect personal data are insufficient and lack substance.
&lt;a class="link" href="https://www.jsdelivr.com/blog/how-the-german-courts-ruling-on-google-fonts-affects-jsdelivr-and-why-it-is-safe-to-use/" target="_blank" rel="noopener"
>https://www.jsdelivr.com/blog/how-the-german-courts-ruling-on-google-fonts-affects-jsdelivr-and-why-it-is-safe-to-use/&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://reddit.com/r/gdpr/comments/sg8sll/no_legitimate_interest_for_using_google_fonts_on/" target="_blank" rel="noopener"
>No legitimate interest for using Google Fonts on websites, says German court&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://news.ycombinator.com/item?id=35793009" target="_blank" rel="noopener"
>Discuss on HN!&lt;/a>&lt;/p></description></item><item><title>Free Discourse S3 object storage and CDN with Cloudflare R2</title><link>https://fariszr.com/discourse-s3-object-storage-cdn-cloudflare-r2/</link><pubDate>Mon, 01 May 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/discourse-s3-object-storage-cdn-cloudflare-r2/</guid><description>&lt;img src="https://fariszr.com/discourse-s3-object-storage-cdn-cloudflare-r2/thumbnail.jpeg" alt="Featured image of post Free Discourse S3 object storage and CDN with Cloudflare R2" />&lt;p>In the official &lt;a class="link" href="https://meta.discourse.org/t/configure-an-s3-compatible-object-storage-provider-for-uploads/148916" target="_blank" rel="noopener"
>discourse S3-compatible object storage setup guide&lt;/a>, it mentions that Cloudflare R2 is not supported, because it doesn&amp;rsquo;t handle gzipped files correctly.
But Cloudflare fixed this in the &lt;a class="link" href="https://developers.cloudflare.com/r2/reference/changelog/#2023-03-16" target="_blank" rel="noopener"
>2023-03-16&lt;/a> update, and I tested it, and it works great on &lt;a class="link" href="https://discourse.aosus.org" target="_blank" rel="noopener"
>discourse.aosus.org&lt;/a>.
Even &lt;a class="link" href="https://gist.github.com/csuhta/0001d1bb74200412bc1d7f9e11ec4ea5" target="_blank" rel="noopener"
>old broken gzip handling tests&lt;/a> work now, so it looks like the problem is solved!&lt;/p>
&lt;p>&lt;del>EDIT: Discourse doesn&amp;rsquo;t use the &lt;a class="link" href="https://meta.discourse.org/t/s3-cdn-url-not-being-used-on-non-image-uploads/175332" target="_blank" rel="noopener"
>CDN URL for direct downloads&lt;/a>, rather it uses the S3 API link, which doesn&amp;rsquo;t work for Cloudflare R2, so any attachments not embedded in the post won&amp;rsquo;t work&lt;/del>
You have to enable &lt;strong>Use S3 CDN for all uploads&lt;/strong> in the settings.&lt;/p>
&lt;p>And Cloudflare R2 is free for up to 10GB! (10,000,000 reads, 1,000,000 writes), so for your average discourse forum, its probably not going to cost you anything!&lt;/p>
&lt;h2 id="setup">setup
&lt;/h2>&lt;p>Follow any pre-environment step in &lt;a class="link" href="https://meta.discourse.org/t/configure-an-s3-compatible-object-storage-provider-for-uploads/148916" target="_blank" rel="noopener"
>the official setup guide&lt;/a>&lt;/p>
&lt;h2 id="environment-setup">environment setup
&lt;/h2>&lt;pre>&lt;code> DISCOURSE_USE_S3: true
DISCOURSE_S3_REGION: "us-east-1" #alias to auto
#DISCOURSE_S3_INSTALL_CORS_RULE: true #it should be supported
DISCOURSE_S3_ENDPOINT: S3_API_URL
DISCOURSE_S3_ACCESS_KEY_ID: xxx
DISCOURSE_S3_SECRET_ACCESS_KEY: xxxx
DISCOURSE_S3_CDN_URL: your cdn url
DISCOURSE_S3_BUCKET: BUCKET_NAME
DISCOURSE_S3_BACKUP_BUCKET: other-private-bucket #optional
DISCOURSE_BACKUP_LOCATION: s3 #optional&lt;/code>&lt;/pre>&lt;p>I&amp;rsquo;ve been using it for a month now, and it&amp;rsquo;s working great, no problems and fantastic speed when viewing images.
There is one caveat though, you can&amp;rsquo;t configure a separate S3 host for backups and the CF R2 isn&amp;rsquo;t the cheapest for storage.&lt;/p></description></item><item><title>Docker compose Gitops using Github Actions</title><link>https://fariszr.com/docker-compose-gitops-github/</link><pubDate>Thu, 09 Mar 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/docker-compose-gitops-github/</guid><description>&lt;img src="https://fariszr.com/docker-compose-gitops-github/thumbnail.jpg" alt="Featured image of post Docker compose Gitops using Github Actions" />&lt;p>So you have heard about how good GitOps is, and how much better it is than having to manage your containers by hand. But almost every time GitOps is mentioned, it means using K8S. But what about the rest of us mere mortals who are still using Docker Compose?&lt;/p>
&lt;p>That&amp;rsquo;s why I started working on &lt;a class="link" href="https://github.com/FarisZR/docker-compose-gitops-action" target="_blank" rel="noopener"
>docker-compose-gitops-action&lt;/a>, a GitHub action to do GitOps with Docker compose!, it should cover most edge cases for Docker compose CI deployments, swarm, uploading sidecar files, post-upload shell commands, non-exposed servers like Homelabs and more.&lt;/p>
&lt;h2 id="server-side-setup">Server side setup
&lt;/h2>&lt;p>The action supports two access modes, SSH and Tailscale SSH.
Tailscale SSH is particularly useful for servers behind NAT or without an exposed SSH demon, allowing you to grant SSH access without manually generating keys or exposing the server.
Both Tailscale SSH and SSH require a user with access to the Docker socket &lt;strong>&lt;a class="link" href="https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user" target="_blank" rel="noopener"
>i.e. without the need for sudo&lt;/a>&lt;/strong>. This user can be different from the main user.&lt;/p>
&lt;h2 id="repo-setup">Repo Setup
&lt;/h2>&lt;p>This action can be used in two ways&lt;/p>
&lt;h3 id="per-project-directories">Per-project directories
&lt;/h3>&lt;p>In this case, each project/service will have its own directory with sidecar files and docker-compose.yml.&lt;/p>
&lt;p>This gives you more flexibility in configuring actions for deploying specific directories, so I think this is the way to go. And if there&amp;rsquo;s a small update, you don&amp;rsquo;t have to redeploy the whole repo.&lt;/p>
&lt;h3 id="single-compose-file">Single compose file
&lt;/h3>&lt;p>If the server is only running a single project, you can put the docker-compose.yml file in the root of the repo and have the action run on every push.&lt;/p>
&lt;h2 id="setting-up-the-github-action">Setting up the GitHub action
&lt;/h2>&lt;p>Before we can do anything, we need to set up the GitHub secrets for authentication.
Mainly SSH private key (PEM format) + public key, or an ephemeral Tailscale API key.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">deploy-project&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">push&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">paths&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;project/**&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;.github/**&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">branches&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">main]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">workflow_dispatch&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">jobs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">deploy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runs-on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ubuntu-latest&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">github.event_name != &amp;#39;pull_request&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">steps&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">checkout&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">actions/checkout@v3&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">fetch-depth&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Tailscale&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">tailscale/github-action@ce41a99162202a647a4b24c30c558a567b926709&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">authkey&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.TAILSCALE_AUTHKEY }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hostname&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Github-actions&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">uses&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">aosus/docker-compose-gitops-action@v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Remote Deployment&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">with&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">remote_docker_host&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">root@100.107.201.124&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>-&lt;span class="l">p project up -d&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tailscale_ssh&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">compose_file_path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">project/docker-compose.yml&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">upload_directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c">#post_upload_command: touch &amp;#34;upload_command.txt&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">docker_compose_directory&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">project&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Whenever the action file is edited or the project folder is changed on the main branch, this action is triggered.
I&amp;rsquo;m using Tailscale SSH, so I need to have the official Tailscale action as a pre-deploy step, to not have to specify any ssh keys or actually open any ports.&lt;/p>
&lt;p>Just replace &lt;code>tailscale_ssh&lt;/code> with this if you want to use pure ssh:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ssh_public_key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.SSH_KEY }}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ssh_private_key&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${{ secrets.SSH_PRIVATE_KEY }}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Be sure to set up these secrets before running the action!&lt;/p>
&lt;p>I also enabled &lt;code>upload_directory&lt;/code> and set &lt;code>docker_compose_directory&lt;/code> as &lt;code>project&lt;/code>.
The &lt;code>project&lt;/code> directory will be uploaded to the server. This is useful if a container needs extra config files, and you don&amp;rsquo;t want to rebuild a custom image with the config files on every image update.
It will also upload the docker-compose file since it is included in the directory.&lt;/p>
&lt;p>If you need to change file permissions after uploading, you can use &lt;code>post_upload_command&lt;/code> to &lt;code>chmod&lt;/code> or &lt;code>chown&lt;/code> files, although in most cases you will need to use sudo.&lt;/p>
&lt;h3 id="managing-secrets-in-compose-files">managing secrets in compose files
&lt;/h3>&lt;p>But what about secrets, you don&amp;rsquo;t want to upload your secret plain text into the repo right?, well you can just use GitHub secrets with environment variables!&lt;/p>
&lt;p>Don&amp;rsquo;t set a value for the variable in the compose file:&lt;/p>
&lt;pre>&lt;code>environment:
- VAR&lt;/code>&lt;/pre>&lt;p>set the variable in GitHub actions&lt;/p>
&lt;pre>&lt;code>- name: Start Deployment
uses: FarisZR/docker-compose-gitops-action@v1
env:
MARIADB_PASSWORD: ${{ secrets.gnulinuxsa_wordpress_mariadb_password }}
with:
remote_docker_host: ${{ secrets.server_address }}&lt;/code>&lt;/pre>&lt;p>That should do the trick, just create an action for each of your projects, and you should have your very own Compose-powered Gitops workflow!&lt;/p>
&lt;h2 id="credits">Credits
&lt;/h2>&lt;p>Photo generated by Lexica AI, and no Lexica, you can&amp;rsquo;t copyright &lt;a class="link" href="https://www.theverge.com/2022/2/21/22944335/us-copyright-office-reject-ai-generated-art-recent-entrance-to-paradise" target="_blank" rel="noopener"
>AI generated images&lt;/a>.&lt;/p></description></item><item><title>Enable Video hardware acceleration on Fedora 36+/RHEL/CentOS</title><link>https://fariszr.com/enable-video-acceleration-fedora/</link><pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate><guid>https://fariszr.com/enable-video-acceleration-fedora/</guid><description>&lt;img src="https://fariszr.com/enable-video-acceleration-fedora/thumbnail.jpg" alt="Featured image of post Enable Video hardware acceleration on Fedora 36+/RHEL/CentOS" />&lt;p>&lt;strong>(This only affects MESA, so only people using open source drivers)&lt;/strong>&lt;/p>
&lt;p>Shortly before Fedora 37 release, Fedora &lt;a class="link" href="https://src.fedoraproject.org/rpms/mesa/c/94ef544b3f2125912dfbff4c6ef373fe49806b52?branch=rawhide" target="_blank" rel="noopener"
>dropped support&lt;/a> for closed Codecs like H264, HEVC, VC1, this also includes other Red Hat distros like RHEL and CentOS stream, and probably clones like Rocky and Almalinux.&lt;/p>
&lt;p>The reason is mainly to avoid &lt;a class="link" href="https://www.phoronix.com/news/Mesa-Optional-Video-Codecs" target="_blank" rel="noopener"
>Closed source software patents&lt;/a>, Not every Distribution is affected though, as it depends on their legal jurisdiction, Ubuntu for example still includes Closed source codecs and Closed source drivers(when needed).&lt;/p>
&lt;h2 id="enable-rpmfusion">Enable RPMfusion
&lt;/h2>&lt;p>MESA Packages with closed source codecs aren&amp;rsquo;t available on the Official repos, but in RPMFusion, which is totally separate from Fedora and Red Hat, that&amp;rsquo;s why they can upload such packages.&lt;/p>
&lt;h3 id="using-the-gui">Using the GUi
&lt;/h3>&lt;p>Click on the link below, and install the Free then the non-free repository matching your distribution.&lt;/p>
&lt;p>&lt;a class="link" href="https://rpmfusion.org/Configuration" target="_blank" rel="noopener"
>https://rpmfusion.org/Configuration&lt;/a>&lt;/p>
&lt;h3 id="using-the-terminal">using the terminal
&lt;/h3>&lt;h4 id="workstation">Workstation
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-&lt;span class="k">$(&lt;/span>rpm -E %fedora&lt;span class="k">)&lt;/span>.noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-&lt;span class="k">$(&lt;/span>rpm -E %fedora&lt;span class="k">)&lt;/span>.noarch.rpm&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="silverblue">Silverblue
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo rpm-ostree install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-&lt;span class="k">$(&lt;/span>rpm -E %fedora&lt;span class="k">)&lt;/span>.noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-&lt;span class="k">$(&lt;/span>rpm -E %fedora&lt;span class="k">)&lt;/span>.noarch.rpm&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="rhel--centos">RHEL / CentOS
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf install --nogpgcheck https://dl.fedoraproject.org/pub/epel/epel-release-latest-&lt;span class="k">$(&lt;/span>rpm -E %rhel&lt;span class="k">)&lt;/span>.noarch.rpm
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-&lt;span class="k">$(&lt;/span>rpm -E %rhel&lt;span class="k">)&lt;/span>.noarch.rpm https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-&lt;span class="k">$(&lt;/span>rpm -E %rhel&lt;span class="k">)&lt;/span>.noarch.rpm&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>CentOS Steam 8 requires an additional step&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf config-manager --enable powertools&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>RHEL 8 also requires an additional step&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo subscription-manager repos --enable &lt;span class="s2">&amp;#34;codeready-builder-for-rhel-8-&lt;/span>&lt;span class="k">$(&lt;/span>uname -m&lt;span class="k">)&lt;/span>&lt;span class="s2">-rpms&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="install-drivers-with-closed-source-codecs-included">Install Drivers with closed source codecs included
&lt;/h2>&lt;h3 id="amd">AMD
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf swap mesa-va-drivers mesa-va-drivers-freeworld
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo dnf swap mesa-vdpau-drivers mesa-vdpau-drivers-freeworld&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If using i686 compat libraries (for steam or alike):&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf swap mesa-va-drivers.i686 mesa-va-drivers-freeworld.i686
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo dnf swap mesa-vdpau-drivers.i686 mesa-vdpau-drivers-freeworld.i686&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="intel">Intel
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf install intel-media-driver&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="not-so-recent-hardware">not so recent hardware
&lt;/h4>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf install libva-intel-driver&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="nvidia">Nvidia
&lt;/h3>&lt;p>Nvidia&amp;rsquo;s driver doesn&amp;rsquo;t support VAAPI, so we need to use a bridge to translate VAAPI calls into NVDEC/NVENC&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">sudo dnf install nvidia-vaapi-driver&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="credits">Credits
&lt;/h2>&lt;p>&lt;a class="link" href="https://ask.fedoraproject.org/t/proprietary-video-codecs-are-no-longer-hardware-accelerated-by-default-on-amd-gpus-on-fedora-37/28965" target="_blank" rel="noopener"
>https://ask.fedoraproject.org/t/proprietary-video-codecs-are-no-longer-hardware-accelerated-by-default-on-amd-gpus-on-fedora-37/28965&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://rpmfusion.org/Howto/Multimedia" target="_blank" rel="noopener"
>https://rpmfusion.org/Howto/Multimedia&lt;/a>&lt;/p>
&lt;p>Photo by &lt;a class="link" href="https://unsplash.com/it/@thomasfos?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" target="_blank" rel="noopener"
>Thomas Foster&lt;/a>&lt;!-- raw HTML omitted --> on &lt;a class="link" href="https://unsplash.com/photos/vWgoeEYdtIY?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" target="_blank" rel="noopener"
>Unsplash&lt;/a>&lt;/p></description></item><item><title>setup a Samba share with network discovery using docker-compose</title><link>https://fariszr.com/docker-smb-network-discovery/</link><pubDate>Sat, 31 Dec 2022 00:00:00 +0000</pubDate><guid>https://fariszr.com/docker-smb-network-discovery/</guid><description>&lt;img src="https://fariszr.com/docker-smb-network-discovery/thumbnail.jpg" alt="Featured image of post setup a Samba share with network discovery using docker-compose" />&lt;p>I am going to show you how to quickly set up a discoverable SMB share, it&amp;rsquo;s much easier than you think!
Massive thanks to &lt;a class="link" href="https://crazymax.dev/" target="_blank" rel="noopener"
>crazymax&lt;/a> for his awesome docker containers that make this possible.&lt;/p>
&lt;h2 id="set-up-the-smb-share">set up the SMB share
&lt;/h2>&lt;p>we&amp;rsquo;re going to use crazymax&amp;rsquo;s smb container, pretty easy to setup and docker native.
its recommended to use host networking for it.&lt;/p>
&lt;h3 id="docker-composeyml">docker-compose.yml
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c">## https://fariszr.com/en/docker-smb-network-discovery/&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;3.5&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">samba&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">crazymax/samba&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">samba&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">network_mode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">host&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./smb:/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./downloads:/downloads&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;TZ=$TIMEZONE&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;SAMBA_LOG_LEVEL=0&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>don&amp;rsquo;t forget to change $TIMEZONE to your local timezone, &lt;a class="link" href="https://wikipedia.org/wiki/List_of_tz_database_time_zones" target="_blank" rel="noopener"
>list of tz time zones&lt;/a>&lt;/p>
&lt;h3 id="configuration">Configuration
&lt;/h3>&lt;p>Unlike normal SMBd, the container uses YAML for configuration.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">auth&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">user&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">root&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">group&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">root&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">uid&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">gid&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">password&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bar&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">global&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;force user = root&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;force group = root&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">share&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">downloads&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">path&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/downloads&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">browsable&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">yes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readonly&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">no&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">guestok&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">yes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">veto&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">no&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">recycle&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">yes&lt;/span>&lt;span class="w"> &lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Here is a basic config for an open local share.
All my files are owned by root, that&amp;rsquo;s why i need to force root as the default user.&lt;/p>
&lt;p>Put the config in the mounted data folder, so in the case of compose file above, its &lt;code>./smb/config.yml&lt;/code>&lt;/p>
&lt;p>You can add more shares or more users and far more in the config.
More details about docker-smb&amp;rsquo;s configuration options are in the project&amp;rsquo;s &lt;a class="link" href="https://github.com/crazy-max/docker-samba" target="_blank" rel="noopener"
>GitHub repo&lt;/a>&lt;/p>
&lt;p>After this you should be able to use the SMB share, though not discoverable.&lt;/p>
&lt;h2 id="wsdd-smb-network-discovery-for-windows">WSDD, SMB network discovery for Windows
&lt;/h2>&lt;p>Add this to the &lt;code>docker-compose.yml&lt;/code> file&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">wsdd&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">jonasped/wsdd&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">network_mode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">host&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s1">&amp;#39;HOSTNAME=$HOSTNAME&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>just replace &lt;code>$HOSTNAME&lt;/code> with your hostname and then start the container&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now you should see the share with the &lt;code>$HOSTNAME&lt;/code> as its name in the network tab (if local device discovery is enabled) in Windows file explorer.&lt;/p>
&lt;h2 id="avahi-smb-network-discovery-for-macos-and-linux">Avahi, SMB network discovery for MacOS and Linux
&lt;/h2>&lt;p>Add this to &lt;code>docker-compose.yml&lt;/code>, make sure to change &lt;code>$HOSTNAME&lt;/code> to whatever you want the share&amp;rsquo;s name to be.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">avahi&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ydkn/avahi&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">hostname&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$HOSTNAME&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">network_mode&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">host&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./avahi-services:/etc/avahi/services:ro&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">restart&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">always&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="avahi-servicessmbservice">avahi-services/smb.service
&lt;/h3>&lt;p>create the file &lt;code>smb.service&lt;/code> with the following content:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; standalone=&amp;#39;no&amp;#39;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;!DOCTYPE service-group SYSTEM &amp;#34;avahi-service.dtd&amp;#34;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;service-group&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;name&lt;/span> &lt;span class="na">replace-wildcards=&lt;/span>&lt;span class="s">&amp;#34;yes&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>%h&lt;span class="nt">&amp;lt;/name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;service&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;type&amp;gt;&lt;/span>_smb._tcp&lt;span class="nt">&amp;lt;/type&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;port&amp;gt;&lt;/span>445&lt;span class="nt">&amp;lt;/port&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/service&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/service-group&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>save it and start the container&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker compose up -d&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You should now have Network discovery working on almost every OS!&lt;/p>
&lt;h2 id="sources">Sources
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/crazy-max/docker-samba/issues/1" target="_blank" rel="noopener"
>https://github.com/crazy-max/docker-samba/issues/1&lt;/a>&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/crazy-max/docker-samba" target="_blank" rel="noopener"
>https://github.com/crazy-max/docker-samba&lt;/a>&lt;/p></description></item><item><title>Setup Cloudflared using Docker without the ZT dashboard</title><link>https://fariszr.com/cloudflared-docker-setup/</link><pubDate>Fri, 28 Oct 2022 00:00:00 +0000</pubDate><guid>https://fariszr.com/cloudflared-docker-setup/</guid><description>&lt;img src="https://fariszr.com/cloudflared-docker-setup/thumbnail-en.jpg" alt="Featured image of post Setup Cloudflared using Docker without the ZT dashboard" />&lt;p>I wanted to set up Cloudflared, but I couldn&amp;rsquo;t find anything about setting it up in docker, especially without the Zero Trust dashboard (because it kept refusing my credit card for some reason).
So here it is!&lt;/p>
&lt;p>I am aiming to set up one tunnel per container, which I think is better and easier to manage than multiple tunnels in one Cloudflared instance.&lt;/p>
&lt;h2 id="docker-composeyaml">docker-compose.yaml
&lt;/h2>&lt;pre>&lt;code>version: '3.1'
services:
cloudflared:
image: cloudflare/cloudflared
restart: always
environment:
- TUNNEL_ORIGIN_CERT=/etc/cloudflared/cert.pem
- TUNNEL_EDGE_IP_VERSION=auto #use IPv4 and IPv6
volumes:
- ./cloudflared:/etc/cloudflared
command: tunnel run YOUR_TUNNEL_NAME&lt;/code>&lt;/pre>&lt;h2 id="linking-cloudflared-with-your-domain">Linking Cloudflared with your domain
&lt;/h2>&lt;h3 id="create-cloudflared-dir">Create &lt;code>cloudflared&lt;/code> dir
&lt;/h3>&lt;p>firstly you need to create the &lt;code>./cloudflared&lt;/code> directory before running any docker commands, because on container start up It&amp;rsquo;s going to create the directory as root, and Cloudflared runs as the distroless &lt;code>nonroot&lt;/code>(id 65532) user, so you will just end up with permission problems.&lt;/p>
&lt;pre>&lt;code>mkdir ./cloudflared&lt;/code>&lt;/pre>&lt;h4 id="rootful">Rootful
&lt;/h4>&lt;pre>&lt;code>sudo chown -R 65532:65532 ./cloudflared&lt;/code>&lt;/pre>&lt;h4 id="rootless">Rootless
&lt;/h4>&lt;p>this is assuming your subid and subgid ranges are &lt;code>100000:65536&lt;/code>&lt;/p>
&lt;pre>&lt;code>sudo chown -R 165531:165531 ./cloudflared&lt;/code>&lt;/pre>&lt;h4 id="user-name-spaces-remapping">User name spaces remapping
&lt;/h4>&lt;p>for some reason, docker in UserNS mode uses different IDs, even though I am using the same subuid/subgid.&lt;/p>
&lt;pre>&lt;code>sudo chown -R 165532:165532 ./cloudflared&lt;/code>&lt;/pre>&lt;h3 id="login">Login
&lt;/h3>&lt;pre>&lt;code>docker run -v $PWD/cloudflared:/home/nonroot/.cloudflared cloudflare/cloudflared login&lt;/code>&lt;/pre>&lt;p>Open the link in your browser and select which domain you would like to use, and then it will generate the origin certificate.&lt;/p>
&lt;h2 id="create-a-new-cloudflared-tunnel">Create a new cloudflared tunnel
&lt;/h2>&lt;pre>&lt;code>docker run -v $PWD/cloudflared:/etc/cloudflared cloudflare/cloudflared tunnel create YOUR_TUNNEL_NAME&lt;/code>&lt;/pre>&lt;p>We switched from &lt;code>/home/nonroot/.cloudflared&lt;/code> to &lt;code>/etc/cloudflared&lt;/code> because tunnel files are generated in the &lt;code>/etc&lt;/code> directory.
We overrode the default certificate location in the compose file using the &lt;code>TUNNEL_ORIGIN_CERT&lt;/code> variable.&lt;/p>
&lt;p>Now you will find in &lt;code>./cloudflared&lt;/code> a &lt;code>cert.pem&lt;/code> file and a &lt;code>.json&lt;/code> file, the name of the file is your Tunnel ID.
Copy the tunnels ID and replace &lt;code>YOUR_TUNNEL_ID&lt;/code> with it in the following steps.&lt;/p>
&lt;h2 id="configuring-the-tunnel">Configuring the tunnel
&lt;/h2>&lt;p>create a &lt;code>config.yml&lt;/code> file inside the &lt;code>./cloudflared&lt;/code> directory&lt;/p>
&lt;p>This is a basic configuration for a WordPress site inside the same docker network as Cloudflared, running on port 80.&lt;/p>
&lt;pre>&lt;code>tunnel: YOUR_TUNNEL_ID
credentials-file: /etc/cloudflared/YOUR_TUNNEL_ID.json
ingress:
- hostname: domain.tld
service: http://wordpress:80
- hostname: www.domain.tld # otherwise its going to end up in a 404
service: http://wordpress:80
- service: http_status:404 # any other domain will result in a 404&lt;/code>&lt;/pre>&lt;p>You could further customize the configuration to your liking.&lt;/p>
&lt;p>&lt;a class="link" href="https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/local/local-management/" target="_blank" rel="noopener"
>here&lt;/a> are the details about it.&lt;/p>
&lt;p>But this would be enough for most setups.&lt;/p>
&lt;h2 id="start-it-up">Start it up!
&lt;/h2>&lt;p>Make sure cloudflared is running in the same network as your other container if you are using DNS hostnames, and it should just work!&lt;/p>
&lt;pre>&lt;code>docker compose up -d&lt;/code>&lt;/pre>&lt;h2 id="dns-records">DNS Records
&lt;/h2>&lt;p>Add a &lt;code>CNAME&lt;/code> record to your domains pointing to &lt;code>YOUR_TUNNEL_ID.cfargotunnel.com&lt;/code>, and make sure to enable Cloudflares proxy (the cloud needs to be orange).&lt;/p>
&lt;h2 id="rootlessuserns-note">Rootless/UserNs note
&lt;/h2>&lt;p>if you see this error&lt;/p>
&lt;pre>&lt;code>WRN The user running cloudflared process has a GID (group ID) that is not within ping_group_range. You might need to add that user to a group within that range, or instead update the range to encompass a group the user is already in by modifying /proc/sys/net/ipv4/ping_group_range. Otherwise cloudflared will not be able to ping this network error="Group ID 65532 is not between ping group 65534 to 65534"
WRN ICMP proxy feature is disabled error="cannot create ICMPv4 proxy: Group ID 65532 is not between ping group 65534 to 65534 nor ICMPv6 proxy: socket: permission denied"&lt;/code>&lt;/pre>&lt;p>Currently, This has no effect, as Cloudflared &lt;a class="link" href="https://github.com/cloudflare/cloudflared/issues/726" target="_blank" rel="noopener"
>still doesn&amp;rsquo;t support&lt;/a> ICMP over QUIC anyway.
I tried fixing it by setting &lt;code>net.ipv4.ping_group_range = 0 2147483647&lt;/code>, but it still didn&amp;rsquo;t work, so just ignore it for now.&lt;/p>
&lt;p>If you have a solution, write it in the comments!&lt;/p></description></item><item><title>How to set "Cache-Control" Header for static files in Caddy</title><link>https://fariszr.com/set-cache-header-caddy-files/</link><pubDate>Fri, 30 Sep 2022 00:00:00 +0000</pubDate><guid>https://fariszr.com/set-cache-header-caddy-files/</guid><description>&lt;img src="https://fariszr.com/set-cache-header-caddy-files/thumbnail.jpg" alt="Featured image of post How to set "Cache-Control" Header for static files in Caddy" />&lt;p>If you are seeing the &amp;ldquo;Serve static assets with an efficient cache policy&amp;rdquo; notice in PageSpeed, you need to add the &lt;code>Cache-Control&lt;/code> header to your website.&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/set-cache-header-caddy-files/pagespeed.webp"
width="970"
height="179"
srcset="https://fariszr.com/set-cache-header-caddy-files/pagespeed_hu7928366471381843467.webp 480w, https://fariszr.com/set-cache-header-caddy-files/pagespeed_hu6402851554976940738.webp 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="541"
data-flex-basis="1300px"
>&lt;/p>
&lt;p>Here is how to do it if you are using Caddy.&lt;/p>
&lt;h2 id="cache-control">Cache-Control
&lt;/h2>&lt;p>To set a &lt;code>Cache-Control&lt;/code> header only for your static files like, .js, .jpg, and others, just add this to the relevant part of your Caddyfile(under the site block if the Caddyfile includes multiple websites)&lt;/p>
&lt;pre>&lt;code>@static {
file
path *.ico *.css *.js *.gif *.webp *.avif *.jpg *.jpeg *.png *.svg *.woff *.woff2
}
header @static Cache-Control max-age=5184000&lt;/code>&lt;/pre>&lt;p>The files are going to be cached for &lt;code>5184000&lt;/code> seconds, so 60 days, you can change it if you want.&lt;/p>
&lt;h2 id="source">Source
&lt;/h2>&lt;p>&lt;a class="link" href="https://caddy.community/t/correct-way-to-set-expires-on-caddy-2/7914/13" target="_blank" rel="noopener"
>https://caddy.community/t/correct-way-to-set-expires-on-caddy-2/7914/13&lt;/a>&lt;/p></description></item><item><title>How to compress static site to Brotli and Gzip</title><link>https://fariszr.com/compress-static-site-brotli-gzip/</link><pubDate>Wed, 14 Sep 2022 00:00:00 +0000</pubDate><guid>https://fariszr.com/compress-static-site-brotli-gzip/</guid><description>&lt;img src="https://fariszr.com/compress-static-site-brotli-gzip/thumbnail.jpg" alt="Featured image of post How to compress static site to Brotli and Gzip" />&lt;p>If you deploy your static site to a Web server like &lt;a class="link" href="https://caddyserver.com" target="_blank" rel="noopener"
>Caddy&lt;/a>, you might need to pre-compress it, especially if you want to use Brotli with Caddy as it&amp;rsquo;s only supported for pre-compressed assets.&lt;/p>
&lt;h2 id="brotli">Brotli
&lt;/h2>&lt;pre>&lt;code>find www.new -type f \( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.xml' -o -name '*.svg' \) \
-exec /bin/sh -c 'brotli -q 11 -o "$1.br" "$1"' /bin/sh {} \;&lt;/code>&lt;/pre>&lt;h2 id="gzip">Gzip
&lt;/h2>&lt;pre>&lt;code>find www.new -type f \( -name '*.html' -o -name '*.js' -o -name '*.css' -o -name '*.xml' -o -name '*.svg' \) \
-exec /bin/sh -c 'gzip -v -f -9 -c "$1" > "$1.gz"' /bin/sh {} \;&lt;/code>&lt;/pre>&lt;h2 id="source">Source
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/maruel/hugo-tidy/blob/main/docker-entrypoint.sh#L49" target="_blank" rel="noopener"
>https://github.com/maruel/hugo-tidy/blob/main/docker-entrypoint.sh#L49&lt;/a>&lt;/p></description></item><item><title>how to fix screen sharing in Chromium browsers on Wayland</title><link>https://fariszr.com/fix-screensharing-wayland-chromium/</link><pubDate>Fri, 02 Sep 2022 00:00:00 +0000</pubDate><guid>https://fariszr.com/fix-screensharing-wayland-chromium/</guid><description>&lt;img src="https://fariszr.com/fix-screensharing-wayland-chromium/image.jpg" alt="Featured image of post how to fix screen sharing in Chromium browsers on Wayland" />&lt;p>If you use a distro with Wayland, you might have noticed, that screen sharing might not work, it will probably show a black screen, instead of your display&lt;/p>
&lt;h2 id="why">Why
&lt;/h2>&lt;p>This is because Wayland doesn&amp;rsquo;t give the permission for a program to capture the screen the same way X.ORG does.&lt;/p>
&lt;p>Since your distro/Desktop environment uses Wayland, it probably uses Pipwire too, we will use it to share our screen.&lt;/p>
&lt;h2 id="make-chromium-use-pipewire">Make Chromium use Pipewire
&lt;/h2>&lt;p>open your chromium browser and open this link &lt;code>chrome://flags/#enable-webrtc-pipewire-capturer&lt;/code>&lt;/p>
&lt;p>&lt;img src="https://fariszr.com/fix-screensharing-wayland-chromium/screenshot.png"
width="729"
height="127"
srcset="https://fariszr.com/fix-screensharing-wayland-chromium/screenshot_hu8326686255490983097.png 480w, https://fariszr.com/fix-screensharing-wayland-chromium/screenshot_hu17094801134711137821.png 1024w"
loading="lazy"
class="gallery-image"
data-flex-grow="574"
data-flex-basis="1377px"
>&lt;/p>
&lt;p>And Then just restart your browser from the restart option, and you should be OK.&lt;/p>
&lt;h2 id="notes">Notes
&lt;/h2>&lt;p>It&amp;rsquo;s still not seamless though, when you share your screen, there will be two pop-ups instead of only one.
One from the browser and then one from Pipewire.&lt;/p>
&lt;p>And when you select your screen, It&amp;rsquo;s going to prompt you to select it again when its actually going to share it to the application, and not just preview it.&lt;/p>
&lt;p>As for Electron apps, this Option should be enabled by default on new version, however on old ones, you might need to find a way to enable it manually.&lt;/p>
&lt;p>But most electron apps have a web version anyway, so just use it instead of the app!&lt;/p></description></item><item><title>Archives</title><link>https://fariszr.com/archives/</link><pubDate>Sun, 24 Apr 2022 00:00:00 +0000</pubDate><guid>https://fariszr.com/archives/</guid><description/></item><item><title/><link>https://fariszr.com/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fariszr.com/</guid><description/></item><item><title>About</title><link>https://fariszr.com/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fariszr.com/about/</guid><description>&lt;p>Hi, I am Faris, passionate about Free and Open source software, and Cloud computing.
I work on &lt;a class="link" href="https://fariszr.com/projects/" >the Aosus Community&lt;/a>, the biggest Arabic community for FOSS, and I created &lt;a class="link" href="https://fariszr.com/projects/" >ARhackintosh&lt;/a>, a place where you found anything hackintosh related in Arabic.&lt;/p>
&lt;p>I spend my time experimenting with FOSS and cloud computing as I like to learn by doing.&lt;/p></description></item><item><title>My Projects</title><link>https://fariszr.com/projects/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fariszr.com/projects/</guid><description>&lt;h2 id="the-aosus-community">The Aosus Community
&lt;/h2>&lt;p>&lt;a class="link" href="https://aosus.org" target="_blank" rel="noopener"
>Aosus&lt;/a> is the biggest Arabic community for FOSS, we want to spread Free and open source software in the Arab world, and spread the culture around contributing to FOSS.&lt;/p>
&lt;p>&lt;a class="link" href="https://aosus.org" target="_blank" rel="noopener"
>&lt;img src="https://aosus.org/wp-content/uploads/2022/07/aosus-preview.jpg"
loading="lazy"
>&lt;/a>&lt;/p>
&lt;p>I Joined Aosus in 2021 to work on a new re-launch for the project, first by merging it with another Linux telegram chat group that had more than 3000 members.&lt;/p>
&lt;p>Then I worked on redesigned the community page, and launching the new official website and blog, of course all of this came with a complete overhaul of Aosus&amp;rsquo;s Technical infrastructure, now fully dockerized.&lt;/p>
&lt;p>I also launched &lt;a class="link" href="https://aosus.org/services" target="_blank" rel="noopener"
>Aosus services&lt;/a>,
where aosus hosts Privacy focused interfaces/proxies for popular websites, like YouTube, Twitter, Reddit etc. without tracking or Ads.&lt;/p>
&lt;p>I also managed &lt;a class="link" href="https://aosus.org/writing-contest" target="_blank" rel="noopener"
>The Aosus Writing Contest&lt;/a> &lt;a class="link" href="https://opencollective.com/aosus/projects/aosus-writing-contest" target="_blank" rel="noopener"
>summary in English&lt;/a>, the first Contest of its kind in the Arabic world to support writers that write about FOSS in Arabic.
You can find ways to support Aosus &lt;a class="link" href="https://aosus.org/en/support-us" target="_blank" rel="noopener"
>here&lt;/a>, you can also contribute to Aosus&amp;rsquo;s &lt;a class="link" href="https://github.com/aosus" target="_blank" rel="noopener"
>projects&lt;/a>&lt;/p>
&lt;h3 id="my-achievements-in-aosus">my Achievements in Aosus
&lt;/h3>&lt;p>(unfortunately most links are only in Arabic, it&amp;rsquo;s an Arabic focused project after all)&lt;/p>
&lt;ul>
&lt;li>&lt;a class="link" href="https://aosus.org/en" target="_blank" rel="noopener"
>Create a page about the Community ant its projects&lt;/a> (partially translated)&lt;/li>
&lt;li>&lt;a class="link" href="https://aosus.org/931" target="_blank" rel="noopener"
>Creating a Matrix server for aosus&lt;/a>&lt;/li>
&lt;li>Redesign the Discourse forum, you can see the old designs on &lt;a class="link" href="https://web.archive.org/web/*/aosus.org" target="_blank" rel="noopener"
>the Internet Archive&lt;/a>&lt;/li>
&lt;li>launching the &lt;a class="link" href="https://aosus.org/924" target="_blank" rel="noopener"
>Aosus writing contest&lt;/a>, the first Contest of its kind in the Arabic world.&lt;/li>
&lt;li>&lt;a class="link" href="https://aosus.org/1359" target="_blank" rel="noopener"
>Aosus joining Open Collective&lt;/a>, Aosus was one of the first Arabic to join the platform, to have financial transparency and independence.&lt;/li>
&lt;li>Launch &lt;a class="link" href="https://aosus.org/services" target="_blank" rel="noopener"
>Aosus services&lt;/a>, to protect user&amp;rsquo;s digital privacy&lt;/li>
&lt;li>create a plan for the upcoming project, &lt;a class="link" href="https://github.com/aosus/torjoman" target="_blank" rel="noopener"
>Torjoman&lt;/a>, a new way to crowdsource translations directly from the users favorite apps. (English description available)&lt;/li>
&lt;li>Add Aosus &lt;a class="link" href="https://twitter.com/Aosusorg/status/1556269856546250753" target="_blank" rel="noopener"
>to new social media platforms&lt;/a>&lt;/li>
&lt;li>Join &lt;a class="link" href="https://aosus.org/1847" target="_blank" rel="noopener"
>Discord&lt;/a> using matrix bridges&lt;/li>
&lt;li>&lt;a class="link" href="https://aosus.org/en/1901" target="_blank" rel="noopener"
>Getting non-profit 501(c)(3) status in the U.S.&lt;/a> with Aosus&amp;rsquo;s new fiscal sponsor, HCB from Hack club.&lt;/li>
&lt;/ul>
&lt;h2 id="arhackintosh">ARhackintosh
&lt;/h2>&lt;p>&lt;a class="link" href="https://%d9%87%d8%a7%d9%83%d9%86%d8%aa%d9%88%d8%b4.com" target="_blank" rel="noopener"
>ARhackintosh&lt;/a>, is the first complete Arabic community for Hackintosh, with an Open source guide, based on dortania, and a Kext archive.&lt;/p>
&lt;p>&lt;strong>If you know Arabic, ARhackintosh needs new maintainers, if you&amp;rsquo;re interested more details &lt;a class="link" href="https://%d9%87%d8%a7%d9%83%d9%86%d8%aa%d9%88%d8%b4.com/%d9%87%d8%a7%d9%83%d9%86%d8%aa%d9%88%d8%b4-%d8%a8%d8%a7%d9%84%d8%b9%d8%b1%d8%a8%d9%8a-%d9%8a%d8%a8%d8%ad%d8%ab-%d8%b9%d9%86-%d9%85%d8%b3%d8%a7%d9%87%d9%85%d9%8a%d9%86-%d8%ac%d8%af%d8%af/" target="_blank" rel="noopener"
>here&lt;/a>&lt;/strong>&lt;/p>
&lt;p>&lt;a class="link" href="https://%d9%87%d8%a7%d9%83%d9%86%d8%aa%d9%88%d8%b4.com" target="_blank" rel="noopener"
>&lt;img src="https://xn--mgbg4a8cpdl.com/wp-content/uploads/2022/09/link-preview.jpeg"
loading="lazy"
>&lt;/a>
(Yes that&amp;rsquo;s an actual word mark, people can read it)&lt;/p>
&lt;p>I started ARhackintosh in 2020, to create a dedicated place for All things Hackintosh in Arabic.&lt;/p>
&lt;p>It helped massively in discovering my passion and love for Linux and technical infrastructure, and got me into the world of git and FOSS.&lt;/p>
&lt;h3 id="artutorial">ARtutorial
&lt;/h3>&lt;p>the biggest part of the project, and the one where I spent most of the dev time is &lt;a class="link" href="https://tutorial.%d9%87%d8%a7%d9%83%d9%86%d8%aa%d9%88%d8%b4.com" target="_blank" rel="noopener"
>ARtutorial&lt;/a>.
More than 4000 words, and 1250+ commits, the biggest Arabic guide for how to install Hackintosh.
Its completely &lt;a class="link" href="https://github.com/ARhackintosh/ARtutorial" target="_blank" rel="noopener"
>open source&lt;/a>&lt;/p>
&lt;p>I tried to keep the guide simple, but also not hide any real complexities of installing a Hackintosh.
Because its actually Complex, hiding it from the user always ends up in lost data, or lost time.&lt;/p>
&lt;h3 id="kext-archive">Kext Archive
&lt;/h3>&lt;p>Kext archive aims to collect all crucial Kexts(Kernel extensions/basically macOS Drivers), with direct links to developers repo and downloads, with handwritten descriptions in Arabic, and clear categories for kexts.&lt;/p>
&lt;p>&lt;a class="link" href="https://xn--mgbg4a8cpdl.com/kextarchive/" target="_blank" rel="noopener"
>&lt;img src="https://xn--mgbg4a8cpdl.com/wp-content/uploads/2021/08/image-1536x870.jpg.webp"
loading="lazy"
>&lt;/a>&lt;/p>
&lt;p>to my knowledge, It&amp;rsquo;s still somewhat unique, as I haven&amp;rsquo;t seen anything similar in English.&lt;/p>
&lt;p>The archive aims to fix the hassle of finding drivers, and finding an up-to-date version of it.
Most Hackintosh guides in Arabic are outdated video guides, with no updates or any kind of edits from the creator.
Kext archive aims to create a simple place where users can find advanced driver/kexts for their devices.
It now has more than 50 drivers listed.&lt;/p></description></item><item><title>Search</title><link>https://fariszr.com/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fariszr.com/search/</guid><description/></item></channel></rss>