[{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/tags/blowfish/","section":"Tags","summary":"","title":"Blowfish","type":"tags"},{"content":"Every technologist eventually reaches a crossroads with their digital presence. You start out hosting notes across various third-party platforms, proprietary publishing services, or heavy CMS setups like WordPress or Ghost. But over time, the friction accumulates: database backups, security patches for abandoned plugins, slow page loads, and fragile build pipelines that crumble the moment your local node or python runtime updates.\nWhen I set out to build Fumoctl, my personal technical corner on the web, I wanted something completely aligned with my engineering philosophy:\nDigital Sovereignty: Every single article, image, and template must live as plain text and standard assets in a Git repository. No hidden databases, no vendor lock-in. Reproducibility \u0026amp; Zero Drift: If I clone this repository on a fresh NixOS workstation, a headless homelab server, or CI/CD five years from now, it must build bit-for-bit identically with a single command. Sub-100ms Build \u0026amp; Load Performance: Instant static asset generation with zero client-side JavaScript framework bloat. Reader-First Technical UX: High-density reading layouts with ample room for long code blocks, sticky table of contents navigation, and seamless search. Here is an architectural walkthrough of how Fumoctl was designed, customized, and deployed using Hugo Extended, Blowfish, and Nix Flakes.\n1. The Core Stack: Hugo Extended + Blowfish # Static site generators are a crowded field, but for pure raw speed, stability, and markdown flexibility, Hugo (written in Go) remains unmatched. Hugo compiles hundreds of content pages in milliseconds without spinning up a heavy V8 runtime.\nFor the theme base, I chose Blowfish (a descendant of Congo built on Tailwind CSS). Blowfish provides:\nClean, semantic HTML5 structure. Pre-configured OpenGraph metadata, RSS feeds, and Twitter cards. Modular layout partials that can be easily overridden in the site\u0026rsquo;s top-level layouts/ directory. However, default theme presets rarely satisfy specific design demands. Blowfish ships with opinions tailored for general writing: narrow reading widths (capped at 65 characters max-w-prose), standard pufferfish branding, and segregated taxonomy pages. To turn it into a dedicated systems engineering blog, we needed deep architectural customizations.\n2. Declarative Environment with Nix Flakes # On a traditional Linux distribution, setting up Hugo might involve apt install hugo or brew install hugo. But on NixOS, dependencies must be declared hermetically. Moreover, standard Hugo repositories often run into discrepancies between vanilla Hugo and hugo-extended (which includes embedded libsass and asset pipelines).\nTo ensure anyone (and any CI system) can build Fumoctl without installing Hugo globally, the entire lifecycle is declared in flake.nix:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 { description = \u0026#34;Hugo static website blog with Blowfish theme\u0026#34;; inputs = { nixpkgs.url = \u0026#34;github:NixOS/nixpkgs/nixos-unstable\u0026#34;; flake-utils.url = \u0026#34;github:numtide/flake-utils\u0026#34;; self.submodules = true; blowfish = { url = \u0026#34;github:nunocoracao/blowfish\u0026#34;; flake = false; }; }; outputs = { self, nixpkgs, flake-utils, blowfish }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; in { # Development shell: \u0026#39;nix develop\u0026#39; devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ hugo git ]; shellHook = \u0026#39;\u0026#39; echo \u0026#34;✨ Hugo + Blowfish development environment loaded.\u0026#34; echo \u0026#34;Hugo version: $(hugo version)\u0026#34; \u0026#39;\u0026#39;; }; # Hermetic production build: \u0026#39;nix build\u0026#39; packages.default = pkgs.stdenv.mkDerivation { name = \u0026#34;hugosite\u0026#34;; src = ./.; buildInputs = [ pkgs.hugo ]; buildPhase = \u0026#39;\u0026#39; mkdir -p themes if [ ! -d themes/blowfish ] || [ -z \u0026#34;$(ls -A themes/blowfish 2\u0026gt;/dev/null)\u0026#34; ]; then cp -r ${blowfish} themes/blowfish chmod -R u+w themes/blowfish fi hugo --minify \u0026#39;\u0026#39;; installPhase = \u0026#39;\u0026#39; cp -r public $out \u0026#39;\u0026#39;; }; # One-line preview runner: \u0026#39;nix run .\u0026#39; apps.default = { type = \u0026#34;app\u0026#34;; program = \u0026#34;${pkgs.writeShellScript \u0026#34;hugo-server\u0026#34; \u0026#39;\u0026#39; exec ${pkgs.hugo}/bin/hugo server -D \u0026#34;$@\u0026#34; \u0026#39;\u0026#39;}\u0026#34;; }; }); } The Developer Experience # With this flake in place, spinning up the blog on any machine with Nix takes exactly one command:\n1 2 # Instant local preview server with live-reloading and draft rendering nix run . And building the production distribution is completely reproducible:\n1 2 nix build # Static HTML/CSS/JS ready for deployment in ./result 3. Dark Glassmorphism: The \u0026ldquo;Nokstella\u0026rdquo; Aesthetic # Most tech blogs default to stark white or flat monochromatic gray palettes. For Fumoctl, I wanted a modern, atmospheric aesthetic inspired by deep night skies and glowing phosphor terminals:\nBase Background: Deep navy-slate (#0a0f1d / #0f172a). Accents: Cyberpunk cyan (#38bdf8) and electric violet (#a855f7). Glassmorphism: Translucent card backgrounds (rgba(15, 23, 42, 0.75)), subtle hairline borders (rgba(255, 255, 255, 0.08)), and hardware-accelerated backdrop blur (backdrop-filter: blur(12px)). The Floating Pill Navigation # Rather than a traditional clunky top banner, the site uses a floating glass pill navbar.\nOn the homepage, the navbar stays invisible while the reader takes in the hero section, fading smoothly into view only after scrolling past the header. On inner pages, a pre-calculated spacer (.header-spacer, height ~5.75rem) prevents any content from clipping behind the fixed glass header. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /* assets/css/custom.css */ .homepage-header { position: fixed !important; top: 0.5rem; left: 0; right: 0; z-index: 100; display: flex; justify-content: center; pointer-events: none; } .homepage-header \u0026gt; div { pointer-events: auto; background: rgba(15, 23, 42, 0.78) !important; backdrop-filter: blur(16px) saturate(180%) !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5), 0 0 20px rgba(56, 189, 248, 0.12) !important; border-radius: 9999px !important; } Committing to Pure Dark Mode # Many websites carry unnecessary runtime overhead trying to support auto-switching light and dark modes with conflicting color tokens. Because Fumoctl\u0026rsquo;s visual identity is intrinsically tied to terminal luminescence, I intentionally disabled the theme switcher (showAppearanceSwitcher = false and autoSwitchAppearance = false). This eliminated DOM flicker on page load, pruned unused CSS paths, and kept the interface cohesive.\n4. Reader-First Layout: Liberating Technical Content # The single most frustrating aspect of reading technical articles on modern blogs is narrow formatting. Many themes limit reading columns to 65ch (~650px). While optimal for short narrative essays, this constraint is disastrous for technical writing:\nComplex shell one-liners or Nix derivations wrap onto three lines, breaking readability. Multi-column performance comparison tables get clipped or require cumbersome horizontal scrollbars. Terminal output logs turn into unreadable walls of wrapped text. Breaking the Prose Ceiling # In layouts/_default/single.html and assets/css/custom.css, I re-architected the reading view into an expansive 2-column flex layout on desktop displays:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 /* Reading column spans comfortably on desktop up to ~1100px */ .single-post-content { flex: 1 1 0% !important; min-width: 0 !important; max-width: calc(100% - 20rem) !important; } /* Ensure code blocks, tables, and pre tags utilize full available width */ .prose pre, .prose table, .prose img { max-width: 100% !important; width: 100% !important; } Sticky \u0026ldquo;Jump To\u0026rdquo; Table of Contents (Index) # Long-form guides can easily span 2,000 to 4,000 words. Readers need to know where they are and navigate effortlessly between sections:\nSticky Positioning: The TOC aside (.single-post-toc) spans the full vertical height of the article (align-self: stretch !important;) while the inner container stays pinned: 1 2 3 4 5 6 .toc { position: sticky !important; top: 5.5rem !important; max-height: calc(100vh - 7rem); overflow-y: auto; } Heading Offset Calculations: To prevent anchor links from jumping underneath the floating glass navbar, all article headings receive an explicit scroll margin: 1 2 3 .prose h1, .prose h2, .prose h3, .prose h4, .anchor { scroll-margin-top: 5.5rem !important; } Active Section Scroll-Spy: A lightweight IntersectionObserver dynamically highlights the active heading in the TOC as the user reads. Viewport Reading Progress Bar # Fixed to the top edge of the browser viewport sits a subtle gradient indicator (linear-gradient(to right, #38bdf8, #a855f7)). It tracks exact scroll depth, giving the reader immediate visual feedback on their progress through deep technical breakdowns.\n5. The Unified Articles Hub (/posts/) # Default Hugo blogs often split taxonomies across multiple fragmented pages: /posts/ for articles, /tags/ for tag lists, and /categories/ for category archives. This forces the reader to click back and forth between different URLs just to explore related topics.\nIn Fumoctl, I consolidated all post discovery into a single Unified Articles Hub powered by vanilla JavaScript:\nInstant Client-Side Search: A debounced search input filters articles in real-time across post titles, excerpts, summaries, tags, and categories without any backend requests or external search APIs. Category Filter Pills: Interactive pills with dynamic badge counts allow filtering by primary domains (Guides, Gaming, Packaging, AI \u0026amp; LLM, Homelab). Inline Tag Dropdown \u0026amp; Expandable Cloud: A clean 🏷️ Filter by Tag dropdown lets users narrow by specific technologies (e.g. NixOS, Wine, Flatpak) with an expandable cloud view for browsing all available keywords. URL State Synchronization: Active filters sync to URL parameters, allowing readers to share filtered views (e.g. /posts/?cat=Guides\u0026amp;tag=NixOS) directly. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // Instant client-side filter orchestration (layouts/posts/list.html) function applyFilters() { const query = searchInput.value.toLowerCase().trim(); let visibleCount = 0; articleCards.forEach(card =\u0026gt; { const cardTitle = card.dataset.title || \u0026#39;\u0026#39;; const cardSummary = card.dataset.summary || \u0026#39;\u0026#39;; const cardCat = card.dataset.category || \u0026#39;\u0026#39;; const cardTags = card.dataset.tags || \u0026#39;\u0026#39;; const matchesQuery = !query || cardTitle.includes(query) || cardSummary.includes(query) || cardTags.includes(query); const matchesCategory = !activeCategory || cardCat.toLowerCase() === activeCategory.toLowerCase(); const matchesTag = !activeTag || cardTags.toLowerCase().split(\u0026#39;,\u0026#39;).includes(activeTag.toLowerCase()); const isVisible = matchesQuery \u0026amp;\u0026amp; matchesCategory \u0026amp;\u0026amp; matchesTag; card.style.display = isVisible ? \u0026#39;\u0026#39; : \u0026#39;none\u0026#39;; if (isVisible) visibleCount++; }); updateEmptyState(visibleCount); } 6. Brand Identity: Retiring Upstream Pufferfish # Blowfish ships by default with pufferfish iconography for tab headers, favicons, and manifest files. To establish a clean personal brand, I designed and injected custom Fumoctl terminal prompt assets (\u0026gt;_):\nstatic/images/fumoctl-logo.svg: Scalable vector mark. static/favicon.ico: Multi-resolution binary icon (16x16, 32x32, 48x48). static/apple-touch-icon.png: High-density iOS touch icon. layouts/partials/favicons.html: Overriding the theme\u0026rsquo;s default favicon injection to ensure no upstream pufferfish assets are loaded by browsers. 7. The Result \u0026amp; What\u0026rsquo;s Next # By combining Hugo\u0026rsquo;s compilation speed with NixOS\u0026rsquo;s declarative guarantees and a modern CSS layer, Fumoctl delivers:\nZero Runtime Dependencies: Pure static output served with optimal caching headers. Flawless Code Presentation: Wide layouts with syntax highlighting that don\u0026rsquo;t cramp complex derivations or configs. Effortless Maintenance: Writing a new post is as simple as creating a markdown file and running nix run .. Future iterations will explore automated NixOS homelab CI pipelines for Git push deployments, localized LLM-assisted search embeddings, and interactive diagram widgets.\nIf you\u0026rsquo;re building your own tech blog or homelab portal, consider taking the declarative route. The upfront investment in reproducible tooling pays dividends every time you publish.\n","date":"2026-09-20","externalUrl":null,"permalink":"/posts/building-a-declarative-homelab-blog/","section":"Posts","summary":"How I architected and built Fumoctl using Hugo Extended, Blowfish, Nix Flakes, and custom glassmorphism for a blazing-fast, reader-first technical blog.","title":"Building a Declarative, Sovereign Tech Blog with Hugo and NixOS","type":"posts"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/tags/css/","section":"Tags","summary":"","title":"CSS","type":"tags"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/tags/flakes/","section":"Tags","summary":"","title":"Flakes","type":"tags"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/","section":"Fumoctl","summary":"","title":"Fumoctl","type":"page"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/categories/guides/","section":"Categories","summary":"","title":"Guides","type":"categories"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/categories/homelab/","section":"Categories","summary":"","title":"Homelab","type":"categories"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/tags/nixos/","section":"Tags","summary":"","title":"NixOS","type":"tags"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2026-09-20","externalUrl":null,"permalink":"/tags/web-development/","section":"Tags","summary":"","title":"Web Development","type":"tags"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/tags/agent/","section":"Tags","summary":"","title":"Agent","type":"tags"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/tags/agentic/","section":"Tags","summary":"","title":"Agentic","type":"tags"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/categories/ai--llm/","section":"Categories","summary":"","title":"AI \u0026 LLM","type":"categories"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/tags/github-copilot/","section":"Tags","summary":"","title":"Github Copilot","type":"tags"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/tags/llm/","section":"Tags","summary":"","title":"LLM","type":"tags"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/tags/nix/","section":"Tags","summary":"","title":"Nix","type":"tags"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/categories/packaging/","section":"Categories","summary":"","title":"Packaging","type":"categories"},{"content":"If you use NixOS as your daily driver, you know the routine whenever a new developer tool is released:\nDownload the official Linux binary. Run ./app. Get hit with: bash: ./app: No such file or directory (because the traditional /lib64/ld-linux-x86-64.so.2 dynamic interpreter doesn\u0026rsquo;t exist at the root). Recently, GitHub released the official Linux builds for the GitHub Copilot Desktop App (an AppImage built on Tauri) and the new standalone GitHub Copilot CLI.\nTo make these first-class citizens on NixOS, I built GithubCopilot-Nix, a Nix flake supporting both x86_64-linux and aarch64-linux. Along the way, packaging them revealed some great lessons in Nix packaging strategies, Node SEA quirks, and automated maintenance.\nHere is a breakdown of how it works under the hood.\nChallenge 1: The Copilot CLI and the Node SEA Dilemma # The GitHub Copilot CLI is distributed as a single 160MB binary. Inspecting it reveals it is compiled as a Node.js Single Executable Application (Node SEA).\nWhy standard Nix tools break it: # In standard Nix derivations, when packaging proprietary or pre-compiled binaries, you typically add autoPatchelfHook to patch the interpreter and inject RPATHs.\nHowever, Node SEA binaries bundle JavaScript assets and V8 snapshots at specific byte offsets within the binary. Running patchelf rewrites ELF sections and shifts table offsets. Worse, stdenv’s default strip step discards trailing payloads. This corrupts the executable or leaves it behaving like an empty Node runtime.\nThe Solution: The Dynamic Linker Wrapper # Instead of modifying the binary, we keep the raw binary untouched and launch it explicitly using the system\u0026rsquo;s dynamic linker:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 stdenv.mkDerivation { pname = \u0026#34;github-copilot-cli\u0026#34;; inherit version src; dontStrip = true; dontPatchELF = true; installPhase = \u0026#39;\u0026#39; mkdir -p $out/libexec $out/bin cp copilot $out/libexec/copilot chmod +x $out/libexec/copilot cat \u0026lt;\u0026lt;EOF \u0026gt; $out/bin/copilot #!/bin/sh exec ${stdenv.cc.bintools.dynamicLinker} \\ --library-path \u0026#34;${lib.makeLibraryPath [ stdenv.cc.libc stdenv.cc.cc.lib ]}\u0026#34; \\ $out/libexec/copilot \u0026#34;\\$@\u0026#34; EOF chmod +x $out/bin/copilot \u0026#39;\u0026#39;; } This bypasses binary patching entirely while ensuring glibc and libstdc++.so.6 resolve cleanly on any NixOS system.\nGenerating Shell Completions in the Sandbox # The Copilot CLI has built-in completion generation (copilot completion bash|zsh|fish). However, running the binary inside the Nix sandbox threw:\n1 Failed to extract bundled package: Error: EACCES: permission denied, mkdir \u0026#39;/homeless-shelter\u0026#39; Because Node SEA extracts runtime cache items to $HOME, and Nix sandboxes set $HOME=/homeless-shelter (which is read-only). By briefly setting export HOME=$(mktemp -d) inside installPhase, we were able to run the wrapper during build and generate fully standalone completions for Bash, Zsh, and Fish into $out/share/.\nChallenge 2: The Desktop App (Tauri AppImage) # The Desktop app is an AppImage with a Tauri frontend. To package this cleanly:\nFHS Environment: We use Nixpkgs\u0026rsquo; appimageTools.wrapType2, which constructs a Bubblewrap FHS container with the necessary graphics, X11, Wayland, and WebKit libraries. Metadata \u0026amp; Icons: We use appimageTools.extract to unpack the SquashFS filesystem at build time. We pull the 256x256 icons into $out/share/icons/hicolor/ and register a .desktop file handling the x-scheme-handler/github-app URL protocol. (Pro-tip: when copying extracted AppImage directories in Nix, remember to run chmod -R u+w on the target icon directories, since extracted SquashFS permissions remain strictly read-only).\nZero-Maintenance Upstream Updates # A package flake is only useful if it doesn\u0026rsquo;t get abandoned after the next release.\nRather than running impure curl operations inside derivation builds (which breaks reproducibility and sandbox rules), we decoupled version tracking into a lockfile: artifacts/versions.json:\n1 2 3 4 5 6 7 8 { \u0026#34;GitHub Copilot Desktop\u0026#34;: { \u0026#34;x86_64-linux\u0026#34;: { \u0026#34;url\u0026#34;: \u0026#34;https://github.com/github/app/releases/download/v1.1.21/GitHub-Copilot-linux-x64.AppImage\u0026#34;, \u0026#34;hash\u0026#34;: \u0026#34;1yjqs029k4a4zrdm0d5qga14lq9zdjayj22zmzs887m7igpily22\u0026#34; } } } A daily GitHub Actions workflow:\nQueries the GitHub API for new tags in github/app and github/copilot-cli. Runs nix-prefetch-url to grab the new SHA-256 hashes for both x86_64 and aarch64. Runs nix flake check and nix build to ensure the new binaries actually compile. Opens an auto-merging Pull Request. On merge, generates a tagged release on main. Trying It Out # You can run either application immediately without modifying your system configuration:\n1 2 3 4 5 # Run the Copilot CLI nix run github:fumoctl/GithubCopilot-Nix#copilot-cli # Run the Copilot Desktop GUI nix run github:fumoctl/GithubCopilot-Nix To add them permanently to your NixOS configuration:\n1 2 3 4 5 6 7 8 # flake.nix inputs.github-copilot-nix.url = \u0026#34;github:fumoctl/GithubCopilot-Nix\u0026#34;; # In your NixOS or Home Manager module: environment.systemPackages = [ inputs.github-copilot-nix.packages.${pkgs.system}.github-copilot-desktop inputs.github-copilot-nix.packages.${pkgs.system}.github-copilot-cli ]; Check out the full repository here: github.com/fumoctl/GithubCopilot-Nix.\nHappy hacking!\n","date":"2026-09-18","externalUrl":null,"permalink":"/posts/packaging-github-copilot-desktop-and-cli-on-nixos/","section":"Posts","summary":"How to handle single-executable Node binaries, Tauri AppImages, and automated daily version locking in Nix.","title":"Packaging GitHub Copilot Desktop and CLI for NixOS (And its challenges)","type":"posts"},{"content":"","date":"2026-09-18","externalUrl":null,"permalink":"/tags/tool-use/","section":"Tags","summary":"","title":"Tool Use","type":"tags"},{"content":"","date":"2026-09-08","externalUrl":null,"permalink":"/tags/flatpak/","section":"Tags","summary":"","title":"Flatpak","type":"tags"},{"content":"","date":"2026-09-08","externalUrl":null,"permalink":"/categories/gaming/","section":"Categories","summary":"","title":"Gaming","type":"categories"},{"content":"","date":"2026-09-08","externalUrl":null,"permalink":"/tags/heroic/","section":"Tags","summary":"","title":"Heroic","type":"tags"},{"content":"Visual Novels (VNs) are notorious edge-cases in PC gaming. Between ancient proprietary engines from the Windows XP era, mandatory Japanese system locales, hard-to-find DirectX 9 dependencies, and quirky video codecs, running them outside of a native Japanese Windows installation used to require hours of terminal wrangling.\nFortunately, modern Linux gaming tooling—specifically Flatpak, Heroic Games Launcher, and ProtonPlus—has made this process painless, repeatable, and distro-agnostic.\nHere is a clean, step-by-step walkthrough to get even the most stubborn visual novels running smoothly on Linux or your Steam Deck.\nThe Recipe: Tools You’ll Need # Instead of polluting your primary operating system with custom locales or complex system-wide Wine prefixes, this workflow isolates everything inside containerized Flatpaks.\nYou will need:\nFlatpak \u0026amp; Flathub: To manage containerized gaming utilities. Heroic Games Launcher: A versatile launcher capable of managing custom Wine/Proton prefixes and running independent game executables. ProtonPlus: A quick manager for downloading specialized Wine and GE-Proton runners. Flatseal (Optional): To easily manage Flatpak permissions if needed. Step 1: Set Up Flatpak and Flathub # If you\u0026rsquo;re on SteamOS (Steam Deck) or Bazzite, Flatpak and Flathub are enabled out of the box—you can skip straight to the locale step.\nFor standard desktop distributions, install Flatpak using your native package manager:\n1 2 3 4 5 6 7 8 9 10 11 # Debian / Ubuntu / Linux Mint / Pop!_OS sudo apt update \u0026amp;\u0026amp; sudo apt install flatpak # Fedora / Red Hat / Rocky Linux sudo dnf install flatpak # Arch Linux / Manjaro / EndeavourOS sudo pacman -S flatpak # openSUSE sudo zypper install flatpak Next, ensure Flathub is registered as a remote repository:\n1 flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo Step 2: Enable Japanese Locales (The Clean Way) # Many older visual novels refuse to run—or render garbled mojibake—unless the operating system reports a Japanese locale (ja_JP.UTF-8).\nTraditional guides often advise generating locales system-wide in /etc/locale.gen, which can get messy. Because we are using Flatpak apps, you only need to configure Flatpak’s locale subsystem:\n1 2 3 4 5 # Tell Flatpak to install both English and Japanese runtimes flatpak config --user --set languages \u0026#34;en;ja\u0026#34; # Apply changes across all runtimes flatpak update Step 3: Install Heroic and ProtonPlus # Grab the required tools directly via your Software Center or through the terminal:\n1 2 3 flatpak install flathub com.heroicgameslauncher.hgl flatpak install flathub com.vysp3r.ProtonPlus flatpak install flathub com.github.tchx84.Flatseal Grabbing the Right Wine/Proton Runner # Open ProtonPlus. Look for Wine / Proton-GE versions. For visual novels, GE-Proton 9-12 (or compatible 8.x/9.x GE releases) is widely regarded as the sweet spot. Newer experimental Proton branches occasionally introduce regressions with legacy 32-bit direct-draw routines, while GE-Proton builds bundle critical media foundation and video playback patches. Step 4: Adding Your Game to Heroic # Heroic makes setting up custom prefix containers straightforward:\nLaunch Heroic Games Launcher and click Add Game in the sidebar. Enter the title of the game. Under Show Wine Settings: Set the Wine Version to your downloaded runner (e.g., GE-Proton9-12). Leave the prefix path default or point it to your preferred storage drive. Heroic handles prefix generation automatically. Select the Executable: If the game is pre-extracted: Browse directly to the game\u0026rsquo;s .exe. If the game has a setup installer: Click Run Installer First, walk through the installer wizard (install into your simulated C: drive or inside Z:/home/\u0026lt;user\u0026gt;/Games), and once finished, re-point the main executable path to the installed game binary. Click Finish. Step 5: Applying Translations and Patches # Most VN releases involve fan translation patches, voice patches, or 18+ content restorations. Depending on the patch packaging:\nPatch Type How to Handle It Loose Files / Folders Simply drag and drop the files directly into the game folder via your file manager. **Self-Extracting .exe** Try opening the .exe with an archive tool like 7-Zip or File Roller. If it extracts files, paste them manually. Installer Executable Open the game\u0026rsquo;s settings page in Heroic, scroll to Wine tools, and choose Run EXE on Prefix. Run the patch installer inside the existing virtual environment. Troubleshooting Common Visual Novel Quirks # If your game boots right away, you\u0026rsquo;re set. If it hiccups, visual novels almost always fail for one of three reasons:\n1. Game Fails to Launch or Displays Mojibake (Locale Missing) # If you haven\u0026rsquo;t enabled the locale inside Heroic for that specific title:\nOpen the game\u0026rsquo;s Settings in Heroic. Head to the Advanced tab and scroll to Environment Variables. Add: LC_ALL = ja_JP.UTF-8 LANG = ja_JP.UTF-8 2. Video Playback Crashes or Shows Black Screens # Opening cinematic animations (OP/ED movies) in older engines often rely on legacy DirectShow or Windows Media Player components.\nIn Heroic\u0026rsquo;s game settings, open the Winetricks menu. Search for and install: wmp9 quartz lavfilters (Avoid bulk-installing unnecessary DLLs, as overlapping media frameworks can conflict). 3. Flickering UI, \u0026ldquo;Tofu\u0026rdquo; Boxes, or Startup Crashes (Disable DXVK) # Older titles from the Windows 95 to Windows 7 eras (pre-2016) render via legacy DirectX 8/9 or DirectDraw calls. DXVK translates DirectX to Vulkan, but older 2D sprite blitting can sometimes break under translation.\nIn Heroic\u0026rsquo;s Wine Settings, locate the DXVK toggle and turn it Off. This forces Wine to use its native OpenGL translation layer (wined3d), which often handles archaic 2D engine rendering significantly better. Wrap Up # Once your game boots with clean fonts and functional cutscene playback, your prefix is permanently configured. You can kick back, hit full screen, or add Heroic as a non-Steam shortcut to enjoy your library seamlessly from Steam Deck\u0026rsquo;s Game Mode. Optimizing these settings ensures low latency and robust multi-step agent execution.\n","date":"2026-09-08","externalUrl":null,"permalink":"/posts/the-definitive-guide-to-playing-visual-novels-on-linux/","section":"Posts","summary":"Ditch Windows without breaking your favorite Japanese VNs, translation patches, or opening movies.","title":"The Painless Guide to Playing Visual Novels on Linux (and Steam Deck)","type":"posts"},{"content":"","date":"2026-09-08","externalUrl":null,"permalink":"/tags/visual-novels/","section":"Tags","summary":"","title":"Visual Novels","type":"tags"},{"content":"","date":"2026-09-08","externalUrl":null,"permalink":"/tags/wine/","section":"Tags","summary":"","title":"Wine","type":"tags"},{"content":"Hi, I’m Juan - Fumoctl.\nI’m an Argentinian systems administrator and Business Informatics student with a passion for free and open-source software, rock-solid systems architecture, and digital sovereignty.\nThis is where I write about the intersection of modern infrastructure engineering, declarative operating systems, and practical homelabbing. If you care about building infrastructure that is deterministic, auditable, and resilient to breaking updates, you’ll feel right at home here.\nWhat I Work On \u0026amp; Write About # Declarative \u0026amp; Immutable Linux: Deep dives into managing systems where configuration is code—primarily focusing on Nix / NixOS, flakes, container layering, and modern atomic deployments. Homelab Architecture \u0026amp; Self-Hosting: Designing reliable, reproducible services using rootless containers, reverse proxies, and automated deployment pipelines. Systems Administration \u0026amp; Automation: Orchestration and config management with Ansible, systemd units, Quadlets, and custom automation scripts in Python, Go, and Bash. Privacy \u0026amp; Security Sovereignty: Practical guides to data autonomy, encrypted backups, PGP verification, and hardening cloud/VPS instances. Connect \u0026amp; Socials # Website: fumoctl.com Email: juanu@fumoctl.com GitHub: @fumoctl YouTube: @Fumoctl X / Twitter: @Fumoctl LinkedIn: in/fumoctl ","externalUrl":null,"permalink":"/about/","section":"Fumoctl","summary":"Hi, I’m Juan - Fumoctl.\nI’m an Argentinian systems administrator and Business Informatics student with a passion for free and open-source software, rock-solid systems architecture, and digital sovereignty.\nThis is where I write about the intersection of modern infrastructure engineering, declarative operating systems, and practical homelabbing. If you care about building infrastructure that is deterministic, auditable, and resilient to breaking updates, you’ll feel right at home here.\n","title":"About","type":"page"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]