[{"content":"Anyone who\u0026rsquo;s viewing this via the actual website rather than the RSS feed will likely have noticed that things look a little different now.\nThere was nothing really wrong with the old m10c theme, but I was bored of it and it was missing some features that I wanted like tags and built in search.\nI quite like the look of the new theme, Hugo-Papermod.\nI\u0026rsquo;ve done my best to avoid any differences in the RSS XML that would result in your RSS readers showing all of my posts as new, but if I screwed it up\u0026hellip; Sorry?\nAnd I\u0026rsquo;ll do it again!\nI also added tags to all of the older posts to aid in discovery. (And also to find my own old posts when I need to, because a lot of these I use as a reference, especially my Proxmox setup guide.)\n","permalink":"https://blog.ssb-tech.net/posts/new-theme-papermod/","summary":"\u003cp\u003eAnyone who\u0026rsquo;s viewing this via the actual website rather than the \u003ca href=\"https://blog.ssb-tech.net/index.xml\"\u003eRSS feed\u003c/a\u003e will likely have noticed that things look a little different now.\u003c/p\u003e\n\u003cp\u003eThere was nothing really wrong with the old \u003ca href=\"https://github.com/vaga/hugo-theme-m10c\"\u003em10c\u003c/a\u003e theme, but I was bored of it and it was missing some features that I wanted like tags and built in search.\u003c/p\u003e\n\u003cp\u003eI quite like the look of the new theme, \u003ca href=\"https://github.com/adityatelange/hugo-PaperMod\"\u003eHugo-Papermod\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve done my best to avoid any differences in the RSS XML that would result in your RSS readers showing \u003cem\u003eall\u003c/em\u003e of my posts as new, but if I screwed it up\u0026hellip; Sorry?\u003c/p\u003e","title":"New Theme Papermod"},{"content":"I\u0026rsquo;m quite fond of the small little IP KVMs that have become more and more available lately.\nMy favorites are the JetKVM and the GliNet Comet series.\nI have 2 of the Comets that I use internally, and I mostly access them via their Tailscale ts.net names.\nI could do I what I usually do and put them behind a reverse proxy, but there\u0026rsquo;s one of these that I keep in my work bag and take with me onto client sites, so there\u0026rsquo;s no reverse proxy in that scenario.\nI could just ignore the certificate error, but where\u0026rsquo;s the fun in that when we can fix it?\nActivate and log into Tailscale on your device.\nEnsure that HTTPS is enabled for your TailNet. This can be done here.\nSSH into the GliNet KVM.\nUpdate Tailscale, you heathen.\ntailscale update Once Tailscale is updated, lets create the renewal script.\nvi /etc/tailscale-cert-renew.sh And the contents of the script:\n#!/bin/sh set -eu HOSTNAME=\u0026#34;your-kvm.your-tailnet.ts.net\u0026#34; CERT_DIR=\u0026#34;/etc/kvmd/user/ssl\u0026#34; TMP_DIR=\u0026#34;/tmp/tailscale-cert\u0026#34; RENEWAL_WINDOW=1209600 # 14 days # Exit if the existing certificate is valid for more than 14 days. if openssl x509 \\ -checkend \u0026#34;$RENEWAL_WINDOW\u0026#34; \\ -noout \\ -in \u0026#34;$CERT_DIR/server.crt\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1; then exit 0 fi echo \u0026#34;Certificate expires within 14 days. Renewing...\u0026#34; rm -rf \u0026#34;$TMP_DIR\u0026#34; mkdir -p \u0026#34;$TMP_DIR\u0026#34; CERT=\u0026#34;$TMP_DIR/server.crt\u0026#34; KEY=\u0026#34;$TMP_DIR/server.key\u0026#34; tailscale cert \\ --cert-file=\u0026#34;$CERT\u0026#34; \\ --key-file=\u0026#34;$KEY\u0026#34; \\ \u0026#34;$HOSTNAME\u0026#34; # Make sure we actually received a valid certificate. openssl x509 \\ -in \u0026#34;$CERT\u0026#34; \\ -noout \\ -subject \\ -issuer \\ -dates # Replace the existing certificate and key. cp \u0026#34;$CERT\u0026#34; \u0026#34;$CERT_DIR/server.crt\u0026#34; cp \u0026#34;$KEY\u0026#34; \u0026#34;$CERT_DIR/server.key\u0026#34; rm -rf \u0026#34;$TMP_DIR\u0026#34; # Restart the KVM\u0026#39;s nginx instance. /etc/init.d/S99kvmd-nginx restart echo \u0026#34;Tailscale certificate renewed and nginx restarted.\u0026#34; Make the script executable.\nchmod 700 /etc/tailscale-cert-renew.sh Run the script once to test it.\n/etc/tailscale-cert-renew.sh You should be able to reload the GLiNet web interface and see that you have a valid LetsEncrypt certificate for your Tailscale MagicDNS name.\nAsideMake sure you\u0026rsquo;re accessing the device at your-kvm.yourtailnet.ts.net and not it\u0026rsquo;s IP address, or the certificate won\u0026rsquo;t validate. Now, lets create a cron job to run the script automatically. The GliNet KVMs already run a cron daemon (at least the ones that I\u0026rsquo;ve used do) - so you should be able to directly edit the crontab.\ncrontab -e To run the script once a day at 0300:\n0 3 * * * /etc/tailscale-cert-renew.sh You should now have a GliNet KVM that automatically renews your Tailscale certificates when needed. :)\n","permalink":"https://blog.ssb-tech.net/posts/glinet-tailscale-certs/","summary":"\u003cp\u003eI\u0026rsquo;m quite fond of the small little IP KVMs that have become more and more available lately.\u003c/p\u003e\n\u003cp\u003eMy favorites are the JetKVM and the GliNet Comet series.\u003c/p\u003e\n\u003cp\u003eI have 2 of the Comets that I use internally, and I mostly access them via their Tailscale ts.net names.\u003c/p\u003e\n\u003cp\u003eI \u003cem\u003ecould\u003c/em\u003e do I what I usually do and put them behind a reverse proxy, but there\u0026rsquo;s one of these that I keep in my work bag and take with me onto client sites, so there\u0026rsquo;s no reverse proxy in that scenario.\u003c/p\u003e","title":"Set up auto-renewing HTTPS certificates on a GLiNet KVM"},{"content":"The following is a list of RSS feeds I follow in Miniflux.\nI\u0026rsquo;ll try to remember to come back and update this on occasion.\ncybersecurity Bad Sector Labs Blog Blog | Sam Curry CrankySec - Fuck InfoSec Ctrl blog CyberMattLee - Rising tides raise all ships. Krebs on Security - In-depth security news and investigation Mozilla Security Blog Nightmare Eclipse - Recent content in Posts on PNC Blog Risky Bulletin - Regular cybersecurity news updates from the Risky Business team\u0026hellip; Threatpost - The First Stop For Security News VulnCheck Blog - Blog posts we have recently written or historic blog posts we have written in the past. development - open source 1-800-RAD-DUDE.com Blog - 1-800-rad-dude.com Blog Alan Pope\u0026rsquo;s blog - Recent content on Alan Pope\u0026rsquo;s blog Alistair Shepherd AntennaPod Blog - AntennaPod Blog posts Anubis Blog - Anubis Blog BigDino Blog - Tales of hacking and stomping on things, by Lee Hutchinson Blog – Linode - Cloud Computing Services Developers Trust Blog on restic - Recent content in Blog on restic Chris Wiegman - Notes - The full list of notes on this site. Codeberg News Coding Horror - programming and human factors Danb Blog - Recent content on Danb Blog despair labs - Recent content on despair labs Docker Blog fasterthanli.me - amos likes to tinker FastMail blog - Blog posts from the Fastmail team F-Droid - Free and Open Source Android App Repository - F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install,… Feed :: TheOrangeOne - All pages Forgejo News - Forgejo is a self-hosted lightweight software forge. Easy to install and low maintenance, it just does the job. From Development to Production on Nick Janetakis - Recent content in From Development to Production on Nick Janetakis Grafana Labs Blog on Grafana Labs - Recent content in Grafana Labs blog on Grafana Labs Healthchecks.io - The Joy of Building a Cron Monitoring Service Home on Enchanted Code - Recent content in Home on Enchanted Code ilja Immich Blog - Immich Blog Info :: LinuxServer.io - History Jakob.fun - The place where Jakob\u0026rsquo;s silly and serious thoughts coalesce into words roughly biannually Jonathan Hodgson - Jonathan Hodgson\u0026rsquo;s Blog Jon Gjengset - PhD Student, Software Engineer and problem solver lervag\u0026rsquo;s blog - Recent content on lervag\u0026rsquo;s blog LeshiCodes Blog - LeshiCodes Blog Linux Kernel Monkey Log - Recent content on Linux Kernel Monkey Log LinuxServer.io Blog - Posts n stuff Load-bearing Tomato - Revealing the unholy truths about game development. lobste.rs Ludicity Máirín Duffy - Dearadh Oscailte Abú! (Open Design Forever!) matrix.org - The Matrix.org Foundation Michael W Lucas - I do everything wrong, but wrong works for me. Minimum Viable Blog - Recent content on Minimum Viable Blog Mischa van den Burg - Recent content in Home on Mischa van den Burg Morten Linderud - Recent content on Morten Linderud Open Source Security Foundation - Linux Foundation Projects OpenTofu Blog - OpenTofu Blog Platform Engineering Monthly - A monthly newsletter on Platform Engineering topics. Plausible Analytics - Plausible is a lightweight and open-source Google Analytics alternative. Your website data is 100% yours and the privacy of your visitors is respected. Posts on Jellyfin: The Free Software Media System - Jellyfin Blog Steph Ango (Obsidian Dev) Tailscale Community - The official community site for developers building with Tailscale. Find how-tos, solutions, and integrations to build your own secure network that just works. Tao of Mac - The Tao of Mac is the personal wiki of Rui Carmo, featuring a technology-oriented blog, links to articles, several compilations of resources around various key… The Atuin Blog - Updates and news about Atuin, the magical shell tool The Cloudflare Blog - Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. The DigitalOcean Blog - The latest from DigitalOcean, the developer cloud for modern apps. The Thunderbird Blog - News, previews, and guides from the Thunderbird Team Tim\u0026rsquo;s blog - Random ponderings Unix Digest - Articles - Articles (occasional rants) about open source, BSD, GNU/Linux, system administration, programming, and other stuff - the pragmatic way Unix Digest - Tutorials - Tutorials related to open source, BSD, GNU/Linux, system administration, programming, and other stuff - the pragmatic way Uptime Robot - Blog Xe Iaso\u0026rsquo;s blog - Thoughts and musings from Xe Iaso gaming and multimedia Aftermath - A website about video games, internet culture \u0026amp; more GamingOnLinux Latest Articles - The latest articles from GamingOnLinux Gematsu - Japanese Video Game News Kotaku - Gaming Reviews, News, Tips and More. Maximum Utmost - Eclectic Gaming Reviews, Articles, and Podcasts for the Boring Averse Nuclear Monster - A New Internet Website About Video Games \u0026amp; More. home automation Home Assistant Local Bytes Blog - LocalBytes Blog homelab - selfhosting Alex\u0026rsquo;s Blog - Sometimes I blog about my homelab or other various things. apalrd\u0026rsquo;s adventures blog - Recent content in Home on apalrd\u0026rsquo;s adventures Apalrd\u0026rsquo;s Bookmarks briancmoses.com (DIY NAS dude) - Brian\u0026rsquo;s blog about DIY NAS servers, homelab servers, 3D-design, 3D-printing, and anything else that captures Brian\u0026rsquo;s attention! ByteHaven - Where I ramble about bytes - Latest posts from ByteHaven - Where I ramble about bytes ChrisProTech - Aussie tech blog around Windows, Networking, and Homelabbing. Fatjon Dauti - What I learned on my own I still remember Framework Blog - RSS feed for Framework Blog franfabrizio.dev - Recent content on franfabrizio.dev FuzzyMistborn - Posts on Random Musings - Recent content in Posts on Random Musings jcx.life - Welcome to jcx.life: my own little corner of the internet Jeff Geerling\u0026rsquo;s Blog - Recent content on Jeff Geerling ktz. - Podcaster. Musician. Photographer. Tailscalar. mag37.org - Recent content on mag37.org MidwesternRodent - Recent content on MidwesternRodent Posts on Shane Dowling - Recent content in Posts on Shane Dowling Randoneering\u0026rsquo;s Blog - Recent content in Blog on selfh.st - Self-hosted news, content, updates, and more. stratself\u0026rsquo;s blog - stratself\u0026rsquo;s blog: sometimes it might makes sense SysAdmins Journal - The IT Journal by IT Admins, for IT Admins from the maintainers of Homebox and other open-source projects. Systems Lab - Just another tech blag Techno Tim - Techno Tim Home - Documentation and More The Tymscar Blog - Recent content on The Tymscar Blog linux It\u0026rsquo;s FOSS - Making You a Better Linux User nixCraft - Linux Tips, Hacks, Tutorials, And Ideas In Blog OMG! Ubuntu - Ubuntu News, Apps, Tips \u0026amp; More Veronica Explains - Linux mom, vintage tech enthusiast, nerdy musician. networking Blog on Tailscale - Recent blog posts from Tailscale ipSpace.net blog privacy THE LOCAL STACK - Recent content on THE LOCAL STACK AdGuard Blog - Thoughts, stories and ideas. FULU - FULU Foundation exists to inform, mobilize, and equip consumers to take back control over their devices and content. noyb.eu - My Privacy is None of Your Business Privacy Guides - Privacy Guides is the most popular \u0026amp; trustworthy non-profit privacy resource to find privacy tools and learn about protecting your digital life. Security, Privacy \u0026amp; Tech Inquiries - I write about security, privacy, Web, technology and tech policy matters. The Mullvad Blog - Keep up to date on developments at Mullvad. New versions of our client, security updates, job posts - find it all here. The Proton Blog - News from the front lines of privacy and security The Proton VPN Blog - Free VPN News TorrentFreak - Breaking File-sharing, Copyright and Privacy News tech - general Advanced Configurations - Advanced configurations for containers Annoying Technology Ars Technica - Serving the Technologist since 1998. News, reviews, and analysis. authentik Blog - authentik Blog Backblaze Blog | Cloud Storage \u0026amp; Cloud Backup - Cloud Storage \u0026amp; Cloud Backup BleepingComputer - BleepingComputer - All Stories Chips and Cheese - The Devil is in the Details! Deep dives into computer hardware and software and the wider industry\u0026hellip; Chris\u0026rsquo;s Wiki :: blog - Recently changed pages in Chris\u0026rsquo;s Wiki :: blog. Cory Doctorow\u0026rsquo;s Blog - No trackers, no ads. Black type, white background. Privacy policy: we don\u0026rsquo;t collect or retain any data at all ever period. cyb.org.uk - blogging about technology \u0026amp; open source Ed Zitron\u0026rsquo;s Where\u0026rsquo;s Your Ed At - The Words of Ed Zitron, a PR person and writer. Foss.video - Welcome to Foss.video, a PeerTube instance dedicated to hosting providers of free and open source software! Interacting with this instanceWe don\u0026rsquo;t have sign-up… How-To Geek - We explain technology. Learn more with our articles, reviews, tips, and the best answers to your most pressing tech questions. Jason Kuehl Blog - Recent content on Jason Kuehl Blog JellyWatch Blog - Latest news, updates, and tips from the JellyWatch team. JRS Systems: the blog - technomancy made simple Klara Inc - Open Source Development. Reimagined. Latest from Tom\u0026rsquo;s Hardware - All the latest content from the Tom\u0026rsquo;s Hardware team Lauren’s data Substack - Machine learning and maps applied to the ordinary infrastructure of daily life: third places, platform algorithms, neighbourhoods, and the politics of what get… Let\u0026rsquo;s Encrypt - Let\u0026rsquo;s Encrypt is a free, automated, and open Certificate Authority brought to you by the nonprofit Internet Security… Liliputing LTT Labs: Blog - Welcome to LTT Labs - your go-to destination for all things tech. Explore comprehensive test results, insightful commentary, and the latest analysis in hardwar… My Blog - Recent content on Sysadmin Tales: A Blog Neil\u0026rsquo;s blog - Recent content on Neil\u0026rsquo;s blog Nonsense, for your Information - Recent content on nonsense.fyi No One\u0026rsquo;s Happy - Learn, evolve, and get better at working together. OpenSource.net - Powered by open, united by community Phoronix - Linux Hardware Reviews, Performance Benchmarks \u0026amp; Open-Source / Free Software News Practical ZFS - Top topics - Top topics randoneering.dev - Random engineering in infrastructure, data, open source, and the great outdoors Rants of a deranged squirrel. - Rants of a Deranged Squirrel Secluded.Site - Recent content in Posts on Secluded.Site ServerHost Hosting Solutions Blog ServeTheHome - Server and Workstation Reviews Sysadmin Central - Covering all things IT System76 Blog RSS Feed - System76 Blog RSS Feed The Spacebar - An inside look at the technology around us. Windows Command Line - Windows Terminal, Console and Command Line, Windows Subsystem for Linux, WSL, Windows Package Manager world news 404 Media - 404 Media is an independent media company founded by technology journalists Jason Koebler, Emanuel Maiberg, Samantha Cole, and Joseph Cox. Techdirt The Register - Biting the hand that feeds IT — Enterprise Technology News and Analysis ","permalink":"https://blog.ssb-tech.net/feeds/","summary":"\u003cp\u003eThe following is a list of RSS feeds I follow in \u003ca href=\"https://miniflux.app\"\u003eMiniflux\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ll try to remember to come back and update this on occasion.\u003c/p\u003e\n\u003ch2 id=\"cybersecurity\"\u003ecybersecurity\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://blog.badsectorlabs.com/\"\u003eBad Sector Labs Blog\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://samcurry.net/\"\u003eBlog | Sam Curry\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://crankysec.com/\"\u003eCrankySec\u003c/a\u003e - Fuck InfoSec\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.ctrl.blog/\"\u003eCtrl blog\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://cybermattlee.com/\"\u003eCyberMattLee\u003c/a\u003e - Rising tides raise all ships.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://krebsonsecurity.com/\"\u003eKrebs on Security\u003c/a\u003e - In-depth security news and investigation\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.mozilla.org/security\"\u003eMozilla Security Blog\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.projectnightcrawler.dev/posts/\"\u003eNightmare Eclipse\u003c/a\u003e - Recent content in Posts on PNC Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://risky.biz/\"\u003eRisky Bulletin\u003c/a\u003e - Regular cybersecurity news updates from the Risky Business team\u0026hellip;\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://threatpost.com/\"\u003eThreatpost\u003c/a\u003e - The First Stop For Security News\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.vulncheck.com/blog\"\u003eVulnCheck Blog\u003c/a\u003e - Blog posts we have recently written or historic blog posts we have written in the past.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"development---open-source\"\u003edevelopment - open source\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://blog.1-800-rad-dude.com\"\u003e1-800-RAD-DUDE.com Blog\u003c/a\u003e - 1-800-rad-dude.com Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://popey.com/blog/\"\u003eAlan Pope\u0026rsquo;s blog\u003c/a\u003e - Recent content on Alan Pope\u0026rsquo;s blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.alistairshepherd.uk/\"\u003eAlistair Shepherd\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://antennapod.org/blog\"\u003eAntennaPod Blog\u003c/a\u003e - AntennaPod Blog posts\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://anubis.techaro.lol/blog\"\u003eAnubis Blog\u003c/a\u003e - Anubis Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.bigdinosaur.org/\"\u003eBigDino Blog\u003c/a\u003e - Tales of hacking and stomping on things, by Lee Hutchinson\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.linode.com\"\u003eBlog – Linode\u003c/a\u003e - Cloud Computing Services Developers Trust\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://restic.net/blog/\"\u003eBlog on restic\u003c/a\u003e - Recent content in Blog on restic\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://chriswiegman.com/\"\u003eChris Wiegman - Notes\u003c/a\u003e - The full list of notes on this site.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.codeberg.org/\"\u003eCodeberg News\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.codinghorror.com/\"\u003eCoding Horror\u003c/a\u003e - programming and human factors\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://danb.me/blog/\"\u003eDanb Blog\u003c/a\u003e - Recent content on Danb Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://despairlabs.com/blog/\"\u003edespair labs\u003c/a\u003e - Recent content on despair labs\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.docker.com/blog\"\u003eDocker Blog\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://fasterthanli.me/\"\u003efasterthanli.me\u003c/a\u003e - amos likes to tinker\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://fastmail.blog/\"\u003eFastMail blog\u003c/a\u003e - Blog posts from the Fastmail team\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://f-droid.org/en/\"\u003eF-Droid - Free and Open Source Android App Repository\u003c/a\u003e - F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install,…\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://theorangeone.net/\"\u003eFeed :: TheOrangeOne\u003c/a\u003e - All pages\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://forgejo.org/\"\u003eForgejo News\u003c/a\u003e - Forgejo is a self-hosted lightweight software forge. Easy to install and low maintenance, it just does the job.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://nickjanetakis.com/\"\u003eFrom Development to Production on Nick Janetakis\u003c/a\u003e - Recent content in From Development to Production on Nick Janetakis\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://grafana.com/blog/\"\u003eGrafana Labs Blog on Grafana Labs\u003c/a\u003e - Recent content in Grafana Labs blog on Grafana Labs\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.healthchecks.io/\"\u003eHealthchecks.io\u003c/a\u003e - The Joy of Building a Cron Monitoring Service\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://enchantedcode.co.uk/\"\u003eHome on Enchanted Code\u003c/a\u003e - Recent content in Home on Enchanted Code\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.ilja.space/\"\u003eilja\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://immich.app/blog\"\u003eImmich Blog\u003c/a\u003e - Immich Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://info.linuxserver.io/\"\u003eInfo :: LinuxServer.io\u003c/a\u003e - History\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jakob.fun/\"\u003eJakob.fun\u003c/a\u003e - The place where Jakob\u0026rsquo;s silly and serious thoughts coalesce into words roughly biannually\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jonathanh.co.uk\"\u003eJonathan Hodgson\u003c/a\u003e - Jonathan Hodgson\u0026rsquo;s Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://thesquareplanet.com/\"\u003eJon Gjengset\u003c/a\u003e - PhD Student, Software Engineer and problem solver\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://lervag.github.io/\"\u003elervag\u0026rsquo;s blog\u003c/a\u003e - Recent content on lervag\u0026rsquo;s blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leshicodes.github.io/blog\"\u003eLeshiCodes Blog\u003c/a\u003e - LeshiCodes Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://kroah.com/log/\"\u003eLinux Kernel Monkey Log\u003c/a\u003e - Recent content on Linux Kernel Monkey Log\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.linuxserver.io/blog\"\u003eLinuxServer.io Blog\u003c/a\u003e - Posts n stuff\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://loadbearingtomato.com/\"\u003eLoad-bearing Tomato\u003c/a\u003e - Revealing the unholy truths about game development.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://lobste.rs/\"\u003elobste.rs\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://ludic.mataroa.blog/\"\u003eLudicity\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.linuxgrrl.com/\"\u003eMáirín Duffy\u003c/a\u003e - Dearadh Oscailte Abú! (Open Design Forever!)\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://matrix.org\"\u003ematrix.org\u003c/a\u003e - The Matrix.org Foundation\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://mwl.io/\"\u003eMichael W Lucas\u003c/a\u003e - I do everything wrong, but wrong works for me.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://isaaclyman.com/blog/\"\u003eMinimum Viable Blog\u003c/a\u003e - Recent content on Minimum Viable Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://mischavandenburg.com/\"\u003eMischa van den Burg\u003c/a\u003e - Recent content in Home on Mischa van den Burg\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://linderud.dev/\"\u003eMorten Linderud\u003c/a\u003e - Recent content on Morten Linderud\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://openssf.org/\"\u003eOpen Source Security Foundation\u003c/a\u003e - Linux Foundation Projects\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://opentofu.org/blog\"\u003eOpenTofu Blog\u003c/a\u003e - OpenTofu Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://pemonthly.com/\"\u003ePlatform Engineering Monthly\u003c/a\u003e - A monthly newsletter on Platform Engineering topics.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://plausible.io/\"\u003ePlausible Analytics\u003c/a\u003e - Plausible is a lightweight and open-source Google Analytics alternative. Your website data is 100% yours and the privacy of your visitors is respected.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jellyfin.org/posts/\"\u003ePosts on Jellyfin: The Free Software Media System\u003c/a\u003e - Jellyfin Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://stephango.com/\"\u003eSteph Ango (Obsidian Dev)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://tailscale.dev/blog\"\u003eTailscale Community\u003c/a\u003e - The official community site for developers building with Tailscale. Find how-tos, solutions, and integrations to build your own secure network that just works.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://taoofmac.com\"\u003eTao of Mac\u003c/a\u003e - The Tao of Mac is the personal wiki of Rui Carmo, featuring a technology-oriented blog, links to articles, several compilations of resources around various key…\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.atuin.sh/\"\u003eThe Atuin Blog\u003c/a\u003e - Updates and news about Atuin, the magical shell tool\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.cloudflare.com/\"\u003eThe Cloudflare Blog\u003c/a\u003e - Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.digitalocean.com/\"\u003eThe DigitalOcean Blog\u003c/a\u003e - The latest from DigitalOcean, the developer cloud for modern apps.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.thunderbird.net\"\u003eThe Thunderbird Blog\u003c/a\u003e - News, previews, and guides from the Thunderbird Team\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.thechases.com\"\u003eTim\u0026rsquo;s blog\u003c/a\u003e - Random ponderings\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://unixdigest.com/\"\u003eUnix Digest - Articles\u003c/a\u003e - Articles (occasional rants) about open source, BSD, GNU/Linux, system administration, programming, and other stuff - the pragmatic way\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://unixdigest.com/\"\u003eUnix Digest - Tutorials\u003c/a\u003e - Tutorials related to open source, BSD, GNU/Linux, system administration, programming, and other stuff - the pragmatic way\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.uptimerobot.com/\"\u003eUptime Robot  - Blog\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://xeiaso.net/\"\u003eXe Iaso\u0026rsquo;s blog\u003c/a\u003e - Thoughts and musings from Xe Iaso\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"gaming-and-multimedia\"\u003egaming and multimedia\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://aftermath.site/\"\u003eAftermath\u003c/a\u003e - A website about video games, internet culture \u0026amp; more\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.gamingonlinux.com/\"\u003eGamingOnLinux Latest Articles\u003c/a\u003e - The latest articles from GamingOnLinux\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.gematsu.com/\"\u003eGematsu\u003c/a\u003e - Japanese Video Game News\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://kotaku.com/\"\u003eKotaku\u003c/a\u003e - Gaming Reviews, News, Tips and More.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://maxutmost.com/\"\u003eMaximum Utmost\u003c/a\u003e - Eclectic Gaming Reviews, Articles, and Podcasts for the Boring Averse\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://nuclearmonster.com/\"\u003eNuclear Monster\u003c/a\u003e - A New Internet Website About Video Games \u0026amp; More.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"home-automation\"\u003ehome automation\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.home-assistant.io/\"\u003eHome Assistant\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.mylocalbytes.com/\"\u003eLocal Bytes Blog\u003c/a\u003e - LocalBytes Blog\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"homelab---selfhosting\"\u003ehomelab - selfhosting\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://blog.alexsguardian.net/\"\u003eAlex\u0026rsquo;s Blog\u003c/a\u003e - Sometimes I blog about my homelab or other various things.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.apalrd.net/\"\u003eapalrd\u0026rsquo;s adventures blog\u003c/a\u003e - Recent content in Home on apalrd\u0026rsquo;s adventures\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://mark.apalrd.net/index.xml\"\u003eApalrd\u0026rsquo;s Bookmarks\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.briancmoses.com/\"\u003ebriancmoses.com (DIY NAS dude)\u003c/a\u003e - Brian\u0026rsquo;s blog about DIY NAS servers, homelab servers, 3D-design, 3D-printing, and anything else that captures Brian\u0026rsquo;s attention!\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.ppb1701.com/\"\u003eByteHaven - Where I ramble about bytes\u003c/a\u003e - Latest posts from ByteHaven - Where I ramble about bytes\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://chrispro.tech/\"\u003eChrisProTech\u003c/a\u003e - Aussie tech blog around Windows, Networking, and Homelabbing.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jondauti.wordpress.com/\"\u003eFatjon Dauti\u003c/a\u003e - What I learned on my own I still remember\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://frame.work/gb/en\"\u003eFramework Blog\u003c/a\u003e - RSS feed for Framework Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://franfabrizio.dev/\"\u003efranfabrizio.dev\u003c/a\u003e - Recent content on franfabrizio.dev\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.fuzzymistborn.com/post/\"\u003eFuzzyMistborn - Posts on Random Musings\u003c/a\u003e - Recent content in Posts on Random Musings\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jcx.life\"\u003ejcx.life\u003c/a\u003e - Welcome to jcx.life: my own little corner of the internet\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.jeffgeerling.com/\"\u003eJeff Geerling\u0026rsquo;s Blog\u003c/a\u003e - Recent content on Jeff Geerling\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.ktz.me/\"\u003ektz.\u003c/a\u003e - Podcaster. Musician. Photographer. Tailscalar.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://mag37.org/\"\u003emag37.org\u003c/a\u003e - Recent content on mag37.org\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.midwesternrodent.com/\"\u003eMidwesternRodent\u003c/a\u003e - Recent content on MidwesternRodent\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://shanedowling.com/posts/\"\u003ePosts on Shane Dowling\u003c/a\u003e - Recent content in Posts on Shane Dowling\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://randoneering.tech/blog/\"\u003eRandoneering\u0026rsquo;s Blog\u003c/a\u003e - Recent content in Blog on\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://selfh.st/\"\u003eselfh.st\u003c/a\u003e - Self-hosted news, content, updates, and more.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://muoi.me/~stratself/\"\u003estratself\u0026rsquo;s blog\u003c/a\u003e - stratself\u0026rsquo;s blog: sometimes it might makes sense\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://sysadminsjournal.com/\"\u003eSysAdmins Journal\u003c/a\u003e - The IT Journal by IT Admins, for IT Admins from the maintainers of Homebox and other open-source projects.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://lab.rapternet.us/\"\u003eSystems Lab\u003c/a\u003e - Just another tech blag\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://technotim.com/\"\u003eTechno Tim\u003c/a\u003e - Techno Tim Home - Documentation and More\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.tymscar.com/\"\u003eThe Tymscar Blog\u003c/a\u003e - Recent content on The Tymscar Blog\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"linux\"\u003elinux\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://itsfoss.com/feed\"\u003eIt\u0026rsquo;s FOSS\u003c/a\u003e - Making You a Better Linux User\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.cyberciti.biz/\"\u003enixCraft\u003c/a\u003e - Linux Tips, Hacks, Tutorials, And Ideas In Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.omgubuntu.co.uk/\"\u003eOMG! Ubuntu\u003c/a\u003e - Ubuntu News, Apps, Tips \u0026amp; More\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://veronicaexplains.net/\"\u003eVeronica Explains\u003c/a\u003e - Linux mom, vintage tech enthusiast, nerdy musician.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"networking\"\u003enetworking\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://tailscale.com/blog/\"\u003eBlog on Tailscale\u003c/a\u003e - Recent blog posts from Tailscale\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.ipspace.net/\"\u003eipSpace.net blog\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"privacy\"\u003eprivacy\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://thelocalstack.eu/\"\u003eTHE LOCAL STACK\u003c/a\u003e - Recent content on THE LOCAL STACK\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://adguard.com/en/blog/index.html\"\u003eAdGuard Blog\u003c/a\u003e - Thoughts, stories and ideas.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://fulu-foundation.ghost.io/\"\u003eFULU\u003c/a\u003e - FULU Foundation exists to inform, mobilize, and equip consumers to take back control over their devices and content.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://noyb.eu/en\"\u003enoyb.eu - My Privacy is None of Your Business\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.privacyguides.org/\"\u003ePrivacy Guides\u003c/a\u003e - Privacy Guides is the most popular \u0026amp; trustworthy non-profit privacy resource to find privacy tools and learn about protecting your digital life.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.lukaszolejnik.com/\"\u003eSecurity, Privacy \u0026amp; Tech Inquiries\u003c/a\u003e - I write about security, privacy, Web, technology and tech policy matters.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"http://www.mullvad.net/blog/\"\u003eThe Mullvad Blog\u003c/a\u003e - Keep up to date on developments at Mullvad. New versions of our client, security updates, job posts - find it all here.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://proton.me\"\u003eThe Proton Blog\u003c/a\u003e - News from the front lines of privacy and security\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://protonvpn.com/blog/\"\u003eThe Proton VPN Blog\u003c/a\u003e - Free VPN News\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://torrentfreak.com/\"\u003eTorrentFreak\u003c/a\u003e - Breaking File-sharing, Copyright and Privacy News\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"tech---general\"\u003etech - general\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://virtualize.link/\"\u003eAdvanced Configurations\u003c/a\u003e - Advanced configurations for containers\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://annoying.technology/\"\u003eAnnoying Technology\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://arstechnica.com/\"\u003eArs Technica\u003c/a\u003e - Serving the Technologist since 1998. News, reviews, and analysis.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://goauthentik.io/blog\"\u003eauthentik Blog\u003c/a\u003e - authentik Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.backblaze.com/blog\"\u003eBackblaze Blog | Cloud Storage \u0026amp; Cloud Backup\u003c/a\u003e - Cloud Storage \u0026amp; Cloud Backup\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.bleepingcomputer.com/\"\u003eBleepingComputer\u003c/a\u003e - BleepingComputer - All Stories\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://chipsandcheese.com\"\u003eChips and Cheese\u003c/a\u003e - The Devil is in the Details! Deep dives into computer hardware and software and the wider industry\u0026hellip;\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://utcc.utoronto.ca/~cks/space/blog/\"\u003eChris\u0026rsquo;s Wiki :: blog\u003c/a\u003e - Recently changed pages in Chris\u0026rsquo;s Wiki :: blog.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://pluralistic.net/\"\u003eCory Doctorow\u0026rsquo;s Blog\u003c/a\u003e - No trackers, no ads. Black type, white background. Privacy policy: we don\u0026rsquo;t collect or retain any data at all ever period.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://cyb.org.uk/\"\u003ecyb.org.uk\u003c/a\u003e - blogging about technology \u0026amp; open source\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.wheresyoured.at/\"\u003eEd Zitron\u0026rsquo;s Where\u0026rsquo;s Your Ed At\u003c/a\u003e - The Words of Ed Zitron, a PR person and writer.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://foss.video/\"\u003eFoss.video\u003c/a\u003e - Welcome to Foss.video, a PeerTube instance dedicated to hosting providers of free and open source software! Interacting with this instanceWe don\u0026rsquo;t have sign-up…\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.howtogeek.com/feed/\"\u003eHow-To Geek\u003c/a\u003e - We explain technology. Learn more with our articles, reviews, tips, and the best answers to your most pressing tech questions.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.jasonkuehl.com/\"\u003eJason Kuehl Blog\u003c/a\u003e - Recent content on Jason Kuehl Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jellywatch.app/blog\"\u003eJellyWatch Blog\u003c/a\u003e - Latest news, updates, and tips from the JellyWatch team.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jrs-s.net\"\u003eJRS Systems: the blog\u003c/a\u003e - technomancy made simple\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://klarasystems.com/\"\u003eKlara Inc\u003c/a\u003e - Open Source Development. Reimagined.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.tomshardware.com/feeds.xml\"\u003eLatest from Tom\u0026rsquo;s Hardware\u003c/a\u003e - All the latest content from the Tom\u0026rsquo;s Hardware team\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://laurenleek.substack.com\"\u003eLauren’s data Substack\u003c/a\u003e - Machine learning and maps applied to the ordinary infrastructure of daily life: third places, platform algorithms, neighbourhoods, and the politics of what get…\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://letsencrypt.org/\"\u003eLet\u0026rsquo;s Encrypt\u003c/a\u003e - Let\u0026rsquo;s Encrypt is a free, automated, and open Certificate Authority brought to you by the nonprofit \u003ca href=\"https://www.abetterinternet.org/\"\u003eInternet Security…\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://liliputing.com/\"\u003eLiliputing\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.lttlabs.com/blog/rss.xml\"\u003eLTT Labs: Blog\u003c/a\u003e - Welcome to LTT Labs - your go-to destination for all things tech. Explore comprehensive test results, insightful commentary, and the latest analysis in hardwar…\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.ssb-tech.net/\"\u003eMy Blog\u003c/a\u003e - Recent content on Sysadmin Tales: A Blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://neilzone.co.uk/\"\u003eNeil\u0026rsquo;s blog\u003c/a\u003e - Recent content on Neil\u0026rsquo;s blog\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://nonsense.fyi/\"\u003eNonsense, for your Information\u003c/a\u003e - Recent content on nonsense.fyi\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://nooneshappy.com/\"\u003eNo One\u0026rsquo;s Happy\u003c/a\u003e - Learn, evolve, and get better at working together.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://opensource.net/\"\u003eOpenSource.net\u003c/a\u003e - Powered by open, united by community\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.phoronix.com/\"\u003ePhoronix\u003c/a\u003e - Linux Hardware Reviews, Performance Benchmarks \u0026amp; Open-Source / Free Software News\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://discourse.practicalzfs.com/top\"\u003ePractical ZFS - Top topics\u003c/a\u003e - Top topics\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.randoneering.dev/\"\u003erandoneering.dev\u003c/a\u003e - Random engineering in infrastructure, data, open source, and the great outdoors\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://jericho.blog/\"\u003eRants of a deranged squirrel.\u003c/a\u003e - Rants of a Deranged Squirrel\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://secluded.site/posts/\"\u003eSecluded.Site\u003c/a\u003e - Recent content in Posts on Secluded.Site\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://serverhost.com/blog/\"\u003eServerHost Hosting Solutions Blog\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.servethehome.com/\"\u003eServeTheHome\u003c/a\u003e - Server and Workstation Reviews\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://sysadmin-central.com/\"\u003eSysadmin Central\u003c/a\u003e - Covering all things IT\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.system76.com/rss.xml\"\u003eSystem76 Blog RSS Feed\u003c/a\u003e - System76 Blog RSS Feed\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.spacebar.news/\"\u003eThe Spacebar\u003c/a\u003e - An inside look at the technology around us.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://devblogs.microsoft.com/commandline/\"\u003eWindows Command Line\u003c/a\u003e - Windows Terminal, Console and Command Line, Windows Subsystem for Linux, WSL, Windows Package Manager\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"world-news\"\u003eworld news\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.404media.co/\"\u003e404 Media\u003c/a\u003e - 404 Media is an independent media company founded by technology journalists Jason Koebler, Emanuel Maiberg, Samantha Cole, and Joseph Cox.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.techdirt.com/\"\u003eTechdirt\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.theregister.com/\"\u003eThe Register\u003c/a\u003e - Biting the hand that feeds IT — Enterprise Technology News and Analysis\u003c/li\u003e\n\u003c/ul\u003e","title":"RSS Feeds I Follow"},{"content":"Overview Effectively, what we\u0026rsquo;re trying to accomplish here today is the opposite of a Tailscale Subnet Router.\nAs in, allowing clients on our local network to talk to devices via their Tailscale subnet IP addresses.\nWhen I started looking into how to do this, I could not find a complete guide on the topic.\nI\u0026rsquo;ll do my best to do this start to finish so that anyone else can follow along with me.\nPrerequisites You already have an OPNSense gateway setup and enabled as a Subnet Router on your Tailnet. You\u0026rsquo;ll also need to have approved the subnets that you told the Subnet Router to advertise in your admin console. Recommendations Create an alias for the Tailscale subnet range so you can avoid typing it in 10x times.\nFirewall \u0026gt; Aliases\nExplanation Tailscale Subnet Routers by default use SNAT (Source NAT).\nThis means that packets sent from your Tailscale devices to the Subnet Router will go through Network Address Translation on their way to the local subnets you\u0026rsquo;ve advertised.\nSo, any devices inside your LAN by default will only ever see the IP address of your OPNSense gateway - they will not see the IP addresses of the individual Tailscale clients. This is the first thing that we will need to disable.\nOnce we have this disabled, we\u0026rsquo;ll need to configure both a static route as well as firewall rules to allow the traffic to pass.\nStep by step Log into your OPNSense router and navigate to VPN \u0026gt; Tailscale \u0026gt; Settings. You\u0026rsquo;ll need to toggle on \u0026ldquo;Advanced Mode\u0026rdquo;. Check the box for \u0026ldquo;Disable SNAT\u0026rdquo;. Now, what we need to do is create a static route - but before we can do that, we need to take care of some other items:\nAdd the Tailscale interface by going to Interfaces \u0026gt; Assignments. Click on the + button and add the Tailscale interface from the dropdown. Don\u0026rsquo;t forget to enable the interface - by default it will be added in a disabled state. Create a new gateway - System \u0026gt; Gateways \u0026gt; Configuration. The interface should be \u0026ldquo;Tailscale\u0026rdquo;. Gateway name is technically arbitrary, but for the sake of simplicity I\u0026rsquo;ve also called it \u0026ldquo;Tailscale\u0026rdquo;. Make sure you disable gateway monitoring. Now that we\u0026rsquo;ve done this, we can finally define our static route (System \u0026gt; Routes \u0026gt; Configuration). The Tailscale subnet is 100.64.0.0/10: Now that we have the routing taken care of, we need to create some firewall rules to allow the traffic to pass. You\u0026rsquo;ll need to create at minimum 2 rules on the OPNSense side (Firewall \u0026gt; Rules):\nOn your LAN interface, define a rule that allows communication from your LAN to your Tailnet. For example - Interface: LAN (Or whatever yours is called) Action: Pass Source: LAN network Destination: 100.64.0.0/10 On your Tailscale interface, define the inverse: Interface: Tailscale Action: Pass Source: 100.64.0.0/10 Destination: LAN network Rule 1 may not be needed if you have the default \u0026ldquo;Allow All\u0026rdquo; rule in your configuration on the LAN interface. I do not, so I needed to allow that traffic.\nFeel free to restrict this traffic however you would like. Personally, I only allow through a few kinds of traffic - DNS, HTTP/HTTPS, and ICMP echo-request / echo-reply.\nYou\u0026rsquo;ll also need to go into your Tailscale ACL controls and allow access there from your local subnets. Any device that you wish to reach via this connection needs to have the --accept-routes option enabled when you run tailscale up. The device you\u0026rsquo;re trying to access needs to have a route to get back to your LAN subnet, after all.\n--accept-routes is enabled by default on MacOS and Windows, but disabled by default on Linux. Once enabled, the device should be reachable from any device on your LAN.\nYou may also want to configure MSS clamping to avoid unnecessary IP fragmentation. Under Firewall \u0026gt; Settings \u0026gt; Normalization, click the + to add a new rule:\nInterface: Select your assigned Tailscale interface. Direction: Any Max MSS: 1240 (for standard Tailscale 1280 MTU) or 1380 (if your Tailnet uses 1420 MTU). Description: Clamp MSS for Tailscale traffic. I highly recommend thinking carefully about your firewall rules and Tailscale ACLs, and what you allow to connect to what.\nNow that I\u0026rsquo;ve gotten this working I\u0026rsquo;ll be spending some time narrowing my LAN to Tailnet access ACL down to specific tags. But that\u0026rsquo;s a whole other post.\nExit Node If, like me, you are also using your home OPNSense gateway as a Tailscale Exit Node, you\u0026rsquo;ll quickly realize that this process broke it.\nThe fix is simple, we need to add an Outbound NAT rule to replace the missing SNAT on the Tailscale service.\nGo to Firewall \u0026gt; NAT \u0026gt; Outbound.\nYour Outbound NAT mode must be set to \u0026ldquo;Hybrid\u0026rdquo; or \u0026ldquo;Manual\u0026rdquo;. If you don\u0026rsquo;t know what this means, set it to \u0026ldquo;Hybrid\u0026rdquo;.\nConfigure a rule that looks like this:\nInterface: WAN Source address: 100.64.0.0/10 Destination address: any Translation target: Interface address .\n","permalink":"https://blog.ssb-tech.net/posts/opnsense-lan-to-tailscale-connectivity/","summary":"\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eEffectively, what we\u0026rsquo;re trying to accomplish here today is the opposite of a Tailscale \u003ca href=\"https://tailscale.com/docs/features/subnet-routers\"\u003eSubnet Router\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eAs in, allowing clients on our local network to talk to devices via their \u003ca href=\"https://tailscale.com/docs/concepts/tailscale-ip-addresses\"\u003eTailscale subnet IP addresses\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eWhen I started looking into how to do this, I could not find a complete guide on the topic.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ll do my best to do this start to finish so that anyone else can follow along with me.\u003c/p\u003e","title":"Routing to Tailscale clients from a local network with OPNSense"},{"content":"This weekend I\u0026rsquo;ve been playing with setting up a self-hosted Fluxer instance.\nIn my traditional fashion, I made things much harder by myself by refusing to use their provided Caddy setup, and instead using Traefik.\nAnd for an additional challenge - I wanted this to be completely inaccessible from the public Internet, instead using my Tailscale mesh network.\nThe setup required some heavy modifications to the Docker Compose file provided by Fluxer\u0026rsquo;s team, and a few tweaks to livekit.yaml.\nNoteThe Docker Compose file for this service is extremely long, so instead of posting it inline here as I normally do, I have created this Gist containing both the modified Compose project and livekit.yaml. With this setup, I\u0026rsquo;m able to access Fluxer at fluxer.mytailnet.ts.net with full TLS encryption and valid certificates.\nI am using:\nA Tailscale sidecar container The Tailscale Traefik provider for TLS certificates Effectively, what you need to do is tell Traefik and the Livekit service to share the Tailscale container\u0026rsquo;s network namespace.\nThen we tell Livekit to advertise the Tailscale container\u0026rsquo;s IP address instead of the one for the Docker bridge (see livekit.yaml, it\u0026rsquo;s the node_ip setting).\nNote - you\u0026rsquo;ll need to add the TS_SOCKET: /var/run/tailscale/tailscaled.sock environment variable to the Tailscale container, and pass the Tailscale socket through to the Traefik container - it needs access to the Tailscale daemon to be able to grab your certificates.\nYou\u0026rsquo;ll also need to have enabled HTTPS on your Tailnet.\nWhat works\nText chat Voice chat What doesn\u0026rsquo;t work (yet)\nVideo chat - I think this has something to do with Livekit video codecs. I need to play a bit more with this and do some more testing. ","permalink":"https://blog.ssb-tech.net/posts/fluxer-over-tailscale/","summary":"\u003cp\u003eThis weekend I\u0026rsquo;ve been playing with setting up a self-hosted \u003ca href=\"https://fluxer.app\"\u003eFluxer\u003c/a\u003e instance.\u003c/p\u003e\n\u003cp\u003eIn my traditional fashion, I made things much harder by myself by refusing to use their provided Caddy setup, and instead using Traefik.\u003c/p\u003e\n\u003cp\u003eAnd for an additional challenge - I wanted this to be completely inaccessible from the public Internet, instead using my Tailscale mesh network.\u003c/p\u003e\n\u003cp\u003eThe setup required some heavy modifications to the Docker Compose file provided by Fluxer\u0026rsquo;s team, and a few tweaks to \u003ccode\u003elivekit.yaml\u003c/code\u003e.\u003c/p\u003e","title":"Fluxer Over Tailscale"},{"content":"For a while now I\u0026rsquo;ve been searching for an automated way to have our clients with higher security needs (mostly local government offices.)\nFinally, I discovered this post on how to do what I was looking for using Intune and ServiceUI.exe.\nServiceUI.exe is a part of the old Microsoft Deployment Toolkit, which has since been deprecated, but I was able to extract the executable and simply use it as is.\nI had to make a few small adjustments to the script to make it behave with Datto RMM but now it works quite nicely.\nWith Datto RMM specifically, you\u0026rsquo;ll need to attach ServiceUI.exe, the PowerShell script, setup.bat and your company_logo.png to the custom component. (Company logo is technically optional, but it does make things look a lot more professional.)\nThe actual component will be \u0026ldquo;Batch\u0026rdquo; and only runs the following command to invoke ServiceUI.exe.\nServiceUI.exe -process:explorer.exe setup.bat Here is the contents of setup.bat:\n%windir%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe -Executionpolicy bypass -file .\\Set-BitlockerPIN.ps1 And here is the PowerShell script itself:\n\u0026lt;# .SYNOPSIS This script sets up BitLocker with a user-defined PIN on the operating system volume. .DESCRIPTION The script creates a directory for BitLocker logs, prompts the user to set a BitLocker startup PIN through a GUI form, and configures BitLocker with the specified PIN. It ensures the PIN meets complexity requirements and logs the process. The script also handles the backup of the BitLocker recovery key to Azure AD. The PIN supports letters, numbers, and special characters (Enhanced PIN). To use alphanumeric PINs, ensure the \u0026#34;Allow enhanced PINs for startup\u0026#34; Group Policy is enabled on the device. A company logo is displayed on the PIN input form. The logo file should be named \u0026#34;Company_logo.png\u0026#34; and placed in the same directory as the script. If the logo file is not found, a warning will be displayed, but the script will continue to execute. .PARAMETER None This script does not take any parameters. .EXAMPLE Run the script without any parameters: .\\Set-BitlockerStartupPIN.ps1 .NOTES Author: Nivi Kolatte Date: 15.09.2024 Version: 1.0 This script requires administrative privileges to run. Ensure that \u0026#34;Company_logo.png\u0026#34; is available in the script\u0026#39;s directory for the logo to be displayed on the form. #\u0026gt; # Create Company\\BitLocker folder if it doesn\u0026#39;t exist $bitlockerFolder = \u0026#34;C:\\ProgramData\\Company\\BitLocker\u0026#34; if (-not (Test-Path $bitlockerFolder)) { New-Item -Path $bitlockerFolder -ItemType Directory -Force | Out-Null } # Create log file name with timestamp $timestamp = Get-Date -Format \u0026#34;yyyyMMdd_HHmmss\u0026#34; $logFile = Join-Path $bitlockerFolder \u0026#34;BitLockerSetup_$timestamp.log\u0026#34; $tagFile = Join-Path $bitlockerFolder \u0026#34;BitLockerSetupComplete.tag\u0026#34; function Log-Message { param([string]$message) Add-Content -Path $logFile -Value \u0026#34;$(Get-Date) - $message\u0026#34; } function Is-PinComplex { param([string]$pin) # Check for sequential numbers (including partial sequences) if ($pin -match \u0026#39;01234|12345|23456|34567|45678|56789|67890\u0026#39;) { return $false } # Check for reverse sequential numbers if ($pin -match \u0026#39;98765|87654|76543|65432|54321|43210\u0026#39;) { return $false } # Check for sequential letters (ascending) if ($pin -imatch \u0026#39;abcde|bcdef|cdefg|defgh|efghi|fghij|ghijk|hijkl|ijklm|jklmn|klmno|lmnop|mnopq|nopqr|opqrs|pqrst|qrstu|rstuv|stuvw|tuvwx|uvwxy|vwxyz\u0026#39;) { return $false } # Check for sequential letters (descending) if ($pin -imatch \u0026#39;zyxwv|yxwvu|xwvut|wvuts|vutsr|utsrq|tsrqp|srqpo|rqpon|qponm|ponml|onmlk|nmlkj|mlkji|lkjih|kjihg|jihgf|ihgfe|hgfed|gfedc|fedcb|edcba\u0026#39;) { return $false } # Check for repeated characters (6 or more repetitions) if ($pin -match \u0026#39;(.)\\1{5,}\u0026#39;) { return $false } # Check for common patterns (repeating sequences of 3+ characters) if ($pin -match \u0026#39;(.{3,})\\1\u0026#39;) { return $false } # Check if all characters are the same if ($pin -match \u0026#39;^(.)\\1*$\u0026#39;) { return $false } # Check for repeating pairs if ($pin -match \u0026#39;(.{2})\\1+\u0026#39;) { return $false } return $true } function Show-PinInputForm { Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $form = New-Object System.Windows.Forms.Form $form.WindowState = [System.Windows.Forms.FormWindowState]::Maximized $form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::None $form.BackColor = [System.Drawing.Color]::White $form.TopMost = $true # Logo $logoBox = New-Object System.Windows.Forms.PictureBox $logoBox.Size = New-Object System.Drawing.Size(100, 50) $logoBox.Location = New-Object System.Drawing.Point(20, 20) $logoBox.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom $logoPath = Join-Path $PSScriptRoot \u0026#34;Company_logo.png\u0026#34; if (Test-Path $logoPath) { $logoBox.Image = [System.Drawing.Image]::FromFile($logoPath) } else { Write-Warning \u0026#34;Logo file not found: $logoPath\u0026#34; } # Title $label = New-Object System.Windows.Forms.Label $label.Text = \u0026#34;Set BitLocker Startup PIN\u0026#34; $label.Font = New-Object System.Drawing.Font(\u0026#34;Segoe UI Semibold\u0026#34;, 20, [System.Drawing.FontStyle]::Regular) $label.AutoSize = $true $label.Location = New-Object System.Drawing.Point(20, 100) # Instructions $instructionLabel = New-Object System.Windows.Forms.Label $instructionLabel.Text = \u0026#34;PIN must be at least 6 characters long and not use simple patterns. Letters, numbers, and special characters are allowed.\u0026#34; $instructionLabel.Font = New-Object System.Drawing.Font(\u0026#34;Segoe UI\u0026#34;, 13) $instructionLabel.AutoSize = $true $instructionLabel.Location = New-Object System.Drawing.Point(20, 140) # PIN Input $pinInput = New-Object System.Windows.Forms.TextBox $pinInput.PasswordChar = \u0026#34;*\u0026#34; $pinInput.Font = New-Object System.Drawing.Font(\u0026#34;Segoe UI\u0026#34;, 12) $pinInput.Size = New-Object System.Drawing.Size(300, 25) $pinInput.Location = New-Object System.Drawing.Point(20, 180) # PIN Confirmation Input $pinConfirmInput = New-Object System.Windows.Forms.TextBox $pinConfirmInput.PasswordChar = \u0026#34;*\u0026#34; $pinConfirmInput.Font = New-Object System.Drawing.Font(\u0026#34;Segoe UI\u0026#34;, 12) $pinConfirmInput.Size = New-Object System.Drawing.Size(300, 25) $pinConfirmInput.Location = New-Object System.Drawing.Point(20, 220) # Set PIN Button $submitButton = New-Object System.Windows.Forms.Button $submitButton.Text = \u0026#34;Set PIN\u0026#34; $submitButton.Font = New-Object System.Drawing.Font(\u0026#34;Segoe UI Semibold\u0026#34;, 12, [System.Drawing.FontStyle]::Regular) $submitButton.Size = New-Object System.Drawing.Size(100, 30) $submitButton.Location = New-Object System.Drawing.Point(20, 260) # Error Label $errorLabel = New-Object System.Windows.Forms.Label $errorLabel.ForeColor = [System.Drawing.Color]::Red $errorLabel.Font = New-Object System.Drawing.Font(\u0026#34;Segoe UI\u0026#34;, 10) $errorLabel.AutoSize = $true $errorLabel.Location = New-Object System.Drawing.Point(20, 300) $form.Controls.AddRange(@($logoBox, $label, $instructionLabel, $pinInput, $pinConfirmInput, $submitButton, $errorLabel)) $script:pin = $null $allowedCharsPattern = \u0026#39;^[a-zA-Z0-9!@#$%^\u0026amp;*()_\\-+=\\[\\]{};:\u0026#39;\u0026#39;\u0026#34;,.\u0026lt;\u0026gt;?/\\\\|`~]+$\u0026#39; $submitButton.Add_Click({ $enteredPin = $pinInput.Text $confirmedPin = $pinConfirmInput.Text if ($enteredPin.Length -ge 6 -and $enteredPin -match $allowedCharsPattern -and $enteredPin -eq $confirmedPin) { if (Is-PinComplex $enteredPin) { $script:pin = $enteredPin Log-Message \u0026#34;PIN set successfully\u0026#34; $form.Close() } else { $errorLabel.Text = \u0026#34;PIN is too simple. Please avoid sequential characters, repeating patterns, or easily guessable combinations.\u0026#34; } } elseif ($enteredPin -ne $confirmedPin) { $errorLabel.Text = \u0026#34;PINs do not match. Please try again.\u0026#34; } else { $errorLabel.Text = \u0026#34;PIN must be at least 6 characters long. Letters, numbers, and special characters are allowed.\u0026#34; } }) $form.Add_Shown({$form.Activate()}) [void]$form.ShowDialog() return $script:pin } Try { $osVolume = Get-BitLockerVolume | Where-Object { $_.VolumeType -eq \u0026#39;OperatingSystem\u0026#39; } # Detects and removes existing TpmPin key protectors as there can only be one if ($osVolume.KeyProtector.KeyProtectorType -contains \u0026#39;TpmPin\u0026#39;) { $osVolume.KeyProtector | Where-Object { $_.KeyProtectorType -eq \u0026#39;TpmPin\u0026#39; } | ForEach-Object { Remove-BitLockerKeyProtector -MountPoint $osVolume.MountPoint -KeyProtectorId $_.KeyProtectorId } } # Sets a recovery password key protector if one doesn\u0026#39;t exist, needed for TpmPin key protector if ($osVolume.KeyProtector.KeyProtectorType -notcontains \u0026#39;RecoveryPassword\u0026#39;) { Enable-BitLocker -MountPoint $osVolume.MountPoint -RecoveryPasswordProtector } # Show PIN input form and get PIN from user $userPIN = Show-PinInputForm Log-Message \u0026#34;User PIN after form: $($userPIN -replace \u0026#39;.\u0026#39;, \u0026#39;*\u0026#39;)\u0026#34; # Log masked PIN for security if (-not $userPIN) { Log-Message \u0026#34;PIN input seems to be empty or invalid.\u0026#34; throw \u0026#34;PIN input cancelled or invalid. BitLocker not enabled.\u0026#34; } Log-Message \u0026#34;Attempting to convert PIN to SecureString\u0026#34; $devicePIN = ConvertTo-SecureString $userPIN -AsPlainText -Force Log-Message \u0026#34;Enabling BitLocker with the provided PIN\u0026#34; Enable-BitLocker -MountPoint $osVolume.MountPoint -Pin $devicePIN -TpmAndPinProtector -ErrorAction Stop # Gets the recovery key and escrows to Azure AD (Get-BitLockerVolume).KeyProtector | Where-Object { $_.KeyProtectorType -eq \u0026#39;RecoveryPassword\u0026#39; } | ForEach-Object { BackupToAAD-BitLockerKeyProtector -MountPoint $osVolume.MountPoint -KeyProtectorId $_.KeyProtectorId } Log-Message \u0026#34;BitLocker enabled successfully and recovery key backed up to Azure AD\u0026#34; # Create tag file New-Item -Path $tagFile -ItemType File -Force | Out-Null Log-Message \u0026#34;Created tag file: $tagFile\u0026#34; Exit 0 } Catch { $ErrorMessage = $_.Exception.Message Log-Message \u0026#34;Error: $ErrorMessage\u0026#34; Write-Warning $ErrorMessage Exit 1 } This is what the prompt will look like when all is set and done.\nOnce they set their PIN, the user will need to reboot their workstation.\n","permalink":"https://blog.ssb-tech.net/posts/powershell-setup-pin/","summary":"\u003cp\u003eFor a while now I\u0026rsquo;ve been searching for an automated way to have our clients with higher security needs (mostly local government offices.)\u003c/p\u003e\n\u003cp\u003eFinally, I discovered this \u003ca href=\"https://localerror.com/intune/deploying-bitlocker-with-a-startup-pin-through-intune-and-powershell/\"\u003epost on how to do what I was looking for using Intune and ServiceUI.exe\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eServiceUI.exe\u003c/code\u003e is a part of the old Microsoft Deployment Toolkit, which has since been deprecated, but I was able to extract the executable and simply use it as is.\u003c/p\u003e\n\u003cp\u003eI had to make a few small adjustments to the script to make it behave with Datto RMM but now it works quite nicely.\u003c/p\u003e","title":"Allow users to set Bitlocker PIN using PowerShell and RMM"},{"content":"Overview There are many ways to back up your Proxmox virtual machines.\nOne of the most popular ways to do so is via Proxmox Backup Server, which gives you many advantages:\nDeduplication of common data. The ability to cherry pick files out of your backups. Tight integration with the Proxmox ecosystem. However, how is one to achieve proper 3-2-1 backups using this mechanism?\nOne way is to run multiple datastores, some local and some using Amazon S3 compatible endpoints.\nHowever, this is a bit much for me, so I came up with a simpler method.\nThe Details I run my PBS on a Beelink N100 mini PC, in which I\u0026rsquo;ve installed a 2TB SATA SSD that I formatted as a single disk zpool.\nI then installed Jim Salter\u0026rsquo;s excellent Sanoid utility, configuring ZFS snapshots on the PBS server.\nThis is the /etc/sanoid.conf that I\u0026rsquo;ve defined. You can, of course, set whatever values you like.\n[pbs-local/datastore] use_template = pbs recursive = yes [template_pbs] frequently = 0 hourly = 36 daily = 3 weekly = 1 monthly = 0 yearly = 0 autosnap = yes autoprune = yes Then, once I had my initial snapshots, I went to my TrueNAS backup server and created a new ZFS replication task.\nI used this to pull the snapshots for the datastore to the backup server.\nThe key is to match the expected snapshot naming schema for Sanoid, like so:\nThis allows me to maintain the deduplicated nature of the Proxmox Backup Server datastore.\nYou can do the same with any offsite NAS that you may have, as long as it\u0026rsquo;s running ZFS. This is easily accomplished with something like Tailscale or Netbird.\nIf I ever have a failure of the Proxmox Backup Server, I can simply set up a new zpool on the repaired system and reverse the direction of the replication.\nI also use ZFS Delegation to avoid doing the replication as root, but I\u0026rsquo;ve covered this topic before.\nAs always - trust nothing and test those backups!\n","permalink":"https://blog.ssb-tech.net/posts/proxmox-pbs-zfs/","summary":"\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eThere are many ways to back up your \u003ca href=\"https://proxmox.com/en/products/proxmox-virtual-environment/overview\"\u003eProxmox\u003c/a\u003e virtual machines.\u003c/p\u003e\n\u003cp\u003eOne of the most popular ways to do so is via \u003ca href=\"https://proxmox.com/en/products/proxmox-backup-server/overview\"\u003eProxmox Backup Server\u003c/a\u003e, which gives you many advantages:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDeduplication of common data.\u003c/li\u003e\n\u003cli\u003eThe ability to cherry pick files out of your backups.\u003c/li\u003e\n\u003cli\u003eTight integration with the Proxmox ecosystem.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eHowever, how is one to achieve proper 3-2-1 backups using this mechanism?\u003c/p\u003e\n\u003cp\u003eOne way is to run multiple datastores, some local and some using Amazon S3 compatible endpoints.\u003c/p\u003e","title":"Deduplicated Proxmox backups with Proxmox Backup Server and ZFS"},{"content":"Short one today.\nI learned that Meilisearch has added experimental support for dumpless upgrades.\nSo far it works well, though to be fair none of my Meilisearch databases are particularly important.\nI have one for better searches in Jellyfin and another for my Karakeep search index.\nLosing either of them would be a minor inconvenience, whereas it\u0026rsquo;s been a massive inconvenience for me to have to dump and manually import every single time I patch Meilisearch.\nYou can either pass meilisearch the --experimental-dumpless-upgrade flag on the CLI, or set the MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE environment variable.\nIn Docker you\u0026rsquo;d set it like this:\nenvironment: - MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE=true ","permalink":"https://blog.ssb-tech.net/posts/meilisearch-dumpless-upgrades/","summary":"\u003cp\u003eShort one today.\u003c/p\u003e\n\u003cp\u003eI learned that \u003ca href=\"https://www.meilisearch.com\"\u003eMeilisearch\u003c/a\u003e has added \u003ca href=\"https://meilisearch.notion.site/Dumpless-upgrade-fff4b06b651f81f1acafe24d4687b3f7\"\u003eexperimental support for dumpless upgrades\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eSo far it works well, though to be fair none of my Meilisearch databases are particularly important.\u003c/p\u003e\n\u003cp\u003eI have one for better searches in \u003ca href=\"https://jellyfin.org\"\u003eJellyfin\u003c/a\u003e and another for my \u003ca href=\"https://karakeep.app\"\u003eKarakeep\u003c/a\u003e search index.\u003c/p\u003e\n\u003cp\u003eLosing either of them would be a minor inconvenience, whereas it\u0026rsquo;s been a \u003cem\u003emassive\u003c/em\u003e inconvenience for me to have to dump and manually import every single time I patch Meilisearch.\u003c/p\u003e","title":"Meilisearch Dumpless Upgrades"},{"content":"Or in other words, how to configure 802.1x authentication with an Active Directory backend.\nI wrote this for work with a few tweaks, but I figured that it would work just as well as a blog post.\nGiven that $job has a lot of people who aren\u0026rsquo;t the most Linux-savvy I tried to keep it as simple as I could, but there are a lot of pieces to making this work.\nIt was written with Ubuntu in mind but it should work in most Linux distributions.\nDebian would be a drop in replacement, something RHEL based like Rocky Linux would require more work because the path names and package names likely differ slightly.\nInstalling free LetsEncrypt TLS certificates We need to get valid TLS certificates for FreeRADIUS to use for its connection.\nNoteNote, if you\u0026rsquo;re in a fully corporate environment, you can bypass LetsEncrypt and use Active Directory self-signed certificates here. However, this did not fit my use case. Install certbot.\nsudo su apt install certbot This is how you get LE certs with an HTTP challenge - feel free to substitute it for a DNS-01 challenge. I normally always use a DNS challenge, but the client I wrote this guide for has their DNS hosted at Network Solutions, who do not have support with certbot.\nCreate an A record that points to our public IP that matches the name we\u0026rsquo;re issuing the certificate for. We need to port forward port 80 to the FreeRADIUS box so that certbot can complete the challenge. First, lets create our deploy hook script that will move our certs into the correct location and set ownership so that FreeRADIUS can access them.\nsudo nano /opt/renewal-certs.sh #!/bin/bash CERT_RENEW_LOCATION=\u0026#39;/etc/letsencrypt/live/freeradius.domain.tld\u0026#39; CERT_INSTALL_LOCATION=\u0026#39;/certs\u0026#39; PRIVATE_KEY_NAME=\u0026#39;privkey.pem\u0026#39; PUBLIC_KEY_NAME=\u0026#39;fullchain.pem\u0026#39; # Test to make sure that install location exists and if not, create it. if ! [[ -d \u0026#34;$CERT_INSTALL_LOCATION\u0026#34; ]]; then echo \u0026#34;Directory not found, creating.\u0026#34; mkdir /certs fi # Test to make sure source files exist. if ! [[ -f \u0026#34;$CERT_RENEW_LOCATION/$PUBLIC_KEY_NAME\u0026#34; ]] || ! [[ -f \u0026#34;$CERT_RENEW_LOCATION/$PRIVATE_KEY_NAME\u0026#34; ]]; then echo \u0026#34;Keys missing from $CERT_RENEW_LOCATION. Exit script.\u0026#34; exit 1 fi # Rename existing certs for safety. mv \u0026#34;$CERT_INSTALL_LOCATION/$PUBLIC_KEY_NAME\u0026#34; \u0026#34;$CERT_INSTALL_LOCATION/$PUBLIC_KEY_NAME.bak\u0026#34; mv \u0026#34;$CERT_INSTALL_LOCATION/$PRIVATE_KEY_NAME\u0026#34; \u0026#34;$CERT_INSTALL_LOCATION/$PRIVATE_KEY_NAME.bak\u0026#34; # Copy the renewed certificates into place. cp \u0026#34;$CERT_RENEW_LOCATION/$PUBLIC_KEY_NAME\u0026#34; \u0026#34;$CERT_INSTALL_LOCATION/$PUBLIC_KEY_NAME\u0026#34; cp \u0026#34;$CERT_RENEW_LOCATION/$PRIVATE_KEY_NAME\u0026#34; \u0026#34;$CERT_INSTALL_LOCATION/$PRIVATE_KEY_NAME\u0026#34; # Set the freerad user to be the owner of the certs. chown -R freerad:freerad \u0026#34;$CERT_INSTALL_LOCATION\u0026#34; # Restart freeradius so that it picks up the new cert. systemctl restart freeradius Quit out of nano with Ctrl+X and save the file.\nNext, we need to make that script executable:\nchmod +x /opt/renewal-certs.sh Run the following command to generate the certs (note that this assumes that you don\u0026rsquo;t already have a web server running on port 80 - if you do, you should use the --webroot flag instead of standalone.):\nsudo certbot certonly --standalone \\ -d radius.domain.tld \\ --non-interactive \\ --agree-tos \\ -m your-email@example.com --deploy-hook \u0026#34;/opt/renewal-certs.sh\u0026#34; If successful, you should have certificates installed at /etc/letsencrypt/live/radius.domain.tld, and a copy of them in /certs that FreeRADIUS can use.\nConfiguring FreeRADIUS, Samba and Kerberos On Ubuntu, we need to disable systemd-resolved as it causes issues with .local addresses.\nThis is not required if you’re using a different top level domain for your Active Directory like .com or .org.\nIt\u0026rsquo;s also not needed on Debian because they do not use systemd-resolved.\nsudo rm /etc/resolv.conf sudo systemctl disable systemd-resolved \u0026amp;\u0026amp; sudo systemctl stop systemd-resolved sudo nano /etc/resolv.conf Then enter a \u0026ldquo;nameserver\u0026rdquo; stanza like so:\nnameserver 10.10.10.9 Obviously the nameserver should be your AD controller.\nQuit out of nano with Ctrl+X and save the file.\nRun the following commands to install Kerberos, winbind and samba (dependencies for FreeRADIUS when you want it to auth against AD):\nsudo add-apt-repository universe sudo apt install winbind samba krb5-user freeradius -y sudo usermod -aG winbindd_priv freerad sudo mv /etc/krb5.conf /etc/krb5.conf.bak Create new krb5.conf file with the following contents:\nsudo nano /etc/krb5.conf # EXAMPLE.LOCAL should be replaced with your actual AD domain. [libdefaults] default_realm = EXAMPLE.LOCAL dns_lookup_realm = false dns_lookup_kdc = false permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 [realms] EXAMPLE.LOCAL = { kdc = kdc.example.local admin_server = kdc.example.local } [domain_realm] .example.local = EXAMPLE.LOCAL example.local = EXAMPLE.LOCAL kdc.example.local = EXAMPLE.LOCAL [logging] default = FILE:/var/log/krb5.log Do the same for the samba configuration file: /etc/samba/smb.conf\nsudo mv /etc/samba/smb.conf /etc/samba/smb.conf.bak # # /etc/samba/smb.conf # # start of global variables [global] # server information, this is the domain/workgroup # Replace this with whatever would come before the backslash when logging into a domain, e.g. DOMAIN\\username workgroup = DOMAIN # Kerberos / authentication information # Replace with the actual domain name realm = DOMAIN.LOCAL # replace with the linux server\u0026#39;s hostname. You can find this info by running \u0026#34;hostname\u0026#34; in the command line. netbios name = RADIUS1 # security used (Active Directory) security = ADS # EoF Edit the hosts file. It should have entries for the system\u0026rsquo;s FQDN, etc.\n127.0.1.1 freeradius.example.local freeradius localhost.localdomain localhost Restart the samba daemon.\nsudo systemctl restart smbd Try to get a Kerberos ticket.\nsudo kinit Administrator@EXAMPLE.LOCAL Make sure that the ticket was issued:\nsudo klist Join the domain and make sure it\u0026rsquo;s joined successfully.\nsudo net ads join -U Administrator sudo net ads testjoin If the join is \u0026ldquo;OK\u0026rdquo;, restart Winbind:\nsudo systemctl restart winbind Test Winbind by listing domain users:\nwbinfo -u Should get output like this:\nroot@freeradius:~# wbinfo -u EXAMPLE\\administrator EXAMPLE\\guest EXAMPLE\\krbtgt EXAMPLE\\testuser In the file /etc/freeradius/3.0/radiusd.conf, change this line to auth = yes:\nauth = no Change or delete the password from this line in /etc/freeradius/3.0/mods-available/eap:\nprivate_key_password = Change these lines to point to your keys:\nprivate_key_file = /your_private_key_location.key # For the \u0026#34;cert\u0026#34; file - use the LetsEncrypt \u0026#34;fullchain.pem\u0026#34; certificate_file = /your_certificate_file.cert Delete or comment-out this line:\n# ca_file = /etc/ssl/certs/ca-certificates.crt If you allow all users to connect to WiFi, then edit /etc/freeradius/3.0/mods-available/mschap and uncomment out these two lines:\nwinbind_username = \u0026#34;%{mschap:User-Name}\u0026#34; winbind_domain = \u0026#34;%{mschap:NT-Domain}\u0026#34; And finally configure the RADIUS client (will be the Unifi APs) in /etc/freeradius/3.0/sites-available/wifi:\n# # /etc/freeradius/3.0/sites-available/wifi # client UniFi-APs { shortname\t= WiFi virtual_server\t= wifi # RADIUS secret - should match what you have in the Unifi Controller. secret = RAD1USp4ssw0rd require_message_authenticator = true # allowed clients (the clients will be the APs, not the end user devices. Make sure you enter the right subnet here) ipaddr\t= 10.0.0.0/24 } server wifi { authorize { # cleans up attributes, required preprocess # we use eap authentication, required eap mschap } authenticate { # mschap authentication Auth-Type mschap { mschap } # eap, this is required eap } } # EoF Create a symlink of our RADIUS site configuration in the sites-enabled directory:\nsudo ln -s /etc/freeradius/3.0/sites-available/wifi /etc/freeradius/3.0/sites-enabled/wifi You can test your configuration by running:\nfreeradius -X Make sure that you can connect to the WiFi network, then proceed to the next step.\nEnable and start the FreeRADIUS service (this will make it run in the background and also start on boot):\nsudo systemctl enable freeradius.service \u0026amp;\u0026amp; sudo systemctl restart freeradius.service Configuring the RADIUS server in Unifi Log into your Unifi network controller and go to Settings \u0026gt; Networks.\nNoteFor some reason Ubiquiti keeps moving this setting around. This is the current location of this setting as of 2026-03-31. Add a new RADIUS server.\nIt should look like the below:\nThe \u0026ldquo;Shared Secret\u0026rdquo; should be the same as the \u0026ldquo;secret\u0026rdquo; field in your FreeRADIUS site configuration.\nConnecting wireless clients Should use \u0026ldquo;EAP-TTLS\u0026rdquo; (might be called just TTLS or Tunneled TLS, at least it is on Android).\nPhase 2 auth should be MSCHAPv2.\nYou\u0026rsquo;ll need to enter the fully qualified domain name of the RADIUS server. (radius.domain.tld, etc)\nOn iOS you may need to trust the certificate the first time you connect.\nCredits https://xenomorph.net/linux/ubuntu/misc/radius-unifi/\n","permalink":"https://blog.ssb-tech.net/posts/freeradius-unifi-certbot/","summary":"\u003cp\u003eOr in other words, how to configure 802.1x authentication with an Active Directory backend.\u003c/p\u003e\n\u003cp\u003eI wrote this for work with a few tweaks, but I figured that it would work just as well as a blog post.\u003c/p\u003e\n\u003cp\u003eGiven that \u003ccode\u003e$job\u003c/code\u003e has a lot of people who aren\u0026rsquo;t the most Linux-savvy I tried to keep it as simple as I could, but there are a lot of pieces to making this work.\u003c/p\u003e","title":"Setting up FreeRADIUS with an Active Directory backend for use with Unifi WiFi"},{"content":"I switched to GrapheneOS I recently purchased a \u0026rsquo;new-to-me\u0026rsquo; Pixel 10 Pro to replace my Pixel 8 Pro.\nThere was nothing particularly wrong with the 8, but I\u0026rsquo;d had it for a few years, and my father is in need of a new phone, so it was a good opportunity for me to upgrade.\nAt the same time, I\u0026rsquo;ve also been meaning to check out GrapheneOS, a custom ROM based on AOSP that has a focus on privacy and security.\nGiven Google\u0026rsquo;s recent track record and their initiatives to lock down Android under the pretext of security theater I figured there had never been a better time to try out GrapheneOS.\nThis post is a record of my experience and of my first impressions.\nInstallation I used the official WebUSB installer method to install GrapheneOS.\nIt was extremely painless, especially compared with my experience installing CyanogenMod in the old days.\nAll I needed to do was install one Android developer package on Fedora, open up Chromium and follow the prompts.\nI was up and running within 15 minutes, which is very impressive.\nFirst Impressions Landing on the home screen at first launch is somewhat intimidating.\nThe default GrapheneOS home screen is very utilitarian and barebones.\nHowever, I was able to install Obtanium without much trouble and get most of the apps I wanted installed.\nHowever, I do have some need for Google Play and Play Services, so I installed those too. GrapheneOS makes it painless to do so by including a mirror of the official APK files in their own App Store. I know this compromises the privacy of the device somewhat, but as always one must take into account their own risk profile and ride the line between convenience and security.\nI do enjoy that GrapheneOS gives the user far more control over what privileges every app on the device has, even for Google Play Services.\nSo far I haven\u0026rsquo;t had too many issues.\nThe only problems I\u0026rsquo;ve run into have been the Reddit client that I prefer to use, and of course Google Wallet.\nI had occasionally used Google Wallet to do NFC payments, but I knew that this was likely to not work on a custom ROM.\nThe reddit client I was using was Relay for reddit. For some reason the Google Play Store showed that the app was not compatible. I found a way to get the application to install via the Aurora Store and the app launched just fine - however I was unable to log in at all.\nThe login webview came up as normal, but no matter what I did, it claimed that my password was incorrect, despite the same one perfectly fine in both the website and Reddit\u0026rsquo;s own official app.\nI can only assume this has something to do with the Play integrity API.\nFrankly I don\u0026rsquo;t use reddit much anymore, so I simply uninstalled both apps and will use the mobile website going forward.\nFinal Thoughts I may come back and update this post later, or publish a follow up.\nSo far I am very impressed with what I\u0026rsquo;ve seen.\nAs far Android as a whole - I very much wish that there were more open-source alternatives.\nI\u0026rsquo;m aware of projects like PostmarketOS and SailfishOS, but the issue is two things - device compatibility, and application support.\nEveryone should support the Keep Android Open initiative!\n","permalink":"https://blog.ssb-tech.net/posts/switching-to-grapheneos/","summary":"\u003ch1 id=\"i-switched-to-grapheneos\"\u003eI switched to GrapheneOS\u003c/h1\u003e\n\u003cp\u003eI recently purchased a \u0026rsquo;new-to-me\u0026rsquo; Pixel 10 Pro to replace my Pixel 8 Pro.\u003c/p\u003e\n\u003cp\u003eThere was nothing particularly wrong with the 8, but I\u0026rsquo;d had it for a few years, and my father is in need of a new phone, so it was a good opportunity for me to upgrade.\u003c/p\u003e\n\u003cp\u003eAt the same time, I\u0026rsquo;ve also been meaning to check out \u003ca href=\"https://grapheneos.org/\"\u003eGrapheneOS\u003c/a\u003e, a custom ROM based on AOSP that has a focus on privacy and security.\u003c/p\u003e","title":"I switched to GrapheneOS"},{"content":"Creating your own documentation with Mkdocs and Obsidian Overview One of the most important parts of being in any highly technical profession is documentation.\nThere are many products centered around doing this, even plenty that are free and open source as I prefer.\nHowever, many of these products rely on databases and force me to use editors that are not my preference.\nI like keeping my notes in Markdown format, so that they are both portable and can be manipulated with common tools on the Linux command line like grep, awk, sed.\nThis also allows me to keep them in version control.\nHowever, I also enjoy the convenience of having a web portal that\u0026rsquo;s easily accessible from any device, and searchable even from my phone.\nFull disclosure, this guide assumes at least some knowledge about Docker and Git - if I tried to get into those too, this post would be miles long.\nThe Tools These are the tools that I use to do my documentation:\nThe Static Site Generator Material for Mkdocs - This takes in Markdown files and generates HTML from them, allowing you to serve that content as a website. You\u0026rsquo;ll also need the Mkdocs Obsidian Bridge plugin so that Mkdocs can understand the Obsidian-style wikilinks. Links like this - [[My Note]] are Obsidian style links. Editors I enjoy the ideas behind the Obsidian project, but my issues with it are:\nIt\u0026rsquo;s closed source. I like doing all my writing in Neovim. You\u0026rsquo;ll want to install the Obsidian.nvim plugin to allow it to interact more natively with Obsidian vaults. You can apply all of these ideas using the actual Obsidian editor (or any other editor with Markdown support) if you so choose.\nI do, however, use the Obsidian mobile app when I need to write notes on mobile. The GitSync Android app allows me to keep the remote repository in sync.\nContinuous Deployment Workflow I\u0026rsquo;m accomplishing this with Forgejo and an Nginx webserver.\nThe Workflow I edit all of my notes in Markdown using Neovim. I push the changes to my Forgejo repository. Forgejo has a configured Action that builds the site using a Runner and then copies it to Docker webserver. The reason I like this so much is that it reduces friction for entering notes significantly, and also allows me to link notes together logically.\nSetting It Up If you want to get this set up yourself you can follow these steps.\nInstall the Obsidian official app and create the vault. There\u0026rsquo;s probably a way to do this without installing it, but to make sure that it\u0026rsquo;s formatted correctly and has the correct directory structure, this is the easiest way.\nInstall the obsidian.nvim plugin (optional, only if you\u0026rsquo;re using Neovim).\nCreate a .gitignore file to exclude some files that are problematic or unnecessary to keep in git.\ncd $VAULT vim .gitignore The file should have the following contents:\n.obsidian/workspace*.json .obsidian/plugins/recent-files-obsidian/data.json site/ # This is where Material for Mkdocs will build your site by default. There\u0026#39;s no need to include this in Git version control. Create a new git repository in the same directory as your Obsidian vault.\ngit init git switch -c main git add . # Or add only a subset of files if you wish git commit -m \u0026#34;initial commit\u0026#34; git remote add origin ssh://git@git.example.tld/example/docs.git # Add an existing remote git push -u origin main # Push your local changes to the remote At this point if you log into the repository you created, you should see the raw markdown files from your Obsidian vault.\nNow, we\u0026rsquo;ll create a new file at the root of your repository called mkdocs.yml. This file is how you configure the mkdocs site. Here\u0026rsquo;s mine with some information redacted.\n--- site_name: Documentation site site_url: https://wiki.example.tld repo_name: ssbtech/wiki repo_url: https://git.example.tld/ssbtech/wiki edit_uri: \u0026#39;docs/\u0026#39; theme: name: material features: - navigation.indexes - navigation.instant - content.code.copy language: en favicon: assets/images/homer.png icon: repo: fontawesome/brands/git-alt logo: fontawesome/regular/folder-open palette: # Palette toggle for light mode - media: \u0026#34;(prefers-color-scheme: light)\u0026#34; scheme: default primary: blue accent: teal toggle: icon: material/brightness-7 name: Switch to dark mode # Palette toggle for dark mode - media: \u0026#34;(prefers-color-scheme: dark)\u0026#34; scheme: slate accent: teal primary: black toggle: icon: material/brightness-4 name: Switch to light mode markdown_extensions: - admonition - attr_list - md_in_html - mdx_truly_sane_lists - toc: permalink: true - def_list - pymdownx.tasklist: custom_checkbox: true plugins: - search - obsidian-bridge - tags: Install mkdocs and its plugins.\npip install mkdocs-material mkdocs-obsidian-bridge mdx_truly_sane_lists You can test the site by running mkdocs serve. It will build the site and expose it on port 8000, so you can see what it looks like.\nEvery git forge is a little different on how you can configure your Actions, but this is how I have mine configured on Forgejo:\nname: Build and Deploy Wiki on: [push] enable-email-notifications: true jobs: deploy: runs-on: wiki container: image: squidfunk/mkdocs-material:9.7.6 steps: - name: Install dependencies run: | apk add --no-cache nodejs # needed for the Forgejo checkout action apk add rsync openssh-client pip install mkdocs-obsidian-bridge mdx_truly_sane_lists - uses: actions/checkout@v4 - name: Build site run: mkdocs build - name: Deploy via rsync run: | eval $(ssh-agent -s) mkdir -p ~/.ssh chmod 700 ~/.ssh echo \u0026#34;${{ secrets.SSH_PRIVATE_KEY }}\u0026#34; | tr -d \u0026#39;\\r\u0026#39; | ssh-add - \u0026gt; /dev/null rsync -az --delete -e \u0026#34;ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\u0026#34; site/ ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/srv/mkdocs-wiki/html/ - name: Upload artifacts uses: https://github.com/christopherHX/gitea-upload-artifact@v4 with: name: site path: site/ The last step is to set up Nginx to point and serve that directory, preferably with a valid SSL certificate.\nThe end result looks something like this:\nSlick, clean, easily searchable, and trivially easy to move elsewhere.\n","permalink":"https://blog.ssb-tech.net/posts/documenting-with-mkdocs/","summary":"\u003ch1 id=\"creating-your-own-documentation-with-mkdocs-and-obsidian\"\u003eCreating your own documentation with Mkdocs and Obsidian\u003c/h1\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eOne of the most important parts of being in any highly technical profession is documentation.\u003c/p\u003e\n\u003cp\u003eThere are many products centered around doing this, even plenty that are free and open source as I prefer.\u003c/p\u003e\n\u003cp\u003eHowever, many of these products rely on databases and force me to use editors that are not my preference.\u003c/p\u003e\n\u003cp\u003eI like keeping my notes in Markdown format, so that they are both portable and can be manipulated with common tools on the Linux command line like \u003ccode\u003egrep\u003c/code\u003e, \u003ccode\u003eawk\u003c/code\u003e, \u003ccode\u003esed\u003c/code\u003e.\u003c/p\u003e","title":"Creating your own documentation with Mkdocs and Obsidian"},{"content":"I ran into a strange issue when trying to transfer a Windows 11 Pro virtual machine from KVM on my Fedora desktop to Proxmox.\nThe VM was using a VirtIO block device for it\u0026rsquo;s storage as I normally do.\nI transferred the VM qcow2 file to Proxmox and created a new VM with matching hardware, the imported the disk with:\nqm importdisk 300 win11.qcow2 fast (fast being the name of my SSD zpool.)\nHowever when I went to boot the VM, it refused to boot at all.\nOriginally, it seemed to be some sort of issue with the EFI bootloader.\nI was able to boot into a Windows install disk and recreate the EFI partition with diskpart and bcdboot.\nOnce I\u0026rsquo;d done that, the VM attempted to boot Windows. Rejoice\u0026hellip; or not.\nNope, now it\u0026rsquo;s getting an INACCESSIBLE BOOT DEVICE BSOD.\nI tried innumerable things without success, the golden ticket wound up being this superuser.com thread.\nI had to first:\nChange my boot disk to use SATA instead of VirtIO. Boot into Windows. Enable Safe Mode bcdedit /set \u0026quot;{current}\u0026quot; safeboot minimal. Shut down the VM, change the boot disk back to VirtIO. Let it boot up into Safe Mode. Revert to normal boot. bcdedit /deletevalue \u0026quot;{current}\u0026quot; safeboot I still don\u0026rsquo;t quite understand what the problem was here.\nWindows has the concept of \u0026ldquo;boot start\u0026rdquo; drivers, yes, where it only loads a subset of the available drivers at first boot, but I had been using the VirtIO drivers since this OS was first installed, so why was this suddenly a problem?\nApparently, rebooting into Safe Mode this way forces Windows to load all available boot start drivers, and because VirtIO worked here, it then remembered that for the next, normal boot.\nIf anyone has an actual explanation for why this happened I\u0026rsquo;d appreciate it.\nI wasted far too much time on this, I could have wiped and reloaded the VM many times over in the time it took me to fix this, but it bothered me far too much to let it go.\nThis kind of nonsense makes me glad I\u0026rsquo;ve switched to using almost exclusively Linux in my personal life.\n","permalink":"https://blog.ssb-tech.net/posts/windows-kvm-woes/","summary":"\u003cp\u003eI ran into a strange issue when trying to transfer a Windows 11 Pro virtual machine from KVM on my Fedora desktop to Proxmox.\u003c/p\u003e\n\u003cp\u003eThe VM was using a VirtIO block device for it\u0026rsquo;s storage as I normally do.\u003c/p\u003e\n\u003cp\u003eI transferred the VM qcow2 file to Proxmox and created a new VM with matching hardware, the imported the disk with:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eqm importdisk \u003cspan style=\"color:#ae81ff\"\u003e300\u003c/span\u003e win11.qcow2 fast\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e(\u003ccode\u003efast\u003c/code\u003e being the name of my SSD zpool.)\u003c/p\u003e","title":"Issues transferring Windows 11 VM from KVM to Proxmox"},{"content":"Using Calibre and the DeDRM plugin to remove DRM from Amazon eBooks Overview I\u0026rsquo;m not going to spend too much time going over this as there are plenty of screenshots in other guides, two of which I\u0026rsquo;ve linked at the bottom of this page.\nHowever I felt their instructions were a bit all over the place and hard to follow, so here\u0026rsquo;s my own notes.\nNoteDisclaimer: This should only be used with Amazon eBooks that you legally own.\nSteps Install Kindle for PC 2.8.0. As of 2026-02-15 this is the version available from Amazon\u0026rsquo;s own website. As soon as you get it installed, open the program\u0026rsquo;s options menu and disable updates. You\u0026rsquo;ll need to sign into your Amazon account and download whatever books you\u0026rsquo;d like to strip DRM from. Go to this repository: Satsuoni/DeDRM repo Under the releases tab, you need to download one of the pre-releases, the current stable version doesn\u0026rsquo;t work to strip the more recent KFX encryption. Download the zip file and extract it. There will be several more zip files within it, do not extract those as that is the format Calibre expects for plugins. Copy the KFXKeyExtractor28.exe file into %localappdata%\\Amazon\\Kindle\\application. You\u0026rsquo;ll need to run a command similar to this one to grab the keys from your downloaded books:\nC:\\Users\\ssbtech\\AppData\\Local\\Amazon\\Kindle\\application\\KFXKeyExtractor28.exe \u0026#34;C:\\Users\\ssbtech\\Documents\\My Kindle Content\u0026#34; kindlekey.txt kindle_account.k4i This application is what gathers the keys needed by the Calibre DeDRM plugin to strip the DRM.\nYou need to run this every time you download new books! Every book you download has a unique key!\nI created this convenience batch script that I run every time I download a new book to strip the DRM from.\n@echo cd C:\\users\\ssbtech C:\\Users\\ssbtech\\AppData\\Local\\Amazon\\Kindle\\application\\KFXKeyExtractor28.exe \u0026#34;C:\\Users\\ssbtech\\Documents\\My Kindle Content\u0026#34; kindlekey.txt kindle_account.k4i Next we need to configure the DeDRM plugin in Calibre. Install Calibre for PC and navigate through the welcome wizard. Once it drops you on the main screen, go into Preferences \u0026gt; Plugins. Click on \u0026ldquo;Add Plugin From File\u0026rdquo;. You\u0026rsquo;ll want to add the DeDRM_plugin.zip file that was in the zip you extracted earlier. Click on OK to go back to the application, don\u0026rsquo;t restart Calibre yet. Click on \u0026ldquo;Get Plugins\u0026rdquo;. Search for a plugin called KFX Input and install that, too. Now you can let Calibre restart to make sure that your new plugins are loaded. Open the plugins menu once more and find the DeDRM plugin that you installed earlier. Click on \u0026ldquo;Customize Plugin\u0026rdquo;. Click on \u0026ldquo;Kindle for PC/MAC eBooks\u0026rdquo; and then \u0026ldquo;Import existing keyfiles\u0026rdquo;. You\u0026rsquo;ll want to import the .k4i file that we created earlier. Next, click on \u0026ldquo;Set Keyfile\u0026rdquo; and set it to the kindlekey.txt file that was created along with the .k4i. At this point you\u0026rsquo;re ready to import Kindle eBooks and strip them of DRM. Click on \u0026ldquo;Add Books\u0026rdquo;. Kindle for PC stores the eBooks under your Documents folder (%USERPROFILE%\\Documents\\My Kindle Content). They\u0026rsquo;re stored in folders that look like strings of random characters, but inside of each of them is a .azw file, that\u0026rsquo;s your eBook. Import that into Calibre. If you did it right, it will show as type KFX, which you can then convert to EPUB. Troubleshooting Files showing KFX-ZIP extension instead of KFX, can\u0026rsquo;t strip DRM. If the files you\u0026rsquo;re importing show the type KFX-ZIP instead of KFX that means that they\u0026rsquo;re still encrypted and can\u0026rsquo;t be run through the DRM stripping process.\nThis is usually because you didn\u0026rsquo;t re-run KFXKeyExtractor28.exe after downloading a new book.\nRemove the book from Calibre, then re-run the batch script we created earlier so it grabs the new keys for the newly downloaded books.\nIf that doesn\u0026rsquo;t work, I\u0026rsquo;d probably start by going back to the GitHub repo and seeing if there\u0026rsquo;s a new release or a new method. Amazon is constantly changing their DRM to make it more difficult to remove.\nConverted files are huge This is because of the images embedded into the EPUB file.\nI typically recommend setting your \u0026ldquo;device\u0026rdquo; in Calibre to something like an actual Kindle e-Reader.\nCalibre will automatically scale images based on that option. If you chose the \u0026ldquo;Generic\u0026rdquo; type it won\u0026rsquo;t scale the images down at all.\nJust make sure when you change the device type to an Amazon eReader that it didn\u0026rsquo;t override your default output type.\nGo under Preferences \u0026gt; Behavior and set the \u0026ldquo;Preferred output format\u0026rdquo; to EPUB.\nLinks to the guides I used Techy-notes guide Thecodeshewrites guide ","permalink":"https://blog.ssb-tech.net/posts/removing-amazon-drm/","summary":"\u003ch1 id=\"using-calibre-and-the-dedrm-plugin-to-remove-drm-from-amazon-ebooks\"\u003eUsing Calibre and the DeDRM plugin to remove DRM from Amazon eBooks\u003c/h1\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;m not going to spend too much time going over this as there are plenty of screenshots in other guides, two of which I\u0026rsquo;ve linked at the bottom of this page.\u003c/p\u003e\n\u003cp\u003eHowever I felt their instructions were a bit all over the place and hard to follow, so here\u0026rsquo;s my own notes.\u003c/p\u003e\n\u003cdiv class=\"callout callout-note\" role=\"note\"\u003e\u003cdiv class=\"callout-title\"\u003eNote\u003c/div\u003e\u003cdiv class=\"callout-content\"\u003e\u003ch3 id=\"disclaimer\"\u003e\u003cstrong\u003eDisclaimer:\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003eThis should only be used with Amazon eBooks that you legally own.\u003c/p\u003e","title":"Using Calibre and the DeDRM plugin to strip DRM from Amazon eBooks"},{"content":"In which I have zero self control While browsing the Amazon Black Friday sales a few weeks ago, a particular deal caught my eye.\nAn ASUS Chromebook CR11 ruggedized laptop for around $130.\nNow, I have enough laptops, but I\u0026rsquo;ve always wanted to try and hack Linux onto one, and this one was attractive due to its ruggedized nature as well as a full blown Intel N100 CPU.\nI purchased the laptop and once it arrived, I began poking around the internet to see how I could get regular Linux on this thing.\nInstalling Linux, Part 1 I have no desire to use Google\u0026rsquo;s spyware filled ChromeOS, so if I couldn\u0026rsquo;t find a way to get Linux on this machine, it was going back to Amazon.\nA few searches guided me to mrchromebox.tech.\nThis specific model I had was very similar to a few other builds that show full compatibility, so my hopes were raised.\nI went through their Getting Started guide, which helped me get Developer Mode enabled, and I found my device ID.\nANRAGGAR\nThis HWID showed full compatibility with the full UEFI replacement firmware, which is exactly what I wanted.\nHowever, on most ChromeOS devices, it\u0026rsquo;s a requirement to disable hardware write protection in order to flash the replacement firmware.\nThis particular model had a Ti50 security chip, so the only method of disabling write protection was to use Closed Case Debugging (CCD). This process requires a \u0026ldquo;SuzyQ\u0026rdquo; ChromeOS debug board.\nA little bit annoying, but not the end of the world.\nI went on eBay and found the debug board listed here..\nIt took around a week to get to my house, and with that in hand, I tried again.\nInstalling Linux, Part 2 I followed the steps listed here to disable hardware write protection.\nNoteNOTE\nA SuzyQ cable is not like a typical USB-C connected device, where it is reversible. If you look closely at the board, one will be labeled side A, the other is side B. On the board that I received, side A needed to be facing up. I had plugged it into the top left USB-C port on my laptop, I\u0026rsquo;m not sure if it will work when plugged into the other one. However, I did power the board by plugging it into the other available USB-C port. The MrChromebox documentation says that it takes 2-3 minutes of pressing the physical presence sensor to enable CCD. In reality, it was more like 5. It paused several times during the process, and it rebooted without ever showing me the \u0026ldquo;PP Done!\u0026rdquo; message that the docs suggest. However, the steps there did work fine. Once I had gone through the steps and verified that hardware write protection was indeed disabled, I proceeded to the next step, which was flashing the firmware.\nI used the provided firmware utility script to flash the firmware.\nGenerally I don\u0026rsquo;t recommend running scripts blindly from the internet, but it truly does make this process a ton easier, and if you wish, you can review the script yourself. (And I recommend doing so before you run it!)\nFirst, I backed up the existing ChromeOS firmware to a USB stick. Handy, in case I ended up needing to return this laptop.\nThen, I proceeded to flash the Coreboot UEFI firmware.\nIt took a few minutes, but once done I was able to boot into the Fedora 43 Sway Spin installer.\nSome problems I noticed right away that the touchpad didn\u0026rsquo;t work in the installer, but I proceeded anyway. I could have grabbed a mouse, but being the lazy person I am, I just navigated the installation with the keyboard instead.\nOnce installed, it took me a few minutes to figure out where all the needed buttons were.\nLinux maps the Chromebook Search key to Super_L, which I needed to open the default drun menu. Can\u0026rsquo;t do much in Sway without being able to open drun or a similar launcher!\nThe touchpad still didn\u0026rsquo;t work, and neither did sound. However, everything else worked a treat.\nIt was a bit strange, since the touchpad appeared normally in libinput --list-devices, and the sound driver appeared normally in pavucontrol, however, neither of them worked at all, and there were a TON of errors in dmesg regarding the sound driver.\nI did some searching on the chultrabook forums, and I was able to find solutions for both issues.\nFixing the audio problem The chultrabook docs contained a \u0026ldquo;Post-Install\u0026rdquo; FAQ that was very helpful - I found their audio script which was all I needed to get sound working.\nFixing the touchpad problem The touchpad issue was a bit trickier to pin down.\nEventually I found this post that pointed me in the right direction.\nThe answer was to configure a libinput quirk:\nsudo vim /etc/libinput/local-overrides.quirks Enter the following content:\n[PNP Touchpad] MatchName=*PNP*Touchpad* AttrResolutionHint=31x31 AttrPressureRange=10:8 Replace PNP with your touchpad model as it shows up in libinput --list-devices.\nOnce you\u0026rsquo;ve done so, write and quit the file, then reboot your Chromebook.\nSuccess! The touchpad now works!\nFinal Thoughts The Chromebook performs very well under Fedora, but I\u0026rsquo;m also not running a full on desktop environment.\nIf KDE is your preferred environment, you may run into more issues, especially since this device only has 4GB of onboard memory, and a relatively weak Intel CPU.\nI haven\u0026rsquo;t used Gnome in quite some time, but I expect it would run into similar limitations.\nThe screen isn\u0026rsquo;t fantastic, but it\u0026rsquo;s good enough for what I\u0026rsquo;m planning on using this laptop for - effectively just tossing it in my bag when I need a super small and light laptop for travelling, or when I want to sit on the couch and chat on Discord while watching TV.\nDo I recommend buying this specifically to run Linux?\nNo, not really.\nIt works great once you get it going, especially for the price, but you can likely find better laptops on eBay.\nUsed ThinkPads are the way to go for cheap laptops.\nThis was just a fun little \u0026ldquo;I wonder if I can do this\u0026rdquo; project.\nThat said, if you already have one of these devices? I highly recommend installing regular Linux on it.\n","permalink":"https://blog.ssb-tech.net/posts/linux-on-a-chromebook/","summary":"\u003ch2 id=\"in-which-i-have-zero-self-control\"\u003eIn which I have zero self control\u003c/h2\u003e\n\u003cp\u003eWhile browsing the Amazon Black Friday sales a few weeks ago, a particular deal caught my eye.\u003c/p\u003e\n\u003cp\u003eAn ASUS Chromebook CR11 ruggedized laptop for around $130.\u003c/p\u003e\n\u003cp\u003eNow, I have enough laptops, but I\u0026rsquo;ve always wanted to try and hack Linux onto one, and this one was attractive due to its ruggedized nature as well as a full blown Intel N100 CPU.\u003c/p\u003e\n\u003cp\u003eI purchased the laptop and once it arrived, I began poking around the internet to see how I could get regular Linux on this thing.\u003c/p\u003e","title":"Linux on an Asus CR11 Chromebook"},{"content":"A few months ago I switched from using Pi-hole to AdGuardHome for serving adblocking DNS to not just the clients in my home, but also my mobile devices.\nThere were a few reasons for this, but chief among them was its proper support for DNS-over-TLS (DOT) and DNS-over-HTTPS (DOH).\nI had previously been running unencrypted DNS via Tailscale, but because of the way I have things configured, this mean that every DNS request from an external device looked like it was coming from my subnet router.\nDOT and DOH both support the concept of unique client IDs, which makes it easier to track down which device a particular request is coming from.\nHowever, one of the few problems that I\u0026rsquo;ve encountered with AdGuard is that there\u0026rsquo;s no way to separate the admin interface from the port that DOH uses.\nI brought up this conundrum in the Discord server for the since defunct Self-Hosted Podcast, and one of the users there (thanks Quietsy!) made an interesting suggestion that I must admit hadn\u0026rsquo;t crossed my mind.\nSimply put a reverse proxy in front of the application and implement access controls based on path.\nDOH runs over the /dns-query endpoint, while the admin interface is at the root /.\nI logged into the VPS that\u0026rsquo;s running my public-facing AdGuard server and installed nginx.\nsudo apt install nginx\nThen I stopped AdGuard:\nsudo systemctl stop AdGuardHome I then edited AdGuardHome.yaml (The path to this file may vary based on how you installed AdGuard).\nsudo vim AdGuardHome.yaml I changed the https_port from its default 443 to 4433, and the http listening port from 80 to 8080.\nThen start AdGuard back up with:\nsudo systemctl start AdGuardHome Then I created a site in Nginx at /etc/nginx/sites-enabled/adguardhome with the following settings:\nproxy_cache_path /var/cache/adguardhome levels=1:2 keys_zone=adguard_cache:10m max_size=3g inactive=120m use_temp_path=off; upstream adguardhome { server 127.0.0.1:4433; keepalive 64; } server { server_name adguard.domain.tld; listen 80; # listen [::]:80 default_server; return 301 https://$host$request_uri; } server { server_name adguard.domain.tld; listen 443 ssl http2; access_log /var/log/nginx/agh.access.log; error_log /var/log/nginx/agh.error.log warn; gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; ssl_dhparam /etc/nginx/ssl/dhparam.pem; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; ssl_session_tickets off; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_certificate /etc/letsencrypt/live/adguard.domain.tld/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/adguard.domain.tld/privkey.pem; ssl_early_data on; add_header X-Frame-Options \u0026#34;SAMEORIGIN\u0026#34; always; add_header X-XSS-Protection \u0026#34;1; mode=block\u0026#34; always; add_header X-Content-Type-Options \u0026#34;nosniff\u0026#34; always; add_header Referrer-Policy no-referrer; add_header Strict-Transport-Security \u0026#34;max-age=63072000\u0026#34; always; add_header Permissions-Policy \u0026#34;interest-cohort=()\u0026#34;; # Discourage Google bots from indexing this site add_header X-Robots-Tag \u0026#34;noindex\u0026#34;; # Allow the dns-query endpoint location /dns-query { proxy_pass https://adguardhome/dns-query; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Connection \u0026#34;\u0026#34;; # Timeouts proxy_connect_timeout 90; proxy_send_timeout 300; proxy_read_timeout 90s; } # For all other locations, return 403. location / { return 403; } } Enable and start the webserver:\nsudo systemctl enable --now nginx Now, lets use doggo to test our DOH configuration.\ndoggo fedoraproject.org @https://adguard.domain.tld/dns-query/client-id NAME TYPE CLASS TTL ADDRESS NAMESERVER fedoraproject.org. A IN 47s 8.43.85.67 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 8.43.85.73 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 152.2.23.104 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 34.211.44.206 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 140.211.169.196 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 38.145.32.21 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 152.2.23.103 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 67.219.144.68 https://adguard.domain.tld/dns-query/client-id fedoraproject.org. A IN 47s 38.145.32.20 https://adguard.domain.tld/dns-query/client-id Perfect!\nNow, lets make sure I can\u0026rsquo;t access the admin interface from the public facing nginx\u0026hellip;\nAwesome.\nI am also notably NOT exposing port 53. Only 853 and 443, both of which are using TCP.\n","permalink":"https://blog.ssb-tech.net/posts/adguard-home-doh-nginx/","summary":"\u003cp\u003eA few months ago I switched from using Pi-hole to AdGuardHome for serving adblocking DNS to not just the clients in my home, but also my mobile devices.\u003c/p\u003e\n\u003cp\u003eThere were a few reasons for this, but chief among them was its proper support for DNS-over-TLS (DOT) and DNS-over-HTTPS (DOH).\u003c/p\u003e\n\u003cp\u003eI had previously been running unencrypted DNS via \u003ca href=\"https://tailscale.com\"\u003eTailscale\u003c/a\u003e, but because of the way I have things configured, this mean that every DNS request from an external device looked like it was coming from my \u003ca href=\"https://tailscale.com/kb/1019/subnets\"\u003esubnet router\u003c/a\u003e.\u003c/p\u003e","title":"Proxying AdGuard Home DNS-over-HTTPS with Nginx"},{"content":"Getting started with Docker So, you wanna use docker-compose\u0026hellip;\nIntroduction Welcome! If you\u0026rsquo;re looking at this post, I assume that you\u0026rsquo;re new to Docker and you want to learn to get up and running quickly.\nThere are several areas of Docker that confuse new users, and I will do my best to address most of the most common pitfalls in this post.\ndocker run vs docker compose Don\u0026rsquo;t use docker run. If the thing you want to run in Docker only gives you the docker run syntax, you take that thing over to Composerize and you convert it.\nThere\u0026rsquo;s nothing particularly wrong with docker run, but docker compose is far easier to maintain and troubleshoot.\nTools like yamllint make diagnosing issues with compose file formatting much more tractable.\nDocker networking This seems to be the area that causes the most confusion.\nDon\u0026rsquo;t worry, you don\u0026rsquo;t need to be a network engineer to get a grasp of this, but having a good understanding of how networking works is very helpful.\nNetwork namespaces Docker uses Linux kernel network namespaces to achieve isolation. You don\u0026rsquo;t need to really understand any of this, but click the link if you\u0026rsquo;re at all interested.\nEffectively what this means is that containers by default are isolated from both the host operating system\u0026rsquo;s networking stack, and from each other.\nWhenever you create a new Docker compose project, by default it will create a new Docker network bridge, with its own internal subnet and DNS.\nContainers in the same Docker compose project (in the same docker-compose.yml file, or separate files linked together with the -p flag) share the same network bridge, and thereby are able to communicate with each other.\nCommunicating with containers by name Docker also does some things under the hood to make it possible for docker containers in the same stack to communicate with each other by service name.\nTo illustrate this, lets go with a simple example.\nservices: app: image: myapp restart: unless-stopped environment: - POSTGRES_CONNECTION_STRING=postgresql://postgres-db1:5432 volumes: - ./config:/config postgres-db1: image: postgres restart: unless-stopped Above, we have an extremely simple docker compose file, with a single application and a PostgreSQL database.\nYou can see in the POSTGRES_CONNECTION_STRING environment variable that it is using the name of the service to communicate with the database.\nCreating network bridges But what if I don\u0026rsquo;t want or can\u0026rsquo;t have the containers in the same Compose project?\nThat\u0026rsquo;s where creating your own bridges comes in.\nThere are several different adapter types in Docker, the most common of them is the bridge, so that\u0026rsquo;s the only one we\u0026rsquo;ll be talking about in this article.\nI\u0026rsquo;m sure as hell not going to get into macvlan or any of that, that\u0026rsquo;s a whole article on its own.\nHere you can create a bridge:\ndocker network create proxy\nWhere proxy can be anything you like, it\u0026rsquo;s just the name you gave the bridge.\nThis is how you can use external bridges in Docker compose:\nservices: app01: networks: - proxy networks: proxy: external: true The key here is that you specify that the network is external to the compose project, and that you specify that each container should use that network instead of the default bridge that would have been created.\nYou can also specify more than one network per container. This can be useful if you have some containers that need external connectivity but others that don\u0026rsquo;t.\nFor example, you could have one bridge that\u0026rsquo;s used by containers to communicate with the database, and another that the frontend uses to talk to the backend.\nExample:\nservices: app01: networks: - proxy - database networks: proxy: external: true database: Note that the database network is not external here, so it will be created when you run docker compose up.\nExposing ports Next, lets get into how Docker handles exposing ports.\nLets take the Jellyfin docker compose file for an example:\n--- services: jellyfin: image: lscr.io/linuxserver/jellyfin:latest container_name: jellyfin environment: - PUID=1000 - PGID=1000 - TZ=Etc/UTC - JELLYFIN_PublishedServerUrl=http://192.168.0.5 #optional volumes: - /path/to/jellyfin/library:/config - /path/to/tvseries:/data/tvshows - /path/to/movies:/data/movies ports: - 8096:8096 - 8920:8920 #optional - 7359:7359/udp #optional - 1900:1900/udp #optional restart: unless-stopped You\u0026rsquo;ll notice the ports key. These are what\u0026rsquo;s known as published ports.\nThe number on the left is what is mapped outside the container (on the machine running Docker), and the one on the right is inside the container.\nIn 99% of cases, you want to leave the number on the right alone.\nFor example, if I wanted the Jellyfin web interface, which by default runs on port 8096, and expose it on port 9001, I could change the compose file like so:\nports: - 9001:8096 - 8920:8920 #optional - 7359:7359/udp #optional - 1900:1900/udp #optional Now, on to another example.\nIn many cases, you may not want to expose any ports at all!\nIf users external to the Docker host don\u0026rsquo;t need to be able to touch that port, then simply don\u0026rsquo;t publish it.\nTake this other example:\nservices: proxy: image: \u0026#34;jc21/nginx-proxy-manager:latest\u0026#34; restart: unless-stopped ports: - \u0026#34;80:80\u0026#34; # Public HTTP Port - \u0026#34;443:443\u0026#34; # Public HTTPS Port - \u0026#34;81:81\u0026#34; # Admin Web Port # Add any other Stream port you want to expose # - \u0026#39;21:21\u0026#39; # FTP volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: unless-stopped environment: DOMAIN: \u0026#34;https://vw.domain.tld\u0026#34; volumes: - ./vw-data/:/data/ You\u0026rsquo;ll notice that the Vaultwarden container doesn\u0026rsquo;t have any published ports.\nThat\u0026rsquo;s because it doesn\u0026rsquo;t need them. In this scenario, we would go into Nginx Proxy Manager and configure a proxy host for it.\nAll communication with Vaultwarden would go through the proxy, so there\u0026rsquo;s no need to expose the port.\nRemember that when containers are talking internally like this, you use the INTERNAL port (the one that would be on the right), instead of the one you would have exposed on the outside.\nNginx can talk to Vaultwarden and vice versa - there is no reason for a user to directly interact with the other container, so why let them?\nThis is also valid syntax:\nports: - 127.0.0.1:8000:80 This is telling the container to only publish port 8000 on localhost. You can substitute any other IP here to make it only listen on that one interface.\nI don\u0026rsquo;t use this a whole lot.\nDocker volumes Docker volumes are simply a piece of your filesystem that you\u0026rsquo;re giving to the container to store persistent data.\nBy definition, Docker containers are ephemeral.\nUnless you configure a volume, any data produced while the container is running will be lost when the container is destroyed.\nThe two most popular ways to configure volumes in Docker are named Docker volumes and bind mounts.\nThis is a named docker volume:\nvolumes: - postgres-data:/var/lib/postgresql/data And this is a bind mount:\nvolumes: - /apps/myapp/postgres-data:/var/lib/postgresql/data NoteIf it\u0026rsquo;s a filesystem path on the left, it\u0026rsquo;s a bind mount.\nThe path on the right is the mountpoint inside the container.\nHonestly as far as I\u0026rsquo;m concerned it\u0026rsquo;s mostly personal preference which you use. I like using bind mounts as it makes it easier to keep track of where your data is.\nNamed docker volumes will store the data in a subdirectory under /var/lib/docker/volumes/.\nWhichever one you use, please make sure you do backups\u0026hellip; I\u0026rsquo;m not going to help you try and get your data back, sorry.\n","permalink":"https://blog.ssb-tech.net/posts/docker-pitfalls/","summary":"\u003ch1 id=\"getting-started-with-docker\"\u003eGetting started with Docker\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eSo, you wanna use docker-compose\u0026hellip;\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eWelcome! If you\u0026rsquo;re looking at this post, I assume that you\u0026rsquo;re new to Docker and you want to learn to get up and running quickly.\u003c/p\u003e\n\u003cp\u003eThere are several areas of Docker that confuse new users, and I will do my best to address most of the most common pitfalls in this post.\u003c/p\u003e\n\u003ch2 id=\"docker-run-vs-docker-compose\"\u003edocker run vs docker compose\u003c/h2\u003e\n\u003cp\u003eDon\u0026rsquo;t use \u003ccode\u003edocker run\u003c/code\u003e. If the thing you want to run in Docker only gives you the \u003ccode\u003edocker run\u003c/code\u003e syntax, you take that thing over to \u003ca href=\"https://www.composerize.com/\"\u003eComposerize\u003c/a\u003e and you convert it.\u003c/p\u003e","title":"Getting started with Docker"},{"content":"Wireguard concepts The point of this post This is intended as a high level starting point for someone who is new to using Wireguard.\nIf you spot any errors or inaccuracies, feel free to open a pull request in the Github repo, or leave a comment.\nOverview Wireguard is a modern VPN protocol.\nInstead of using a traditional client server model, Wireguard uses a peer-to-peer mechanism. Authentication is handled with private and public key pairs, and optionally a pre-shared key.\nWireguard configuration on Linux There are many ways to configure Wireguard on Linux, but the one that I typically use is by setting up a file in /etc/wireguard.\nOn Linux, this is the default location that the wg-quick userspace utility looks for Wireguard interface configurations.\nThe interface configuration can contain both the definition for the interface as well as any peers.\nAnatomy of the Wireguard configuration file Consider the following configuration file: /etc/wireguard/demo.conf.\nWhen using wg-quick, the interface name will be generated from the name of the configuration file. In this case, the interface name would be demo.\nSample configuration file:\n[Interface] Address = 192.168.99.5/24 PrivateKey = 2COm4EMxP5tcF9cpgCxBAsSGRwrjoMmWjkr+emGtylY= ListenPort = 51820 [Peer] PublicKey = Tlm3payEVQWCj7GK7Y7usejF4mix5FBULZryxfu9kxY= PreSharedKey = chooseabetterkey AllowedIPs = 192.168.99.8/32,10.10.33.0/24 Endpoint = demo.wireguard.com:51820 Lets break down this config file into its components.\nWireguard uses an INI-like syntax.\nThere are 2 top level blocks that can be configured, [Interface] and [Peer]. There can be multiple [Peer] definitions, but only one [Interface].\nThe Interface Block Address What address do you want this particular peer to have? Make sure you set the correct CIDR notation for your subnet size. This one is /24 for a 254 address subnet. PrivateKey The private key is a secret! I\u0026rsquo;ve generated this one just for this demo, it will be discarded after. But make sure you secure these properly!\nListenPort The default listen port is 51820, but you can set it to anything you want. This is the network port that Wireguard will use to listen on this interface. The Peer Block PublicKey This is the public key associated with the peer\u0026rsquo;s private key, not the private key of this interface. PreSharedKey Preshared keys are optional but good for additional security. This is a key that needs to match on both sides of the connection. AllowedIPs The AllowedIPs stanza is an interesting one. It is what Wireguard uses to make internal routing decisions, but those packets need to arrive at the Wireguard interface first. This is the reason I like to use wg-quick to set up my interfaces - if you don\u0026rsquo;t, you\u0026rsquo;ll need to add the routes yourself. This is personal preference.\nThe configuration above would allow this peer to have the 192.168.99.8 IP on the Wireguard internal subnet, and any traffic destined for 10.10.33.0/24 would also get passed along.\nNote how I\u0026rsquo;m using /32 for the Wireguard internal subnet here. This is because I only want that one IP to be valid for this peer. The subnet is still a /24 as configured on the interface itself.\nEndpoint Endpoint is technically an optional field. But it does need to be set on at least one side of each connection.\nAt least one of the peers needs to have a public IP address that\u0026rsquo;s reachable on the internet. (And the ListenPort needs to be open.)\nThere are other stanzas that can be used in the configuration file, but these are the basic ones.\nYou can see the wg and wg-quick man pages for more information on configuration file syntax.\nUsing wg-quick with systemd When installing Wireguard on Linux, it comes with a template systemd unit file for wg-quick.\nThis is a wrapper that allows you to quickly set Wireguard configurations that load at startup.\nTo start up our config file from earlier using wg-quick, you can do it like so:\nsudo systemctl start wg-quick@demo Where the text after the @ symbol will always been the config file name without its .conf extension. For more info on this, see the Arch Wiki page on systemd.\nIf you want to make the configuration start at boot, you can enable the systemd unit.\nsudo systemctl enable wg-quick@demo NoteYes, you can have multiple Wireguard interfaces on one machine. Just make sure that they listen on different ports. General usage notes While Wireguard as a protocol is peer-to-peer, many people do still use it in a client-server architecture.\nOne use case for this is if you\u0026rsquo;re using Wireguard to set up your own privacy VPN endpoint.\nNoteIf you do use Wireguard as a VPN server, remember that you also need to enable IP forwarding. ","permalink":"https://blog.ssb-tech.net/posts/on-wireguard/","summary":"\u003ch1 id=\"wireguard-concepts\"\u003eWireguard concepts\u003c/h1\u003e\n\u003ch2 id=\"the-point-of-this-post\"\u003eThe point of this post\u003c/h2\u003e\n\u003cp\u003eThis is intended as a high level starting point for someone who is new to using Wireguard.\u003c/p\u003e\n\u003cp\u003eIf you spot any errors or inaccuracies, feel free to open a pull request in the \u003ca href=\"https://github.com/bladewdr/ssb-tech-blog\"\u003eGithub repo\u003c/a\u003e, or leave a comment.\u003c/p\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://www.wireguard.com/\"\u003eWireguard\u003c/a\u003e is a modern VPN protocol.\u003c/p\u003e\n\u003cp\u003eInstead of using a traditional client server model, Wireguard uses a peer-to-peer mechanism. Authentication is handled with private and public key pairs, and optionally a pre-shared key.\u003c/p\u003e","title":"Wireguard configuration concepts"},{"content":"I recently purchased an AsRock Challenger ITX Intel Arc A380 GPU to use for my video transcoding needs.\nIt was a bit of a headache to get it passed through to a VM in Proxmox, so here\u0026rsquo;s a log of what worked for me, in hopes that it will help someone else down the road.\nMy VM is Ubuntu 24.04 LTS, running kernel 6.14 (HWE kernel).\nProxmox is version 8.4.\nI was able to successfully pass through the GPU to the VM, but it shows as UNCLAIMED in lshw:\nroot@gputest:/home/scott# lshw -c display *-display UNCLAIMED description: VGA compatible controller product: DG2 [Arc A380] vendor: Intel Corporation physical id: 10 bus info: pci@0000:06:10.0 version: 05 width: 32 bits clock: 33MHz capabilities: msi pm vga_controller cap_list configuration: latency=0 I\u0026rsquo;m not going to go through all the troubleshooting I did, just share what worked.\nWe need to force the kernel to load the i915 driver.\nIn addition to this, because I\u0026rsquo;m using cloud-init and one of Ubuntu\u0026rsquo;s Cloud Images, I can\u0026rsquo;t simply specify the kernel parameter in /etc/default/grub. If you\u0026rsquo;re not using cloud-init, ignore the next step and simply specify this in /etc/default/grub like so:\nGRUB_CMDLINE_LINUX_DEFAULT=\u0026quot;quiet i915.force_probe=56a5\nThen run update-grub and reboot.\nNoteThese steps may be different if you\u0026rsquo;re using a distro like Fedora - I believe they now use grubby to accomplish this task. However, for those of us using cloud-init, you can instead simply specify these parameters in /etc/modprobe.d/\nsudo vim /etc/modprobe.d/i915.conf\nNote2025/08/23: This originally was set to load in the Xe driver and disable i915, but I found out later that things like Jellyfin do not have support for Xe, at least not yet. For now, you must force_probe the i915 driver instead.\nAs of today I have tested this with both Jellyfin and Plex and can confirm the card works as expected.\noptions i915 force_probe=56a5 Now that that\u0026rsquo;s done, here are my PCI passthrough settings on the VM:\nroot@pve01:~# qm config 100 \u0026lt;snip\u0026gt; cpu: host hostpci0: 0000:2f:00,pcie=1 \u0026lt;---- This is the GPU itself hostpci1: 0000:30:00,pcie=1 \u0026lt;---- This is the GPU\u0026#39;s audio controller. \u0026lt;snip\u0026gt; It does not need to be set as the primary GPU, but you do want your machine type to be q35 and you want to enable PCI express.\nYou also want to give the VM more than 4GB RAM (so it can initialize the large BAR).\nAnd now on to the thing that took me hours to figure out.\nCSM needs to be disabled in BIOS. At least with my motherboard, leaving it on meant that resizable BAR was disabled. Without this, passthrough didn\u0026rsquo;t work at all.\nOther settings you should enable:\nAbove 4G Decoding (May also be called large memory allocation). SR-IOV IOMMU Most of these can be found either in your PCI subsystem or chipset settings.\nNow the card shows normally in lshw.\nscott@gputest:~$ sudo lshw -c display *-display description: VGA compatible controller product: DG2 [Arc A380] vendor: Intel Corporation physical id: 0 bus info: pci@0000:01:00.0 version: 05 width: 64 bits clock: 33MHz capabilities: pciexpress msi pm vga_controller bus_master cap_list rom configuration: driver=xe latency=0 resources: iomemory:38000-37fff irq:38 memory:c0000000-c0ffffff memory:380000000000-3801ffffffff I\u0026rsquo;m not sure that this is 100% needed, but Intel also recommend installing their repo and some of their support packages. It can\u0026rsquo;t hurt, so why not.\nsudo apt-get update sudo apt-get install -y software-properties-common sudo add-apt-repository -y ppa:kobuk-team/intel-graphics sudo apt-get install -y libze-intel-gpu1 libze1 intel-metrics-discovery intel-opencl-icd clinfo intel-gsc sudo apt-get install -y intel-media-va-driver-non-free libmfx-gen1 libvpl2 libvpl-tools libva-glx2 va-driver-all vainfo For some reason the intel_gpu_top and clinfo tools don\u0026rsquo;t seem to recognize the card. Possible that they just haven\u0026rsquo;t been updated with support for the newer Xe driver over i915.\nHowever, we can confirm that things are working with vainfo and ffmpeg.\nI actually took this a step further and built a Docker image with vainfo and ffmpeg (since again, my intention is to use this for Jellyfin in Docker.)\nroot@f7fe7efcfa51:/# vainfo Trying display: wayland error: XDG_RUNTIME_DIR is invalid or not set in the environment. Trying display: x11 error: can\u0026#39;t connect to X server! Trying display: drm libva info: VA-API version 1.22.0 libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so libva info: Found init function __vaDriverInit_1_22 libva info: va_openDriver() returns 0 vainfo: VA-API version: 1.22 (libva 2.22.0) vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 24.3.4 () vainfo: Supported profile and entrypoints VAProfileNone : VAEntrypointVideoProc VAProfileNone : VAEntrypointStats VAProfileMPEG2Simple : VAEntrypointVLD VAProfileMPEG2Main : VAEntrypointVLD VAProfileH264Main : VAEntrypointVLD VAProfileH264Main : VAEntrypointEncSliceLP VAProfileH264High : VAEntrypointVLD VAProfileH264High : VAEntrypointEncSliceLP VAProfileJPEGBaseline : VAEntrypointVLD VAProfileJPEGBaseline : VAEntrypointEncPicture root@f7fe7efcfa51:/# ffmpeg -hide_banner -encoders | grep qsv V..... av1_qsv AV1 (Intel Quick Sync Video acceleration) (codec av1) V..... h264_qsv H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (Intel Quick Sync Video acceleration) (codec h264) V..... hevc_qsv HEVC (Intel Quick Sync Video acceleration) (codec hevc) V..... mjpeg_qsv MJPEG (Intel Quick Sync Video acceleration) (codec mjpeg) V..... mpeg2_qsv MPEG-2 video (Intel Quick Sync Video acceleration) (codec mpeg2video) V..... vp9_qsv VP9 video (Intel Quick Sync Video acceleration) (codec vp9) Me IRL this weekend.\n","permalink":"https://blog.ssb-tech.net/posts/proxmox-intel-arc-a380/","summary":"\u003cp\u003eI recently purchased an AsRock Challenger ITX Intel Arc A380 GPU to use for my video transcoding needs.\u003c/p\u003e\n\u003cp\u003eIt was a bit of a headache to get it passed through to a VM in Proxmox, so here\u0026rsquo;s a log of what worked for me, in hopes that it will help someone else down the road.\u003c/p\u003e\n\u003cp\u003eMy VM is Ubuntu 24.04 LTS, running kernel 6.14 (HWE kernel).\u003c/p\u003e\n\u003cp\u003eProxmox is version 8.4.\u003c/p\u003e","title":"Passing through Intel Arc A380 to a Proxmox VM"},{"content":"Connecting an existing FreshRSS instance to OIDC Specifically, how to do so without starting over.\nWARNING: I am not responsible for you losing data by doing this. Make sure you have a recent backup before you go digging around and changing things in the filesystem.\nOverview It came to my attention yesterday that the FreshRSS project directly supports OIDC now.\nI\u0026rsquo;ve been using the Traefik ForwardAuth middleware to get the same functionality for some time, but this allows me to get rid of a layer.\nIt was also slightly clunky, since after I\u0026rsquo;d logged in through Authentik, I\u0026rsquo;d have to also authenticate via FreshRSS. I\u0026rsquo;m aware I could have simply disabled authentication, but I chose not to at the time.\nI had been using the LinuxServer.io Docker container, but this functionality depended on Apache\u0026rsquo;s OIDC module, so it was unsupported. (The LinuxServer.io container uses nginx instead of Apache.)\nOIDC Setup The first thing I needed to do was switch to the official container on DockerHub.\nThe volume layout was a bit different from the LinuxServer container, so I had to stop the container and move some files around.\nAll you should need to do is move the data and extensions folders out from under the config directory that the LSIO container uses, and make them their own separate bind mounts.\nNext, I set about configuring OIDC.\nI followed their official instructions to set up OIDC with Authentik.\nThis was mostly fine, though I had to make a few tweaks to their environment variables. The OIDC_SCOPES variable in particular was my issue - it would not work if I had it in quotes. I can only assume that this was being interpreted as a single string by the application.\nHere\u0026rsquo;s what my Docker Compose file wound up looking like at the end. The OIDC values have been replaced with gibberish, make sure to substitute your own if you copy and paste.\nNote2025/08/21 - After posting this I began noticing that my feeds were no longer auto-updating. As it turns out, the FreshRSS official Docker image disabled this feature by default unless you pass it the CRON_MIN environment variable. I have updated the Docker Compose file below with a setting that works. services: freshrss: container_name: freshrss environment: - PUID=1000 - PGID=1000 - TZ=America/New_York - CRON_MIN=2,32 - TRUSTED_PROXY=172.23.0.0/16 - OIDC_ENABLED=1 - OIDC_PROVIDER_METADATA_URL=\u0026#34;https://auth.example.com/application/o/fresh-rss-oidc/.well-known/openid-configuration\u0026#34; - OIDC_REMOTE_USER_CLAIM=preferred_username - OIDC_CLIENT_ID=1234455262233 - OIDC_CLIENT_SECRET=7dfadkfjakldgjad7897324 - OIDC_CLIENT_CRYPTO_KEY=dfajfakjglkdg70708723434 - OIDC_SCOPES=openid email profile - OIDC_X_FORWARDED_HEADERS=X-Forwarded-Host X-Forwarded-Port X-Forwarded-Proto volumes: - ./data:/var/www/FreshRSS/data - ./extensions:/var/www/FreshRSS/extensions restart: unless-stopped image: \u0026#34;freshrss/freshrss:latest\u0026#34; labels: - traefik.enable=true - traefik.http.routers.freshrss-external.rule=Host(`rss.example.com`) - traefik.http.routers.freshrss-external.entrypoints=https - traefik.http.routers.freshrss-external.middlewares=default-headers@file - traefik.http.routers.freshrss-external.tls=true - traefik.http.routers.freshrss-external.service=freshrss-external - traefik.http.services.freshrss-external.loadbalancer.server.port=80 networks: - proxy networks: proxy: external: true I\u0026rsquo;m not 100% certain that the TRUSTED_PROXY setting is necessary. The subnet there is the subnet used by my proxy docker network. It works and I\u0026rsquo;m too lazy to try removing it.\nYou can get this subnet by running docker network inspect \u0026lt;network-name\u0026gt;\n\u0026gt; docker-ubuntu in ~/docker/freshrss/data docker network inspect proxy docker network inspect proxy [ { \u0026#34;Name\u0026#34;: \u0026#34;proxy\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;4b6ea83029c955c6f7609af4552ed053760aadbb6de5c72f8390d3cfa96023b1\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2022-09-24T02:01:57.289063399Z\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;bridge\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: {}, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;172.23.0.0/16\u0026#34;, \u0026lt;-------- This \u0026#34;Gateway\u0026#34;: \u0026#34;172.23.0.1\u0026#34; } ] }, \u0026lt;snip\u0026gt; Not losing data Onto the meat and potatoes.\nIn order to not log into a completely fresh profile with no articles and none of your settings - your user ID in FreshRSS needs to match that of your identity provider.\nMine did not, so I had to do a little detective work.\nI stopped the container with a docker compose down and started poking about in the filesystem, trying to figure out how I could change my username.\nAs it turns out, it\u0026rsquo;s quite simple - each user has their own directory under data/users/, and each user has their own individual sqlite database. I poked around in the database a little, but there didn\u0026rsquo;t seem to be any indication of who owned each entry it stored in its tables.\nThis led me to believe that maybe FreshRSS was just reading the directory structure to find its list of users.\ndocker-ubuntu in ~/docker/freshrss/data \u0026gt; tree users users ├── MyUsername │ ├── config.php │ ├── db.sqlite │ └── log.txt ├── _ │ ├── db.sqlite │ ├── index.html │ ├── log.txt │ ├── log_api.txt │ └── log_pshb.txt └── index.html 2 directories, 10 files So as an experiment, I simply renamed the user folder under data/users to match the one from my identity provider and started FreshRSS back up. Imagine my surprise when that simply worked.\nI was logged in automatically with Authentik SSO, and I had all of my feeds, saved articles and settings.\n","permalink":"https://blog.ssb-tech.net/posts/freshrss-oidc/","summary":"\u003ch1 id=\"connecting-an-existing-freshrss-instance-to-oidc\"\u003eConnecting an existing FreshRSS instance to OIDC\u003c/h1\u003e\n\u003cp\u003eSpecifically, how to do so without starting over.\u003c/p\u003e\n\u003cp\u003e\u003cspan style=color:red\u003e\u003cstrong\u003eWARNING:\u003c/strong\u003e I am not responsible for you losing data by doing this. Make sure you have a recent backup before you go digging around and changing things in the filesystem.\u003c/span\u003e\u003c/p\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eIt came to my attention yesterday that the \u003ca href=\"https://freshrss.org/index.html\"\u003eFreshRSS\u003c/a\u003e project directly supports OIDC now.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve been using the Traefik \u003ca href=\"https://doc.traefik.io/traefik/middlewares/http/forwardauth/\"\u003eForwardAuth middleware\u003c/a\u003e to get the same functionality for some time, but this allows me to get rid of a layer.\u003c/p\u003e","title":"Connecting an Existing FreshRSS instance to OIDC"},{"content":"Proxmox installation steps Overview This post is a reference for me and anyone else that finds it useful.\nI\u0026rsquo;ve never bothered documenting this before, which leads me to missing stuff every time I set up a new box.\nThis post will continue to be updated if I add anything new.\nInstallation The primary thing to get right here is the storage configuration. Even if it\u0026rsquo;s a single disk I will typically do ZFS. Click on Options. Choose whichever ZFS topology you want. Click on Advanced Options. Change Compression from \u0026ldquo;on\u0026rdquo; to \u0026ldquo;lz4\u0026rdquo;. This is the default anyway, but I like to be sure. ashift should be set to 12 or 13. I typically do 12, as this targets 4k physical sectors. Make sure that you have the correct disks selected. Any disk you do not want to be part of the boot pool should be set to \u0026ldquo;do not use\u0026rdquo;. For the rest of the setup you can pretty much just click Next Next Next. I do recommend setting the correct time zone and a good DNS server. Post-Install If you don\u0026rsquo;t have an Enterprise subscription we need to disable the enterprise repository and enable the one for no-subscription. Click on the individual node (not Datacenter) and go to Updates \u0026gt; Repositories. Click on Add and select No Subscription from the dropdown. Next, select the repositories labeled \u0026ldquo;Enterprise\u0026rdquo; and disable them. Enable Debian Non-Free-Firmware repository. This one can\u0026rsquo;t be done through the GUI. SSH in or use the web shell. Edit the sources vim /etc/apt/sources.list.d/pve-no-enterprise.list` Add the following repo, replacing $VERSION with your release\u0026rsquo;s Debian base (bookworm, trixie): https://deb.debian.org/debian $VERSION main non-free-firmware Install the firmware microcode for your hardware vendor Run apt update apt install intel-microcode OR apt install amd64-microcode At this point, update the system. Reboot if needed. Next, lets fix the ZFS configuration. Click on the Datacenter tab and go do down to Storage. Select your zpool (or pools) and click Edit. You will have to do this for every pool individually. Enable thin provisioning. Change the Block Size to 64k or 128k. Either is probably fine, but the default 16k is abysmal for VM disks. This setting is the zvol equivalent to the recordsize property on datasets. Having this not match your workload will usually result in massive write amplification. This is why ZFS got a reputation for quickly killing SSDs, but it doesn\u0026rsquo;t have to if you configure your pool correctly. Click on the individual node again. Go to System \u0026gt; Network and select any bridges that you\u0026rsquo;ve created. Set them to be VLAN-aware. Set up the notification system under Datacenter \u0026gt; Notifications. I will typically have it send me only backup failure notices and warnings about things like failed disks. I will typically set up a VM template at this stage to save myself a bunch of work later. See links below for options. Other Notes If you\u0026rsquo;re trying to set up a new ZFS storage pool and the disks aren\u0026rsquo;t showing up in the interface - the Proxmox interface will not show you any disks that have partitions on them.\nIf you\u0026rsquo;re expecting a drive to show up and it\u0026rsquo;s not you can go through the following procedure on the command line.\nlsblk to find the path of the drive that you need to wipe. lsblk -o NAME,SIZE,MODEL Wipe out all of the partitions. wipefs -a /dev/sdX The ZFS labels (which contain pool metadata) may survive wipefs as it\u0026rsquo;s stored on the first few sectors of the drive, so lets make sure that any existing labels are gone. If this drive has never been used in a zpool before, you can skip this step. zpool labelclear /dev/sdX Try creating your pool again. Links Reddit comment explaining the effect of a poorly matched recordsize property Setting up a cloud-init VM template A repository for Packer templates for Various OS images Automate Creating VM templates on Proxmox with Packer ","permalink":"https://blog.ssb-tech.net/posts/proxmox-setup-notes/","summary":"\u003ch1 id=\"proxmox-installation-steps\"\u003eProxmox installation steps\u003c/h1\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eThis post is a reference for me and anyone else that finds it useful.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve never bothered documenting this before, which leads me to missing stuff every time I set up a new box.\u003c/p\u003e\n\u003cp\u003eThis post will continue to be updated if I add anything new.\u003c/p\u003e\n\u003ch2 id=\"installation\"\u003eInstallation\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eThe primary thing to get right here is the storage configuration. Even if it\u0026rsquo;s a single disk I will typically do ZFS.\n\u003cul\u003e\n\u003cli\u003eClick on Options. Choose whichever ZFS topology you want.\u003c/li\u003e\n\u003cli\u003eClick on Advanced Options. Change Compression from \u0026ldquo;on\u0026rdquo; to \u0026ldquo;lz4\u0026rdquo;. This is the default anyway, but I like to be sure.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eashift\u003c/code\u003e should be set to 12 or 13. I typically do 12, as this targets 4k physical sectors.\u003c/li\u003e\n\u003cli\u003eMake sure that you have the correct disks selected. Any disk you do not want to be part of the boot pool should be set to \u0026ldquo;do not use\u0026rdquo;.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eFor the rest of the setup you can pretty much just click Next Next Next. I do recommend setting the correct time zone and a good DNS server.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"post-install\"\u003ePost-Install\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eIf you don\u0026rsquo;t have an Enterprise subscription we need to disable the enterprise repository and enable the one for no-subscription.\n\u003cul\u003e\n\u003cli\u003eClick on the individual node (not Datacenter) and go to Updates \u0026gt; Repositories.\n\u003cimg alt=\"Picture showing where to click on the individual node\" loading=\"lazy\" src=\"/posts/proxmox-setup-notes/images/pvesetup-node.png\"\u003e\u003c/li\u003e\n\u003cli\u003eClick on Add and select \u003ccode\u003eNo Subscription\u003c/code\u003e from the dropdown.\u003c/li\u003e\n\u003cli\u003eNext, select the repositories labeled \u0026ldquo;Enterprise\u0026rdquo; and disable them.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eEnable Debian Non-Free-Firmware repository.\n\u003cul\u003e\n\u003cli\u003eThis one can\u0026rsquo;t be done through the GUI. SSH in or use the web shell.\u003c/li\u003e\n\u003cli\u003eEdit the sources\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003evim /etc/apt/sources.list.d/pve-no-enterprise.list\u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eAdd the following repo, replacing $VERSION with your release\u0026rsquo;s Debian base (bookworm, trixie):\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehttps://deb.debian.org/debian $VERSION main non-free-firmware\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003eInstall the firmware microcode for your hardware vendor\n\u003cul\u003e\n\u003cli\u003eRun \u003ccode\u003eapt update\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eapt install intel-microcode\u003c/code\u003e OR \u003ccode\u003eapt install amd64-microcode\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eAt this point, update the system. Reboot if needed.\u003c/li\u003e\n\u003cli\u003eNext, lets fix the ZFS configuration.\n\u003cul\u003e\n\u003cli\u003eClick on the \u003ccode\u003eDatacenter\u003c/code\u003e tab and go do down to \u003ccode\u003eStorage\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSelect your zpool (or pools) and click Edit. You will have to do this for every pool individually.\u003c/li\u003e\n\u003cli\u003eEnable thin provisioning.\u003c/li\u003e\n\u003cli\u003eChange the Block Size to 64k or 128k. Either is probably fine, but the default 16k is abysmal for VM disks. This setting is the \u003ccode\u003ezvol\u003c/code\u003e equivalent to the \u003ccode\u003erecordsize\u003c/code\u003e property on datasets. Having this not match your workload will usually result in massive write amplification. This is why ZFS got a reputation for quickly killing SSDs, but it doesn\u0026rsquo;t have to if you configure your pool correctly.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eClick on the individual node again. Go to \u003ccode\u003eSystem \u0026gt; Network\u003c/code\u003e and select any bridges that you\u0026rsquo;ve created. Set them to be VLAN-aware.\u003c/li\u003e\n\u003cli\u003eSet up the notification system under \u003ccode\u003eDatacenter \u0026gt; Notifications\u003c/code\u003e. I will typically have it send me only backup failure notices and warnings about things like failed disks.\u003c/li\u003e\n\u003cli\u003eI will typically set up a VM template at this stage to save myself a bunch of work later. See links below for options.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"other-notes\"\u003eOther Notes\u003c/h2\u003e\n\u003cp\u003eIf you\u0026rsquo;re trying to set up a new ZFS storage pool and the disks aren\u0026rsquo;t showing up in the interface - the Proxmox interface will not show you any disks that have partitions on them.\u003c/p\u003e","title":"Proxmox Setup Notes"},{"content":"Setting up brscan-skey with the Brother DS-940DW on Ubuntu Overview The Brother DS-940DW is a little handheld mobile scanner. Getting it working on Linux in a way where you can actually use the Start/Stop button on the scanner to scan things was a bit of a bear, so I thought I\u0026rsquo;d document how I did it.\nPart of the issue is that brscan-skey depends on the old version of libsane, and does not work with libsane1.\nSolving the dependency problem First, install the provided driver from the support page for the DS-940DW You can grab the scan-key-tool deb package from here.\nGoing by this Brother support article, I was able to get brscan-skey installed.\nSteps sudo su apt update apt install sane libsane1 sane-utils imagemagick wget -c http://jp.archive.ubuntu.com/ubuntu/pool/universe/s/sane-backends/libsane_1.1.1-5_amd64.deb dpkg -i libsane_1.1.1-5_amd64.deb apt install evince Configure scanning I had all sorts of fun figuring out how this all works, and honestly it\u0026rsquo;s pretty simple.\nPressing the Start/Stop button on the scanner while brscan-skey is running calls this shell script:\n/opt/brother/scanner/brscan-skey/script/scantofile.sh\nYou can have it do whatever you want by editing the script.\nBy default it uses psutils and pod2pdf, and a bunch of other utilities to scan first to PostScript, then convert it to PDF.\nI don\u0026rsquo;t often have the need to scan complex documents, so I simplified this quite a bit, as I\u0026rsquo;ll show you here in second.\nBut before we do anything, we need to add some udev rules.\nCreate a file at /etc/udev/rules.d/NN-brother-mfp-brscan5-1.0.2-2.rules with the following content:\nudev rules ACTION!=\u0026#34;add\u0026#34;, GOTO=\u0026#34;brother_mfp_end\u0026#34; SUBSYSTEM==\u0026#34;usb\u0026#34;, GOTO=\u0026#34;brother_mfp_udev_1\u0026#34; SUBSYSTEM!=\u0026#34;usb_device\u0026#34;, GOTO=\u0026#34;brother_mfp_end\u0026#34; LABEL=\u0026#34;brother_mfp_udev_1\u0026#34; ATTRS{idVendor}==\u0026#34;04f9\u0026#34;, GOTO=\u0026#34;brother_mfp_udev_2\u0026#34; GOTO=\u0026#34;brother_mfp_end\u0026#34; LABEL=\u0026#34;brother_mfp_udev_2\u0026#34; ATTRS{bInterfaceClass}!=\u0026#34;0ff\u0026#34;, GOTO=\u0026#34;brother_mfp_end\u0026#34; ATTRS{bInterfaceSubClass}!=\u0026#34;0ff\u0026#34;, GOTO=\u0026#34;brother_mfp_end\u0026#34; ATTRS{bInterfaceProtocol}!=\u0026#34;0ff\u0026#34;, GOTO=\u0026#34;brother_mfp_end\u0026#34; MODE=\u0026#34;0666\u0026#34; GROUP=\u0026#34;scanner\u0026#34; ENV{libsane_matched}=\u0026#34;yes\u0026#34; LABEL=\u0026#34;brother_mfp_end\u0026#34; Reload udev rules:\nsudo udevadm control --reload \u0026amp;\u0026amp; sudo udevadm trigger\nScanner group You\u0026rsquo;ll also want to add the user you want doing the scanning to the scanner group.\nsudo usermod -aG scanner \u0026lt;username\u0026gt;\nSystemd service I created a systemd service to run brscan-skey at boot.\nCreate the following at /etc/systemd/system/brscan-skey.service\n[Unit] Description=Brother scan-key-tool [Service] User=scott Type=forking ExecStart=/opt/brother/scanner/brscan-skey/brscan-skey ExecStop=/opt/brother/scanner/brscan-skey/brscan-skey --terminate [Install] WantedBy=multi-user.target Run the following commands:\nsudo systemctl daemon-reload sudo systemctl enable --now brscan-skey.service You may need to reboot. I did.\nSimple scantofile.sh script First make a backup of the existing script, just in case.\nsudo mv /opt/brother/scanner/brscan-skey/script/scantofile.sh /opt/brother/scanner/brscan-skey/script/scantofile.sh.bak\nNow we create our own version:\nsudo vim /opt/brother/scanner/brscan-skey/script/scantofile.sh\n#! /bin/sh set +o noclobber BASE=$HOME/brscan pdf_name=$(date | sed s/\u0026#39; \u0026#39;/\u0026#39;_\u0026#39;/g | sed s/\u0026#39;\\:\u0026#39;/\u0026#39;_\u0026#39;/g) output_full=\u0026#34;$BASE/$pdf_name\u0026#34; # scan the image in using scanimage scanimage -x 215.9 -y 279.4 --format=png \u0026gt; \u0026#34;$output_full\u0026#34;.png # Convert to PDF using imagemagick convert \u0026#34;$output_full\u0026#34;.png \u0026#34;$output_full\u0026#34;.pdf echo \u0026#34;Created file $output_full.pdf\u0026#34; # Cleanup rm \u0026#34;$output_full\u0026#34;.png My script assumes a lot of things. It assumes that you\u0026rsquo;re using letter size paper, for one. This was a really quick and dirty hack - maybe I\u0026rsquo;ll refactor this in the future.\nFuture goals My goal for getting this working was to have a small, low power x86 PC that just runs this and nothing else. It\u0026rsquo;s connected via WiFi so I can put it anywhere, and hopefully make it so that I actually use it.\nI have it exposing an NFS share that I\u0026rsquo;m going to mount as an ingestion folder for Paperless-NGX.\nMaybe I\u0026rsquo;ll create another blog post soon showing how I got that set up.\n","permalink":"https://blog.ssb-tech.net/posts/linux-scanning-ds940dw/","summary":"\u003ch1 id=\"setting-up-brscan-skey-with-the-brother-ds-940dw-on-ubuntu\"\u003eSetting up brscan-skey with the Brother DS-940DW on Ubuntu\u003c/h1\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eThe Brother DS-940DW is a little handheld mobile scanner. Getting it working on Linux in a way where you can actually use the Start/Stop button on the scanner to scan things was a bit of a bear, so I thought I\u0026rsquo;d document how I did it.\u003c/p\u003e\n\u003cp\u003ePart of the issue is that brscan-skey depends on the old version of \u003ccode\u003elibsane\u003c/code\u003e, and does not work with \u003ccode\u003elibsane1\u003c/code\u003e.\u003c/p\u003e","title":"Setting up brscan-skey with the Brother DS-940DW on Ubuntu"},{"content":"How to set up the Cially Dashboard for Discord Cially is a dashboard for Discord that shows you interesting statistics regarding your server, or multiple servers.\nI\u0026rsquo;m going to show you how to get it set up on your own system using Docker.\nNoteThis guide does assume some familiarity with Docker and Docker Compose, as well as Linux in general. 1. Setting up an application Cially takes the form of a Discord bot. In order for your bot to join a server you have to create an application for it in your Discord developer account portal.\nGo to the Discord Developer Portal and create a new application. You can call it whatever you wish, I called mine \u0026ldquo;Cially.\u0026rdquo; Under \u0026ldquo;Installation\u0026rdquo;, set \u0026ldquo;Install Link\u0026rdquo; to \u0026ldquo;None\u0026rdquo;, and uncheck \u0026ldquo;User Install\u0026rdquo;. Under \u0026ldquo;Bot\u0026rdquo;: Enable all three sliders under \u0026ldquo;Privileged Gateway Intents.\u0026rdquo; Uncheck \u0026ldquo;Public Bot\u0026rdquo;. (Unless you would like other people to be able to add your bot to their servers, too.) If you get an error with this step, make sure you completed \u0026lsquo;set Install Link to None in step 2.\u0026rsquo; Click the \u0026ldquo;Reset\u0026rdquo; button to get your bot token. Save this in a text editor, we\u0026rsquo;ll need it when setting up the bot on our server. This will only be shown once, so if you lose it, you\u0026rsquo;ll have to reset it again. I keep this in my password manager vault to make sure I don\u0026rsquo;t lose it. Under \u0026ldquo;Oauth2\u0026rdquo;: Copy the Client ID from the Oauth2 screen and save it with the bot token you copied earlier. Give the bot the following Oauth2 scopes: (Source) bot applications.commands At the bottom of the screen it will give you a URL. Paste that into your browser, and it should prompt you to join the bot to one of your servers. 2. Setting up Cially in Docker This is a relatively complicated docker compose file. The project provides one, but I chose to take it and make some changes so it would work behind my Traefik reverse proxy.\nEither one works, but if you\u0026rsquo;d like to use my compose file, this is it:\nnetworks: traefik: external: true services: cially: image: ghcr.io/skellgreco/cially-bot:${CIALLY_DOCKER_TAG:-latest} container_name: ${CIALLY_CONTAINER_NAME:-cially} restart: ${CIALLY_RESTART:-unless-stopped} mem_limit: ${CIALLY_MEM_LIMIT:-200g} env_file: - .env networks: - traefik volumes: - ./config:/config - /etc/localtime:/etc/localtime:ro environment: - PUID=${PUID:-1000} - PGID=${PGID:-1000} - TZ=${TZ} - TOKEN=${CIALLY_BOT_TOKEN} - CLIENT_ID=${CIALLY_BOT_CLIENT_ID} - API_URL=http://cially:3001 - DEBUGGING=${CIALLY_BOT_DEBUGGING:-FALSE} - POCKETBASE_URL=http://pocketbase:8090 - GUILD_COLLECTION=guilds - MESSAGE_COLLECTION=messages - INVITE_COLLECTION=invites - MEMBER_LEAVES_COLLECTION=member_leaves - MEMBER_JOINS_COLLECTION=member_joins - PORT=3001 depends_on: - pocketbase - cially-web labels: - com.centurylinklabs.watchtower.enable=${CIALLY_WATCHTOWER_ENABLED:-true} - autoheal=${CIALLY_AUTOHEAL_ENABLED:-true} cially-web: image: ghcr.io/skellgreco/cially-web:latest container_name: cially-web restart: unless-stopped environment: # Runtime environment variables - NEXT_PUBLIC_BOT_API_URL=http://cially:3001 - POCKETBASE_URL=http://pocketbase:8090 - MESSAGE_COLLECTION=messages - INVITE_COLLECTION=invites - MEMBER_LEAVES_COLLECTION=member_leaves - MEMBER_JOINS_COLLECTION=member_joins - GUILDS_COLLECTION=guilds env_file: - .env depends_on: - pocketbase labels: - joyride.host.name=${CIALLY_HOST_NAME:-cially}.${HOST_DOMAIN} - traefik.enable=${CIALLY_TRAEFIK_ENABLED:-true} - traefik.http.routers.cially.entrypoints=websecure - traefik.http.routers.cially.rule=Host(`${CIALLY_HOST_NAME:-cially}.${HOST_DOMAIN}`) - traefik.http.services.cially.loadbalancer.server.port=3000 - com.centurylinklabs.watchtower.enable=${CIALLY_WATCHTOWER_ENABLED:-true} - autoheal=${CIALLY_AUTOHEAL_ENABLED:-true} networks: - traefik # Need to expose this via Traefik as well since the user needs to log into Pocketbase and configure it. pocketbase: image: ghcr.io/keksiqc/pocketbase:0.26.6 container_name: cially-pocketbase restart: unless-stopped env_file: - .env volumes: - ./pocketbase-data:/pb/pb_data networks: - traefik labels: - joyride.host.name=${CIALLY_POCKETBASE_HOST_NAME:-cially-pb}.${HOST_DOMAIN} - traefik.enable=${CIALLY_TRAEFIK_ENABLED:-true} - traefik.http.routers.cially-pb.entrypoints=websecure - traefik.http.routers.cially-pb.rule=Host(`${CIALLY_POCKETBASE_HOST_NAME:-cially-pb}.${HOST_DOMAIN}`) - traefik.http.services.cially-pb.loadbalancer.server.port=8090 - com.centurylinklabs.watchtower.enable=${CIALLY_WATCHTOWER_ENABLED:-true} - autoheal=${CIALLY_AUTOHEAL_ENABLED:-true} Like I said, this is a doozy, and unusually, it\u0026rsquo;s not as simple as docker compose up and it works. There\u0026rsquo;s one or two extra steps required.\nLog into your server and create a new folder for Cially.\nsudo mkdir -p /apps/cially\nsudo chown -R $USER:$USER /apps/cially \u0026amp;\u0026amp; cd /apps/cially\nNow, create a .env file in this directory: vim .env or nano .env\nAdd the following two environment variables, replacing them with your Client ID and token where appropriate:\nCIALLY_BOT_TOKEN=\u0026lt;BOT_TOKEN_GOES_HERE\u0026gt; CIALLY_BOT_CLIENT_ID=\u0026lt;CLIENT_ID_GOES_HERE\u0026gt; Save and exit the file.\nCreate your docker compose file, either using mine from above, or the one from the project\u0026rsquo;s GitHub.\nNoteKeep in mind if you use my compose file - I am not exposing any ports because everything is being routed through Traefik. If you want to expose them, you\u0026rsquo;ll have to re-add the ports key. Run docker compose up -d. Now, we need to import the collections provided by the Cially team into Pocketbase, which is one of the containers that the compose file is spinning up. But first, we need to log into Pocketbase and create a user account. To do so: Run docker logs \u0026lt;pocketbase-container-name\u0026gt; In the standard output for that container, you will see an http://0.0.0.0 URL. Copy and paste it into your browser, and replace 0.0.0.0 with the IP address of your server or your fully qualified domain name, if you\u0026rsquo;re using a reverse proxy. Set up a user account for Pocketbase when prompted. Once you\u0026rsquo;ve logged into Pocketbase, navigate to Settings, and then Import Collections. You\u0026rsquo;ll need to paste the content from the JSON file that\u0026rsquo;s provided for this purpose. Once you\u0026rsquo;ve done so, I recommend giving everything a restart just to be on the safe side. docker compose up -d --force-recreate At this point, you should be able to navigate to the IP address of your server on port 3000, or to your fully qualified domain name and see the Cially dashboard. It will take a few minutes for your server to become available as Cially collects data. ","permalink":"https://blog.ssb-tech.net/posts/cially-discord-bot/","summary":"\u003ch1 id=\"how-to-set-up-the-cially-dashboard-for-discord\"\u003eHow to set up the Cially Dashboard for Discord\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/cially/cially\"\u003eCially\u003c/a\u003e is a dashboard for Discord that shows you interesting statistics regarding your server, or multiple servers.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m going to show you how to get it set up on your own system using Docker.\u003c/p\u003e\n\u003cdiv class=\"callout callout-note\" role=\"note\"\u003e\u003cdiv class=\"callout-title\"\u003eNote\u003c/div\u003e\u003cdiv class=\"callout-content\"\u003eThis guide does assume some familiarity with Docker and Docker Compose, as well as Linux in general.\u003c/div\u003e\u003c/div\u003e\n\u003ch2 id=\"1-setting-up-an-application\"\u003e1. Setting up an application\u003c/h2\u003e\n\u003cp\u003eCially takes the form of a Discord bot. In order for your bot to join a server you have to create an application for it in your Discord developer account portal.\u003c/p\u003e","title":"How to set up the Cially Dashboard for Discord"},{"content":"Note2025/05/24 - edited to fix some mistakes I caught later in my docker compose. Continuing on the theme of moving all of my stuff out of other people\u0026rsquo;s cloud and into mine - I was recently made aware of Dawarich while listening to the Linux Unplugged podcast.\nThis is touted as an alternative to Google\u0026rsquo;s Location History feature, which is something that I use frequently. I need it to account for my time at work, so it\u0026rsquo;s fairly important that I have a solution like this.\nI chose to set this up on one of my VPS machines at Linode rather than running it in my house. The logic behind this being that a datacenter is most likely going to have more reliable power than I do at my house.\nThat said, I don\u0026rsquo;t think I\u0026rsquo;m going to be going through the work of making this truly highly-available - it\u0026rsquo;s just not worth the headache when I\u0026rsquo;m the only user.\nInstallation As always, I set this up using their provided Docker image.\nDawarich can do either an HTTP or MQTT backend. I already have a webserver on this VPS, so I\u0026rsquo;ll be using HTTP.\nI highly recommend setting up valid HTTPS certificates for this. I\u0026rsquo;m using free certificates from LetsEncrypt. The Traefik reverse proxy I\u0026rsquo;m using has an ACME client built right in, which is very handy.\nThe compose file for this app is quite the doozy. The project provides one, but I made some of my own changes, and added labels for Traefik.\nnetworks: traefik: external: true services: dawarich: image: freikin/dawarich:${DAWARICH_DOCKER_TAG:-latest} container_name: ${DAWARICH_CONTAINER_NAME:-dawarich} restart: ${DAWARICH_RESTART:-unless-stopped} volumes: - /etc/localtime:/etc/localtime:ro - ./etc/dawarich/storage:/var/app/storage networks: - traefik stdin_open: true tty: true entrypoint: web-entrypoint.sh command: [\u0026#34;bin/rails\u0026#34;, \u0026#34;server\u0026#34;, \u0026#34;-p\u0026#34;, \u0026#34;3000\u0026#34;, \u0026#34;-b\u0026#34;, \u0026#34;::\u0026#34;] environment: - RAILS_ENV=production - REDIS_URL=redis://dawarich_redis:6379/0 - DATABASE_HOST=dawarich_db - DATABASE_PORT=5432 - DATABASE_USERNAME=${DAWARICH_POSTGRES_USER:-postgres} - DATABASE_PASSWORD=${DAWARICH_POSTGRES_PW:-password} - DATABASE_NAME=dawarich_production - MIN_MINUTES_SPENT_IN_CITY=${DAWARICH_MIN_MINS:-30} - APPLICATION_HOSTS=localhost,::1,127.0.0.1,${DAWARICH_HOST_NAME:-dawarich}.${HOST_DOMAIN} - TIME_ZONE=${TZ} - APPLICATION_PROTOCOL=http # Can be either km or mi - DISTANCE_UNIT=${DAWARICH_DISTANCE_UNIT:-mi} - PROMETHEUS_EXPORTER_ENABLED=false - PROMETHEUS_EXPORTER_HOST=0.0.0.0 - PROMETHEUS_EXPORTER_PORT=9394 - SECRET_KEY_BASE=${DAWARICH_SECRET_KEY_BASE:-1234567890} - RAILS_LOG_TO_STDOUT=\u0026#34;true\u0026#34; # SMTP settings - SMTP_SERVER=${DAWARICH_SMTP_SERVER} - SMTP_PORT=${DAWARICH_SMTP_PORT} - SMTP_USERNAME=${DAWARICH_SMTP_USERNAME} - SMTP_PASSWORD=${DAWARICH_SMTP_PASSWORD} - SMTP_FROM=${DAWARICH_SMTP_FROM} - SMTP_DOMAIN=${DAWARICH_SMTP_DOMAIN} logging: driver: \u0026#34;json-file\u0026#34; options: max-size: \u0026#34;100m\u0026#34; max-file: \u0026#34;5\u0026#34; healthcheck: test: [ \u0026#34;CMD-SHELL\u0026#34;, \u0026#34;wget -qO - http://127.0.0.1:3000/api/v1/health | grep -q \u0026#39;\\\u0026#34;status\\\u0026#34;\\\\s*:\\\\s*\\\u0026#34;ok\\\u0026#34;\u0026#39;\u0026#34;, ] interval: 10s retries: 30 start_period: 30s timeout: 10s depends_on: dawarich_db: condition: service_healthy restart: true dawarich_redis: condition: service_healthy restart: true deploy: resources: limits: cpus: \u0026#34;0.50\u0026#34; # Limit CPU usage to 50% of one core memory: \u0026#34;4G\u0026#34; # Limit memory usage to 2GB labels: - joyride.host.name=${DAWARICH_HOST_NAME:-dawarich}.${HOST_DOMAIN} - traefik.enable=${DAWARICH_TRAEFIK_ENABLED:-true} - traefik.http.routers.dawarich.entrypoints=websecure - traefik.http.routers.dawarich.rule=Host(`${DAWARICH_HOST_NAME:-dawarich}.${HOST_DOMAIN}`) # - traefik.http.services.dawarich.loadbalancer.server.scheme=https # enable if the service wants to connect over https - traefik.http.services.dawarich.loadbalancer.server.port=3000 - com.centurylinklabs.watchtower.enable=${DAWARICH_WATCHTOWER_ENABLED:-true} - autoheal=${DAWARICH_AUTOHEAL_ENABLED:-true} dawarich_sidekiq: image: freikin/dawarich:${DAWARICH_DOCKER_TAG:-latest} container_name: dawarich_sidekiq volumes: - ./etc/dawarich/public:/var/app/public - ./etc/dawarich/watched:/var/app/tmp/imports/watched - ./etc/dawarich/storage:/var/app/storage networks: - traefik stdin_open: true tty: true entrypoint: sidekiq-entrypoint.sh command: [\u0026#34;bundle\u0026#34;, \u0026#34;exec\u0026#34;, \u0026#34;sidekiq\u0026#34;] restart: on-failure environment: - RAILS_ENV=production - REDIS_URL=redis://dawarich_redis:6379/0 - DATABASE_HOST=dawarich_db - DATABASE_PORT=5432 - DATABASE_USERNAME=${DAWARICH_POSTGRES_USER:-postgres} - DATABASE_PASSWORD=${DAWARICH_POSTGRES_PW:-password} - DATABASE_NAME=dawarich_production - APPLICATION_HOSTS=localhost,::1,127.0.0.1,${DAWARICH_HOST_NAME:-dawarich}.${HOST_DOMAIN} - BACKGROUND_PROCESSING_CONCURRENCY=10 - APPLICATION_PROTOCOL=http - DISTANCE_UNIT=km - PROMETHEUS_EXPORTER_ENABLED=false - PROMETHEUS_EXPORTER_HOST=dawarich - PROMETHEUS_EXPORTER_PORT=9394 - SECRET_KEY_BASE=${DAWARICH_SECRET_KEY_BASE:-1234567890} - RAILS_LOG_TO_STDOUT=\u0026#34;true\u0026#34; logging: driver: \u0026#34;json-file\u0026#34; options: max-size: \u0026#34;100m\u0026#34; max-file: \u0026#34;5\u0026#34; healthcheck: test: [\u0026#34;CMD-SHELL\u0026#34;, \u0026#34;bundle exec sidekiqmon processes | grep $${HOSTNAME}\u0026#34;] interval: 10s retries: 30 start_period: 30s timeout: 10s depends_on: dawarich_db: condition: service_healthy restart: true dawarich_redis: condition: service_healthy restart: true dawarich: condition: service_healthy restart: true deploy: resources: limits: cpus: \u0026#34;0.50\u0026#34; # Limit CPU usage to 50% of one core memory: \u0026#34;4G\u0026#34; # Limit memory usage to 2GB dawarich_redis: image: redis:7.4-alpine container_name: dawarich_redis command: redis-server networks: - traefik volumes: - ./etc/dawarich/redis:/var/shared/redis restart: always healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;redis-cli\u0026#34;, \u0026#34;--raw\u0026#34;, \u0026#34;incr\u0026#34;, \u0026#34;ping\u0026#34;] interval: 10s retries: 5 start_period: 30s timeout: 10s dawarich_db: image: postgis/postgis:17-3.5-alpine shm_size: 1G container_name: dawarich_db volumes: - ./etc/dawarich/db:/var/lib/postgresql/data networks: - traefik environment: - POSTGRES_USER=${DAWARICH_POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${DAWARICH_POSTGRES_PW:-password} - POSTGRES_DB=dawarich_production restart: always healthcheck: test: [\u0026#34;CMD\u0026#34;, \u0026#34;pg_isready\u0026#34;, \u0026#34;-U\u0026#34;, \u0026#34;postgres\u0026#34;] interval: 10s retries: 5 start_period: 30s timeout: 10s And here are the environment variables that I have set.\nDAWARICH_SMTP_SERVER=mail.mydomain.net DAWARICH_SMTP_PORT=465 DAWARICH_SMTP_USERNAME=no-reply@mydomain.net DAWARICH_SMTP_PASSWORD=\u0026lt;your password here\u0026gt; DAWARICH_SMTP_FROM=no-reply@mydomain.net DAWARICH_SMTP_DOMAIN=mydomain.net DAWARICH_HOST_NAME=trips DAWARICH_POSTGRES_PW=changeme HOST_DOMAIN=mydomain.net DAWARICH_SECRET_KEY_BASE=changeme # you can generate this with openssl rand -hex 32 Configuration Most of the configuration is done for you already via the provided environment variables.\nI will also note here that while I\u0026rsquo;ve included the settings for SMTP - I have yet to actually get it working.\nFrom what I understand, SMTP support is relatively new to this application, so it could be either me being hamfisted, or an actual bug. I have not sorted that out yet.\nBut it\u0026rsquo;s not critical, so I chose to leave it there and move on.\nThe default username and password are demo@dawarich.app and password. Obviously we\u0026rsquo;ll need to change this\u0026hellip; but this is more complicated than you might expect, as I\u0026rsquo;ll get to in a moment.\nNitpicks User management in the Dawarich web UI is extremely basic, almost to the point of being useless. The only thing you can do is create a new, unprivileged user.\nIf you want to create a new admin account and get rid of the default one, you\u0026rsquo;ll have to drop into the Ruby console for that.\nThis is detailed on their website, but it\u0026rsquo;s not ideal.\nFixes To create a new admin user First, create a new user from the web interface.\nThen do the following steps on the command line:\n# Shell into the container docker exec -it dawarich_app /bin/sh # Enter the Ruby console bin/rails console # Promote your user to admin User.find_by(email: \u0026#39;user@example.com\u0026#39;).update(admin: true) # Exit the ruby console. exit If you\u0026rsquo;d like to delete the default admin user (and I recommend that you do) enter this command before exiting the Ruby console:\nUser.find_by(email: \u0026#39;demo@dawarich.app\u0026#39;).destroy Using Dawarich with OwnTracks Using Dawarich with OwnTracks is quite straightforward.\nGet API URL Log into the Dawarich web interface. Click on your email address in the top right hand side of the page and go to Account. From there you can copy the API URL example they\u0026rsquo;ve provided for OwnTracks. Configuring the app Install the app on your smartphone from your app store of choice (I like using F-Droid whenever possible) and then go into Preferences In Preferences, click on Connection Change the Mode to HTTP Enter the URL into the provided field. You do not need to set a username and password under Credentials - the URL you copied earlier contains the needed API key for authentication. ","permalink":"https://blog.ssb-tech.net/posts/dawarich-google-location-history-alternative/","summary":"\u003cdiv class=\"callout callout-note\" role=\"note\"\u003e\u003cdiv class=\"callout-title\"\u003eNote\u003c/div\u003e\u003cdiv class=\"callout-content\"\u003e2025/05/24 - edited to fix some mistakes I caught later in my docker compose.\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003eContinuing on the theme of moving all of my stuff out of \u003cem\u003eother people\u0026rsquo;s cloud\u003c/em\u003e and into mine - I was recently made aware of \u003ca href=\"https://dawarich.app\"\u003eDawarich\u003c/a\u003e while listening to the \u003ca href=\"https://linuxunplugged.com/614\"\u003eLinux Unplugged\u003c/a\u003e podcast.\u003c/p\u003e\n\u003cp\u003eThis is touted as an alternative to Google\u0026rsquo;s Location History feature, which is something that I use frequently. I need it to account for my time at work, so it\u0026rsquo;s fairly important that I have a solution like this.\u003c/p\u003e","title":"Dawarich - A Google Location History Alternative"},{"content":"Replacing Pocket Casts Overview I\u0026rsquo;ve been a user of Pocket Casts for a long time now. There\u0026rsquo;s nothing wrong with it, and in fact it\u0026rsquo;s been a fantastic experience the entire time I\u0026rsquo;ve used it.\nHowever, I\u0026rsquo;ve been trying to move whatever I can to open-source, self-hosted projects, and this was one that I\u0026rsquo;d wanted to take a crack at for quite a while.\nHere were my requirements for a podcast manager:\nSelf-hosted and open-source. The ability to listen on either mobile or desktop, and pick up from where I left off in either location. I went through a few iterations of this, and I\u0026rsquo;m still not quite happy with the result, but let me go over the projects that I tried out, and the issues I ran into with them.\nPinePods PinePods is a newer project, built mostly with Rust and Python.\nI tried this one first because it had a pretty web interface, a companion Android app, and support for sync through Gpodder compatible servers like Opodsync or the NextCloud GpodderSync app..\nInstallation was fairly straightforward, and I chose to integrate it with my existing Authentik instance for OIDC sign on.\nAll of that worked great - until I installed the mobile app and realized that OIDC is not yet supported. I assume that this is the reason that I was not seeing my progress synced between the mobile app and the web interface.\nNext I tried using AntennaPod for the mobile app, with GpodderSync on NextCloud as the backend.\nSomehow enabling GpodderSync in PinePods wiped out all of my existing podcast subscriptions. Thankfully I had them all backed up in an OPML file still, so it was just a matter of re-importing the file again.\nHowever, none of the podcasts I had added to PinePods ever showed up in GpodderSync on NextCloud. However, adding a podcast via AntennaPod worked fine.\nSupposedly there is a large update to sync coming to PinePods in the next few days, but I chose to move on and try out something else.\nPodFetch PodFetch is another podcast manager. This one comes with its own built-in Gpodder sync mechanism that just needs to be enabled via an environment variable.\nThe good:\nI actually think PodFetch has the nicer UI of the two.\nThe bad:\nI found the documentation for this project severely lacking and often found myself digging through GitHub issues to try and figure out how to configure certain things correctly.\nOnce again, OIDC is essentially a non-starter unless you\u0026rsquo;re not planning on ever using a mobile app.\nAlso, make sure that the user you try to connect AntennaPod with is not an administrator, or it will just fail. I had to enable additional logging in PodFetch to figure that out.\nOnce I finally figured out that headache, I discovered that sync is essentially broken. Listening to a podcast on either end and then forcing a full sync in AntennaPod does not change either side. I\u0026rsquo;m not sure what\u0026rsquo;s going on here, but this was the nail in the coffin. I was no longer interested in spending more time trying to get this working.\nNextPod Next, I tried out NextPod. This is the companion app to GpodderSync.\nIt is extremely basic and missing what I\u0026rsquo;d consider basic features like skip forward and back buttons, but at the very least - the synchronization of listen state works reliably.\nFor the moment I\u0026rsquo;ve settled on this setup:\nGpodderSync for the backend, which keeps track of my subscriptions and listen state NextPod for listening on desktop AntennaPod for listening on mobile It does essentially everything that was on my laundry list, but I\u0026rsquo;m not particularly happy with NextPod as my desktop solution. It works, but it lacks even the most basic management features, and the playback UI is, as I said, barebones.\nIf anyone else has any suggestions I\u0026rsquo;d like to hear about them. I\u0026rsquo;ll check out the updated PinePods when the sync updates drops, but for the moment I\u0026rsquo;ll leave this as it is.\nUpdate: I was just made aware of RePod which looks much more functional than NextPod.\n","permalink":"https://blog.ssb-tech.net/posts/replacing-pocket-casts/","summary":"\u003ch1 id=\"replacing-pocket-casts\"\u003eReplacing Pocket Casts\u003c/h1\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve been a user of \u003ca href=\"https://pocketcasts.com\"\u003ePocket Casts\u003c/a\u003e for a long time now. There\u0026rsquo;s nothing wrong with it, and in fact it\u0026rsquo;s been a fantastic experience the entire time I\u0026rsquo;ve used it.\u003c/p\u003e\n\u003cp\u003eHowever, I\u0026rsquo;ve been trying to move whatever I can to open-source, self-hosted projects, and this was one that I\u0026rsquo;d wanted to take a crack at for quite a while.\u003c/p\u003e\n\u003cp\u003eHere were my requirements for a podcast manager:\u003c/p\u003e","title":"Replacing Pocket Casts"},{"content":"Bit of a PSA of sorts, as this is the second time that I have run into this particular issue.\nScenario: You had a Unifi gateway such as a UDM Pro with a static IP address. You switched your WAN IP from static to dynamic, for whatever reason. (In my case it was a different ISP that doesn\u0026rsquo;t hand out actual static IPs, only DHCP reservations.)\nYou may find that the router holds onto that old IP, and if you\u0026rsquo;re like me, you\u0026rsquo;ll be confused as hell.\nThe cause is your IPSec site-to-site tunnel.\nYou\u0026rsquo;ll need to make note of any settings it had (Pre-shared keys, remote endpoints, remote subnets, etc), delete the tunnel completely, and then once you\u0026rsquo;ve successfully gotten your new IP from DHCP, recreate the tunnel from scratch.\nOnce you remove the tunnel, you should get an IP from DHCP almost immediately.\n","permalink":"https://blog.ssb-tech.net/posts/unifi-ipsec-and-dhcp/","summary":"\u003cp\u003eBit of a PSA of sorts, as this is the second time that I have run into this particular issue.\u003c/p\u003e\n\u003cp\u003eScenario: You had a Unifi gateway such as a UDM Pro with a static IP address. You switched your WAN IP from static to dynamic, for whatever reason. (In my case it was a different ISP that doesn\u0026rsquo;t hand out actual static IPs, only DHCP reservations.)\u003c/p\u003e\n\u003cp\u003eYou may find that the router holds onto that old IP, and if you\u0026rsquo;re like me, you\u0026rsquo;ll be confused as hell.\u003c/p\u003e","title":"PSA: Unifi IPSec site-to-site tunnels and DHCP"},{"content":"Overview ZFS replication is my favored way of doing backups. In my homelab I have 2 systems running TrueNAS scale - one on my production hypervisor, with the disk controller passed through, and the backup server, which is just a baremetal TrueNAS Scale installation.\nUnfortunately, most of the tutorials on this process seem to use either the root account or at the very least, an account with admin privileges. In this article, I\u0026rsquo;m going to go over how you can get a ZFS replication setup working with a completely unprivileged user, without ever leaving the GUI.\nI will be doing a pull replication - where the production system has NO access to the backup system. The backup system will reach out and initiate the backups.\nThat way, there\u0026rsquo;s no risk of the backup server being compromised because you had SSH credentials for it just lying around on production.\nYou should also lock down access to the backup system in other ways, but that\u0026rsquo;s beyond the scope of this article.\nCreate user accounts for backup Go to Credentials \u0026gt; Users and create new users. Just create normal user accounts. Don\u0026rsquo;t give it any extra permissions. I also recommend unchecking the box that allows it to be used for SMB authentication.\nMake sure that you change the login shell from \u0026ldquo;nologin\u0026rdquo; to \u0026ldquo;bash\u0026rdquo; or \u0026ldquo;zsh\u0026rdquo;.\nI normally just call mine backup_user, but you can make it anything you want.\nCreate SSH credentials On the backup system: Click Credentials \u0026gt; Backup Credentials Add an SSH Keypair. Give it a name. Click on generate keypair. This will generate a PUBLIC key and a PRIVATE key. The private key will be used on the backup system to verify the public key. I recommend copying the public key into a notepad or something. We\u0026rsquo;ll need it again in a moment. Click Save. Add a new SSH Connection. Name it something that makes sense. Setup method - manual For host, enter the IP address of the production system. If your machine is offsite and you have them both connected via Tailscale, you can use the Tailscale IP here, even if the machine is local. Tailscale connections are designed to be peer-to-peer, so it should stay in the same local network and get the full wire rate. For username, change it to the username of the user you created on the REMOTE system. That is, the PRODUCTION system. Select the private key you created in step 2. Click \u0026ldquo;Discover remote host key\u0026rdquo;. Click Save. On the production system: Navigate to Credentials \u0026gt; Users. Edit the user you created for your backup tasks. Take the public key you copied earlier and paste it into the Authorized Keys field. (Or upload it if you saved it as a file.) Click save. Giving users needed permissions Because we\u0026rsquo;re not using the root user, we need to give our backup users the ability to run certain commands with sudo, without a password. None of these commands can be used destructively. Navigate back to Credentials \u0026gt; Users. Edit the backup user. On the backup system: Type the following into the \u0026ldquo;Allowed sudo commands with no password\u0026rdquo; box. The asterisks are wildcards. /sbin/zfs mount * /sbin/zfs create * /sbin/zfs receive * On the production system: Type the following into the \u0026ldquo;Allowed sudo commands with no password\u0026rdquo; box. The asterisks are wildcards. /sbin/zfs send * /sbin/zfs snapshot * /sbin/zfs list * /sbin/zfs get * This will allow a completely unprivileged user to run the backups for us.\nCreating the replication task On the backup system: Navigate to Data Protection \u0026gt; Replication Tasks. Click Add. For the source location, choose \u0026ldquo;On a different system\u0026rdquo;. Choose the SSH connection we created earlier. When prompted if you would like to use sudo for ZFS commands, allow it. This is needed because the user we\u0026rsquo;re doing this operation with does not have admin privileges. For the source, choose the dataset that you would like to back up. I usually also check off the recursive box, though this has no effect unless you\u0026rsquo;ve created nested datasets. For the destination, choose either the pool itself or the dataset ABOVE the one you want to replicate to. The dataset CANNOT exist prior to the first replication. It will fail if you create it in advance. You\u0026rsquo;ll need to type in the name of the dataset in the text box. This is slightly unintuitive, I know. I don\u0026rsquo;t know why they did it this way. Choose a schedule and a retention policy. Run your first replication. The first one will take a long time if there is a lot of data to copy, but subsequent runs will take only as much time as it takes to send the changed blocks down the wire. Keep in mind when choosing your retention policies on the source and destination systems that the source and destination systems NEED to have at least one snapshot in common!\nHappy replicating!\n","permalink":"https://blog.ssb-tech.net/posts/truenas-zfs-replication-without-root/","summary":"\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eZFS replication is my favored way of doing backups. In my homelab I have 2 systems running TrueNAS scale - one on my production hypervisor, with the disk controller passed through, and the backup server, which is just a baremetal TrueNAS Scale installation.\u003c/p\u003e\n\u003cp\u003eUnfortunately, most of the tutorials on this process seem to use either the root account or at the very least, an account with admin privileges. In this article, I\u0026rsquo;m going to go over how you can get a ZFS replication setup working with a completely unprivileged user, without ever leaving the GUI.\u003c/p\u003e","title":"TrueNAS Scale - ZFS Replication Without Root"},{"content":"Connecting rclone to SharePoint Online I found the official instructions for this extremely difficult to follow, so here\u0026rsquo;s what worked for me, with an Office 365 Business tenant.\nFirst you need to create a custom client id. The default client ID will likely end up getting throttled, and the token will expire after an hour or so, causing your operations to stall.\nApp Registration Open this link and log in with an administrator account that has privileges to create a new app registration for your tenant. Click \u0026ldquo;New registration\u0026rdquo; Give your app a name, I just called mine \u0026ldquo;rclone\u0026rdquo;. For Supported account types, you want the \u0026ldquo;Accounts in this organizational directory only\u0026rdquo; option. create a redirect URI of type Web. Type (don\u0026rsquo;t copy and paste) this into the URI field. http://localhost:53682/. Don\u0026rsquo;t miss the trailing slash. Copy and keep the Application (client) ID under the app name for later use. Under manage select Certificates \u0026amp; secrets, click New client secret. Enter a description (can be anything) and set Expires to however long you\u0026rsquo;d like. Copy and keep that secret Value for later use (you won\u0026rsquo;t be able to see this value afterwards). Under Manage select API Permissions. Click \u0026ldquo;add a permission\u0026rdquo; and select Microsoft Graph. You want \u0026ldquo;Application Permissions\u0026rdquo;, not \u0026ldquo;Delegated permissions\u0026rdquo;. Give the API key the following permissions: Files.ReadWrite Files.Read.All Files.ReadWrite.All User.Read Sites.Read.All Click the button for \u0026ldquo;Grant Admin consent for \u0026rdquo; Create rclone remote rclone config\nGive the remote a name\nSelect 35 for OneDrive.\nEnter your client ID and client secret when prompted.\nWhen you get to the step where it asks you to authenticate in a browser, it will likely fail no matter what you do. This is okay.\nOpen your rclone.conf in a text editor. On Linux it\u0026rsquo;s by default in $HOME/.config/rclone\nvim $HOME/rclone/rclone.conf\nIn the app registration you created earlier you can find the tenant ID in the overview. You can also find it in the Entra ID admin panel. Save that value.\nAdd the following lines to your rclone.conf\nauth_url = https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/authorize token_url = https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token tenant = YOUR_TENANT_ID client_credentials = true Getting the DriveId Next, get the DriveID of the SharePoint site that you\u0026rsquo;re trying to create a remote for.\nWe\u0026rsquo;ll be using the Microsoft Graph PowerShell module for this. You\u0026rsquo;ll want to connect to Graph with a user that has admin privileges.\nThe \u0026ldquo;site-name\u0026rdquo; mentioned here is the same one at the end of the SharePoint link, like\nhttps://contoso.sharepoint.com/sites/Accounting\nSo in this case, Accounting would be the site name.\nConnect-MgGraph -Scopes \u0026#34;Sites.Read.All\u0026#34; $site = Get-MgSite -Search \u0026#34;Site-name\u0026#34; $drive = Get-MgSiteDrive -SiteId $site.Id $drive | Format-List The last command here will print quite a bit of information - there may be more than one document library associated with a single SharePoint site, especially if you\u0026rsquo;ve enabled Teams for that site.\nAdd this as a line to your configured Sharepoint remote.\ndrive_id = DRIVE_ID_HERE drive_type = documentLibrary Attempt reconnection Run the following command:\nrclone config reconnect \u0026lt;remotename\u0026gt;: At this point you should be able to follow the steps and get rclone connected.\nYou can verify by running:\nrclone ls \u0026lt;remotename\u0026gt;: Don\u0026rsquo;t get me wrong, I\u0026rsquo;m sure there are some unnecessary steps here, but this is what I did, and it works.\nIt will allow you to connect rclone to SharePoint with a custom client ID and avoid it timing out after an hour or so.\nI used this to migrate 3TB of data from a SharePoint site to a local NAS.\nAdditional tips If you still run into issues with rate limiting, you can look into the --tpslimit, --transfers or --checkers options to limit the number of API calls rclone is making to SharePoint. I wound up using --tpslimit 10. It copied at a reasonable speed without making Microsoft throttle me down to nothing.\nrclone official website\n","permalink":"https://blog.ssb-tech.net/posts/using-rclone-for-sharepoint-online/","summary":"\u003ch1 id=\"connecting-rclone-to-sharepoint-online\"\u003eConnecting rclone to SharePoint Online\u003c/h1\u003e\n\u003cp\u003eI found the \u003ca href=\"https://rclone.org/onedrive/\"\u003eofficial instructions\u003c/a\u003e for this extremely difficult to follow, so here\u0026rsquo;s what worked for me, with an Office 365 Business tenant.\u003c/p\u003e\n\u003cp\u003eFirst you need to create a custom client id. The default client ID will likely end up getting throttled, and the token will expire after an hour or so, causing your operations to stall.\u003c/p\u003e\n\u003ch2 id=\"app-registration\"\u003eApp Registration\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eOpen \u003ca href=\"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\"\u003ethis link\u003c/a\u003e and log in with an administrator account that has privileges to create a new app registration for your tenant.\u003c/li\u003e\n\u003cli\u003eClick \u0026ldquo;New registration\u0026rdquo;\u003c/li\u003e\n\u003cli\u003eGive your app a name, I just called mine \u0026ldquo;rclone\u0026rdquo;.\u003c/li\u003e\n\u003cli\u003eFor Supported account types, you want the \u0026ldquo;Accounts in this organizational directory only\u0026rdquo; option.\u003c/li\u003e\n\u003cli\u003ecreate a redirect URI of type Web. Type (don\u0026rsquo;t copy and paste) this into the URI field. \u003ccode\u003ehttp://localhost:53682/\u003c/code\u003e. Don\u0026rsquo;t miss the trailing slash.\u003c/li\u003e\n\u003cli\u003eCopy and keep the Application (client) ID under the app name for later use.\u003c/li\u003e\n\u003cli\u003eUnder manage select Certificates \u0026amp; secrets, click New client secret. Enter a description (can be anything) and set Expires to however long you\u0026rsquo;d like. Copy and keep that secret Value for later use (you won\u0026rsquo;t be able to see this value afterwards).\u003c/li\u003e\n\u003cli\u003eUnder Manage select API Permissions. Click \u0026ldquo;add a permission\u0026rdquo; and select Microsoft Graph. You want \u0026ldquo;Application Permissions\u0026rdquo;, not \u0026ldquo;Delegated permissions\u0026rdquo;.\u003c/li\u003e\n\u003cli\u003eGive the API key the following permissions:\n\u003cul\u003e\n\u003cli\u003eFiles.ReadWrite\u003c/li\u003e\n\u003cli\u003eFiles.Read.All\u003c/li\u003e\n\u003cli\u003eFiles.ReadWrite.All\u003c/li\u003e\n\u003cli\u003eUser.Read\u003c/li\u003e\n\u003cli\u003eSites.Read.All\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eClick the button for \u0026ldquo;Grant Admin consent for \u003corg name\u003e\u0026rdquo;\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"create-rclone-remote\"\u003eCreate rclone remote\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003erclone config\u003c/code\u003e\u003c/p\u003e","title":"Connecting rclone to SharePoint Online"},{"content":"Summary For the past year and half I\u0026rsquo;ve been running my own mailserver using Postfix and Dovecot.\nThis setup works quite well, and I\u0026rsquo;ve also installed Roundcube to have a convenient web frontend. It\u0026rsquo;s nice to have, though most of the time I interact with the mailserver via apps like Thunderbird or FairEmail.\nWhile Roundcube is quite nice, I don\u0026rsquo;t necessarily trust the authentication mechanism to be exposed directly to the internet.\nSince I set this up, I put Apache basic authentication in front of it as a deterrent. However, the issue with basic auth is that it requires you to log in again every single time you restart your browser.\nSo, I decided on a more robust solution. I\u0026rsquo;ve already been running Authentik for some of the other services in my lab that support OIDC, so I decided to tie back into that system, which will give me nice single sign on features.\nConfiguring Apache Here\u0026rsquo;s how you can set this up for yourself.\nFirst, install the Apache module that adds OIDC support:\nsudo apt install mod_auth_openidc Enable the module:\nsudo a2enmod auth_openidc Here\u0026rsquo;s an example Apache virtual host with working OIDC support:\n\u0026lt;VirtualHost *:443\u0026gt; ServerAdmin webmaster@localhost ServerName mail.example.net OIDCProviderMetadataURL \u0026#34;https://auth.example.net/application/o/roundcube-webmail/.well-known/openid-configuration\u0026#34; OIDCClientID redacted OIDCClientSecret redacted OIDCRedirectURI \u0026#34;https://mail.example.net/roundcube\u0026#34; OIDCCacheType file OIDCCacheDir /var/cache/mod_auth_openidc OIDCCryptoPassphrase redacted \u0026lt;Location /roundcube\u0026gt; AuthType openid-connect Require valid-user \u0026lt;/Location\u0026gt; \u0026lt;Location /\u0026gt; AuthType openid-connect Require valid-user \u0026lt;/Location\u0026gt; # Handle the case if someone hits the root location and redirect them to /roundcube RewriteEngine On RewriteCond %{REQUEST_URI} ^/$ RewriteCond %{HTTP_REFERER} !auth.example.net [NC] RewriteRule ^/$ /roundcube/ [R=302,L] ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined SSLEngine on SSLCertificateFile /etc/letsencrypt/live/mail.example.net/cert.pem SSLCertificateKeyFile /etc/letsencrypt/live/mail.example.net/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/mail.example.net/fullchain.pem \u0026lt;/VirtualHost\u0026gt; Once you have this set up, restart apache:\nsudo systemctl restart apache2 On systems other than Ubuntu the service may be called httpd rather than apache2.\nOIDC configuration options OIDCClientId, OIDCProviderMetadataURL and OIDCClientSecret can both be found in your Authentik provider configuration.\nOIDCCryptoPassphrase is only used internally. You can generate any random string for this, I\u0026rsquo;d recommend at least 64 characters.\nDon\u0026rsquo;t forget that you also need to set the redirect URI in the provider settings!\n","permalink":"https://blog.ssb-tech.net/posts/apache-oidc-authentik/","summary":"\u003ch2 id=\"summary\"\u003eSummary\u003c/h2\u003e\n\u003cp\u003eFor the past year and half I\u0026rsquo;ve been running my own mailserver using \u003ca href=\"https://www.postfix.org/\"\u003ePostfix\u003c/a\u003e and \u003ca href=\"https://www.dovecot.org/\"\u003eDovecot\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThis setup works quite well, and I\u0026rsquo;ve also installed \u003ca href=\"https://roundcube.net/\"\u003eRoundcube\u003c/a\u003e to have a convenient web frontend. It\u0026rsquo;s nice to have, though most of the time I interact with the mailserver via apps like Thunderbird or FairEmail.\u003c/p\u003e\n\u003cp\u003eWhile Roundcube is quite nice, I don\u0026rsquo;t necessarily trust the authentication mechanism to be exposed directly to the internet.\u003c/p\u003e\n\u003cp\u003eSince I set this up, I put Apache basic authentication in front of it as a deterrent. However, the issue with basic auth is that it requires you to log in again \u003cem\u003eevery single time\u003c/em\u003e you restart your browser.\u003c/p\u003e","title":"Configuring Apache for OIDC with an Authentik backend"},{"content":"Thoughts on starting a media server in 2025 NoteBe warned that the text below is an opinion piece. My way is not the be-all-end-all, but this is how I would choose to do it if I was starting over from scratch. Recently, someone on one of the less technical Discord servers that I frequent asked a question about setting up Plex.\nWhile Plex is still a perfectly capable media server - I still run it myself, simply because migrating the 20+ users I have using it to Jellyfin would be difficult - there are some privacy concerns over the changes that Plex has made in recent years.\nThis got me thinking, how would I suggest setting up a media server in 2025? What would that look like if someone was starting from nothing?\nThis article is oriented at newer users who have little knowledge of networking, but want to break free from the tyranny that is their cable provider or the various streaming services.\nI used to recommend Alex Kretzschmar\u0026rsquo;s PerfectMediaServer website to people who are getting started, but that\u0026rsquo;s gotten a bit too complicated, in my opinion. (Sorry Alex, but I don\u0026rsquo;t think I want to push Linux newbies towards NixOS.)\n1. Which media server do I want to use? If you don\u0026rsquo;t have interest in sharing your media outside of your home, go with Jellyfin.\nIt\u0026rsquo;s a newer piece of software, but it is completely free and open source, and hardware transcoding is not locked behind a paywall like it is with Plex.\nAnother potential option is Emby, but while I\u0026rsquo;ve heard good things about it (Jellyfin happens to be a fork of it!) I have very little experience with it personally.\nPlex is still a viable option if you\u0026rsquo;re not super technical and want to be able to share your media library easily with friends and family. Just know that running Plex does now come with some privacy implications.\nIf you do go with Plex, make sure you follow the steps in this Reddit comment to disable most of the problematic \u0026ldquo;features\u0026rdquo;.\n2. What hardware do I need? The hardware requirements for running a basic, single person media server are more modest than you might expect. What I would recommend for someone getting started is a PC with a relatively recent Intel processor, 8GB of RAM, and an SSD boot drive. Something like an off-lease Dell Optiplex 3060 micro PC from eBay would work fine.\nWhat\u0026rsquo;s the reason for recommending an Intel CPU specifically?\nIn short, QuickSync.\nIntel QuickSync is a hardware transcoding technology integrated into the iGPU on modern Intel CPUs. This means that you don\u0026rsquo;t need a separate video card for handling transcoding tasks.\nTranscoding is important for media servers, as they often need to transcode one video format to another on the fly. Without hardware transcoding, you\u0026rsquo;ll be stuck with software transcoding, which will result in a ton of unnecessary CPU usage and power draw.\nQuickSync is by far the best in class when it comes to price to performance and performance per watt.\nThat said, avoid the earlier iterations of QuickSync, as they were far less performant.\nAnything above 8th generation Intel should be sufficient for this task. Think i5-8500, i7-8700 - even the i3 variant would probably be enough for a low power machine with only a few users.\nYou\u0026rsquo;ll also need some storage. This doesn\u0026rsquo;t have to be anything special - if your data needs are modest, you can probably get away with a single hard drive, or even a USB external hard drive, if that\u0026rsquo;s all you have on hand and you want to get started.\nIf you need more space, though, look into getting a NAS to store the media. This will be a separate box that your media server will talk to over the network in order to get to the media files.\nThere are pre-assembled solutions such as the TrueNAS Mini, or Synology. However, these almost always come without hard drives that you will have provide yourself, and the units themselves are very expensive for the hardware that you\u0026rsquo;re getting.\nIf you\u0026rsquo;d prefer to go the DIY route - I recommend having a look at Brian C. Moses\u0026rsquo; blog. He has some excellent guides for setting up affordable and power efficient systems that don\u0026rsquo;t compromise too much on performance.\nIn case you were wondering - yes - you can combine both of these devices into one, and run your media server on your NAS. Just make sure you adjust your hardware requirements accordingly.\nYou will need a more powerful processor and more RAM to run both of these services in one box.\nI would recommend at minimum an i5 processor, 8th gen or better, and 16GB of RAM. 32GB of RAM would be preferable.\n3. What operating system should I run this on? I know it\u0026rsquo;s a difficult thing if all you\u0026rsquo;ve ever touched is Windows or Mac, but I don\u0026rsquo;t recommend running your media server on either of those platforms.\nYou could run the media server on either Unraid or TrueNAS Scale - nothing wrong with either of those platforms, but I do recommend taking the time to learn how to install a Linux operating system like Ubuntu and a containerization stack like Docker.\nNoteI deliberately put the link for the Ubuntu Desktop installer there, as it will be less scary than the server operating systems I normally use, which are CLI only, without a graphical user interface. Docker will give you far more flexibility when you\u0026rsquo;re running it on a general purpose Linux OS rather than a specialized one like TrueNAS or Unraid.\nBut, it\u0026rsquo;s all about what you\u0026rsquo;re comfortable with. If you want to get started with your media server while running everything on Windows - go for it!\n4. Is there any other software that I should use? Oh boy. I\u0026rsquo;m so glad you asked.\nThis is a rabbit hole beyond imagining - once you start, I dare you to stop until everything with regards to your media collection is automated.\nI will not go into more detail here, because it will make this post a mile long if I do. Maybe I\u0026rsquo;ll make another post at some point going into detail about how I, in particular, have my media stack configured.\nIn no particular order:\nSonarr - TV show collection organizer / downloader Radarr - Movies collection organizer / downloader Lidarr - Music collection manager / downloader Prowlarr - Indexer manager for the other *Arr apps. Bazarr - Subtitles manager Overseerr / Jellyseerr - Manage requests for movies and TV shows. Sabnzbd - Usenet client Recylarr - Automatically applies the TraSH Guides recommendations to your media. (I\u0026rsquo;ll admit this one is a bit niche, but I love it.) Note(Jellyseerr being a fork of Overseerr that works with Jellyfin. Supposedly, Jellyfin support is coming to Overseerr, but it hasn\u0026rsquo;t made it there yet.) 5. Final Thoughts Some final notes before I wrap this up.\nPlease, if you care about your own sanity - organize your media in a way that makes sense.\nMake a central directory on whatever you\u0026rsquo;re using to store everything and call it whatever.\n/mnt/media, D:\\media, /Volumes/Rick Astley Fan Page, I don\u0026rsquo;t care.\nI highly recommend structuring it the way they suggest to in the aforementioned TraSH Guides as it will save you a lot of headaches down the line.\nIt all comes back to this - you have a central folder, whatever it is called, and all of your media goes inside it, and is broken down further from there.\nThis allows you to have one folder you need to share out rather than twenty, and in most cases will allow you to use hard links.\nThis also leads me into something else that is very important that will be made easier by having everything centralized into one root directory.\nBackups. I don\u0026rsquo;t care if you have hardware RAID, or ZFS, btrfs, whatever. I don\u0026rsquo;t care what level of redundancy you have.\nRAID is not a backup!\nIf you don\u0026rsquo;t care if you lose your media collection, that\u0026rsquo;s fine. But if you\u0026rsquo;re like me, you\u0026rsquo;ve been collecting and curating this stuff for years and losing it would hurt. So, I have backups.\nEven if your backup is just copying one external hard drive to another periodically - that\u0026rsquo;s better than nothing.\nI\u0026rsquo;m of the opinion that backups should be automated, and monitored at all times, but that\u0026rsquo;s beyond the scope of this particular post.\nBut please, however you have to do it - make backups!\n","permalink":"https://blog.ssb-tech.net/posts/media-server-thoughts/","summary":"\u003ch1 id=\"thoughts-on-starting-a-media-server-in-2025\"\u003eThoughts on starting a media server in 2025\u003c/h1\u003e\n\u003cdiv class=\"callout callout-note\" role=\"note\"\u003e\u003cdiv class=\"callout-title\"\u003eNote\u003c/div\u003e\u003cdiv class=\"callout-content\"\u003eBe warned that the text below is an opinion piece. My way is not the be-all-end-all, but this is how \u003cem\u003eI\u003c/em\u003e would choose to do it if I was starting over from scratch.\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003eRecently, someone on one of the less technical Discord servers that I frequent asked a question about setting up \u003ca href=\"https://plex.tv\"\u003ePlex\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eWhile Plex is still a perfectly capable media server - I still run it myself, simply because migrating the 20+ users I have using it to \u003ca href=\"https://jellyfin.org\"\u003eJellyfin\u003c/a\u003e would be difficult - there are some \u003ca href=\"https://www.reddit.com/r/selfhosted/comments/180maoe/plex_crossed_a_line_with_your_week_in_review/\"\u003eprivacy concerns\u003c/a\u003e over the changes that Plex has made in recent years.\u003c/p\u003e","title":"Thoughts on starting a media server in 2025"},{"content":"This post is a bit of a PSA of sorts.\nI spent at least an hour trying to figure out why this wasn\u0026rsquo;t working.\nAs it turns out, in order for this feature to work, you\u0026rsquo;ll need to first turn on audit logging in your Office 365 tenant.\nMaybe this seems obvious to some - it wasn\u0026rsquo;t at all obvious to me. I wrongly assumed that these events would be raised in some way - not that the alerts were just tracking the user activity logs.\nThe Microsoft documentation I referenced above also claims that logging is enabled by default - but on every 365 tenant I checked (and through my work I have access to more than 2 dozen) it was NOT turned on by default, even on several newly created tenants. This feature really should be on by default.\nOnce you enable the feature it may still be up to 24 hours before your alerts start working.\nI\u0026rsquo;m currently using this feature to track when users submit requests to release messages from Quarantine - anyone else have some useful policies? Share down in the comments.\n","permalink":"https://blog.ssb-tech.net/posts/365-quarantine-release-alerts/","summary":"\u003cp\u003eThis post is a bit of a PSA of sorts.\u003c/p\u003e\n\u003cp\u003eI spent at least an hour trying to figure out why this wasn\u0026rsquo;t working.\u003c/p\u003e\n\u003cp\u003eAs it turns out, in order for this feature to work, you\u0026rsquo;ll need to first \u003ca href=\"https://learn.microsoft.com/en-us/purview/audit-log-enable-disable?tabs=microsoft-purview-portal#turn-on-auditing\"\u003eturn on audit logging in your Office 365 tenant\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eMaybe this seems obvious to some - it wasn\u0026rsquo;t at all obvious to me. I wrongly assumed that these events would be raised in some way - not that the alerts were just tracking the user activity logs.\u003c/p\u003e","title":"PSA - alert policies in Office 365 depend on audit logging"},{"content":"Overview Recently I watched the following YouTube video by apalrd on how he was using Nginx as a caching reverse proxy for Debian, Ubuntu, and other APT repositories.\nI was inspired by this to do the same, but instead of spinning up an LXC for this task, I\u0026rsquo;m using the built-in Docker functionality in TrueNAS. With Electric Eel introducing Docker Compose support, TrueNAS is now far more flexible in this regard.\nHere\u0026rsquo;s how to accomplish this: 1. Create ZFS datasets If you don\u0026rsquo;t already have one, I recommend having a single root dataset to manage all of your docker app configurations. I just called mine appdata. With how TrueNAS handles mounting datasets, the mountpoint for this ends up being /mnt/\u0026lt;poolname\u0026gt;/appdata. This will be important later. My pool is just named tank, so I\u0026rsquo;ll be using that name going forward.\nCreate 2 child datasets of appdata - one for the nginx config, and one for the actual proxy cache (as well as our access.log and error.log)\n/mnt/tank/appdata/debcache /mnt/tank/appdata/nginx I recommend setting the owner of these datasets to be systemd-timesync, as the UID TrueNAS uses for that user matches up with the UID of the nginx user inside of the container. It will save a lot of headaches with regards to permissions. Alternatively, you could tell Nginx to run as a specific user. 2. Create Nginx configuration file Here\u0026rsquo;s my (slightly modified) version of apalrd\u0026rsquo;s debcache.conf:\n# Global Cache settings proxy_cache_path /var/debcache/cache levels=2:2 keys_zone=generic:500m inactive=90d max_size=1000g min_free=5g loader_files=1000 loader_sleep=50ms loader_threshold=300ms use_temp_path=off; # Log with cache status log_format cachelog \u0026#39;$remote_addr [$time_local] \u0026#34;$request\u0026#34; $status \u0026#34;$http_user_agent\u0026#34; \u0026#34;$upstream_cache_status\u0026#34;\u0026#39;; # URI paths to avoid cache # These paths will change to indicate new release contents # All other .deb files can be cached nearly indefinitely, as the # version number is coded into the file name. map $request_uri $nocache { ~InRelease 1; ~Release 1; ~Contents 1; } # Deb Server server { # IF you need legacy IP, enable this one listen 80 reuseport; #Log settings access_log /var/debcache/access.log cachelog; error_log /var/debcache/error.log; # Cache Location slice 1m; proxy_cache generic; proxy_ignore_headers Expires Cache-Control; proxy_cache_valid 200 206 90d; proxy_cache_valid 301 302 0; proxy_set_header Range $slice_range; proxy_cache_lock on; proxy_cache_lock_age 2m; proxy_cache_lock_timeout 1h; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; proxy_cache_revalidate on; #Nocache for those entries proxy_cache_bypass $nocache; proxy_no_cache $nocache; # 1G max file proxy_max_temp_file_size 1024m; # Cache key proxy_cache_key $http_host$uri$slice_range; # Upstream Configuration proxy_next_upstream error timeout http_404; # Cache status add_header X-Cache-Status $upstream_cache_status; proxy_redirect off; proxy_ignore_client_abort on; # Upstream request headers proxy_ssl_server_name on; # Redirect Locations # Must include trailing slash! # Debian location /debian/ { proxy_pass http://deb.debian.org/debian/; proxy_set_header Host \u0026#34;deb.debian.org\u0026#34;; } location /debsec/ { proxy_pass http://deb.debian.org/debian-security/; proxy_set_header Host \u0026#34;deb.debian.org\u0026#34;; } # Ubuntu location /ubuntu/ { proxy_pass http://us.archive.ubuntu.com/ubuntu/; proxy_set_header Host \u0026#34;us.archive.ubuntu.com\u0026#34;; } location /ubusec/ { proxy_pass http://security.ubuntu.com/ubuntu/; proxy_set_header Host \u0026#34;security.ubuntu.com\u0026#34;; } # Kali location /kali/ { proxy_pass http://http.kali.org/kali/; proxy_set_header Host \u0026#34;http.kali.org\u0026#34;; } # Proxmox (non-enterprise) location /proxmox/ { proxy_pass http://download.proxmox.com/debian/; proxy_set_header Host \u0026#34;download.proxmox.com\u0026#34;; } # Caddy Server location /caddy/ { proxy_pass https://dl.cloudsmith.io/public/caddy/stable/deb/debian/; proxy_set_header Host \u0026#34;dl.cloudsmith.io\u0026#34;; } # Nodesource NodeJS location /node/ { proxy_pass https://deb.nodesource.com/node_20.x/; proxy_set_header Host \u0026#34;deb.nodesource.com\u0026#34;; } # Stats endpoint location = /nginx_status { stub_status; } # Static Files (conversion scripts) root /var/debcache/static/; autoindex on; } NoteSave this configuration file to /mnt/tank/appdata/nginx/debcache.conf on your TrueNAS server. Some notes on what I\u0026rsquo;ve changed:\nInactive items are only kept for 90 days instead of 10 years. Fixed a missing slash in the nodesource repository (by the time you read this it may be fixed on apalrd\u0026rsquo;s original post). 3. Change the HTTP listening port for the TrueNAS GUI so that Nginx can bind that port instead. Go to System \u0026gt; General Settings and click on Settings under the GUI box. Change the Web Interface HTTP port to something other than 80. I\u0026rsquo;ve set mine to 8080. 4. Install Nginx as a custom app Go to the Apps screen, click on Discover Apps, and click on the three dots to the right of Custom App. You should have an option to Install via YAML.\nPaste the below compose syntax into the Custom Config box. Change the path names if necessary.\nservices: nginx: container_name: nginx-debcache image: nginx ports: - \u0026#39;80:80\u0026#39; restart: unless-stopped volumes: - /mnt/tank/appdata/debcache:/var/debcache - /mnt/tank/appdata/nginx:/etc/nginx/conf.d:ro Click save, and the Nginx container should deploy.\nYou can test the configuration by going to http://your-truenas-ip/debian. It should be proxying the Debian apt repository. If you see the index for that repo, the proxy is working.\n5. Configure a DNS record (optional) This step is technically optional, but I recommend it. Whatever you\u0026rsquo;re using for local DNS resolution, whether it be PiHole, BIND, dnsmasq, etc - create a DNS A record or CNAME for TrueNAS that you\u0026rsquo;ll use for this specific purpose.\n6. Modify your sources.list files I recommend either using the script that apalrd created, or the Ansible playbook that I wrote..\nPlease leave a comment if you run into any problems with the playbook - I have come across one or two edge cases that I didn\u0026rsquo;t account for already - if there are any more, please let me know which repositories and I will attempt to fix it. No promises on turnaround time, though. :P\nConclusion And that\u0026rsquo;s all she wrote! You should now have a caching web proxy running off your TrueNAS server with access to that big honking storage array you\u0026rsquo;ve got in your basement with all the Linux ISOs on it.\nPlease leave a comment if you found this useful.\n","permalink":"https://blog.ssb-tech.net/posts/caching-apt-repositories/","summary":"\u003ch1 id=\"overview\"\u003eOverview\u003c/h1\u003e\n\u003cp\u003eRecently I watched the following YouTube video by \u003ca href=\"https://www.apalrd.net\"\u003eapalrd\u003c/a\u003e on how he was using Nginx as a caching reverse proxy for Debian, Ubuntu, and other APT repositories.\u003c/p\u003e\n\u003cdiv style=\"position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;\"\u003e\n      \u003ciframe allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen\" loading=\"eager\" referrerpolicy=\"strict-origin-when-cross-origin\" src=\"https://www.youtube.com/embed/ydfsjWDPDyU?autoplay=0\u0026amp;controls=1\u0026amp;end=0\u0026amp;loop=0\u0026amp;mute=0\u0026amp;start=0\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;\" title=\"YouTube video\"\u003e\u003c/iframe\u003e\n    \u003c/div\u003e\n\n\u003cp\u003eI was inspired by this to do the same, but instead of spinning up an LXC for this task, I\u0026rsquo;m using the built-in Docker functionality in TrueNAS. With \u003ca href=\"https://www.truenas.com/blog/truenas-electric-eel-powers-up-your-storage/\"\u003eElectric Eel introducing Docker Compose support\u003c/a\u003e, TrueNAS is now far more flexible in this regard.\u003c/p\u003e","title":"Caching Apt Repositories on TrueNAS"},{"content":"NoteZFS 2.2.7 has been released with up to Linux kernel 6.12 support, so this workaround is no longer necessary. I\u0026rsquo;ll leave the post up in case this is ever useful in the future. I previously posted about how to build a desktop OS from the Fedora Server installer.\nThis is a bit of a follow up to that post.\nI\u0026rsquo;ve now been running Fedora 41 for a few days and it\u0026rsquo;s been a mostly pleasant experience.\nHowever, I am also an avid ZFS user, and the current stable release of OpenZFS does not support Linux kernel 6.11 yet.\nSo, being the enterprising fellow that I am, I chose to build ZFS 2.3.0 RC3 from source using the official instructions\nThat went well, but one thing that tripped me up (and caused some issues for specific applications like syncthing, which keeps it\u0026rsquo;s local database in the user\u0026rsquo;s home directory) is that when you do this, unlike when you install zfsutils-linux on Ubuntu, it doesn\u0026rsquo;t set up the automatic import and mounting of your pool.\nIt took some digging around to find the correct process, because it seems to be a bit different between the official ZFS source and the one that ships with Ubuntu, but here\u0026rsquo;s what wound up working for me:\nFirst, go through and enable the following services:\nsudo systemctl enable zfs.target zfs-mount.target zfs-import.target zfs-import-cache.service There are plenty of posts online telling you that you should create your cache file in /etc/zfs/zfs-list-cache/, but this is wrong (or at least outdated. I’m not sure if that location is Ubuntu specific?)\nThe ACTUAL location of the cache file (which you can see if you actually read through the systemd unit file for zfs-import-cache.service) is /usr/local/etc/zfs.\nIf the file doesn\u0026rsquo;t exist, create it.\nsudo touch /usr/local/etc/zfs/zpool.cache Import your pool.\nsudo zpool import poolname If you print out the cache file now it should no longer be empty.\ncat /usr/local/etc/zfs/zpool.cache Next time you reboot your pools should automatically mount and mount all filesystems.\nAnd yes, I did cross post this over on the PracticalZFS forum.\n","permalink":"https://blog.ssb-tech.net/posts/fedora41-and-zfs/","summary":"\u003cdiv class=\"callout callout-note\" role=\"note\"\u003e\u003cdiv class=\"callout-title\"\u003eNote\u003c/div\u003e\u003cdiv class=\"callout-content\"\u003eZFS 2.2.7 has been released with up to Linux kernel 6.12 support, so this workaround is no longer necessary.\nI\u0026rsquo;ll leave the post up in case this is ever useful in the future.\u003c/div\u003e\u003c/div\u003e\n\u003cp\u003eI previously posted about how to \u003ca href=\"/posts/building-fedora-server-into-a-desktop/\"\u003ebuild a desktop OS from the Fedora Server installer\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThis is a bit of a follow up to that post.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve now been running Fedora 41 for a few days and it\u0026rsquo;s been a mostly pleasant experience.\u003c/p\u003e","title":"Fedora 41 and ZFS"},{"content":"How to build up Fedora Server Edition to a desktop In this article I\u0026rsquo;ll go over how to take the Fedora Server Edition image and build it up into a full desktop.\n1. Download the Fedora Server Edition ISO. Download the latest .iso (DVD) release from here.\nBurn it to a USB stick using whichever method you prefer, and boot into it.\n2. Set Software Selections to custom Once in the Fedora installer, the first step we want to take is to go into \u0026ldquo;Software Selections\u0026rdquo; and set it to \u0026ldquo;custom\u0026rdquo; instead of the Server Edition.\nThe \u0026ldquo;Server Edition\u0026rdquo; option will install the Cockpit web management package along with the OS. Not something I want installed on my desktop, personally, but if you think that\u0026rsquo;s something you\u0026rsquo;d like to have, go for it. It will make no difference to the rest of the process.\n3. Create disk partitions. Enter the partition layout editor and select \u0026ldquo;custom\u0026rdquo;.\nFor the \u0026ldquo;New mount points\u0026rdquo; dropdown, pick btrfs.\nClick the plus button to create a new partition.\nWe will create a single partition of 1GB for the EFI bootloader, leaving the rest for the OS and user data.\nSet the mountpoint of this partition to /boot/efi, and check the box to reformat the disk. This will allow you to select the \u0026ldquo;EFI System Partition\u0026rdquo; type.\nFor the rest of the disk, create a single btrfs partition.\nWe will subdivide this using btrfs subvolumes.\nUse the + button to create 4 more \u0026ldquo;partitions\u0026rdquo;, though since we\u0026rsquo;ve already occupied the entire disk these will be subvolumes instead of partitions. You can see this by looking on the right side under the \u0026ldquo;Volume\u0026rdquo; dropdown. By default the btrfs volume name is \u0026ldquo;fedora\u0026rdquo;.\nThere\u0026rsquo;s no real reason to change this, so we\u0026rsquo;ll leave it alone.\nFor the first subvolume, give it a mount point of / and change the name from root to @. For the second subvolume, the mount point will be /home and the name will be @home. Subvolume 3 will be mounted at /var/log and called @log. Subvolume 4 will be mounted at /.snapshots and called @.snapshots. Following these naming conventions will allow us to use the timeshift snapshotting tool, as it expects the Ubuntu btrfs naming conventions, as I\u0026rsquo;ve used here.\nIf you wish to create any more subvolumes, you can do so now, but that\u0026rsquo;s enough for me.\nHere\u0026rsquo;s what it should look like at the end:\nKeep in mind that if you\u0026rsquo;d like to use a different layout than the one I used here, or if you\u0026rsquo;d like to put your /home partition on a different disk entirely, that\u0026rsquo;s entirely OK.\nJust keep in mind - you need at least a root partition / and an EFI partition /boot/efi.\n4. Create a user I recommend creating a user at this point with superuser privileges. You can also, if you wish, enable the root account and give it a password, but I won\u0026rsquo;t bother.\n5. Finish the installation At this point you can proceed with installing the OS.\nOnce the installation is done, reboot your machine and log into your new operating system.\n6. Install updates You\u0026rsquo;ll be greeted with a command line only interface. Don\u0026rsquo;t worry, we\u0026rsquo;ll fix that in a moment.\nFirst, run the below command and get any pending updates out of the way.\nReboot may or may not be necessary if there\u0026rsquo;s not a new kernel.\nsudo dnf upgrade \u0026amp;\u0026amp; sudo reboot now 7. Install software. I don\u0026rsquo;t use a typical desktop environment, I use SwayWM, but I do use GDM as my display manager, so we\u0026rsquo;ll be installing a few Gnome packages.\nIf you wish to install Gnome instead of Sway, you can use the following command:\nsudo dnf group install gnome Install SwayWM sudo dnf group install swaywm Install the Gnome Display Manager (GDM) This package will handle our login sessions and give us the nice login screen to log into Sway with.\nsudo dnf install gdm Set default systemd target Right now, the multi-user.target is the default systemd target on login.\nIf we want our GUI to start upon boot, we need to make this graphical.target instead.\nsudo systemctl set-default graphical.target Install recommended packages These packages can be useful if we need to build anything from source, or if you use anything like Neovim with nvim-treesitter.\nsudo dnf install make automake gcc gcc-c++ kernel-devel tar unzip Install the RPMFusion repos and media codecs. Red Hat does not bundle these codecs with Fedora, so if you want hardware decoding for H264 and H265 (and you do), run this command to take care of all of that.\nsudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm sudo dnf group install multimedia sound-and-video If the above command doesn\u0026rsquo;t work for some reason, try running another sudo dnf upgrade.\nFor some reason the first time I added the rpmfusion repos, the multimedia group wasn\u0026rsquo;t available until after I ran that.\nExtra Goodies Set dark GTK theme by default:\ngsettings set org.gnome.desktop.interface color-scheme \u0026#39;prefer-dark\u0026#39; Most GTK applications will respect this and avoid the eye-rending white backgrounds.\nUpdate 2024/12/24\nYou probably also want to install some fonts. After writing this post, I discovered that Fedora Server is missing a Dingbats font that contains specific symbols.\nYou can get this by installing a font like Symbola.\nI also recommend installing a Nerd Font like FiraCode from the Nerd Font website..\nYou can put them in a few different locations:\n/usr/share/fonts /usr/local/share/fonts ~/.local/share/fonts Personally, I like installing them to /usr/local/share/fonts so that they\u0026rsquo;re available to all users on the system. (Even though I\u0026rsquo;m typically the only user on my systems.)\nDownload the font zip file.\nsudo unzip font.zip -d /usr/local/share/fonts/ sudo chown root:root /usr/local/share/fonts/font*.ttf Start and enable GDM sudo systemctl enable gdm \u0026amp;\u0026amp; sudo systemctl start gdm This will kick you over to the Gnome Display Manager and allow you to log into any desktop environments or Wayland compositors you may have installed.\nIf you\u0026rsquo;re using Gnome, it will be a very basic installation without any packages to speak of besides the absolute necessities, but this is why we install things from the server edition - to get a more minimal install without bloat.\nI\u0026rsquo;ve also created a script to make the process of getting things to a full desktop simpler - feel free to use it as a base!\nFedora setup script gist\n","permalink":"https://blog.ssb-tech.net/posts/building-fedora-server-into-a-desktop/","summary":"\u003ch1 id=\"how-to-build-up-fedora-server-edition-to-a-desktop\"\u003eHow to build up Fedora Server Edition to a desktop\u003c/h1\u003e\n\u003cp\u003eIn this article I\u0026rsquo;ll go over how to take the Fedora Server Edition image and build it up into a full desktop.\u003c/p\u003e\n\u003ch2 id=\"1-download-the-fedora-server-edition-iso\"\u003e1. Download the Fedora Server Edition ISO.\u003c/h2\u003e\n\u003cp\u003eDownload the latest .iso (DVD) release from \u003ca href=\"https://fedoraproject.org/server/download\"\u003ehere.\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eBurn it to a USB stick using whichever method you prefer, and boot into it.\u003c/p\u003e\n\u003ch2 id=\"2-set-software-selections-to-custom\"\u003e2. Set Software Selections to custom\u003c/h2\u003e\n\u003cp\u003eOnce in the Fedora installer, the first step we want to take is to go into \u0026ldquo;Software Selections\u0026rdquo; and set it to \u0026ldquo;custom\u0026rdquo; instead of the Server Edition.\u003c/p\u003e","title":"How to build up Fedora Server Edition to a desktop"},{"content":"I recently set up my OpnSense firewall at home with a connection to AirVPN using Wireguard.\nIt was a bit more complicated than when I did it for Mullvad last year, so I figured I\u0026rsquo;d document it here for anyone who finds this useful.\n1. Device Creation Log into your AirVPN account and navigate to the client area. Click on Manage Devices. You can either edit the existing \u0026ldquo;default\u0026rdquo; device or create a new one. Either way, I recommend editing the name of the device so that you know what it is, 6 months down the line. I called mine \u0026ldquo;Opnsense\u0026rdquo;.\nWhile you\u0026rsquo;re here, I recommend opening up a Notepad or equivalent and copying and pasting the public key that\u0026rsquo;s on this page. We\u0026rsquo;ll need it later.\n2. Generate Wireguard configuration file Next, head back to the client area and head into the Config Generator.\nFor OS, you\u0026rsquo;ll want to choose \u0026ldquo;Router\u0026rdquo;. You\u0026rsquo;ll also want to decide which device (if you have more than one) that this configuration will apply to.\nPick the \u0026ldquo;Wireguard\u0026rdquo; protocol.\nPick a server as well - I picked Switzerland.\nOnce you\u0026rsquo;re done, click the Generate button at the bottom of the page. This will download a Wireguard configuration file - save this, we\u0026rsquo;ll need it in a moment.\n3. Create Wireguard tunnel in Opnsense. Next, we\u0026rsquo;ll be heading into Opnsense. Log in and navigate to the VPN section. Under Wireguard, create a peer.\nName: AirVPN Public Key: you can get this from the wireguard configuration file you downloaded in step 2. Pre-Shared Key: you can get this from the wireguard configuration file you downloaded in Step 2. Allowed IPs: 0.0.0.0/0 Endpoint Address: Get this from the config file as well. Endpoint port: Get this from the config file as well. It\u0026#39;s usually 1637. Keepalive Interval: You can either set this to 15 or leave it blank. Save, and next we\u0026rsquo;ll head over to Instances. Configure a new instance with the following options:\nMake sure you enable advanced features.\nName: AirVPNLocal Public Key: Here you\u0026#39;ll want to put the public key we got in step 1. This is the public key of OPNSENSE. Private Key: Grab this from the config file. It will be under the Interface section. Tunnel address: Set this to the /32 address that was in the config file. It will be something in the 10.128.0.0/10 range. Peer: Select the AirVPN peer we created earlier. MTU: 1320 Disable Routes: Checked Make sure you have Disable Routes checked, it\u0026rsquo;s important.\nHit Save. Don\u0026rsquo;t forget to check the box to enable Wireguard, and hit apply at the bottom of the page.\nOnce done, head over to the \u0026ldquo;Status\u0026rdquo; page - the tunnel should show a status of \u0026ldquo;Up\u0026rdquo; now, but we\u0026rsquo;ve still got some work to do before we can use it.\n4. Create an interface. Head over to Interfaces \u0026gt; Assignments.\nUnder \u0026ldquo;Assign a new interface\u0026rdquo;, pick the Wireguard device we just created. If it\u0026rsquo;s your first one, it\u0026rsquo;ll be wg0.\nClick on the interface, Enable it. I also recommend giving it a descriptive name. Mine is called \u0026ldquo;airvpn_wg\u0026rdquo;.\n5. Create a gateway. Next we have to create a gateway for AirVPN clients to use.\nHead over to System \u0026gt; Gateways \u0026gt; Configuration.\nCreate a new gateway and give it a descriptive name. I called mine AIRVPN_GW.\nHere are the other settings you\u0026rsquo;ll need to configure on this gateway.\nInterface: airvpn_wg Address family: IPv4 IP Address: 10.128.0.1 Far Gateway: checked Disable Gateway Monitoring: checked. Click Save, and then Apply.\n6. Create Outbound NAT rule. Most Wireguard VPN providers will require you to configure outbound NAT, and AirVPN is no exception.\nGo to Firewall \u0026gt; NAT \u0026gt; Outbound and add a rule.\nConfigure only the following settings and leave everything else default.\nInterface: airvpn_wg Source Address: LAN net (or whatever you\u0026#39;re using.) Description: Outbound NAT for AirVPN 7. Configure Policy Routing How we\u0026rsquo;re going to accomplish this is we\u0026rsquo;re going to create an alias for the devices we want to route out over the VPN tunnel.\nCall it VPN_Required or whatever you like.\nOnce you\u0026rsquo;ve created the alias, navigate to Firewall \u0026gt; Rules \u0026gt; Your network.\nCreate a pass rule with the following settings defined:\nInterface: LAN Protocol: Any Source: VPN_Required Destination: Any Gateway: AIRVPN_GW Advanced features: Set local tag: vpntraffic Next head to Rules \u0026gt; Floating and define a Block rule.\nInterface: Your WAN interface Source: Any Protocol: Any Destination: Any Advanced features: Match local tag: vpntraffic This block rule will serve as a \u0026ldquo;kill switch\u0026rdquo; preventing our VPN traffic from leaking if the tunnel goes down for some reason.\n8. Test It I recommend testing the connection at this point. Add your current device\u0026rsquo;s IP address to the alias you\u0026rsquo;re using for policy routing for testing purposes.\nGo to sites like https://ipleak.net/ and make sure that everything is reported correctly. Make sure you don\u0026rsquo;t have any DNS leaks, either - it should show DNS servers in the same region as the AirVPN server that you chose.\n9. Bonus Points - Port Forwarding If you have a need for port forwarding, AirVPN supports up to 5 ports per account.\nHead on back to the Client Area on their website and click on Ports.\nIf you\u0026rsquo;re forwarding ports for a protocol like Bittorrent, you\u0026rsquo;ll need to use the :1 pool of addresses.\nI suggest using AirVPN\u0026rsquo;s tool on the same page to find a free port in that range.\nOnce you\u0026rsquo;ve determined which port to use, configure it for yourself. Configure which device it\u0026rsquo;s for, set the protocol to TCP+UDP.\nI recommend using ipv4 only.\nOn Opnsense, configure a rule on the airvpn_wg interface. (Firewall \u0026gt; Rules \u0026gt; airvpn_wg)\nAction: Pass Protocol: TCP/UDP Destination: The IP address of your box of Linux ISOs. Port: the port you defined in AirVPN. Reply-To: AIRVPN_GW You\u0026rsquo;ll also want to configure a port forward under NAT \u0026gt; Port Forward.\nInterface: airvpn_wg Protocol: TCP/UDP Destination: airvpn_wg address Destination port range: the port you defined in AirVPN. Redirect target IP: The IP address of your box of Linux ISOs. Redirect target port: the port you defined in AirVPN. Now, in your Bittorrent client, make sure you turn off port randomization, and set the port to the same one.\nYou may also have to allow that port through the system firewall, if there is one.\nIf you\u0026rsquo;re running something like Transmission in Docker, don\u0026rsquo;t forget to publish the port in your docker compose.\n","permalink":"https://blog.ssb-tech.net/posts/airvpn-on-opnsense/","summary":"\u003cp\u003eI recently set up my OpnSense firewall at home with a connection to AirVPN using Wireguard.\u003c/p\u003e\n\u003cp\u003eIt was a bit more complicated than when I did it for Mullvad last year, so I figured I\u0026rsquo;d document it here for anyone who finds this useful.\u003c/p\u003e\n\u003ch2 id=\"1-device-creation\"\u003e1. Device Creation\u003c/h2\u003e\n\u003cp\u003eLog into your AirVPN account and navigate to the client area. Click on\nManage Devices. You can either edit the existing \u0026ldquo;default\u0026rdquo; device or\ncreate a new one. Either way, I recommend editing the name of the device\nso that you know what it is, 6 months down the line. I called mine\n\u0026ldquo;Opnsense\u0026rdquo;.\u003c/p\u003e","title":"Setting up OpnSense as an AirVPN client"},{"content":"So, story time.\nBoss has a friend who does IT for an electrical contractor 30 mins or so away from our primary office, and he brings us in because he’s having DNS issues he can’t figure out\nGet onsite there and go over his setup – typical mess of a network closet with no brand consistency and mismatched patch cables, whatever. Otherwise looks good from a network perspective.\nHowever, he mentions that he has no access to the firewall since it’s owned by the ISP.\nPersonally, I would have been on top of the ISP until they either gave me the ability to log into that thing, or had them put it in bridge mode and installed my own firewall. I do not like and do not trust any firewall that I do not control. Sorry, it\u0026rsquo;s just the paranoid sysadmin in me.\nSo we finally start looking at the problem and the issue is that he keeps having to hardcode his DNS settings and he’s having weird WiFi connectivity issues. Everything points to DHCP not setting the DNS settings consistently… which made me suspect a rogue DHCP server.\nSo I connect my laptop and run an ifconfig – and I see that DHCP is coming from some IP address 10.0.0.69. Knowing at this point he didn’t have access to the firewall, I asked him if that was one of his servers, he says no. I asked him where he WAS running DHCP, and was told it was on one of the domain controllers, but he didn’t remember which one. Red flag #1.\nI do an nmap scan to see if I can grab the vendor of the device that has that IP – it reports back that it’s from Axis Communications. Not being familiar with it, I asked about that vendor, and was told that that was the brand of their security cameras and NVR.\nI go okay, so there’s your problem right there. You have 2 DHCP servers, and it’s a question of which one responds first to the DHCPDISCOVER broadcast.\nSo we go look at the cameras – no passwords for ANYTHING. we lucked into finding the password for the computer controlling them written on a piece of paper near the rack it was installed in.\nEventually I figured out that one of the cameras had DHCP running, and we turned it off, but then we tried getting a new DHCP lease – it got one, but in the wrong subnet entirely.\nThis of course, understandably confuses me. I noticed that the DHCP server is now 192.168.0.1, which isn’t even the right subnet. What the deuce?\nRan another network scan, that IP has the same vendor as before, but a different MAC address now, so it’s a totally different device.\nTurns out, the appliance they have running their cameras? it’s 2 devices in one – a windows PC acting as the controller, and the built in switch which has its own DHCP server (and presumably it’s own operating system and mainboard).\nOf course, no password for that either, so I Googled and found it’s on a sticker on the bottom of the unit. I found the password, logged in and turned off DHCP. That’s it, right? We’ve fixed it?\nTried to get a lease once more… nothing happens at all.\nTurns out… he was NOT running DHCP on the domain controller. Or anywhere except for this NVR, that had it turned on by default.\nI installed the DHCP role on one of the domain controllers and configured it with the proper settings, and a reasonable scope, along with primary and secondary DNS for their domain. Did another ipconfig /renew on one of their machines and… success! We got an IP in the correct subnet, and it is assigning the correct DNS servers to the client machines. WiFi works perfectly as well!\nHow the network got into this state and stayed operational for as long as it did? Who can say? Miracles happen every day, right?\nLike they say, it’s always DNS. But sometimes it’s not DNS, sometimes it’s badly misconfigured DHCP.\n","permalink":"https://blog.ssb-tech.net/posts/adventures-in-dhcp-and-dns/","summary":"\u003cp\u003eSo, story time.\u003c/p\u003e\n\u003cp\u003eBoss has a friend who does IT for an electrical contractor 30 mins or so away from our primary office, and he brings us in because he’s having DNS issues he can’t figure out\u003c/p\u003e\n\u003cp\u003eGet onsite there and go over his setup – typical mess of a network closet with no brand consistency and mismatched patch cables, whatever. Otherwise looks good from a network perspective.\u003c/p\u003e\n\u003cp\u003eHowever, he mentions that he has no access to the firewall since it’s owned by the ISP.\u003c/p\u003e","title":"Adventures in DNS (But it's actually DHCP this time.)"}]