[
  {
    "path": ".chezmoiroot",
    "content": "chezmoi\n"
  },
  {
    "path": ".editorconfig",
    "content": "; https://editorconfig.org/\n\n; top-most EditorConfig file\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n; my custom property that trim trailing empty last lines\ntrim_trailing_lastline = true\n\n[{*.just,*.justfile}]\nindent_size = 4\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: budimanjojo\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"extends\": [\n    \"github>budimanjojo/renovate-config:default.json5\"\n  ],\n  \"username\": \"budimanjojo-bot[bot]\",\n  \"gitAuthor\": \"budimanjojo-bot <111944664+budimanjojo-bot[bot]@users.noreply.github.com>\",\n  \"repositories\": [\"budimanjojo/nix-config\"],\n  \"automerge\": true,\n  \"ignoreTests\": true,\n  \"lockFileMaintenance\": {\n    \"enabled\": true,\n    \"schedule\": [\n      \"after 1am and before 3am\"\n    ]\n  },\n  \"nix\": {\n    \"enabled\": true\n  },\n  \"customManagers\": [\n    {\n      \"customType\": \"regex\",\n      \"fileMatch\": [\"chezmoi\\\\/\\\\.chezmoiscripts\\\\/.+\\\\.sh\\\\.tmpl$\"],\n      \"matchStrings\": [\n        \"# renovate: depName=(?<depName>.*) datasource=(?<datasource>.*)\\\\n\\\\s*current_\\\\w+_version=(?<currentValue>.+)\\\\b\"\n      ]\n    },\n    {\n      \"customType\": \"regex\",\n      \"fileMatch\": [\n        \"^system/.*\\\\.nix$\"\n      ],\n      \"matchStrings\": [\n        \"image *= *\\\"(?<depName>.*?):(?<currentValue>.*?)(@(?<currentDigest>sha256:[a-f0-9]+))?\\\"\"\n      ],\n      \"datasourceTemplate\": \"docker\"\n    }\n  ],\n  \"packageRules\": [\n    {\n      \"matchDepNames\": \"wez/wezterm\",\n      \"versioning\": \"regex:^(?<major>\\\\d{4})(?<minor>\\\\d{2})(?<patch>\\\\d{2})-(?<build>\\\\d+)-(?<revision>.+)$\"\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/workflows/broken-link-check.yaml",
    "content": "---\nname: Broken link check\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 0 * * 0\"\n\njobs:\n  check:\n    name: Check\n    runs-on: ubuntu-latest\n    steps:\n      - name: Generate token\n        uses: tibdex/github-app-token@v2\n        id: generate-token\n        with:\n          app_id: \"${{ secrets.BOT_APP_ID }}\"\n          private_key: \"${{ secrets.BOT_APP_PRIVATE_KEY }}\"\n\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n        with:\n          token: \"${{ steps.generate-token.outputs.token }}\"\n\n      - name: Scan for broken links\n        uses: lycheeverse/lychee-action@v2\n        id: lychee\n        env:\n          GITHUB_TOKEN: \"${{ steps.generate-token.outputs.token }}\"\n        with:\n          args: --verbose --no-progress --exclude-mail './**/*.md'\n\n      - name: Find link checker issue\n        id: broken-link-check-issue\n        uses: micalevisk/last-issue-action@v2\n        with:\n          state: open\n          labels: |\n            broken-links\n\n      - name: Update issue\n        uses: peter-evans/create-issue-from-file@v6\n        with:\n          title: Broken links detected 🔗\n          issue-number: \"${{ steps.broken-link-check-issue.outputs.issue-number }}\"\n          content-filepath: ./lychee/out.md\n          token: \"${{ steps.generate-token.outputs.token }}\"\n          labels: |\n            broken-links\n"
  },
  {
    "path": ".github/workflows/build-push-cache.yaml",
    "content": "---\nname: Build Push Cache\n\non:\n  workflow_dispatch: {}\n  push:\n    paths:\n      - \"system/**\"\n      - \"home/**\"\n      - \"packages/**\"\n      - \"overlays/**\"\n      - \"flake.lock\"\n      - \"*nix\"\n\njobs:\n  define-matrix:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        id: cache\n        with:\n          path: /tmp/nix-cache\n          key: nix-flake-inputs-${{ hashFiles('**/flake.lock') }}\n          restore-keys: |\n            nix-flake-inputs-\n\n      - uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5\n\n      - name: Import Nix store cache\n        if: steps.cache.outputs.cache-hit == 'true'\n        run: nix copy --all --no-check-sigs --from /tmp/nix-cache\n\n      - name: Build and Export .#gc-keep to Nix store cache\n        run: |\n          # remove everything before copying so the cache doesn't grow over time\n          sudo rm -rf /tmp/nix-cache\n          nix copy --no-check-sigs --to /tmp/nix-cache .#gc-keep\n\n      - id: set-matrix\n        run: |\n          set -Eeu\n          matrix=\"$(nix eval --json '.#ghActions.matrix')\"\n          echo \"matrix=$matrix\" >> \"$GITHUB_OUTPUT\"\n\n  build-push:\n    name: ${{ matrix.attrset }}\n    needs: define-matrix\n    runs-on: ${{ matrix.runner }}\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJSON(needs.define-matrix.outputs.matrix) }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n\n      - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        id: cache\n        with:\n          path: /tmp/nix-cache\n          key: nix-flake-inputs-${{ hashFiles('**/flake.lock') }}\n\n      - name: Brutally add more storage to our runner\n        uses: wimpysworld/nothing-but-nix@687c797a730352432950c707ab493fcc951818d7 # main\n        with:\n          hatchet-protocol: carve\n\n      - uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31.10.5\n\n      - name: Import Nix store cache\n        if: steps.cache.outputs.cache-hit == 'true'\n        run: nix copy --no-check-sigs --all --from /tmp/nix-cache\n\n      - name: Login to Cachix\n        uses: cachix/cachix-action@1eb2ef646ac0255473d23a5907ad7b04ce94065c # v17\n        with:\n          name: budimanjojo\n          authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'\n          skipPush: true\n\n      - name: Build\n        id: build\n        run: |\n          result=$(nix build .#${{ matrix.attrset }} --print-out-paths)\n          echo \"result=$result\" >> $GITHUB_OUTPUT\n\n      - name: Push\n        run: cachix push budimanjojo ${{ steps.build.outputs.result }}\n"
  },
  {
    "path": ".github/workflows/check-flakes.yaml",
    "content": "---\nname: Check Flakes\n\non:\n  pull_request_target:\n    types:\n      - opened\n      - auto_merge_enabled\n  push:\n    paths:\n      - '**.nix'\n      - flake.lock\n\njobs:\n  check_flakes:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31\n        with:\n          nix_path: nixpkgs=channel:nixos-unstable\n          extra_nix_config: |\n            access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}\n      - run: nix flake check\n"
  },
  {
    "path": ".github/workflows/renovate.yaml",
    "content": "---\nname: Renovate\n\non:\n  workflow_dispatch:\n    inputs:\n      dryRun:\n        description: Dry-Run\n        default: \"false\"\n        required: false\n      logLevel:\n        description: Log-level\n        default: debug\n        required: false\n  schedule:\n    - cron: 0 * * * *\n  push:\n    paths:\n      - .github/renovate.json\n\nenv:\n  LOG_LEVEL: debug\n  RENOVATE_DRY_RUN: false\n  RENOVATE_CONFIG_FILE: .github/renovate.json\n\njobs:\n  renovate:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - name: Generate Token\n        uses: tibdex/github-app-token@v2\n        id: generate-token\n        with:\n          app_id: ${{ secrets.BOT_APP_ID }}\n          private_key: ${{ secrets.BOT_APP_PRIVATE_KEY }}\n      - name: Override default config from dispatch variables\n        run: |\n          echo \"RENOVATE_DRY_RUN=${{ github.event.inputs.dryRun || env.RENOVATE_DRY_RUN }}\" >> \"${GITHUB_ENV}\"\n          echo \"LOG_LEVEL=${{ github.event.inputs.logLevel || env.LOG_LEVEL }}\" >> \"${GITHUB_ENV}\"\n      - name: Renovate\n        uses: renovatebot/github-action@v46.1.12\n        with:\n          configurationFile: \"${{ env.RENOVATE_CONFIG_FILE }}\"\n          token: \"${{ steps.generate-token.outputs.token }}\"\n"
  },
  {
    "path": ".github/workflows/update-nvfetcher.yaml",
    "content": "---\nname: Update nvfetcher\n\non:\n  workflow_dispatch: {}\n  schedule:\n    - cron: 0 * * * *\n  push:\n    paths:\n      - packages/nvfetcher.toml\n\njobs:\n  update-nvfetcher:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n        # TODO: https://github.com/peter-evans/create-pull-request/issues/4228\n        with:\n          persist-credentials: false\n      - uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34\n        with:\n          nix_conf: |\n            keep-env-derivations = true\n            keep-outputs = true\n      - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2\n        with:\n          primary-key: nvfetcher-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}\n          restore-prefixes-first-match: nvfetcher-${{ runner.os }}-\n          gc-max-store-size-linux: 1G\n          purge: true\n          purge-prefixes: nvfetcher-${{ runner.os }}-\n          purge-created: 0\n          purge-last-accessed: 0\n          purge-primary-key: never\n      - run: nix profile install github:berberman/nvfetcher\n      - id: update_nvfetcher\n        run: |\n          cd packages\n          echo \"OUTPUT<<EOF\" >> $GITHUB_OUTPUT\n          nvfetcher | sed -n '/Changes/,/$!d/p' >> $GITHUB_OUTPUT\n          echo \"EOF\" >> $GITHUB_OUTPUT\n      - name: Generate token\n        uses: tibdex/github-app-token@v2\n        id: generate-token\n        with:\n          app_id: ${{ secrets.BOT_APP_ID }}\n          private_key: ${{ secrets.BOT_APP_PRIVATE_KEY }}\n      - name: Create Pull Request\n        id: cpr\n        uses: peter-evans/create-pull-request@v8\n        with:\n          token: ${{ steps.generate-token.outputs.token }}\n          title: \"chore(deps): update packages managed by nvfetcher\"\n          commit-message: \"chore(deps): update packages managed by nvfetcher\"\n          delete-branch: true\n          labels: nvfetcher\n          committer: budimanjojo-bot <111944664+budimanjojo-bot[bot]@users.noreply.github.com>\n          author: budimanjojo-bot <111944664+budimanjojo-bot[bot]@users.noreply.github.com>\n          body: |\n            ${{ steps.update_nvfetcher.outputs.OUTPUT }}\n      - name: Automerge\n        if: steps.cpr.outputs.pull-request-operation == 'created'\n        uses: peter-evans/enable-pull-request-automerge@v3\n        with:\n          token: ${{ steps.generate-token.outputs.token }}\n          pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}\n          merge-method: squash\n"
  },
  {
    "path": ".github/workflows/update-sops-keys.yaml",
    "content": "---\nname: Update SOPS keys\n\non:\n  workflow_dispatch: {}\n  schedule:\n    - cron: 0 * * * *\n  push:\n    paths:\n      - .sops.yaml\n\njobs:\n  update-sops-keys:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n        # TODO: https://github.com/peter-evans/create-pull-request/issues/4228\n        with:\n          persist-credentials: false\n      - uses: cachix/install-nix-action@ab739621df7a23f52766f9ccc97f38da6b7af14f # v31\n        with:\n          nix_path: nixpkgs=channel:nixos-unstable\n          extra_nix_config: |\n            access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}\n      - uses: workflow/nix-shell-action@v4.0.0\n        with:\n          packages: sops,findutils\n          script: |\n            export SOPS_AGE_KEY=${{ secrets.SOPS_AGE_KEY }}\n            find . -type f -name \\*.sops.yaml ! -name .sops.yaml -exec sops updatekeys --yes {} \\;\n      - name: Generate token\n        uses: tibdex/github-app-token@v2\n        id: generate-token\n        with:\n          app_id: ${{ secrets.BOT_APP_ID }}\n          private_key: ${{ secrets.BOT_APP_PRIVATE_KEY }}\n      - name: Create Pull Request\n        id: cpr\n        uses: peter-evans/create-pull-request@v8\n        with:\n          token: ${{ steps.generate-token.outputs.token }}\n          title: \"chore(sops): update sops secrets with new public keys\"\n          commit-message: \"chore(deps): update sops secrets with new public keys\"\n          delete-branch: true\n          committer: budimanjojo-bot <111944664+budimanjojo-bot[bot]@users.noreply.github.com>\n          author: budimanjojo-bot <111944664+budimanjojo-bot[bot]@users.noreply.github.com>\n      - name: Automerge\n        if: steps.cpr.outputs.pull-request-operation == 'created'\n        uses: peter-evans/enable-pull-request-automerge@v3\n        with:\n          token: ${{ steps.generate-token.outputs.token }}\n          pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}\n          merge-method: squash\n"
  },
  {
    "path": ".gitignore",
    "content": ".luarc.json\nresult\n"
  },
  {
    "path": ".justfile",
    "content": "set unstable := true\nset shell := ['bash', '-euo', 'pipefail', '-c']\n\n# `nix` is required\nrequireNixToRun := require('nix')\n\n# the user running this needs to be a nix trusted-user for the extra-substituters to work\nexport NIX_CONFIG := '''\n    extra-experimental-features = nix-command flakes\n    extra-substituters = https://viperml.cachix.org https://budimanjojo.cachix.org\n    extra-trusted-public-keys = viperml.cachix.org-1:qZhKBMTfmcLL+OG6fj/hzsMEedgKvZVFRRAhq7j8Vh8= budimanjojo.cachix.org-1:S0gy6IKTFXis9fFqEbVAS2zsvnZw/30NV2bWvGiN1YQ=\n'''\n\n# we use nix shebang to get into a nix shell with packages that's needed for each recipe\nnixShebang := '/usr/bin/env -S nix shell --inputs-from ' + justfile_directory()\nrealShebang := '/usr/bin/env bash -euo pipefail'\n\n[private]\ndefault:\n    @just -l\n\n# update packages managed by `nvfetcher`\n[group('repository')]\nupdate-packages:\n    #! {{ nixShebang }} nixpkgs#nvfetcher -c {{ realShebang }}\n    cd ./packages\n    nvfetcher\n\n# update SOPS keys\n[group('repository')]\nupdate-sops-keys:\n    #! {{ nixShebang }} nixpkgs#sops nixpkgs#findutils -c {{ realShebang }}\n    find . -type f -name \\*.sops.yaml ! -name .sops.yaml -exec sops updatekeys --yes {} \\;\n\n# do `nixos-rebuild switch` for current system\n[group('nix')]\nnixos-switch:\n    sudo nixos-rebuild switch --flake .#{{ shell('hostname') }}\n\n# do `home-manager switch` for current system\n[group('nix')]\nhome-switch:\n    home-manager switch --flake .#{{ shell('whoami') }}@{{ shell('hostname') }}\n\n# create NixOS ISO\n[group('nix')]\nmake-iso:\n    nix build .#nixosConfigurations.nixos-livecd.config.system.build.isoImage\n\n# garbage collect all unused store entries\n[group('nix')]\ngc day='7':\n    # garbage collect all unused nix store entries (system-wide)\n    sudo nix-collect-garbage --delete-older-than {{ day }}d\n    # garbage collect all unused nix store entries (user-wide)\n    # ref: https://github.com/NixOS/nix/issues/8508\n    nix-collect-garbage --delete-older-than {{ day }}d\n\n# verify all the store entries\n# nix store can contains corrupted entries if the nix store object has been modified unexpectedly\n# we need to fix the corrupted entries manually via `sudo nix store delete <path-1> <path-2> ...`\n[group('nix')]\nverify-store:\n    nix store verify --all\n\n# repair nix store objects\n[group('nix')]\nrepair-store *paths:\n    nix store repair {{ paths }}\n\n# show all the auto gc root in the nix store\n[group('nix')]\ngcroot:\n    #! {{ nixShebang }} nixpkgs#eza -c {{ realShebang }}\n    eza -lag /nix/var/nix/gcroots/auto/\n"
  },
  {
    "path": ".sops.yaml",
    "content": "---\ncreation_rules:\n  - path_regex: .*secret\\.sops\\.ya?ml$\n    key_groups:\n      - age:\n          - age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          - age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          - age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          - age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          - age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          - age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org>\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n<img src=\"https://raw.githubusercontent.com/NixOS/nixos-artwork/376ed4ba8dc2e611b7e8a62fdc680967ead5bd87/logo/nix-snowflake.svg\" align=\"center\" width=\"100px\" height=\"100px\"/>\n\n### My Infrastructure IaC\n\n...Managed with NixOS :snowflake:&nbsp; and chezmoi :robot:&nbsp;\n\n</div>\n\n## :book:&nbsp; Overview\n\nThis repository contains my machine configurations in Infrastructure as Code style.\nThis is possible thanks to combination of [NixOS modules](https://nixos.wiki/NixOS_modules), [home-manager](https://github.com/nix-community/home-manager/), and [chezmoi](https://www.chezmoi.io/).\n\nThis repo is a [Nix Flake](https://nixos.wiki/wiki/Flakes) that you can import and use yourself but I don't recommend doing that.\nYou should take it as an inspiration for your infrastructure instead.\n\nPlease note that this is a Work In Progress, so stuffs may change without further notice and this README can get outdated.\nMy goal is to have everything as reproducible as possible.\n\nFor NixOS machines, everything is managed by NixOS modules and `home-manager`.\nAs for non NixOS machines, I use combination of `home-manager` and `chezmoi`.\nI use `chezmoi` mainly to configure the system using [chezmoi scripts](https://www.chezmoi.io/user-guide/use-scripts-to-perform-actions/) while `home-manager` to configure applications in the userspace.\n\n## :camera:&nbsp; Screenshots\n\n**Sway**\n![sway](https://user-images.githubusercontent.com/13085918/155890643-aa0adb3c-695f-497d-9d5d-d4bb61907cd0.png)\n\n**Tmux**\n![tmux](https://user-images.githubusercontent.com/13085918/155890745-5fc83821-8008-4f91-8ebc-5852badeca22.png)\n\n**Fish prompt**\n![fish](https://user-images.githubusercontent.com/13085918/155890660-57039ad8-d769-4044-ad38-a4b821c634e9.png)\n\n**Neovim**\n![nvim1](https://user-images.githubusercontent.com/13085918/155890675-a3b8b3f8-479c-4fd0-9ed6-c74e0fc4de0a.png)\n![nvim2](https://user-images.githubusercontent.com/13085918/155890676-38b95046-c35d-4db5-aa01-b9d8ee44f9d0.png)\n\n## :package:&nbsp; Modules\n\n- [Flake Parts](https://github.com/hercules-ci/flake-parts) to manage `flakes` outputs.\n- [home-manager](https://github.com/nix-community/home-manager) to manage my home directory.\n- [sops-nix](https://github.com/Mic92/sops-nix) is used to encrypt/decrypt my secrets safely.\n- [Disko](https://github.com/nix-community/disko) to declaratively manage disks.\n- [NUR](https://github.com/nix-community/NUR) for packages not available in the official NixOS repository.\n- [nixvim](https://github.com/nix-community/nixvim) to create reproducible Neovim package.\n\n## :open_file_folder:&nbsp; Directory structure\n\nThe structure of this repository is highly opinionated.\nI shamelessly took the pieces I believe is the best from people and modified it.\n\n- [./flake.nix](./flake.nix) is the entrypoint for `nixos-rebuild` and `home-manager` commands.\n- [./flake.lock](./flake.lock) is the lock file for this flake, updated daily by [budimanjojo-bot](https://github.com/apps/budimanjojo-bot) powered by [Renovate](https://github.com/renovatebot/renovate).\n- [./flakeLib.nix](./flakeLib.nix) is where I put helper functions used in `flake.nix` file, this is where the magic happens.\n- [./lib](./lib) is where I put helper functions used in NixOS and `home-manager` modules.\n- [./packages](./packages) is where I put my own packages, updated daily by [budimanjojo-bot](https://github.com/apps/budimanjojo-bot) powered by [nvfetcher](https://github.com/berberman/nvfetcher).\n- [./overlays](./overlays) contains overlays for packages used in NixOS and `home-manager` modules.\n- [./shell.nix](./shell.nix) accessible via `nix develop` to have tools needed available in current shell.\n- [./system](./system) contains my own NixOS modules and per machine system configurations.\n- [./home](./home) contains my own `home-manager` modules and per user configurations.\n- [./chezmoi](./chezmoi) contains files used by `chezmoi`.\n\n## :inbox_tray:&nbsp; How do I bootstrap a new machine\n\n### NixOS\n\n- Clone this repository in a directory inside the machine.\n- Edit `./flake.nix` file and add the new machine specs inside `outputs.flake.nixosConfigurations` schema.\n- Create `./system/hosts/<hostname>/default.nix` for the new machine and configure it.\n- Create `./home/budiman/hosts/<hostname>.nix` for the new machine and configure it.\n- Run `git add .` then `sudo nixos-rebuild switch --flake .#<hostname>` and I'm done.\n\n### Non NixOS\n\n- Install Nix and enable Flake.\n- Edit `./flake.nix` file and add the new machine specs inside `outputs.flake.homeConfigurations` schema.\n- Create `./home/budiman/hosts/<hostname>.nix` for the new machine and configure it.\n- Run `git add .` then `nix run nixpkgs.home-manager -c home-manager switch --flake .#budiman@<hostname>` and I'm done.\n\n## :pencil:&nbsp; Neovim\n\nMy `neovim` setup is packaged with [nixvim](https://github.com/nix-community/nixvim) and exposed at `legacyPackages.neovim` from this flake.\nYou can run it directly if you have `nix` installed and `flakes` enabled with: `nix run github:budimanjojo/nix-config#neovim`.\n\n## :fish:&nbsp; Fish\n\nFish is enabled using the `home-manager` module.\n\nOn my non NixOS machines, `fish` is patched to work without system intervention and I have a `chezmoi` script that will switch the default shell for the user to `~/.nix-profile/bin/fish`.\n\nThese are the plugins I'm using:\n\n- [Starship](https://starship.rs/)\n- [FZF Fish](https://github.com/patrickF1/fzf.fish)\n- [Puffer Fish](https://github.com/nickeb96/puffer-fish)\n- [autopair.fish](https://github.com/jorgebucaran/autopair.fish)\n- [fish-abbreviation-tips](https://github.com/gazorby/fish-abbreviation-tips)\n\n## :abcd:&nbsp; Fonts\n\nStarship requires powerline fonts to work.\nI suggest [Nerd-fonts](https://github.com/ryanoasis/nerd-fonts).\nThe font in the screenshot above is using UbuntuMono Nerd Font Regular.\nUnifont is also for some glyphs to work.\nOn non NixOS machines, `home-manager` will install these fonts automatically.\n\n## :scroll:&nbsp; Cheatsheet\n\n### Sway/i3/Hyprland keybindings\n\nI use `Super` key for Sway/i3.\n`hjkl` keys are mapped to `left`, `down`, `up`, `right` arrow keys.\n`S` means Super key, [0-9] means number key 0 to 9.\n\n|    Keypress     | Description                                |\n| :-------------: | :----------------------------------------- |\n|      `S+t`      | Open terminal app                          |\n|      `S+w`      | Open browser                               |\n|      `S+f`      | Open file manager                          |\n|    `S+grave`    | Open rofi apps menu                        |\n|     `S+Esc`     | Open rofi apps menu                        |\n|     `S+F4`      | Close window                               |\n|      `S+k`      | Change focus to window above               |\n|      `S+j`      | Change focus to window below               |\n|      `S+h`      | Change focus to left side window           |\n|      `S+l`      | Change focus to right side window          |\n|   `S+Shift+k`   | Move focused window up                     |\n|   `S+Shift+j`   | Move focused window down                   |\n|   `S+Shift+h`   | Move focused window left                   |\n|   `S+Shift+l`   | Move focused window right                  |\n|   `S+Ctrl+h`    | Split opened windows horizontally          |\n|   `S+Ctrl+v`    | Split opened windows vertically            |\n|   `S+Ctrl+q`    | Toggle opened windows split                |\n|     `S+Tab`     | Go to next workspace                       |\n|  `S+Shift+Tab`  | Go to previous workspace                   |\n|   `S+Ctrl+t`    | Toggle window border on/off                |\n|   `S+Ctrl+g`    | Toggle gaps on/off                         |\n|   `S+Ctrl+f`    | Toggle fullscreen mode on/off              |\n|   `S+Ctrl+s`    | Change container layout to stacking        |\n|   `S+Ctrl+w`    | Change container layout to tabbed          |\n|   `S+Ctrl+e`    | Toggle split layout to horizontal/vertical |\n| `S+Shift+Space` | Toggle window floating on/off              |\n|    `S+Space`    | Swap focus between tiling/floating window  |\n|   `S+Shift+p`   | Move current focused window to scratchpad  |\n|      `S+p`      | Show/hide scratchpad window                |\n|    `S+[0-9]`    | Go to workspace #[0-9]                     |\n| `S+Shift+[0-9]` | Move focused window to workspace #[0-9]    |\n|   `S+Shift+r`   | Go to resize container mode                |\n|   `S+Shift+g`   | Go to resize gaps mode                     |\n|  `S+Ctrl+Del`   | Go to logout mode                          |\n|  `Printscreen`  | Go to screenshot mode                      |\n|   `S+Shift+c`   | Reload configuration                       |\n|   `S+Shift+e`   | Exit                                       |\n\n### Neovim keybindings\n\nThe prefix key is `Space`.\nYou can override this using your custom `.vimrc.local` file.\n`<Leader>` means you need to press prefix key first.\nIf they are not in the table, that means it's using the default Vim keybindings.\n\n|      Mode       |   Keypress   | Description                             |\n| :-------------: | :----------: | :-------------------------------------- |\n|    `Normal`     | `<Leader>w`  | Save file                               |\n|    `Normal`     | `<Leader>x`  | Save file and quit                      |\n|    `Normal`     | `<Leader>qq` | Quit                                    |\n|    `Normal`     | `<Leader>qa` | Force quit without saving               |\n|    `Normal`     | `<Leader>wq` | Save file and quit                      |\n|    `Normal`     |     `Y`      | Yank from cursor to end of file         |\n|    `Normal`     | `Control+k`  | Move to the split window above          |\n|    `Normal`     | `Control+j`  | Move to the split window below          |\n|    `Normal`     | `Control+h`  | Move to the left split window           |\n|    `Normal`     | `Control+l`  | Move to the right split window          |\n|    `Normal`     | `<Leader>s`  | Open new horizontal split window        |\n|    `Normal`     | `<Leader>v`  | Open new vertical split window          |\n|    `Insert`     | `Control+k`  | Move cursor Up                          |\n|    `Insert`     | `Control+j`  | Move cursor Down                        |\n|    `Insert`     | `Control+h`  | Move cursor Left                        |\n|    `Insert`     | `Control+l`  | Move cursor Right                       |\n|    `Normal`     | `<Leader>tn` | Open new tab                            |\n|    `Normal`     | `<Leader>td` | Close tab                               |\n|    `Normal`     | `<Leader>th` | Go to previous tab                      |\n|    `Normal`     | `<Leader>tl` | Go to next tab                          |\n|    `Normal`     | `<Leader>te` | Open new tab with current buffer's path |\n|    `Normal`     | `<Leader>hh` | Jump back to older cursor position      |\n|    `Normal`     | `<Leader>ll` | Jump forward to newer cursor position   |\n| `Normal/Visual` |    `Tab`     | Indent current line or selection        |\n| `Normal/Visual` | `Shift+Tab`  | De-indent current line or selection     |\n|    `Normal`     | `<Leader>lr` | Restart LSP client                      |\n|    `Normal`     | `<Leader>fz` | Open Picker                             |\n|    `Normal`     | `<Leader>ff` | Open Picker to find files               |\n|    `Normal`     | `<Leader>fg` | Open Picker to live grep                |\n|    `Normal`     | `<Leader>fc` | Open Picker to see git log              |\n|    `Normal`     | `<Leader>fb` | Open Picker to see opened buffers       |\n|    `Normal`     | `<Leader>fh` | Open Picker to find help                |\n|    `Normal`     | `<Leader>fk` | Open Picker to see keymappings          |\n|    `Normal`     | `<Leader>fe` | Open Picker to find lsp diagnostics     |\n|    `Normal`     | `<Leader>xx` | Toggle Trouble                          |\n|    `Normal`     | `<Leader>xr` | Toggle Trouble to find lsp references   |\n|    `Normal`     | `Control+f`  | Toggle oil.nvim file manager            |\n|  `Normal/Term`  | `Control+t`  | Toggle floating terminal                |\n|    `Normal`     | `<Leader>pp` | Format buffer with null-ls              |\n|    `Visual`     | `<Leader>pp` | Range format buffer with null-ls        |\n|    `Normal`     |     `rn`     | Do LSP buffer rename                    |\n|    `Normal`     |     `gd`     | Do LSP buffer get definition            |\n|    `Normal`     |     `gD`     | Do LSP buffer get declaration           |\n|    `Normal`     |     `gh`     | Do LSP buffer get hover                 |\n|    `Normal`     |     `gr`     | Do LSP buffer get references            |\n|    `Normal`     |     `gi`     | Do LSP buffer get implementation        |\n\n### Tmux keybindings\n\nI override the default keybindings for Tmux to be more reasonable.\nPrefix key is `Alt+a` for local session and `Alt+z` for nested session.\n`<prefix>` means you need to press prefix key first, `<repeat>` means you don't need to press prefix key again after triggering it (I use unconventional way instead of `-r` flag so it will stay forever unless I press `Esc` key), `<copy-mode>` means you must be in copy-mode first.\nThe table below lists all the keybindings set.\n\n| Keypress              | Description                                                     |\n| :-------------------: | :-------------------------------------------------------------- |\n|`<prefix><repeat>h`    | Move selection to left pane                                     |\n|`<prefix><repeat>j`    | Move selection to pane below                                    |\n|`<prefix><repeat>k`    | Move selection to pane above                                    |\n|`<prefix><repeat>l`    | Move selection to right pane                                    |\n|`<prefix><repeat>H`    | Resize current pane to the left by 2 columns                    |\n|`<prefix><repeat>J`    | Resize current pane downwards by 2 lines                        |\n|`<prefix><repeat>K`    | Resize current pane upwards by 2 lines                          |\n|`<prefix><repeat>L`    | Resize current pane to the right by 2 columns                   |\n|`<prefix><repeat>Alt+n`| Move selection to next window                                   |\n|`<prefix><repeat>Alt+p`| Move selection to previous window                               |\n|`<prefix><repeat>>`    | Swap to next pane                                               |\n|`<prefix><repeat><`    | Swap to previous pane                                           |\n|`<prefix>Alt+s`        | Split window horizontally with current pane path                |\n|`<prefix>Alt+v`        | Split window vertically with current pane path                  |\n|`<prefix>c`            | Open a new window with current pane path                        |\n|`<prefix>Esc`          | Exit prefix key table                                           |\n|`<prefix>Space`        | Exit prefix key table                                           |\n|`<prefix>a`            | Enter copy mode                                                 |\n|`<prefix>:`            | Enter tmux command prompt                                       |\n|`<prefix>x`            | Close current pane                                              |\n|`<prefix>X`            | Close current window                                            |\n|`<copy-mode>b`         | Move cursor to word beginning                                   |\n|`<copy-mode>e`         | Move cursor to word ending                                      |\n|`<copy-mode>Home`      | Move cursor to start of line                                    |\n|`<copy-mode>0`         | Move cursor to start of line                                    |\n|`<copy-mode>End`       | Move cursor to end of line                                      |\n|`<copy-mode>$`         | Move cursor to end of line                                      |\n|`<copy-mode>PageUp`    | Move cursor one page up                                         |\n|`<copy-mode>PageDn`    | Move cursor one page down                                       |\n|`<copy-mode>v`         | Begin selection                                                 |\n|`<copy-mode>Space`     | Begin selection                                                 |\n|`<copy-mode>V`         | Select a line                                                   |\n|`<copy-mode>Alt+v`     | Toggle box selection                                            |\n|`<copy-mode>y`         | Copy selection to clipboard                                     |\n|`<copy-mode>Y`         | Copy from cursor to end of line to clipboard                    |\n|`<copy-mode>Esc`       | Exit copy mode                                                  |\n|`<copy-mode>q`         | Exit copy mode                                                  |\n\n### Zellij keybindings\n\nI'm migrating my tmux to [Zellij](https://zellij.dev/).\nI mimicked my tmux configuration to work in zellij but not everything works the same.\nPrefix key is `Alt+a`, I use the \"switch to normal mode\" in zellij to achieve this.\n`<normal>` means you need to be in normal mode, `<pane>` means pane mode, and so on.\nThe table below lists all the keybindings set.\n\n|        Keypress        | Description                                                |\n| :--------------------: | :--------------------------------------------------------- |\n|    `<locked>Alt+a`     | Swith to normal mode (act like prefix key in tmux)         |\n|    `<normal>Alt+s`     | Create new horizontal split window and back to locked mode |\n|    `<normal>Alt+v`     | Create new vertical split window and back to locked mode   |\n|      `<normal>r`       | Switch to renametab mode                                   |\n|      `<normal>h`       | Move selection to left pane                                |\n|      `<normal>j`       | Move selection to pane below                               |\n|      `<normal>k`       | Move selection to pane above                               |\n|      `<normal>l`       | Move selection to right pane                               |\n|      `<normal>>`       | Move pane around                                           |\n|      `<normal>H`       | Resize current pane upwards                                |\n|      `<normal>J`       | Resize current pane downwards                              |\n|      `<normal>H`       | Resize current pane to the left                            |\n|      `<normal>L`       | Resize current pane to the right                           |\n|    `<normal>Alt+n`     | Go to next window                                          |\n|    `<normal>Alt+p`     | Go to previous window                                      |\n|      `<normal>c`       | Open new tab and back to locked mode                       |\n|      `<normal>x`       | Close current pane and back to locked mode                 |\n|      `<normal>a`       | Open pane with $EDITOR and back to locked mode             |\n| `<normal>Esc/' '/\"\\n\"` | Switch back to locked mode                                 |\n|   `<renametab>Alt+a`   | Switch to normal mode                                      |\n|   `<renametab>\"\\n\"`    | Switch to locked mode                                      |\n|    `<renametab>Esc`    | Confirm tab name and back to locked mode                   |\n\n## :coffee:&nbsp; Acknowledgements\n\nI wrote most of the codes by myself, but there are a lot of stuffs inspired by these repositories.\n\n- [MatthiasBenaets/nixos-config](https://github.com/MatthiasBenaets/nixos-config) for the awesome YouTube video introducing NixOS to me.\n- [viperML/dotfiles](https://github.com/viperML/dotfiles) for answering stupid questions on Discord.\n- [bjw-s/nix-config](https://github.com/bjw-s/nix-config) for being inspiration for my repo structure.\n- [truxnell/nix-config](https://github.com/truxnell/nix-config) for being inspiration for my flakeLib functions.\n"
  },
  {
    "path": "chezmoi/.chezmoi.yaml.tmpl",
    "content": "{{- /* Checks if running interactively */ -}}\n{{- $interactive := stdinIsATTY -}}\n\n{{- /* Template file for chezmoi config file */ -}}\n{{- $headless := false -}}{{/* true if this machine does not have a screen and keyboard */}}\n{{- $agekey := false -}}{{/* true if this machine has agekey to decrypt secret */}}\n\n{{- $hostname := .chezmoi.hostname -}}\n{{- if eq $hostname \"budimanjojo-main\" -}}\n{{-   $headless = false -}}\n{{-   $agekey = true -}}\n{{- end -}}\n{{- if eq $hostname \"budimanjojo-ubuntu\" -}}\n{{-   $headless = false -}}\n{{-   $agekey = true -}}\n{{- end -}}\n{{- if hasKey . \"headless\" -}}\n{{-   $headless = .headless -}}\n{{- else if $interactive -}}\n{{-   $headless = promptBool \"headless\" -}}\n{{- end -}}\n{{- if hasKey . \"agekey\" -}}\n{{-   $agekey = .agekey -}}\n{{- else if $interactive -}}\n{{-   $agekey = promptBool \"agekey\" -}}\n{{- end -}}\n\n{{- if $interactive -}}\n{{-   writeToStdout \"💡 Tip: you can re-enter your data with `chezmoi init --data=false`.\\n\" -}}\n{{- end -}}\n\ndata:\n  headless: {{ $headless }}\n  agekey: {{ $agekey }}\nformat: yaml\nencryption: age\n{{- if $agekey }}\nage:\n  identity: /home/budiman/.config/sops/age/keys.txt\n  recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n{{- end }}\nmerge:\n  command: nvim\n  args:\n    - -d\n    - \"{{ \"{{ .Destination }}\" }}\"\n    - \"{{ \"{{ .Source }}\" }}\"\n    - \"{{ \"{{ .Target }}\" }}\"\ngit:\n  autoAdd: true\n"
  },
  {
    "path": "chezmoi/.chezmoiignore",
    "content": "{{- if .headless }}\n.config/alacritty/\n.config/kitty/\n.config/termite/\n.config/wezterm/\n{{- end }}\n"
  },
  {
    "path": "chezmoi/.chezmoiscripts/run_once_after_90-cleanup.sh.tmpl",
    "content": "#!/bin/bash\n\necho -e \"\\033[0;32m>>>>> Begin Executing Cleanup Job <<<<<\\033[0m\"\n\n# This script will ensure that I fixes changes I have made in the past\n# For example, I used to install stuffs in /usr/local/bin but now I'm not anymore\n# So this script will make sure I cleaned up my old unused stuffs\n\nset -eufo pipefail\n\nfunction not_needed_executable () {\n  local dir=$2\n  local path=$dir/$1\n  local current_path\n  current_path=$(command -v \"$1\" || echo \"notfound\")\n\n  if [ \"$current_path\" == \"notfound\" ]; then\n    echo \"\\\"$1\\\" not found, skip deleting\"\n    return\n  fi\n\n  if [ \"$current_path\" != \"$path\" ] && [ -f \"$path\" ]; then\n    if [ \"$EUID\" -ne 0 ]; then\n      sudo rm \"$path\"\n      echo \"deleted $path, not needed anymore\"\n    else\n      rm \"$path\"\n      echo \"deleted $path, not needed anymore\"\n    fi\n  fi\n}\n\nfunction not_needed_directory () {\n  local dir=$1\n  if [ -d \"$dir\" ]; then\n    if [ \"$EUID\" -ne 0 ]; then\n      sudo rm -rf \"$dir\"\n      echo \"deleted $dir directory, not needed anymore\"\n    else\n      rm -rf \"$dir\"\n      echo \"deleted $dir directory, not needed anymore\"\n    fi\n  fi\n}\n\nfunction not_needed_file() {\n  local file=$1\n  if [ -f \"$file\" ]; then\n    if [ \"$EUID\" -ne 0 ]; then\n      sudo rm \"$file\"\n      echo \"deleted $file, not needed anymore\"\n    else\n      rm \"$file\"\n      echo \"deleted $file, not needed anymore\"\n    fi\n  fi\n}\n\nnot_needed_executable \"kubectl\" \"{{ .chezmoi.homeDir }}/.local/bin\"\nnot_needed_executable \"flux\" \"{{ .chezmoi.homeDir }}/.local/bin\"\nnot_needed_executable \"kustomize\" \"/usr/local/bin\"\nnot_needed_executable \"sops\" \"/usr/local/bin\"\nnot_needed_executable \"fzf\" \"/usr/local/bin\"\nnot_needed_executable \"starship\" \"/usr/local/bin\"\nnot_needed_executable \"zoxide\" \"/usr/local/bin\"\nnot_needed_executable \"rg\" \"/usr/local/bin\"\nnot_needed_executable \"fd\" \"/usr/local/bin\"\nnot_needed_executable \"bat\" \"/usr/local/bin\"\nnot_needed_executable \"age\" \"/usr/local/bin\"\nnot_needed_executable \"age-keygen\" \"/usr/local/bin\"\nnot_needed_executable \"chezmoi\" \"{{ .chezmoi.homeDir }}/.local/bin\"\nnot_needed_executable \"node\" \"/usr/local/bin\"\n\nnot_needed_directory \"{{ .chezmoi.homeDir }}/.local/share/nvim/lsp_servers\"\nnot_needed_directory \"{{ .chezmoi.homeDir }}/.local/share/aquaproj-aqua\"\nnot_needed_directory \"{{ .chezmoi.homeDir }}/.local/share/fonts/UbuntuMono\"\nnot_needed_directory \"{{ .chezmoi.homeDir }}/.local/share/fonts/Hack\"\nnot_needed_directory \"{{ .chezmoi.homeDir }}/.local/share/fonts/unifont\"\nnot_needed_directory \"{{ .chezmoi.homeDir }}/.local/share/fonts/unifont_upper\"\n\nnot_needed_file \"{{ .chezmoi.homeDir }}/.config/fish/conf.d/nix.fish\"\n\nif [ \"$(command -v brew)\" ]; then\n  echo \"uninstalling brew\"\n  /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)\"\nfi\n\necho -e \"\\033[0;32m>>>>> Finish Executing Cleanup Job<<<<<\\033[0m\"\n"
  },
  {
    "path": "chezmoi/.chezmoiscripts/run_once_before_10-setup-fish.sh.tmpl",
    "content": "{{ if (eq .chezmoi.os \"linux\") -}}\n#!/bin/bash\nset -eufo pipefail\n\necho -e \"\\0033[0;32m>>>>> Begin Setting Up Fish Shell <<<<<\\033[0m\"\n\nset_default_shell() {\n  local expected_default=\"{{ .chezmoi.homeDir }}/.nix-profile/bin/fish\"\n  local current_default\n\n  current_default=$(grep \"^{{ .chezmoi.username }}:\" /etc/passwd | cut -d: -f7)\n\n  if [ ! -x \"$expected_default\" ]; then\n    echo \"No fish binary found in $expected_default, skip changing default shell\"\n    return\n  fi\n\n  if [ \"$current_default\" != \"$expected_default\" ]; then\n    echo \"Changing default shell to fish\"\n    if ! grep -wq \"$expected_default\" /etc/shells; then\n      echo \"Adding $expected_default to /etc/shells\"\n      echo \"$expected_default\" | sudo tee -a /etc/shells >/dev/null\n    fi\n    chsh -s \"$expected_default\"\n  fi\n}\n\n# Set fish as default shell\nset_default_shell\n\necho -e \"\\0033[0;32m>>>>> Finish Setting Up Fish Shell <<<<<\\033[0m\"\n{{ end -}}\n"
  },
  {
    "path": "chezmoi/.chezmoiscripts/run_once_before_20-install-packages-archlinux.sh.tmpl",
    "content": "{{ if (eq .chezmoi.osRelease.id \"arch\") -}}\n#!/bin/bash\n\nset -eufo pipefail\n\necho -e \"\\033[0;32m>>>>> Begin Setting Up Arch Linux Packages <<<<<\\033[0m\"\n\npackages=(\n  curl\n  luajit\n  npm\n  unzip\n)\n\naur_packages=(\n)\n\n{{   if (not .headless) -}}\npackages+=(\n  wezterm\n)\n{{   end -}}\n\necho \"updating packages\"\n{{   if ne .chezmoi.username \"root\" -}}\nsudo pacman -Syu --noconfirm\n{{   else -}}\npacman -Syu --noconfirm\n{{   end -}}\n\nfor package in ${packages[@]}; do\n  if [ \"$(pacman -Qq $package 2> /dev/null)\" != $package ]; then\n    echo \"installing $package\"\n{{-   if ne .chezmoi.username \"root\" }}\n    sudo pacman -S --noconfirm $package\n{{-   else }}\n    pacman -S --noconfirm $package\n{{-   end }}\n  fi\ndone\n\n## Install yay\nif [ ! $(command -v yay) ]; then\n  echo \"installing yay\"\n{{-   if ne .chezmoi.username \"root\" }}\n  sudo pacman -S --needed --noconfirm git base-devel\n{{-   else}}\n  pacman -S --needed --noconfirm git base-devel\n{{-   end }}\n  git clone https://aur.archlinux.org/yay.git /tmp/yay\n  cd /tmp/yay\n  makepkg -si --noconfirm\n  rm -rf /tmp/yay\nfi\n\nfor aur_package in ${aur_packages[@]}; do\n  if [ $(pacman -Qq $aur_package 2> /dev/null) != $aur_package ]; then\n    echo \"installing $aur_package from AUR\"\n    yay -S --noconfirm $aur_package\n  fi\ndone\n\necho -e \"\\033[0;32m>>>>> Finish Setting Up Arch Linux Packages <<<<<\\033[0m\"\n{{ end -}}\n"
  },
  {
    "path": "chezmoi/.chezmoiscripts/run_once_before_20-install-packages-ubuntu.sh.tmpl",
    "content": "{{ if (and (eq .chezmoi.os \"linux\") (eq .chezmoi.osRelease.id \"debian\" \"ubuntu\")) -}}\n#!/bin/bash\n\nset -eufo pipefail\n\necho -e \"\\033[0;32m>>>>> Begin Setting Up Ubuntu Packages <<<<<\\033[0m\"\n\n# List of ppa repositories to add\nrepositories=(\n)\n\n# List of packages to install\npackages=(\n  curl\n  unzip\n)\n\n# List of snap packages to install\nsnaps=()\n\nfor repository in ${repositories[@]}; do\n  ppa_repo_source=${repository#ppa:}\n  if ! $(apt-cache policy | grep http | awk '{print $2}' | sort -u | grep $ppa_repo_source &> /dev/null); then\n    echo \"adding $repository repository to apt\"\n{{ if ne .chezmoi.username \"root\" -}}\n    sudo add-apt-repository -y $repository\n{{ else -}}\n    add-apt-repository -y $repository\n{{ end -}}\n    echo \"false\"\n  fi\ndone\n\nfor package in ${packages[@]}; do\n  if ! $(dpkg-query -W -f='installed' $package &> /dev/null); then\n    echo \"installing $package\"\n{{ if ne .chezmoi.username \"root\" -}}\n    sudo apt install -y $package\n{{ else -}}\n    apt install -y ${packages[@]}\n{{ end -}}\n  fi\ndone\n\n# renovate: depName=wez/wezterm datasource=github-releases\ncurrent_wezterm_version=20240203-110809-5046fc22\n\nif [ ! $(command -v wezterm) ] || [ $(wezterm -V | head -n1 | cut -d\" \" -f2) != \"$current_wezterm_version\" ]; then\n  echo \"installing / upgrading wezterm\"\n  curl -Lo /tmp/wezterm https://github.com/wez/wezterm/releases/download/\"$current_wezterm_version\"/WezTerm-\"$current_wezterm_version\"-Ubuntu20.04.AppImage\n  chmod +x /tmp/wezterm\n  mv /tmp/wezterm {{ .chezmoi.homeDir }}/.local/bin/wezterm\nfi\n\n{{ if (eq .chezmoi.osRelease.id \"ubuntu\") -}}\nfor snap in ${snaps[@]}; do\n  echo \"installing $snap using snap\"\n{{   if ne .chezmoi.username \"root\" -}}\n  sudo snap install $snap\n{{   else -}}\n  snap install $snap\n{{   end -}}\ndone\n{{ end -}}\n\necho -e \"\\033[0;32m>>>>> Finish Setting Up Ubuntu Packages <<<<<\\033[0m\"\n{{ end -}}\n"
  },
  {
    "path": "chezmoi/.chezmoiscripts/run_onchange_after_80-setup-terminal.sh.tmpl",
    "content": "{{ if (and (eq .chezmoi.os \"linux\") (eq .chezmoi.osRelease.id \"debian\" \"ubuntu\")) -}}\n#!/bin/bash\n\necho -e \"\\033[0;32m>>>>> Begin Setting Up Nix Applications <<<<<\\033[0m\"\n\nset -eufo pipefail\n\n# This script will ensure the applications installed by Nix is known by the OS\n# So `update-alternatives` program used by Debian based OS to open program can work properly\n\n# Run this script whenever the content of the host configuration for this machine changes\n# {{ include (print \"../\" \"/home/\" .chezmoi.username \"/hosts/\" .chezmoi.hostname \".nix\") | sha256sum }}\n\nfunction add_nix_app_to_update_alternative() {\n  local program=\"{{ .chezmoi.homeDir }}/.nix-profile/bin/$1\"\n  local priority=$2\n  local to_link=\"${3:-x-terminal-emulator}\"\n\n  local found\n  found=$(update-alternatives --quiet --list \"$to_link\" | grep -x \"$program\" || true)\n\n  # if program is not installed, proceed to remove it from the list\n  # this is not really an efficient approach but there's no way to get `update-alternatives` to show binary that doesn't exist\n  if [[ ! -x \"$program\" ]]; then\n    sudo update-alternatives --quiet --remove \"$to_link\" \"$program\"\n    return\n  fi\n\n  if [[ -n \"$found\" ]]; then\n    local cur_priority\n    cur_priority=\"$(update-alternatives --quiet --display \"$to_link\" | sed -n \"s|^$program - priority \\([0-9]\\+\\)$|\\1|p\")\"\n    if [ \"$cur_priority\" != \"$priority\" ]; then\n      echo \"updating $program priority as $to_link from $cur_priority to $priority\"\n      sudo update-alternatives --install \"$(which \"$to_link\")\" \"$to_link\" \"$program\" \"$priority\"\n      return\n    fi\n  else\n    echo \"installing $program as $to_link with priority of $priority\"\n    sudo update-alternatives --install \"$(which \"$to_link\")\" \"$to_link\" \"$program\" \"$priority\"\n    return\n  fi\n}\n\nadd_nix_app_to_update_alternative \"contour\" \"9999\"\nadd_nix_app_to_update_alternative \"alacritty\" \"9998\"\nadd_nix_app_to_update_alternative \"kitty\" \"9997\"\nadd_nix_app_to_update_alternative \"wezterm\" \"9996\"\n\necho -e \"\\033[0;32m>>>>> Finish Setting Up Nix Applications <<<<<\\033[0m\"\n{{ end -}}\n"
  },
  {
    "path": "chezmoi/dot_config/fontconfig/fonts.conf.tmpl",
    "content": "{{ if eq .chezmoi.hostname \"budimanjojo-main\" -}}\n<fontconfig>\n<alias>\n    <family>serif</family>\n    <prefer>\n        <family>Hack Nerd Font Mono</family>\n        <family>Ubuntu Nerd Font</family>\n    </prefer>\n</alias>\n<alias>\n    <family>sans-serif</family>\n    <prefer>\n        <family>Hack Nerd Font Mono</family>\n        <family>Ubuntu Nerd Font</family>\n    </prefer>\n</alias>\n<alias>\n    <family>sans</family>\n    <prefer>\n        <family>Hack Nerd Font Mono</family>\n        <family>Ubuntu Nerd Font</family>\n    </prefer>\n</alias>\n<alias>\n    <family>monospace</family>\n    <prefer>\n        <family>Hack Nerd Font Mono</family>\n        <family>Ubuntu Nerd Font</family>\n    </prefer>\n</alias>\n<match target=\"font\">\n    <edit name=\"hinting\" mode=\"assign\">\n        <bool>true</bool>\n    </edit>\n    <edit name=\"hintstyle\" mode=\"assign\">\n        <const>hintslight</const>\n    </edit>\n    <edit name=\"antialias\" mode=\"assign\">\n        <bool>true</bool>\n    </edit>\n    <edit name=\"autohint\" mode=\"assign\">\n        <bool>true</bool>\n    </edit>\n    <edit name=\"embeddedbitmap\" mode=\"assign\">\n        <bool>false</bool>\n    </edit>\n</match>\n</fontconfig>\n{{ end -}}\n"
  },
  {
    "path": "chezmoi/dot_config/termite/config",
    "content": "[options]\n#allow_bold = true\n#audible_bell = false\n#bold_is_bright = true\n#cell_height_scale = 1.0\n#cell_width_scale = 1.0\n#clickable_url = true\n#dynamic_title = true\nfont = UbuntuMono Nerd Font 12\n# font = Hack Nerd Font Mono 12\n#fullscreen = true\n#icon_name = terminal\n#mouse_autohide = false\n#scroll_on_output = false\n#scroll_on_keystroke = true\n# Length of the scrollback buffer, 0 disabled the scrollback buffer\n# and setting it to a negative value means \"infinite scrollback\"\nscrollback_lines = 10000\n#search_wrap = true\n#urgent_on_bell = true\n#hyperlinks = false\n\n# $BROWSER is used by default if set, with xdg-open as a fallback\n#browser = xdg-open\n\n# \"system\", \"on\" or \"off\"\n#cursor_blink = system\n\n# \"block\", \"underline\" or \"ibeam\"\n#cursor_shape = block\n\n# Hide links that are no longer valid in url select overlay mode\n#filter_unmatched_urls = true\n\n# Emit escape sequences for extra modified keys\n#modify_other_keys = false\n\n# set size hints for the window\n#size_hints = false\n\n# \"off\", \"left\" or \"right\"\n#scrollbar = off\n\n[colors]\n# If both of these are unset, cursor falls back to the foreground color,\n# and cursor_foreground falls back to the background color.\n#cursor = #dcdccc\n#cursor_foreground = #dcdccc\n\nforeground = #C0CAF5\n#foreground_bold = #ffffff\nbackground = #1A1B26\n\n# 20% background transparency (requires a compositor)\nbackground = rgba(29, 31, 33, 0.9)\n\n# If unset, will reverse foreground and background\n# highlight = #2f2f2f\n\n# Colors from color0 to color254 can be set\ncolor0 = #15161E\ncolor1 = #F7768E\ncolor2 = #9ECE6A\ncolor3 = #E0AF68\ncolor4 = #7AA2F7\ncolor5 = #BB9AF7\ncolor6 = #7DCFFF\ncolor7 = #A9B1D6\ncolor8 = #414868\ncolor9 = #F7768E\ncolor10 = #9ECE6A\ncolor11 = #E0AF68\ncolor12 = #7AA2F7\ncolor13 = #BB9AF7\ncolor14 = #7DCFFF\ncolor15 = #C0CAF5\n\n[hints]\n#font = Monospace 12\n#foreground = #dcdccc\n#background = #3f3f3f\n#active_foreground = #e68080\n#active_background = #3f3f3f\n#padding = 2\n#border = #3f3f3f\n#border_width = 0.5\n#roundness = 2.0\n\n# vim: ft=dosini cms=#%\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  description = \"My NixOS configurations IaC\";\n\n  inputs = {\n    # nixpkgs and unstable\n    nixpkgs.url = \"github:nixos/nixpkgs/nixos-25.11\";\n    nixpkgs-unstable.url = \"github:nixos/nixpkgs/nixpkgs-unstable\";\n\n    # flake-parts - very lightweight flake framework\n    # https://flake.parts\n    flake-parts.url = \"github:hercules-ci/flake-parts\";\n\n    # home-manager - home user modules\n    # https://github.com/nix-community/home-manager\n    home-manager = {\n      url = \"github:nix-community/home-manager/release-25.11\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n\n    # sops-nix - secrets with `sops`\n    # https://github.com/Mic92/sops-nix\n    sops-nix = {\n      url = \"github:Mic92/sops-nix\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n\n    # Disko - declarative disk partitioning and formatting\n    # https://github.com/nix-community/disko\n    disko = {\n      url = \"github:nix-community/disko\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n\n    # NUR - Nix User Repository\n    # https://github.com/nix-community/NUR\n    nur.url = \"github:nix-community/NUR\";\n\n    # nvfetcher - tool to automate nix packages updates\n    # https://github.com/berberman/nvfetcher\n    nvfetcher = {\n      url = \"github:berberman/nvfetcher\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n\n    # nixvim - Neovim distribution built around Nix modules\n    # https://github.com/nix-community/nixvim\n    nixvim = {\n      url = \"github:nix-community/nixvim/nixos-25.11\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n\n    # Talhelper - A tool to help creating Talos Kubernetes cluster\n    # https://github.com/budimanjojo/talhelper\n    talhelper = {\n      url = \"github:budimanjojo/talhelper\";\n    };\n\n    # Catppuccin for Nix - Soothing pastel theme for Nix\n    # https://github.com/catppuccin/nix\n    catppuccin.url = \"github:catppuccin/nix/release-25.11\";\n\n    # nixGL - A wrapper tool for nix OpenGL application\n    # https://github.com/nix-community/nixGL\n    nixgl = {\n      url = \"github:nix-community/nixGL\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n\n    # tdarr-plugins - My Tdarr plugins repo fork\n    # https://github.com/budimanjojo/tdarr-plugins\n    tdarr-plugins = {\n      url = \"github:budimanjojo/tdarr-plugins\";\n      flake = false;\n    };\n\n    # cache-nix-action - Cache Nix Store in GitHub Actions\n    # https://github.com/nix-community/cache-nix-action\n    cache-nix-action = {\n      url = \"github:nix-community/cache-nix-action\";\n      flake = false;\n    };\n  };\n\n  outputs =\n    { flake-parts, ... }@inputs:\n    let\n      # function to make `pkgs` for defined system with my overlays\n      mkPkgsWithSystem =\n        system:\n        let\n          localSystem = system;\n          config = {\n            allowUnfree = true;\n            allowUnfreePredicate = _: true;\n          };\n        in\n        import inputs.nixpkgs {\n          inherit localSystem config;\n          overlays = builtins.attrValues (import ./overlays { inherit inputs config; });\n        };\n\n      flakeLib = import ./flakeLib.nix { inherit inputs mkPkgsWithSystem; };\n    in\n    inputs.flake-parts.lib.mkFlake { inherit inputs; } {\n      # systems for which you want to build the `perSystem` attributes\n      systems = [\n        \"x86_64-linux\"\n        \"aarch64-linux\"\n      ];\n\n      # everything below `perSystem` will be enumerated and have `${system}`\n      # added in the middle by `flake-parts`\n      perSystem =\n        {\n          system,\n          inputs',\n          self',\n          pkgs,\n          ...\n        }:\n        {\n          # override pkgs used by everything in `perSystem` to have my overlays\n          _module.args.pkgs = mkPkgsWithSystem system;\n          # accessible via `nix fmt` to format code\n          formatter = pkgs.nixfmt-rfc-style;\n          # accessible via `nix build .#<name>`\n          legacyPackages = import ./packages { inherit inputs' self' pkgs; };\n          packages = {\n            # this is for cache-nix-action so stuffs don't get garbage collected\n            # before I cache the nix store, mostly to not redownload inputs a lot\n            gc-keep =\n              (import \"${inputs.cache-nix-action}/saveFromGC.nix\" {\n                inherit pkgs inputs;\n                inputsInclude = [\n                  \"nixpkgs\"\n                  \"nixpkgs-unstable\"\n                  \"flake-parts\"\n                  \"home-manager\"\n                  \"sops-nix\"\n                  \"disko\"\n                  \"nur\"\n                  \"nvfetcher\"\n                  \"nixvim\"\n                  \"talhelper\"\n                  \"catppuccin\"\n                  \"nixgl\"\n                ];\n              }).package;\n          };\n          # accessible via `nix develop`\n          devShells.default = import ./shell.nix { inherit inputs' pkgs; };\n        };\n\n      # all the other flake outputs those don't require `${system}`\n      # string will be here and handled by `flake-parts`\n      flake = rec {\n        ghActions.matrix = flakeLib.mkGithubMatrix {\n          inherit nixosConfigurations homeConfigurations;\n        };\n\n        nixosConfigurations = {\n          # this is a NixOS live CD that has SSH enabled and some of my stuffs baked in\n          # build with `nix build .#nixosConfigurations.nixos-livecd.config.system.build.isoImage`\n          nixos-livecd = flakeLib.mkSystem {\n            hostname = \"nixos-livecd\";\n            homeUsers = [ ];\n            baseModules = [ ./system/hosts ];\n            extraModules = [\n              { config.ghMatrix.enable = false; }\n            ];\n          };\n          budimanjojo-main = flakeLib.mkSystem {\n            hostname = \"budimanjojo-main\";\n          };\n          budimanjojo-nas = flakeLib.mkSystem {\n            hostname = \"budimanjojo-nas\";\n            extraModules = [ inputs.disko.nixosModules.disko ];\n          };\n          budimanjojo-firewall = flakeLib.mkSystem {\n            hostname = \"budimanjojo-firewall\";\n            extraModules = [ inputs.disko.nixosModules.disko ];\n          };\n          # this is my Oracle always free instance that I use as my WireGuard relay server\n          # so my firewall can live behind a NAT and be fine in case I switch to a different\n          # ISP that doesn't give me public routeable IP\n          budimanjojo-oracle = flakeLib.mkSystem {\n            hostname = \"budimanjojo-oracle\";\n            system = \"aarch64-linux\";\n          };\n        };\n\n        homeConfigurations = {\n          \"budiman@budimanjojo-ubuntu\" = flakeLib.mkHome {\n            hostname = \"budimanjojo-ubuntu\";\n            system = \"x86_64-linux\";\n          };\n        };\n\n      };\n    };\n}\n"
  },
  {
    "path": "flakeLib.nix",
    "content": "{ inputs, mkPkgsWithSystem }:\nlet\n  # my own custom lib will be accessible with `lib.myLib.<name>`\n  lib = inputs.nixpkgs.lib.extend (final: prev: { myLib = import ./lib { lib = final; }; });\n\n  # This module is passed to all `nixosConfigurations` and `homeConfigurations` so I can\n  # specify whether a machine should be built and pushed on CI or not\n  ghMatrixModules =\n    {\n      config,\n      lib,\n      pkgs,\n      ...\n    }:\n    let\n      cfg = config.ghMatrix;\n      inherit (lib) mkOption types;\n\n      defaultRunners = {\n        x86_64-linux = \"ubuntu-24.04\";\n        aarch64-linux = \"ubuntu-24.04-arm\";\n        x86_64-darwin = \"macos-13\";\n        aarch64-darwin = \"macos-14\";\n      };\n    in\n    {\n      options.ghMatrix = {\n        enable = mkOption {\n          type = types.bool;\n          default = true;\n        };\n        system = mkOption {\n          type = types.str;\n          default = pkgs.stdenv.hostPlatform.system;\n          readOnly = true;\n        };\n        runner = mkOption {\n          type = types.oneOf [\n            types.str\n            (types.listOf types.str)\n          ];\n          default = defaultRunners.${cfg.system};\n        };\n      };\n    };\nin\n{\n  mkGithubMatrix =\n    {\n      nixosConfigurations ? { },\n      homeConfigurations ? { },\n    }:\n    let\n      matrixSet = name: cfg: {\n        system = cfg.config.ghMatrix.system;\n        runner = cfg.config.ghMatrix.runner;\n        attrset =\n          if cfg.config ? system && cfg.config.system ? build then\n            \"nixosConfigurations.${name}.config.system.build.toplevel\"\n          else if cfg ? activationPackage then\n            \"homeConfigurations.${name}.activationPackage\"\n          else\n            throw \"${name} is neither `nixosConfigurations` or `homeConfigurations`\";\n      };\n\n      filterAttr = attr: lib.filterAttrs (n: v: v.config.ghMatrix.enable) attr;\n      mkList = attr: cfgs: builtins.attrValues (builtins.mapAttrs attr cfgs);\n      result.include = mkList matrixSet (filterAttr (nixosConfigurations // homeConfigurations));\n    in\n    result;\n\n  mkSystem =\n    {\n      hostname,\n      adminUser ? \"budiman\", # default user to login with `sudo` access\n      system ? \"x86_64-linux\",\n      nixpkgs ? inputs.nixpkgs,\n      # myPkgs defaulted to the `legacyPackages` from this flake\n      myPkgs ? inputs.self.legacyPackages.${system},\n      # homeUsers is list of home-manager users for the host\n      # each user needs a `./home-manager/<name>/default.nix` file present\n      # set it to empty list to disable home-manager altogether for the host\n      homeUsers ? [ adminUser ],\n      # baseModules is the base of the entire machine building\n      baseModules ? [\n        inputs.sops-nix.nixosModules.sops\n        inputs.home-manager.nixosModules.home-manager\n        inputs.catppuccin.nixosModules.catppuccin\n        ./system/_modules # all machines get my own NixOS modules\n        ./system/hosts # entrypoint to all machines\n        {\n          config = {\n            mySystem = {\n              adminUser = adminUser;\n            };\n          };\n        }\n      ],\n      # extraModules is additional modules you want to add for the host\n      extraModules ? [ ],\n    }:\n    let\n      mkHomeUsers = lib.optionals (homeUsers != [ ]) [\n        {\n          config.home-manager = {\n            users = builtins.listToAttrs (\n              builtins.map (name: {\n                name = name;\n                value = {\n                  imports = [ ./home/${name} ];\n                  config.myHome = {\n                    username = name;\n                  };\n                };\n              }) homeUsers\n            );\n            useGlobalPkgs = true;\n            useUserPackages = true;\n            extraSpecialArgs = {\n              inherit inputs myPkgs hostname;\n            };\n            sharedModules = [\n              inputs.sops-nix.homeManagerModules.sops\n              inputs.catppuccin.homeModules.catppuccin\n              ./home/_modules # all users get my own home-manager modules\n            ];\n          };\n        }\n      ];\n    in\n    nixpkgs.lib.nixosSystem {\n      inherit system lib;\n      pkgs = mkPkgsWithSystem system;\n      specialArgs = {\n        inherit\n          inputs\n          myPkgs\n          hostname\n          ;\n      };\n      modules = baseModules ++ extraModules ++ mkHomeUsers ++ [ ghMatrixModules ];\n    };\n\n  mkHome =\n    {\n      hostname,\n      username ? \"budiman\",\n      system ? \"x86_64-linux\",\n      nixpkgs ? inputs.nixpkgs,\n      # myPkgs defaulted to the `legacyPackages` from this flake\n      myPkgs ? inputs.self.legacyPackages.${system},\n    }:\n    inputs.home-manager.lib.homeManagerConfiguration {\n      inherit lib;\n      pkgs = mkPkgsWithSystem system;\n      extraSpecialArgs = {\n        inherit\n          inputs\n          myPkgs\n          hostname\n          ;\n        # need to have this because a lot of my modules depends on `osConfig` argument\n        osConfig = { };\n      };\n      modules = [\n        inputs.sops-nix.homeManagerModules.sops\n        inputs.catppuccin.homeModules.catppuccin\n        ./home/_modules # all users get my own home-manager modules\n        {\n          imports = [ ./home/${username} ];\n          config.myHome.username = username;\n        }\n        ghMatrixModules\n      ];\n    };\n}\n"
  },
  {
    "path": "home/_modules/_default/default.nix",
    "content": "{ config, ... }:\nlet\n  myHome = config.myHome;\nin\n{\n  imports = [\n    ./nix.nix\n    ./sops.nix\n  ];\n\n  config = {\n    home = {\n      username = myHome.username;\n      homeDirectory = \"/home/\" + myHome.username;\n      stateVersion = \"23.11\";\n    };\n\n    programs.home-manager.enable = true;\n\n    catppuccin = {\n      flavor = \"mocha\";\n      accent = \"mauve\";\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/_default/nix.nix",
    "content": "{\n  lib,\n  osConfig,\n  pkgs,\n  inputs,\n  ...\n}:\nlet\n  isNixos = lib.myLib.isNixos osConfig;\nin\n{\n  # only enable when we are not on NixOS\n  nix = lib.mkIf (!isNixos) {\n    package = pkgs.nix;\n    registry = {\n      stable.flake = inputs.nixpkgs;\n      unstable.flake = inputs.nixpkgs-unstable;\n    };\n\n    gc.automatic = true;\n\n    settings = {\n      nix-path = \"nixpkgs=${inputs.nixpkgs.outPath}\";\n      extra-substituters = [ \"https://budimanjojo.cachix.org\" ];\n      trusted-substituters = [ \"https://budimanjojo.cachix.org\" ];\n      extra-trusted-public-keys = [\n        \"budimanjojo.cachix.org-1:S0gy6IKTFXis9fFqEbVAS2zsvnZw/30NV2bWvGiN1YQ=\"\n      ];\n      experimental-features = [\n        \"nix-command\"\n        \"flakes\"\n      ];\n      auto-optimise-store = true;\n      keep-outputs = true;\n      keep-derivations = false;\n      cores = 0;\n      max-jobs = \"auto\";\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/_default/sops.nix",
    "content": "{\n  lib,\n  osConfig,\n  pkgs,\n  config,\n  ...\n}:\nlet\n  isNixos = lib.myLib.isNixos osConfig;\nin\n{\n  config = {\n    home.packages = lib.mkIf (!isNixos) [\n      pkgs.sops\n      pkgs.age\n    ];\n    sops.age = {\n      keyFile = \"${config.home.homeDirectory}/.config/sops/age/keys.txt\";\n      generateKey = true; # generate the key if it doesn't exist\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/browser/firefox/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  options,\n  ...\n}:\nlet\n  inherit (lib)\n    getExe\n    mkOption\n    types\n    mkPackageOption\n    mkIf\n    ;\n\n  cfg = config.myHome.browser.firefox;\n  rofiExe = getExe config.programs.rofi.package;\n  firefoxExe = getExe config.programs.firefox.package;\n\n  # need a wrapper to replace Firefox profile manager\n  # https://github.com/nix-community/home-manager/issues/3117\n  rofiWrapper = pkgs.writeShellScriptBin \"rofi-firefox-wrapper\" ''\n    cfgFile=$HOME/.mozilla/firefox/profiles.ini\n\n    if test -f \"$cfgFile\"; then\n      i=0\n      profiles=$(grep 'Name' \"$cfgFile\" | sed 's/Name=//g')\n\n      while read -r; do\n        i=$((i+1))\n      done < <(echo \"$profiles\")\n\n      if [ $i -gt 1 ]; then\n        options=$(echo \"$profiles\" | sed ':a; N; $!ba; s/\\n/\\\\n/g')\n        chosen=$(echo -e \"$options\" | ${rofiExe} -dmenu -P \"Select Firefox Profile\")\n        if [ \"$chosen\" != \"\" ]; then\n          ${firefoxExe} -P \"$chosen\"\n        fi\n      else\n        ${firefoxExe}\n      fi\n    else\n      ${firefoxExe}\n    fi\n  '';\nin\n{\n  options.myHome.browser.firefox = {\n    enable = mkOption {\n      type = types.bool;\n      default = false;\n      description = ''\n        Enable Firefox Web Browser.\n        This module include a wrapper that will open up `rofi` profile selector when you have more than one profile or open Firefox normally otherwise.\n\n        The wrapper can be accessed by pressing `Super+W` on the keyboard in i3, Hyprland, and Sway window manager.\n      '';\n    };\n    package = mkPackageOption pkgs \"firefox\" { };\n    profiles = mkOption {\n      type = options.programs.firefox.profiles.type;\n      default = {\n        \"\" = {\n          id = 0;\n          name = \"${config.myHome.username}\";\n          isDefault = true;\n        };\n      };\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    programs.firefox = {\n      enable = true;\n      package = cfg.package;\n      profiles = cfg.profiles;\n    };\n\n    wayland.windowManager = {\n      hyprland.settings.bind = [ \"SUPER, w, exec, ${getExe rofiWrapper}\" ];\n      sway.config.keybindings.\"Mod4+w\" = \"exec --no-startup-id ${getExe rofiWrapper}\";\n    };\n\n    xsession.windowManager.i3.config.keybindings.\"Mod4+w\" = \"exec --no-startup-id ${getExe rofiWrapper}\";\n  };\n}\n"
  },
  {
    "path": "home/_modules/default.nix",
    "content": "{ lib, osConfig, ... }:\nlet\n  inherit (lib)\n    mkOption\n    types\n    myLib\n    mkDefault\n    ;\nin\n{\n  imports = [\n    ../../system/_modules/myHardware.nix\n    # base module enabled for all users\n    ./_default\n\n    ./browser/firefox\n\n    ./editor/neovim\n\n    ./homelab/kubernetes\n\n    ./multiplexer/tmux\n    ./multiplexer/zellij\n\n    ./programs/beeaccounting\n    ./programs/chezmoi\n    ./programs/fontconfig\n    ./programs/obs-studio\n    ./programs/go\n    ./programs/qmk\n    ./programs/yamllint\n\n    ./services/opencloud-client\n\n    ./shell/dircolors\n    ./shell/fish\n    ./shell/git\n    ./shell/lf\n    ./shell/nix-direnv\n    ./shell/starship\n\n    ./terminal-emulator/alacritty\n    ./terminal-emulator/contour\n    ./terminal-emulator/kitty\n    ./terminal-emulator/wezterm\n\n    ./windowmanager/add-on/blueman-applet\n    ./windowmanager/add-on/dunst\n    ./windowmanager/add-on/gtk-theme\n    ./windowmanager/add-on/nm-applet\n    ./windowmanager/add-on/nwg-bar\n    ./windowmanager/add-on/pasystray\n    ./windowmanager/add-on/picom\n    ./windowmanager/add-on/py3status\n    ./windowmanager/add-on/rofi\n    ./windowmanager/add-on/screenshotter\n    ./windowmanager/add-on/swayidle\n    ./windowmanager/add-on/swaylock\n    ./windowmanager/add-on/terminal-emulator\n    ./windowmanager/add-on/theme/tokyonight\n    ./windowmanager/add-on/waybar\n    ./windowmanager/add-on/xdg\n\n    ./windowmanager/hyprland\n    ./windowmanager/i3\n    ./windowmanager/sway\n  ];\n\n  options.myHome = {\n    username = mkOption {\n      type = types.str;\n      default = \"\";\n    };\n    isWayland = mkOption {\n      type = types.bool;\n      default = false;\n    };\n  };\n\n  # defaulting to use the system config on NixOS machine\n  config.myHardware = mkDefault (myLib.copyFromSystem \"myHardware\" osConfig);\n}\n"
  },
  {
    "path": "home/_modules/editor/neovim/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.editor.neovim;\nin\n{\n  options.myHome.editor.neovim = {\n    enable = lib.mkEnableOption \"Neovim\";\n    package = lib.mkPackageOption pkgs \"neovim\" { };\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    home.packages = [ cfg.package ];\n\n    programs.fish = {\n      shellAliases = {\n        vimdiff = \"nvim -d\";\n      };\n      shellAbbrs = {\n        vi = \"nvim\";\n        vim = \"nvim\";\n      };\n    };\n\n    home.sessionVariables = {\n      EDITOR = \"nvim\";\n      VISUAL = \"nvim\";\n      MANPAGER = \"nvim +Man!\";\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/homelab/kubernetes/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  myPkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.homelab.kubernetes;\nin\n{\n  options.myHome.homelab.kubernetes = {\n    enable = lib.mkEnableOption \"Kubernetes tools for homelab\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    myHome = {\n      programs.yamllint.enable = true;\n      shell = {\n        git.enable = true;\n        nix-direnv.enable = true;\n      };\n    };\n\n    sops.secrets = {\n      kubeconfig = {\n        sopsFile = ./secret.sops.yaml;\n        path = \"${config.home.homeDirectory}/.kube/config\";\n      };\n      talosconfig = {\n        sopsFile = ./secret.sops.yaml;\n        path = \"${config.home.homeDirectory}/.talos/config\";\n      };\n    };\n\n    home.packages = with pkgs; [\n      age\n      envsubst\n      fluxcd\n      go-task\n      jq\n      just\n      kubectl\n      kubernetes-helm\n      kustomize\n      nodePackages.zx\n      talosctl\n      myPkgs.kubectl-rook-ceph\n      myPkgs.talhelper\n    ];\n\n    programs.fish.shellAbbrs = {\n      # kubectl\n      k = {\n        position = \"anywhere\";\n        expansion = \"kubectl\";\n      };\n      kgp = {\n        position = \"anywhere\";\n        expansion = \"kubectl get pod\";\n      };\n      kgn = {\n        position = \"anywhere\";\n        expansion = \"kubectl get nodes\";\n      };\n      kl = \"kubectl logs\";\n      kdp = \"kubectl describe pod\";\n\n      # flux\n      f = {\n        position = \"anywhere\";\n        expansion = \"flux\";\n      };\n      fgk = {\n        position = \"anywhere\";\n        expansion = \"flux get ks\";\n      };\n      fsa = \"flux suspend kustomizatin --all\";\n      fra = \"flux resume kustomization --all\";\n\n      # talosctl\n      t = \"talosctl\";\n\n      # talhelper\n      th = \"talhelper\";\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/homelab/kubernetes/secret.sops.yaml",
    "content": "kubeconfig: ENC[AES256_GCM,data:pfVS55vfo3YNYpYLfQSot40QWl2VEJaVsO6SfbI2284uO/OLsK6VD/6XQBT9UhzPU8dbJolYePEfeaRIcU5v1qqX6ZsZRx4SSMuGw7yEjjSzVY3OdRIWeochIa5pUD8ke/l9g+3HfJxoD+cOOyobGw22okKqQ+VmHizl/45EoKp2pJa7qB9TtW/HD7HhNxmVKtGGfxuI5uvz46kFGkDviaRPKcrO4etwDSwLPuQ8OZWAAUilwZKgA9jCo6lRWtPdpQv9TDb4SGjWKeGiKo2Ojl2zUcxmKIBjbRtL29AwkYDcLWmzg+pTgHDNp9V8NLv/bIB6zRzdJQx4Ay/JAwskxy+7Zy1f+H3yj0c+Eh45JAVFuPdIqQoPBSjZBt3Rc0SOJXXg1yjT3OAk0EDkeepNBwNHdUSzYNvWDc+PXJDwbpDm+ivG3nYIGgUJ0vcWWZ9PpRDQ+GZ64wYOH7fCPR9JlesusdaCPqj0roSqXTBmsE42MlHFKYWTtOeNbWdpMr4EVmHhLxyPc/6pC3pBdxy430Ql0zfJrillLp55Qra+VDRg/ojXS307nT/eD/ItVatS+fopvh19Pw6pfXir2ouRsDfUr/hZ2inA9kYGmWHD5R1pzCazxywQ0lcIz0Rfvw1R07mF0bAVkUU+NSlALiCbfvWp4bhnS1SOBzZlW4Xdy1BWYnc8NfJIaDocNi8jISPRV0EjDrAj6SsjZ5VUdvD972XlTXM6/jfPtNr0sWwCMnquT+yvjLFKneqntgBk+zIdjj8d3w3l/Om2n2MOH3mxmweo1h9Oa8X/YYv43lZ0YxSdROWvVZY6SgYTTVj+/rCIm1yiGmwK0jGLEvHsWnnn1ROPZeDDFJ9+nB2pp79yn+c+WyoqOQLnfLSMVMrWnEBBaWIGVc7/mHhD/beUAl/ScLXkeyG3B1QOIiShjWtRsRpXZRz8wpl1JXNNnt6ybKpQIyBo4TPiRRzfdaoFbpk0OP2qIAnV3MQ7VcUNBNd5PbIK+/+jesvSQrEZAy2sCU9LBIOBEwd3tvfQOzPoY0V4y0rTHgIY8AWMBHA84JurzlbmHZ12OSVudn0LEca7xApUffyxe2TmesA2xdbTXAPTIAxS1CEc8UJeAjvDovZNZ+an8KAO8DLSHk0n7mYIDtCMtGNogZ2YYoDAhYppS65fUEkT8KRq1XCNTo6ZHGVfej3J6JCIoO5vtU/Yjq6O0TbEtYZyN5rlMGJJxK7zsjaBqlZFI/hYNehOwRb11kBjIC73grPGKMsgMhSlBT0nNT4YRfaUOr98sVheaYDpNVdqRhIDyLSD3QPTpky3VSdD4KiQheuyYUgFW7JvcF8GBknSQYR8PVWP3S3eAqj2YS2BxR0K7CeWK9PJYxVqAnkdjhhqk7CglFlS2UK76ER0gXIeRkpEWt4PaWDyMGJ10awOtYg6H0YUhTpKFs+r20XpC4bkoAfJajo3wnw30IwlqxIlL6M19wF87sc6lxzzifdJWTw78y1dKPorSGMBVAbbvaIF4gEEPd6W3dCyumMOaENayb3BEouABANGvhoQN4UWPI7tsYRnrx+s2yZBU1/tVtVQhGAobQcyeubbiERHRLbN+2q/kRleGKLQi2dG46pqNjYgYBG7QDIR7tg8c7FpNVx54SLhyFjvTnunGsSg5aRQKPE+NctreDhve4ILSog3cyrB6MBhhumIXg6KGhta9Sd/y78errveNp9FRQaKvvwgB1EShP2Qmj4epCZJCukiY8pPZA5dNQ0n+BC2E2A/MNaYv+OtQYzWGUs4+XGcvuJcQTH/M9MrLqNZSP9romUdhImFGE7vHpERDGueLkanDBX3cXU+d93FZ64YyPhkrv8VWY5EneCWKePiEkSuKiJp3W39qxNn4dzFFMcJHh51DA1dOEwevWrHfzPsr2QQjJ89Za7K4UzoU+Z4Ek0zm/LBG46rQ1opr2LVd17/km/yPNRivscIyDN1o+RH0cQtPPUhou6UpnA5gmMVXAtmHfXSjibE/ttdZ1dkzwnVDJAd55vmKEvQulE5e5g9WoaXo0Ysy58Kehp1g2tEjpA1uqrCbjqHfj9GvifDciAJa822a7NUwKu8ktW9vBpx/CPamnXkij+GFLm0uYWZdKrvcF47WqUXQ8Q4h7Y4ze5NheTJQenRMpjo65TyQmRqJlsZDc/Ie+Q9+9oarCS4o0aLvSedfDrd6tvKEExQ5ih6dtYazeYcZdT2ordG8G3Neww1EvKR6M+/+EMlwFTE/FyrX8Uk4kSFz25u7C+dp92O9B9WlvINHjBCkmJEOSXBzcr9sTxJLifAIIAfx8KCVZygGaEMV6JT1luXch7eme9CbV/nauQwhp1paqjFafMUG8B2SW17Ho9dkEW25HI+uDmQ1IvAUCRVhY3ngRZGpUKoaYVR3ZgEPsIF1F0dN9edQbjhog9rG0liD5Y0ZVGb9BKrSqUwHWp4zkAPW5NAIvxO9sq9/OfF/Jo3Qq45klbd/4n/w0M//R425HGwAs4JiPfBZctDcmZncqJdqULvUPRg9mjUWF8PagsJaVHW1Ukp8b+R7D5xLa+brc0+4QMs9VxgOah+BBJOAAq20IPjkeIwwc+EQI3/tyqiO3iCoNFE4hJssM5je3yAikimKYF1Do7JA9K/PrRoVtbkTlq6HrQBG9UM/CIeIsU1szkv55anWDhC/RcFB9J6WnVN6LLY/ZXkGM26D9hJui7oTsbyEVZUNwSB2CboNXB+EXpi79O+4RFJAnCh1OHYzbNEOOj6Zkju2E/Dk+8OttpOCrfRgk1n5xNsveP6fC8gj0vSrVimrWhnG2GCATL/0muwOW6nYnw1sLtCwiMKzemF3DpLYcFBWwQhwzyDk0z6raOgSErcH+j9UJsZEJNUfwt1mXS7yqWADLGvsnBMkKdbyGMUNr0w0BKwnK5ENS9gL6z5BSD32Zk5T7DB+JLgrn47Ry987tKUQQRqCrME2UUUnuk5UeUVWMdCzXME0lVsRqkInI2MTegoCkbT/HBK101+PjdLoNYPm9sHT6omfGdxlzNC9rnah15gXB2lCm4bliQ8+flq,iv:Dikm/1W8siNC4rK9elXCAWJvGNaGJtPRF+fL9kOBYLU=,tag:swr75OwWPjUwr8eMgBooEw==,type:str]\ntalosconfig: ENC[AES256_GCM,data:zukEgmExjlNc1gvDQXedX0uCQgmsaLuIpaVJQLlLNBLGA4Jr9JZ86oKlVR9dDqhnZ22Y08KvNNZs0fG//nttr8GBU94a9N0mbRU+DhVCho3PfAc4Iza0ztZZSEjofIFmR8q1LTKQhbqmnGGF2JB45kos6+do46MpfxMIJndpwJqRY/IAAeeB5pCs0aF8HxPF2ehmzcKXEZMBb5g2RJtP9G/z8xlzqgrHDnvbichR13LCpBcoV6X23FYs93dQxrVwskW+39STa5R/570deUHwQLUzC1kmjqh5ktMvpzSJtzAzpeyM6gJMzMsdpqFb5pzX9myuulWF/boU6qa1qH6/XBOtoT005Dayi1L7sMUgRcuXCGOxCJUCt/KORHFqXL2kNjkwXjIBUT4YkAjsq3110pEXRhLUPr3dKMTxdrrWcpeCk13DqroSyYJKo/DYoMuw5YJDDQ7Nbc3rFL9aaOMwoCaT7r0lZgKThwR9pOPerMxODhxHj0FzQclvZzxITZ25SKnsJnRIbVfm7pIWIRnBaf3ZhuLgZMCzeTiCz4iBwakoUmHmw3acW+C7RP4KVZXN/Qz8HQHxGsKD3ITnextTDIxkFSeoC8JJHLkRy8aCROv82xwR2YrfXxGRnxlyHfY+CaVRl0Ff5Q7nhOk1EJMbiLtP39S7e6HEKpk8z9eP99QLatIhNMfP1uVhdEbaJxyNnrVAokaG4RrxXf3zJdrd9vqpCYVmRScVht3kjmh7L5OXUd8JZ9t9gpFtlf7AfkmFQv2J9IFZDHGB9PmXf4iW+NX3+b+2BkW6F57z6cYddHvIlTcgJGWYLGSd6EHiPbM4Y9V9yWbwGlBQIJCN/Vm3/M3z1EKZCREMUouUbKDnLr4/sowYkb4m9OcBsnMcCuzVe374HmoRCKb3WiWzT7+JabkK0kRcp/PcS69mg8F5B+UyHUwH2tx2WeS2OBR7pc5MeEL142PW8iIalSI3dFfZUw4xMTvePiQ0vku7hxcg0cSysQXtgamXHVhzxLW8utbiWJINvt4etRBhUVeFHWW1kaPFSdQNbDyDH3EJyy6gKEL5KYNQqHLBo0kybJVEOti6dwC9xf1hdH0VS53Ssh2RPE9bKPBIRy7aag+DpZLHgiZijVcnWqdyd5rBRU3dA+j9qzu4jdHqm64puKUG57g/1yN+8hfoaYiOV1XdwZ5s8YcWtPOX4ufRoJAKMIGDG2EVALYurasdv/r+2r1zuoWI/yDM3i85WefpHz47Wr0A4WagzxlMA9pGBZui5UxoLfaFdL5WcPkh7RRkF6bjlnERQKEKanf7W/1Vb6szHChXZYf1XBb4qjAhmwhqXjmG8NvdY8kLte6RjZfU2GQuWe1nyGbIOGeqEy22XO571crOuusZnnIHVGmrymj9WJKPN8VSsoiMpTN7ZukNKrfZ9fpImwy3dqrMZhUuU9PxNaaJxKy8UGNxRDXG3KzWCqm9DakBJYtwgaotSjrQAzYj0f0DvjTACvJRyUUSGo9kdFyRYr836qVP1C2/bF1FYqwFh7a0m/WUZV+GUG2aUTlV6lF55+pgmSFSv3VjxSVvoEzQdqXSN40AaVoEf3CBHAjtMUqt00JQWZGAFQ78qpQdr5Xo8RPUXsmmGAnVY/bmnvtlM7Vs0dbEt7RPrgvMbIxdJvDZXXv0z01pFueY8g/fibc/TIDQ2k/if5xWbSAYaWG8K8KnTIK91hHr8GYhkYkYP5nWfzr9txEAvy71asngO6aVR/ZMQcot0GYkW1eiipjuAuxR6o6wqn5fDRO1ZsE4RFa67D7Mu7eZHNAm75iRd2ECp2EWqm6wvoKjnXSratlPH3s58cTwzuMJYxa4EmM0t9ZdePfQPQ00xqshakBczvu+D1Wz6tOq5V+kRWncjko49uPMfmbyYBuBOF7AhFK+MpFFuc7JWUYW6YW0K4FgZkt0pE6hwFWyneIoDFSwcMVKs0X6JAu3TX7xL8ssT0StDvRWIVR0MQkmnGEho5Im9w8W8UXnzopl/91prX5ACPfsvgsxYOEIaCkyH5EkEsaMAUBWHhRadkXZBWaHYqChruc23y78sO3xfRY6YnFOrnf2kFURF6pFE7XYNBOE0fb+P+QnvdxFCsQlC9xcBYuC0MAJoKyYbdnwyb52a/GDPpvzKmC7BHjgh3FW9riHY4O4CgXLcYO0DX9J92KE67fjArXQufjU2xiUKYS6zHb0w5jf4lorgeVU/r3RcEp3jKdGmp2/mMXJtqM823zdy9RKgmGlgg==,iv:u+cocP8bYeHCUtjuQrjfrQsoUMV4c3p3mDVYS6Hd07o=,tag:TsTIFGKw0t/0YB6Efl42Mw==,type:str]\nsops:\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3UGVVYkw4ellQNU53OXRQ\n            YmM3NjhNcFFmMThKMVBuNHZ2eElVYTlSWlZvCmZ0bSsvSjN5N1Vpd21BVmlrOWRm\n            aXJzS0ZlQjduWlNiQjJabXA4UG9HQlEKLS0tIFQ5bUFtWCtJcCtSUGNCODk5OVJB\n            azQ2bjFCd2V1VThBNERVb2V4VVkvMXcKqMOvVXQeb2RLQ+ce2s/YJ+gUoCbksFys\n            WXLUNGwYF1rKOGdtWp3UBajpKIE2w4Gr6LAPm2Fn0raiVIWw4ucxVA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwQUg4YlMwYzUyc0ZUYStj\n            dFJpeUJtbjZwdjI1SjMrMlVYT1I5TGdNWW5rCm1CaVpPME0vOEpSckc4azlTSlJj\n            VlVReCtObXNLQWtVem9BMHF0Z0NlWjQKLS0tIGtiMjhpM0s1TnVWTEZudmM0aWo4\n            b2c1UUIvZHhndGF3NFF1V2dZYUxwRHMKlAvYwXo53J93mEbTShaYEHYgAfLmYz9R\n            rZNxgFI7at1ayTpFQabtJDWmmKMbZK7hOZWrWnYGJ5FEAI144x8aFA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4WEZTYTcwajd1SEhYTUJZ\n            L2xvZHdoaG5DTTB0UndyRS8xZnRlclB2KzFRCjlIQXdQTjA5eEE2ZmpVTWhzZk5W\n            c0xQOCtFRURGMGxVa1d2VWUyaGlnU2sKLS0tIHUzVmJRcVVyQ1pqV3ZRSUZydDJw\n            aGliY0ZIVlJPOHlzRTQzQWpzYTc1a1EKgmvkBFxGdcQbrRvARsq90WIUbqyx7lbt\n            ZNkd2FHqFuGECe2L31AaW3pYj1x33EbI8+Dh6jU6OjZ7cuD1bwjcjA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkUkd5VjNES1UxMlhqeEda\n            T3ozZ1BBdk84M2pCOVhYWTBlZHFSTzViTVVVCmorNkZ3ZTRmcWpSaWZJMWFFb2Nu\n            OUpVQ3d0bFhPREdzSTVXQmFzQ2gyMkEKLS0tIFZuRWQ4U0dBbnZYTy82WVhsckhT\n            WFRqcXV3ZkR4c1dtd3I0QTM5WC9HQ0EKECenrjPSPpZW/0kPZj31pA2P4rJ5Tvxx\n            Fim4Ob8aUqAGjpI+0so+6imQfQ9a6b1jCOZMuwrbqbR93wfd7sYL1w==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKd1ExR0c5SUpidUl6N2JD\n            WE5JSzJaUkE1RE1rWlZ2V052Rld6SVdwZldJCk5LUTByMnhhV1lzcjVkYW1CclFp\n            SDNrK1RZVzhjNStoRXUyOGt2WVNkRFUKLS0tIC9kVm01M3JTTlRRak9nblZRTDIw\n            TzJPY2NQYkk4RVVWR1dlamVadGJNU2cKkEN8xZ2Oz/f3oA24WBzpVCMLyz4HsZMh\n            KVnwkjo746csT+vM9Z7qzJw0Xo/dG7d/pv67AcFRZFp4HB4B5qvyEg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiMnR1TDNKV3pGeEg1ek1R\n            M1NGZ2ZoeWw3aGZzeHkzV0J6RHpWd1V3Y1VJClVhQnNSSnpTQ3NRY2RCNXZPMGR3\n            bUtpNzVjc2szcEg4dXlJR21UZkVkRm8KLS0tIEdzbHVaK0MrRmk1QTV2ejhtYTky\n            ZDVSUEtLdFVqQ0xUZnRlVW5rOURoMWcKfAQ4D2gvDwQE1ZxIz4DfnVfv2OekuqlX\n            0XuGxTpE/XP1yXqc7WgBr4DjV9L5ooYU5RiZD6hOaTlhPChE0VGVpg==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2025-07-22T13:29:06Z\"\n    mac: ENC[AES256_GCM,data:krY/qF48bsUnCyeQ1gL8eA14QDpZHZk8zsjUhaJ3UFbaIvVkVZpdbhsgNjcNd8OSU7i36kSz8PvSobMKddBsNTW7J4+oAFuouBAteF/TUCAuMiUU7dz5WhCZ5O3gcY+eDdeErXmiYNtzZJz6WGq3k9mMoN2zjLQCs0dMZG3iXVg=,iv:fhGIMHp2/q8FBh9LeZUlJREfaS279RkhpQDw9OqfksY=,tag:BBE3OeojIWz8SdjPPI4rTA==,type:str]\n    unencrypted_suffix: _unencrypted\n    version: 3.10.2\n"
  },
  {
    "path": "home/_modules/multiplexer/tmux/config/tmux.conf",
    "content": "# SERVER OPTIONS\nset -g buffer-limit 20 # number of buffers, older than limit buffer will be removed\nset -g default-terminal \"tmux-256color\" # 256 color\nset -g escape-time 0 # time for tmux to wait for key sequence\nset -ga terminal-features \",*256col*:RGB\" # true color support\nset -ga terminal-overrides ',*:Smulx=\\E[4::%p1%dm' # undercurl support\nset -ga terminal-overrides ',*:Setulc=\\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m' # undercurl colors\n\n# SESSION OPTIONS\nset -g base-index 1 # starting index for new window\nset -g display-time 0 # time for which status line messages and indicators are displayed in miliseconds, 0 means diplay until a key is pressed\nset -g history-limit 10000 # maximum number of lines held in window history\nset -g mouse on # enable mouse support\nset -g prefix M-a # prefix key is Alt+a\nset -g prefix-timeout 0 # disable timeout after prefix key is pressed\nset -g renumber-windows on # automatically renumber window when a window is closed\n# set -g repeat-time 32767 # typing command without pressing prefix key again in the specified time in miliseconds (can be 200000 after 354926a956ba07ef38d2ddd91a7820e3c9634ab0)\nset -g status-keys vi # use vi style keybindings in command prompt\n\n# WINDOW OPTIONS\nset -g aggressive-resize on # tmux will resize the window to the size of the smallest or largest session rather than the session it is attached\nset -g automatic-rename on # automatic window renaming\nset -g automatic-rename-format '#{pane_current_command}' # format of automatic window renaming\nset -g mode-keys vi # use vi style keybindings in copy mode\nset -g pane-base-index 1 # starting index for new pane\n\n# PANE OPTIONS\nset -g allow-rename off # allow programs to change the window name\nset -g remain-on-exit off # destroy window when program exits\n\n# KEYBINDING SETTINGS\n# Some keybindings below have `switch-client -T prefix` appended to the command. I use it to mimic the behavior of `bind -r`\n# so I can repeat commands without typing prefix key again. The problem with using `bind -r` is it doesn't work for bindings that\n# doesn't have `-r` flag.\n# For example, if I have a key to move to a pane with `-r` flag and a key to kill pane without `-r` flag\n# in `prefix` keytable, to move to a pane and kill it I have to:\n# Press prefix key, move to a pane, wait for `repeat-time`, press prefix key, kill the pane.\n# Now, I can do:\n# Press prefix key, move to a pane, kill the pane\n\n# The downside of this approach is I can be stucked in prefix mode forever, what I do is have a key to exit prefix mode which\n# I bound to `Esc` and `Space` key\n\n# unbind all the default key bindings\nunbind -T root -a\nunbind -T prefix -a\nunbind -T copy-mode -a\nunbind -T copy-mode-vi -a\n\n# root key-table\nbind -T root -N \"Alt+z to send prefix key to client\" M-z send-prefix\nbind -T root -N \"select pane on mouse click on pane\" MouseDown1Pane select-pane -t = \\; send-keys -M\nbind -T root -N \"select window on mouse click on status\" MouseDown1Status select-window -t =\nbind -T root -N \"resize pane on mouse drag on border\" MouseDrag1Border resize-pane -M\nbind -T root -N \"select previous window on mouse wheel up on status\" WheelUpStatus previous-window\nbind -T root -N \"select next window on mouse wheel down on status\" WheelDownStatus next-window\nbind -T root -N \"go into copy mode to scroll history on mouse wheel up on pane\" WheelUpPane \\\n  if-shell -F \"#{||:#{pane_in_mode},#{mouse_any_flag}}\" { send-keys -M } { copy-mode -e }\n\n# prefix key-table\nbind -T prefix -N \"h to move to left pane\" h select-pane -L \\; switch-client -T prefix\nbind -T prefix -N \"j to move to bottom pane\" j select-pane -D \\; switch-client -T prefix\nbind -T prefix -N \"k to move to top pane\" k select-pane -U \\; switch-client -T prefix\nbind -T prefix -N \"l to move to right pane\" l select-pane -R \\; switch-client -T prefix\n\nbind -T prefix -N \"H to resize pane to the left by 2 columns\" H resize-pane -L 2 \\; switch-client -T prefix\nbind -T prefix -N \"J to resize pane downwards by 2 lines\" J resize-pane -D 2 \\; switch-client -T prefix\nbind -T prefix -N \"K to resize pane upwards by 2 lines\" K resize-pane -U 2 \\; switch-client -T prefix\nbind -T prefix -N \"L to resize pane to the right by 2 columns\" L resize-pane -R 2 \\; switch-client -T prefix\n\nbind -T prefix -N \"Alt+n to move to next window\" M-n next-window \\; switch-client -T prefix\nbind -T prefix -N \"Alt+p to move to previous window\" M-p previous-window \\; switch-client -T prefix\n\nbind -T prefix -N \"> to swap pane with the next pane\" > swap-pane -D \\; switch-client -T prefix\nbind -T prefix -N \"< to swap pane with the previous pane\" < swap-pane -U \\; switch-client -T prefix\n\nbind -T prefix -N \"Alt+s to split window horizontally with current pane path\" M-s split-window -hc \"#{pane_current_path}\"\nbind -T prefix -N \"Alt+v to split window vertically with current pane path\" M-v split-window -vc \"#{pane_current_path}\"\nbind -T prefix -N \"c to open a new window with current pane path\" c new-window -c \"#{pane_current_path}\"\n\nbind -T prefix -N \"Esc to switch to root key table\" -r Escape switch-client -T root\nbind -T prefix -N \"Space to switch to root key table\" -r Space switch-client -T root\nbind -T prefix -N \"a to enter copy mode\" a copy-mode\nbind -T prefix -N \": to enter tmux command prompt\" : command-prompt\n\nbind -T prefix -N \"x to close current pane\" x kill-pane\nbind -T prefix -N \"X to close current window\" X kill-window\n\n# copy-mode-vi key-table\nbind -T copy-mode-vi -N \"h to move cursor left\" h send-keys -X cursor-left\nbind -T copy-mode-vi -N \"j to move cursor down\" j send-keys -X cursor-down\nbind -T copy-mode-vi -N \"k to move cursor down\" k send-keys -X cursor-up\nbind -T copy-mode-vi -N \"l to move cursor down\" l send-keys -X cursor-right\nbind -T copy-mode-vi -N \"b to move cursor to word beginning\" b send-keys -X previous-word\nbind -T copy-mode-vi -N \"e to move cursor to word ending\" e send-keys -X next-word-end\nbind -T copy-mode-vi -N \"Home to move cursor to start of line\" Home send-keys -X start-of-line\nbind -T copy-mode-vi -N \"0 to move cursor to start of line\" 0 send-keys -X start-of-line\nbind -T copy-mode-vi -N \"End to move cursor to end of line\" End send-keys -X end-of-line\nbind -T copy-mode-vi -N \"$ to move cursor to end of line\" \\$ send-keys -X end-of-line\nbind -T copy-mode-vi -N \"PageUp to move cursor one page up\" Npage send-keys -X page-up\nbind -T copy-mode-vi -N \"PageDn to move cursor one page down\" Ppage send-keys -X page-down\nbind -T copy-mode-vi -N \"g to move cursor to top of history\" g send-keys -X history-top\nbind -T copy-mode-vi -N \"G to move cursor to bottom of history\" G send-keys -X history-bottom\n\nbind -T copy-mode-vi -N \"Scroll up 5 line on mouse wheel up on pane\" WheelUpPane select-pane \\; send-keys -X -N 5 scroll-up\nbind -T copy-mode-vi -N \"Scroll down 5 line on mouse wheel down on pane\" WheelDownPane select-pane \\; send-keys -X -N 5 scroll-down\nbind -T copy-mode-vi -N \"Select a line and copy it to clipboard on double click on pane\" DoubleClick1Pane \\\n  select-pane \\; send-keys -X select-line \\; run-shell -d 0.3 \\; send-keys -X copy-pipe-and-cancel\nbind -T copy-mode-vi -N \"Begin selection on mouse drag on pane\" MouseDrag1Pane \\\n  select-pane \\; send-keys -X begin-selection\nbind -T copy-mode-vi -N \"Copy selection to clipboard on releasing mouse drag on pane\" MouseDragEnd1Pane \\\n  send-key -X copy-pipe-and-cancel\n\nbind -T copy-mode-vi -N \"v to begin selection\" v send-keys -X begin-selection\nbind -T copy-mode-vi -N \"Space to begin selection\" Space send-keys -X begin-selection\nbind -T copy-mode-vi -N \"V to select a line\" V send-keys -X select-line\nbind -T copy-mode-vi -N \"Alt+v to toggle box selection\" M-v send-keys -X rectangle-toggle\nbind -T copy-mode-vi -N \"y to copy selection to clipboard\" y send-keys -X copy-pipe-and-cancel\nbind -T copy-mode-vi -N \"Y to copy from cursor to end of line to clipboard\" Y send-keys -X copy-pipe-end-of-line-and-cancel\n\nbind -T copy-mode-vi -N \"Esc to exit copy mode\" Escape send-keys -X cancel\nbind -T copy-mode-vi -N \"q to exit copy mode\" q send-keys -X cancel\n"
  },
  {
    "path": "home/_modules/multiplexer/tmux/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.multiplexer.tmux;\n  inherit (lib) mkEnableOption mkIf;\nin\n{\n  options.myHome.multiplexer.tmux = {\n    enable = mkEnableOption \"tmux\";\n  };\n\n  config = mkIf (cfg.enable) {\n    catppuccin.tmux = {\n      enable = true;\n      extraConfig = ''\n        set -g @catppuccin_window_number_position \"right\"\n        set -g @catppuccin_window_current_number_color \"#{@thm_green}\"\n        set -g @catppuccin_window_text \"\"\n        set -g @catppuccin_window_number \"#[bold]Tab ###I \"\n        set -g @catppuccin_window_current_text \"\"\n        set -g @catppuccin_window_current_number \"#[bold]Tab ###I \"\n        set -g @catppuccin_window_status_style \"custom\"\n        set -g @catppuccin_window_right_separator \"#[fg=#{@_ctp_status_bg},reverse]#[none]\"\n\n        set -g @catppuccin_window_left_separator \"#[fg=#{@_ctp_status_bg}]#[none]\"\n        set -g @catppuccin_window_middle_separator \"#[bg=#{@catppuccin_window_number_color},fg=#{@catppuccin_window_text_color}]\"\n        set -g @catppuccin_window_current_middle_separator \"#[bg=#{@catppuccin_window_current_number_color},fg=#{@catppuccin_window_current_text_color}]\"\n\n        set -g window-status-separator \"\"\n        set -g status-left-length 0\n        set -g status-left \"#[fg=#{@thm_fg} bold]TMUX (#S) \"\n        set -ga status-left \"#{?client_prefix,#[fg=#{@thm_red} bold]PREFIX ,#{?#{==:#{pane_mode},copy-mode},#[fg=#{@thm_yellow} bold]COPY ,#[fg=#{@thm_green} bold]NORMAL }}\"\n        set -g status-right \"\"\n      '';\n    };\n\n    programs.tmux = {\n      enable = true;\n      package = pkgs.tmux;\n      extraConfig = ''\n        ${\n          if config.myHome.isWayland then\n            \"set -g copy-command '${pkgs.wl-clipboard}/bin/wl-copy'\"\n          else\n            \"set -g copy-command '${pkgs.xsel}/bin/xsel -i -b'\"\n        }\n\n        ${builtins.readFile ./config/tmux.conf}\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/multiplexer/zellij/config/config.kdl",
    "content": "default_mode \"normal\"\ntheme \"catppuccin-mocha\"\nkeybinds clear-defaults=true {\n  normal {\n    bind \"Alt a\" { SwitchToMode \"tmux\"; }\n    bind \"Alt z\" { SwitchToMode \"locked\"; }\n  }\n\n  locked {\n    bind \"Alt z\" { SwitchToMode \"tmux\"; }\n  }\n\n  tmux {\n    bind \"Alt s\" { NewPane \"Right\"; SwitchToMode \"normal\"; }\n    bind \"Alt v\" { NewPane \"Down\"; SwitchToMode \"normal\"; }\n    bind \"r\" { SwitchToMode \"RenameTab\"; }\n    bind \"h\" { MoveFocus \"Left\"; }\n    bind \"j\" { MoveFocus \"Down\"; }\n    bind \"k\" { MoveFocus \"Up\"; }\n    bind \"l\" { MoveFocus \"Right\"; }\n    bind \">\" { MovePane; }\n    bind \"H\" { Resize \"Left\"; }\n    bind \"J\" { Resize \"Down\"; }\n    bind \"K\" { Resize \"Up\"; }\n    bind \"L\" { Resize \"Right\"; }\n    bind \"Alt n\" { GoToNextTab; }\n    bind \"Alt p\" { GoToPreviousTab; }\n    bind \"c\" { NewTab; SwitchToMode \"normal\"; }\n    bind \"x\" { CloseFocus; SwitchToMode \"normal\"; }\n    bind \"a\" { EditScrollback; SwitchToMode \"normal\"; }\n    bind \"Esc\" \"Space\" \"Enter\" { SwitchToMode \"normal\"; }\n  }\n\n  RenameTab {\n    bind \"Alt a\" { SwitchToMode \"tmux\"; }\n    bind \"Enter\" { SwitchToMode \"normal\"; }\n    bind \"Esc\" { UndoRenameTab; SwitchToMode \"normal\"; }\n  }\n}\n\nplugins {\n  tab-bar { path \"tab-bar\"; }\n  status-bar { path \"status-bar\"; }\n  strider { path \"strider\"; }\n  compact-bar { path \"compact-bar\"; }\n}\n\nui {\n  pane_frames {\n    rounded_corners true\n  }\n}\n"
  },
  {
    "path": "home/_modules/multiplexer/zellij/config/layouts/default.kdl",
    "content": "layout {\n  pane\n  pane size=1 borderless=true {\n    plugin location=\"zellij:compact-bar\"\n  }\n}\n"
  },
  {
    "path": "home/_modules/multiplexer/zellij/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.multiplexer.zellij;\n  cmd = lib.getExe config.programs.zellij.package;\n  inherit (lib) mkEnableOption mkPackageOption mkIf;\nin\n{\n  options.myHome.multiplexer.zellij = {\n    enable = mkEnableOption \"Zellij\";\n    package = mkPackageOption pkgs \"zellij\" { };\n  };\n\n  config = mkIf (cfg.enable) {\n    programs = {\n      zellij = {\n        enable = true;\n        package = cfg.package;\n      };\n\n      fish.interactiveShellInit = ''\n        set -gx ZELLIJ_AUTO_ATTACH true\n        set -gx ZELLIJ_AUTO_EXIT true\n        eval (${cmd} setup --generate-auto-start fish | string collect)\n      '';\n    };\n\n    xdg.configFile.\"zellij\".source = ./config;\n  };\n}\n"
  },
  {
    "path": "home/_modules/programs/beeaccounting/default.nix",
    "content": "{\n  lib,\n  osConfig,\n  config,\n  pkgs,\n  ...\n}:\nwith lib;\nlet\n  systemEnabled = myLib.systemEnabled \"mySystem.containers.beeaccounting.enable\" osConfig;\n  isAdminUser = myLib.isAdminUser config.myHome.username osConfig;\n  adminUser = osConfig.users.users.${osConfig.mySystem.adminUser};\nin\n{\n  config = mkIf (systemEnabled && isAdminUser) {\n    xdg.desktopEntries = {\n      bee-app = {\n        name = \"Bee2.9\";\n        genericName = \"Accounting Software\";\n        icon = ./bee-app.ico;\n        exec =\n          let\n            script = pkgs.writeShellScriptBin \"bee-app\" ''\n              ${pkgs.xorg.xhost}/bin/xhost +local:${toString adminUser.uid} > /dev/null 2>&1 && \\\n              ${pkgs.docker}/bin/docker exec --user ${toString adminUser.uid} beeaccounting-app java -Xmx1g -jar /app/BeeUI-2.9.jar\n            '';\n          in\n          \"${script}/bin/bee-app\";\n        categories = [\n          \"Office\"\n          \"Finance\"\n          \"Java\"\n        ];\n        comment = \"Bee Accounting 2.9 Application\";\n        type = \"Application\";\n        terminal = false;\n        settings = {\n          StartupWMClass = \"AppLauncher\";\n        };\n      };\n\n      bee-updater = {\n        name = \"Bee2.9-Updater\";\n        genericName = \"Software Updater\";\n        icon = ./bee-updater.ico;\n        exec =\n          let\n            script = pkgs.writeShellScriptBin \"bee-updater\" ''\n              ${pkgs.xorg.xhost}/bin/xhost +local:${toString adminUser.uid} > /dev/null 2>&1 && \\\n              ${pkgs.docker}/bin/docker exec --user ${toString adminUser.uid} beeaccounting-app java -Xmx1g -jar /app/BeeUpdater-2.9.jar\n            '';\n          in\n          \"${script}/bin/bee-updater\";\n        categories = [\n          \"Office\"\n          \"Finance\"\n          \"Java\"\n        ];\n        comment = \"Bee Accounting 2.9 Updater\";\n        type = \"Application\";\n        terminal = false;\n      };\n    };\n\n    wayland.windowManager.hyprland.settings.windowrulev2 = [\n      \"workspace 2, class:^(AppLauncher)$\"\n      \"nodim, class:^(AppLauncher)$\"\n      \"nofocus, class:^(AppLauncher)$, title:^(win)(.*)$\"\n      \"noborder, class:^(AppLauncher)$, title:^(win)(.*)$\"\n      \"noanim, class:^(AppLauncher)$, title:^(win)(.*)$\"\n      \"nofocus, class:^(AppLauncher)$, title:^(JidePopup)$\"\n      \"noborder, class:^(AppLauncher)$, title:^(JidePopup)$\"\n      \"noanim, class:^(AppLauncher)$, title:^(JidePopup)$\"\n      \"maxsize 400 180, class:^(AppLauncher)$, title:^(JidePopup)$\"\n    ];\n  };\n}\n"
  },
  {
    "path": "home/_modules/programs/chezmoi/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.programs.chezmoi;\n  inherit (lib) mkEnableOption mkIf;\nin\n{\n  options.myHome.programs.chezmoi = {\n    enable = mkEnableOption \"chezmoi\";\n  };\n\n  config = mkIf (cfg.enable) {\n    # we want to do stuffs after HM has finished linking stuffs in `$NIX_PROFILES/bin`\n    home.activation.chezmoi = lib.hm.dag.entryAfter [ \"installPackages\" ] ''\n      # I want chezmoi to have access to the userspace $PATH\n      _saved_path=$PATH\n      PATH=\"${config.home.path}/bin:$PATH\"\n      # a lot of my chezmoi scripts needs system programs to work, might be a bad idea idk\n      PATH=$PATH:/usr/local/bin:/usr/bin:/bin\n\n      run ${pkgs.chezmoi}/bin/chezmoi apply -S ${../../../..} $VERBOSE_ARG\n\n      # return it back\n      PATH=$_saved_path\n    '';\n  };\n}\n"
  },
  {
    "path": "home/_modules/programs/fontconfig/default.nix",
    "content": "{\n  lib,\n  osConfig,\n  pkgs,\n  ...\n}:\n{\n  config = lib.mkIf (!lib.myLib.isNixos osConfig) {\n    fonts.fontconfig.enable = true;\n    home.packages = with pkgs; [\n      nerd-fonts.ubuntu-mono\n      unifont\n    ];\n  };\n}\n"
  },
  {
    "path": "home/_modules/programs/go/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.programs.go;\nin\n{\n  options.myHome.programs.go.enable = lib.mkEnableOption \"Go\";\n\n  config = lib.mkIf (cfg.enable) {\n    programs.go = {\n      enable = true;\n      package = pkgs.unstable.go;\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/programs/obs-studio/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.programs.obs-studio;\nin\n{\n  options.myHome.programs.obs-studio = {\n    enable = lib.mkEnableOption \"OBS Studio\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.obs-studio = {\n      enable = true;\n      plugins = lib.mkIf config.myHome.isWayland [ pkgs.obs-studio-plugins.wlrobs ];\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/programs/qmk/default.nix",
    "content": "{\n  lib,\n  config,\n  osConfig,\n  pkgs,\n  ...\n}:\nlet\n  inherit (lib) myLib mkEnableOption mkIf;\n  cfg = config.myHome.programs.qmk;\n  systemEnabled = myLib.systemEnabled \"mySystem.programs.qmk.enable\" osConfig;\n  isNixos = myLib.isNixos osConfig;\nin\n{\n  options.myHome.programs.qmk = {\n    enable = mkEnableOption \"QMK\";\n  };\n\n  config = mkIf (cfg.enable) {\n    warnings = mkIf (!systemEnabled && isNixos) [\n      ''\n        You have enabled QMK home-manager module but not the NixOS system module and you are using NixOS.\n        Some things might not work properly.\n      ''\n    ];\n    myHome.shell.git.enable = true;\n\n    xdg.configFile.\"qmk/qmk.ini\".source = ./qmk.ini;\n\n    home.packages = with pkgs; [ qmk ];\n  };\n}\n"
  },
  {
    "path": "home/_modules/programs/qmk/qmk.ini",
    "content": "[user]\nqmk_home = /home/budiman/Github/qmk_firmware\nkeyboard = crkbd/rev1\nkeymap = budimanjojo\n\n[mass_compile]\nkeymap = default\n\n[config]\n\n[console]\n\n[compile]\n\n[docs]\n\n[flash]\n\n[format_c]\n\n[format_python]\n\n[format_text]\n\n[generate_autocorrect_data]\n\n[generate_compilation_database]\n\n[generate_develop_pr_list]\n\n[generate_dfu_header]\n\n[generate_info_json]\n\n[hello]\n\n[info]\n\n[lint]\n\n[kle2json]\n\n[list_keymaps]\n\n[list_layouts]\n\n[new_keyboard]\n\n[new_keymap]\n\n[painter_convert_graphics]\n\n[painter_make_font_image]\n\n[painter_convert_font_image]\n\n[general]\n"
  },
  {
    "path": "home/_modules/programs/yamllint/config.yaml",
    "content": "---\nextends: relaxed\n\nyaml-files:\n  - '*.yaml'\n  - '*.yml'\n  - '.yamllint'\n\nrules:\n  empty-lines:\n    max: 1\n  empty-values: enable\n  indentation:\n    indent-sequences: consistent\n  new-line-at-end-of-file: disable\n  truthy:\n    allowed-values:\n      - 'true'\n      - 'false'\n      - 'yes'\n      - 'no'\n      - 'on'\n      - 'off'\n"
  },
  {
    "path": "home/_modules/programs/yamllint/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.programs.yamllint;\nin\n{\n  options.myHome.programs.yamllint = {\n    enable = lib.mkEnableOption \"YAMLlint\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    home.packages = [ pkgs.yamllint ];\n    xdg.configFile.\"yamllint/config\".source = ./config.yaml;\n  };\n}\n"
  },
  {
    "path": "home/_modules/services/opencloud-client/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.services.opencloud-client;\nin\n{\n  options.myHome.services.opencloud-client = {\n    enable = lib.mkEnableOption \"OpenCloud client\";\n  };\n\n  config = lib.mkIf cfg.enable {\n    systemd.user.services.opencloud-client = {\n      Unit = {\n        Description = \"OpenCloud client\";\n        After = [ \"graphical-session.target\" ];\n        PartOf = [ \"graphical-session.target\" ];\n      };\n\n      Service = {\n        Environment = [ \"PATH=${config.home.profileDirectory}/bin\" ];\n        ExecStart = \"${pkgs.unstable.opencloud-desktop}/bin/opencloud\";\n      };\n\n      Install = {\n        WantedBy = [ \"graphical-session.target\" ];\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/shell/dircolors/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.shell.dircolors;\nin\n{\n  options.myHome.shell.dircolors = {\n    enable = lib.mkEnableOption \"dircolors\";\n  };\n\n  config = lib.mkIf cfg.enable {\n    programs.dircolors = {\n      enable = true;\n      settings = {\n        DIR = \"01;34\";\n        LINK = \"01;36\";\n        FIFO = \"40;33\";\n        SOCK = \"01;35\";\n        BLK = \"01;33\";\n        CHR = \"33\";\n        ORPHAN = \"31\";\n        EXEC = \"01;32\";\n        \"*.7z\" = \"01;31\";\n        \"*.bz2\" = \"01;31\";\n        \"*.gz\" = \"01;31\";\n        \"*.lz\" = \"01;31\";\n        \"*.lzma\" = \"01;31\";\n        \"*.lzo\" = \"01;31\";\n        \"*.rar\" = \"01;31\";\n        \"*.tar\" = \"01;31\";\n        \"*.tbz\" = \"01;31\";\n        \"*.tgz\" = \"01;31\";\n        \"*.xz\" = \"01;31\";\n        \"*.zip\" = \"01;31\";\n        \"*.zst\" = \"01;31\";\n        \"*.zstd\" = \"01;31\";\n        \"*.bmp\" = \"01;35\";\n        \"*.tiff\" = \"01;35\";\n        \"*.tif\" = \"01;35\";\n        \"*.TIFF\" = \"01;35\";\n        \"*.gif\" = \"01;35\";\n        \"*.jpeg\" = \"01;35\";\n        \"*.jpg\" = \"01;35\";\n        \"*.png\" = \"01;35\";\n        \"*.webp\" = \"01;35\";\n        \"*.pot\" = \"01;35\";\n        \"*.pcb\" = \"01;35\";\n        \"*.gbr\" = \"01;35\";\n        \"*.scm\" = \"01;35\";\n        \"*.xcf\" = \"01;35\";\n        \"*.spl\" = \"01;35\";\n        \"*.stl\" = \"01;35\";\n        \"*.dwg\" = \"01;35\";\n        \"*.ply\" = \"01;35\";\n        \"*.apk\" = \"01;31\";\n        \"*.deb\" = \"01;31\";\n        \"*.rpm\" = \"01;31\";\n        \"*.jad\" = \"01;31\";\n        \"*.jar\" = \"01;31\";\n        \"*.crx\" = \"01;31\";\n        \"*.xpi\" = \"01;31\";\n        \"*.avi\" = \"01;35\";\n        \"*.divx\" = \"01;35\";\n        \"*.m2v\" = \"01;35\";\n        \"*.m4v\" = \"01;35\";\n        \"*.mkv\" = \"01;35\";\n        \"*.MOV\" = \"01;35\";\n        \"*.mov\" = \"01;35\";\n        \"*.mp4\" = \"01;35\";\n        \"*.mpeg\" = \"01;35\";\n        \"*.mpg\" = \"01;35\";\n        \"*.sample\" = \"01;35\";\n        \"*.wmv\" = \"01;35\";\n        \"*.3g2\" = \"01;35\";\n        \"*.3gp\" = \"01;35\";\n        \"*.gp3\" = \"01;35\";\n        \"*.webm\" = \"01;35\";\n        \"*.flv\" = \"01;35\";\n        \"*.ogv\" = \"01;35\";\n        \"*.f4v\" = \"01;35\";\n        \"*.3ga\" = \"01;35\";\n        \"*.aac\" = \"01;35\";\n        \"*.m4a\" = \"01;35\";\n        \"*.mp3\" = \"01;35\";\n        \"*.mp4a\" = \"01;35\";\n        \"*.oga\" = \"01;35\";\n        \"*.ogg\" = \"01;35\";\n        \"*.wma\" = \"01;35\";\n        \"*.flac\" = \"01;35\";\n        \"*.alac\" = \"01;35\";\n        \"*.mid\" = \"01;35\";\n        \"*.midi\" = \"01;35\";\n        \"*.pcm\" = \"01;35\";\n        \"*.wav\" = \"01;35\";\n        \"*.ass\" = \"01;33\";\n        \"*.srt\" = \"01;33\";\n        \"*.ssa\" = \"01;33\";\n        \"*.sub\" = \"01;33\";\n        \"*.git\" = \"01;33\";\n        \"*README\" = \"33\";\n        \"*README.rst\" = \"33\";\n        \"*README.md\" = \"33\";\n        \"*LICENSE\" = \"33\";\n        \"*COPYING\" = \"33\";\n        \"*INSTALL\" = \"33\";\n        \"*COPYRIGHT\" = \"33\";\n        \"*AUTHORS\" = \"33\";\n        \"*HISTORY\" = \"33\";\n        \"*CONTRIBUTOS\" = \"33\";\n        \"*PATENTS\" = \"33\";\n        \"*VERSION\" = \"33\";\n        \"*NOTICE\" = \"33\";\n        \"*CHANGES\" = \"33\";\n        \"*CHANGELOG\" = \"33\";\n        \"*log\" = \"33\";\n        \"*.txt\" = \"33\";\n        \"*.md\" = \"33\";\n        \"*.markdown\" = \"33\";\n        \"*.nfo\" = \"33\";\n        \"*.org\" = \"33\";\n        \"*.pod\" = \"33\";\n        \"*.rst\" = \"33\";\n        \"*.tex\" = \"33\";\n        \"*.texttile\" = \"33\";\n        \"*.bib\" = \"35\";\n        \"*.json\" = \"35\";\n        \"*.jsonl\" = \"35\";\n        \"*.jsonnet\" = \"35\";\n        \"*.libsonnet\" = \"35\";\n        \"*.rss\" = \"35\";\n        \"*.xml\" = \"35\";\n        \"*.fxml\" = \"35\";\n        \"*.toml\" = \"35\";\n        \"*.yaml\" = \"35\";\n        \"*.yml\" = \"35\";\n        \"*.dtd\" = \"35\";\n        \"*.cbr\" = \"35\";\n        \"*.cbz\" = \"35\";\n        \"*.chm\" = \"35\";\n        \"*.pdf\" = \"35\";\n        \"*.PDF\" = \"35\";\n        \"*.epub\" = \"35\";\n        \"*.awk\" = \"35\";\n        \"*.bash\" = \"35\";\n        \"*.bat\" = \"35\";\n        \"*.BAT\" = \"35\";\n        \"*.sed\" = \"35\";\n        \"*.sh\" = \"35\";\n        \"*.zsh\" = \"35\";\n        \"*.vim\" = \"35\";\n        \"*.py\" = \"35\";\n        \"*.ipynb\" = \"35\";\n        \"*.rb\" = \"35\";\n        \"*.gemspec\" = \"35\";\n        \"*.pl\" = \"35\";\n        \"*.PL\" = \"35\";\n        \"*.t\" = \"35\";\n        \"*.msql\" = \"35\";\n        \"*.mysql\" = \"35\";\n        \"*.pgsql\" = \"35\";\n        \"*.sql\" = \"35\";\n        \"*.r\" = \"35\";\n        \"*.R\" = \"35\";\n        \"*.cljw\" = \"35\";\n        \"*.scala\" = \"35\";\n        \"*.sc\" = \"35\";\n        \"*.dart\" = \"35\";\n        \"*.asm\" = \"35\";\n        \"*.cl\" = \"35\";\n        \"*.lisp\" = \"35\";\n        \"*.rkt\" = \"35\";\n        \"*.el\" = \"35\";\n        \"*.elc\" = \"35\";\n        \"*.eln\" = \"35\";\n        \"*.lua\" = \"35\";\n        \"*.c\" = \"35\";\n        \"*.C\" = \"35\";\n        \"*.h\" = \"35\";\n        \"*.H\" = \"35\";\n        \"*.tcc\" = \"35\";\n        \"*.c++\" = \"35\";\n        \"*.h++\" = \"35\";\n        \"*.hpp\" = \"35\";\n        \"*.hxx\" = \"35\";\n        \"*ii.\" = \"35\";\n        \"*.m\" = \"35\";\n        \"*.M\" = \"35\";\n        \"*.cc\" = \"35\";\n        \"*.cs\" = \"35\";\n        \"*.cp\" = \"35\";\n        \"*.cpp\" = \"35\";\n        \"*.cxx\" = \"35\";\n        \"*.go\" = \"35\";\n        \"*.f\" = \"35\";\n        \"*.F\" = \"35\";\n        \"*.nim\" = \"35\";\n        \"*.nimble\" = \"35\";\n        \"*.s\" = \"35\";\n        \"*.S\" = \"35\";\n        \"*.rs\" = \"35\";\n        \"*.scpt\" = \"35\";\n        \"*.swift\" = \"35\";\n        \"*.vala\" = \"35\";\n        \"*.vapi\" = \"35\";\n        \"*.hs\" = \"35\";\n        \"*.lhs\" = \"35\";\n        \"*.zig\" = \"35\";\n        \"*.v\" = \"35\";\n        \"*.pyc\" = \"35\";\n        \"*.tf\" = \"35\";\n        \"*.tfstate\" = \"35\";\n        \"*.tfvars\" = \"35\";\n        \"*.css\" = \"35\";\n        \"*.less\" = \"35\";\n        \"*.sass\" = \"35\";\n        \"*.scss\" = \"35\";\n        \"*.htm\" = \"35\";\n        \"*.html\" = \"35\";\n        \"*.jhtm\" = \"35\";\n        \"*.mht\" = \"35\";\n        \"*.eml\" = \"35\";\n        \"*.coffee\" = \"35\";\n        \"*.java\" = \"35\";\n        \"*.js\" = \"35\";\n        \"*.mjs\" = \"35\";\n        \"*.jsm\" = \"35\";\n        \"*.jsp\" = \"35\";\n        \"*.rasi\" = \"35\";\n        \"*.php\" = \"35\";\n        \"*.twig\" = \"35\";\n        \"*.vb\" = \"35\";\n        \"*.vba\" = \"35\";\n        \"*.vbs\" = \"35\";\n        \"*.Dockerfile\" = \"35\";\n        \"*.dockerignore\" = \"35\";\n        \"*.Makefile\" = \"35\";\n        \"*.MANIFEST\" = \"35\";\n        \"*.am\" = \"35\";\n        \"*.in\" = \"35\";\n        \"*.hin\" = \"35\";\n        \"*.scan\" = \"35\";\n        \"*.m4\" = \"35\";\n        \"*.old\" = \"35\";\n        \"*.out\" = \"35\";\n        \"*.SKIP\" = \"35\";\n        \"*.diff\" = \"35\";\n        \"*.patch\" = \"35\";\n        \"*.tmpl\" = \"35\";\n        \"*.j2\" = \"35\";\n        \"*PKGBUILD\" = \"35\";\n        \"*config\" = \"35\";\n        \"*.conf\" = \"35\";\n        \"*.service\" = \"31\";\n        \"*.@.service\" = \"31\";\n        \"*.socket\" = \"31\";\n        \"*.swap\" = \"31\";\n        \"*.device\" = \"31\";\n        \"*.mount\" = \"31\";\n        \"*.automount\" = \"31\";\n        \"*.target\" = \"31\";\n        \"*.path\" = \"31\";\n        \"*.timer\" = \"31\";\n        \"*.snapshot\" = \"31\";\n        \"*.allow\" = \"31\";\n        \"*.swp\" = \"31\";\n        \"*.swo\" = \"31\";\n        \"*.tmp\" = \"31\";\n        \"*.pid\" = \"31\";\n        \"*.state\" = \"31\";\n        \"*.lock\" = \"31\";\n        \"*.lockfile\" = \"31\";\n        \"*.pacnew\" = \"31\";\n        \"*.un\" = \"31\";\n        \"*.orig\" = \"31\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/shell/fish/default.nix",
    "content": "{\n  lib,\n  config,\n  osConfig,\n  pkgs,\n  myPkgs,\n  ...\n}:\nlet\n  inherit (lib) myLib mkEnableOption mkIf;\n\n  cfg = config.myHome.shell.fish;\n  isNixos = myLib.isNixos osConfig;\nin\n{\n  options.myHome.shell.fish = {\n    enable = mkEnableOption \"fish shell\";\n  };\n\n  config = mkIf (cfg.enable) {\n    myHome.shell = {\n      dircolors.enable = true;\n      starship.enable = true;\n    };\n\n    catppuccin = {\n      fish.enable = true;\n      # TODO: wait for the IFD issue is fixed\n      # ref: https://github.com/catppuccin/nix/issues/392\n      fzf.enable = mkIf (pkgs.stdenv.hostPlatform.system == \"x86_64-linux\") true;\n      bat.enable = true;\n    };\n\n    programs = {\n      fish = {\n        enable = true;\n        package = mkIf (!isNixos) (\n          pkgs.fish.override {\n            fishEnvPreInit = ''\n              # This will ensure that $NIX_PROFILE variable is set as fast as possible\n              # so Nix managed programs can work properly without needing to tweak the OS.\n              # Usually this is done by Nix installer by putting these lines inside fish system config dir\n              # but it's a hit or miss especially when fish is not installed in the system beforehand.\n              # And I also found problem that vendor completion of Nix installed programs are not working\n              # without calling fish twice, I assume it's because $NIX_PROFILE is not set fast enough\n              if test -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish'\n                . '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish'\n              end\n            '';\n          }\n        );\n        plugins =\n          with pkgs.fishPlugins;\n          [\n            {\n              name = \"autopair\";\n              src = autopair.src;\n            }\n            {\n              # TODO: might be better off use native fzf support\n              name = \"fzf-fish\";\n              src = fzf-fish.src;\n            }\n            {\n              name = \"puffer\";\n              src = puffer.src;\n            }\n            {\n              name = \"abbreviation-tips\";\n              src = myPkgs.fish-plugins.abbreviation-tips.src;\n            }\n            {\n              name = \"fish-completion-sync\";\n              src = myPkgs.fish-plugins.fish-completion-sync.src;\n            }\n          ]\n          ++ [\n            (mkIf (config.programs.tmux.enable && !config.programs.zellij.enable) {\n              name = \"tmux-fish\";\n              src = myPkgs.fish-plugins.tmux-fish.src;\n            })\n          ];\n\n        functions = {\n          mkcd = {\n            body = \"mkdir -p $argv; cd $argv\";\n            description = \"mkdir and cd into in\";\n          };\n        };\n\n        shellInit = ''\n          # disable fish greeting\n          set -g fish_greeting\n\n          # Termite and Alacritty tmux ssh error workaround\n          if test $TERM = xterm-termite; or test $TERM = alacritty\n            set -gx TERM xterm-256color\n          end\n        '';\n\n        interactiveShellInit = ''\n          # use vi keybindings\n          fish_vi_key_bindings\n\n          # fzf-fish\n          set fzf_preview_dir_cmd eza --all --color=always\n          set fzf_fd_opts --hidden --exclude=.git --exclude=.github --exclude=.cache\n          fzf_configure_bindings --git_log=\\cg --git_status=\\cs --variables=\\cv --directory=\\cf --history=\\cr\n\n          ${lib.optionalString config.programs.tmux.enable \"set fish_tmux_autostart true\"}\n        '';\n\n        shellAbbrs = {\n          cpr = \"cp -rf\";\n          rmr = \"rm -rf\";\n          md = \"mdir -p\";\n          rd = \"rmdir\";\n\n          # eza\n          lsa = \"eza -lag --git --icons --sort=type\";\n          ll = \"eza -l --git --icons --sort = type\";\n        };\n      };\n\n      zoxide.enable = true;\n      bat.enable = true;\n\n      fzf = {\n        enable = true;\n        defaultCommand = \"${pkgs.fd}/bin/fd --type f\";\n        defaultOptions = [\n          \"--cycle\"\n          \"--layout reverse\"\n          \"--border\"\n          \"--height 40%\"\n          \"--preview-window wrap\"\n          \"--marker *\"\n        ];\n        changeDirWidgetCommand = \"${pkgs.fd}/bin/fd --type d\";\n        changeDirWidgetOptions = [ \"--preview 'tree -C {} | head -200'\" ];\n      };\n    };\n    home.packages = with pkgs; [\n      eza\n      fd\n    ];\n  };\n}\n"
  },
  {
    "path": "home/_modules/shell/git/default.nix",
    "content": "{\n  config,\n  lib,\n  options,\n  ...\n}:\nlet\n  cfg = config.myHome.shell.git;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    mkIf\n    ;\nin\n{\n  options.myHome.shell.git = {\n    enable = mkEnableOption \"git\";\n    config = mkOption {\n      type = options.programs.git.extraConfig.type;\n      default = options.programs.git.extraConfig.default;\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    programs.git = {\n      enable = true;\n      settings = cfg.config;\n    };\n\n    programs.gh = {\n      enable = true;\n      settings = {\n        # https://github.com/nix-community/home-manager/issues/4744\n        version = 1;\n      };\n    };\n\n    programs.fish.shellAbbrs = {\n      g = \"git\";\n      ga = \"git add .\";\n      gaa = \"git add --all\";\n      gc = \"git commit\";\n      gcm = \"git commit -m\";\n      gs = \"git status\";\n      gp = \"git push\";\n      gpl = \"git pull\";\n      gl = \"git log\";\n      gd = \"git diff\";\n      gds = \"git diff --staged\";\n      gr = \"git restore .\";\n      grs = \"git restore --staged .\";\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/shell/lf/configs/colors",
    "content": "# vim:ft=dircolors\n# (This is not a dircolors file but it helps to highlight colors and comments)\n\n# default values from dircolors\n# (entries with a leading # are not implemented in lf)\n# #no     00              # NORMAL\n# fi      00              # FILE\n# #rs     0               # RESET\n# di      01;34           # DIR\n# ln      01;36           # LINK\n# #mh     00              # MULTIHARDLINK\n# pi      40;33           # FIFO\n# so      01;35           # SOCK\n# #do     01;35           # DOOR\n# bd      40;33;01        # BLK\n# cd      40;33;01        # CHR\n# or      40;31;01        # ORPHAN\n# #mi     00              # MISSING\n# su      37;41           # SETUID\n# sg      30;43           # SETGID\n# #ca     30;41           # CAPABILITY\n# tw      30;42           # STICKY_OTHER_WRITABLE\n# ow      34;42           # OTHER_WRITABLE\n# st      37;44           # STICKY\n# ex      01;32           # EXEC\n\n# default values from lf (with matching order)\n# ln      01;36   # LINK\n# or      31;01   # ORPHAN\n# tw      01;34   # STICKY_OTHER_WRITABLE\n# ow      01;34   # OTHER_WRITABLE\n# st      01;34   # STICKY\n# di      01;34   # DIR\n# pi      33      # FIFO\n# so      01;35   # SOCK\n# bd      33;01   # BLK\n# cd      33;01   # CHR\n# su      01;32   # SETUID\n# sg      01;32   # SETGID\n# ex      01;32   # EXEC\n# fi      00      # FILE\n\n# file types (with matching order)\nln      01;36   # LINK\nor      31;01   # ORPHAN\ntw      34      # STICKY_OTHER_WRITABLE\now      34      # OTHER_WRITABLE\nst      01;34   # STICKY\ndi      01;34   # DIR\npi      33      # FIFO\nso      01;35   # SOCK\nbd      33;01   # BLK\ncd      33;01   # CHR\nsu      01;32   # SETUID\nsg      01;32   # SETGID\nex      01;32   # EXEC\nfi      00      # FILE\n\n# archives or compressed (dircolors defaults)\n*.tar   01;31\n*.tgz   01;31\n*.arc   01;31\n*.arj   01;31\n*.taz   01;31\n*.lha   01;31\n*.lz4   01;31\n*.lzh   01;31\n*.lzma  01;31\n*.tlz   01;31\n*.txz   01;31\n*.tzo   01;31\n*.t7z   01;31\n*.zip   01;31\n*.z     01;31\n*.dz    01;31\n*.gz    01;31\n*.lrz   01;31\n*.lz    01;31\n*.lzo   01;31\n*.xz    01;31\n*.zst   01;31\n*.tzst  01;31\n*.bz2   01;31\n*.bz    01;31\n*.tbz   01;31\n*.tbz2  01;31\n*.tz    01;31\n*.deb   01;31\n*.rpm   01;31\n*.jar   01;31\n*.war   01;31\n*.ear   01;31\n*.sar   01;31\n*.rar   01;31\n*.alz   01;31\n*.ace   01;31\n*.zoo   01;31\n*.cpio  01;31\n*.7z    01;31\n*.rz    01;31\n*.cab   01;31\n*.wim   01;31\n*.swm   01;31\n*.dwm   01;31\n*.esd   01;31\n\n# image formats (dircolors defaults)\n*.jpg   01;35\n*.jpeg  01;35\n*.mjpg  01;35\n*.mjpeg 01;35\n*.gif   01;35\n*.bmp   01;35\n*.pbm   01;35\n*.pgm   01;35\n*.ppm   01;35\n*.tga   01;35\n*.xbm   01;35\n*.xpm   01;35\n*.tif   01;35\n*.tiff  01;35\n*.png   01;35\n*.svg   01;35\n*.svgz  01;35\n*.mng   01;35\n*.pcx   01;35\n*.mov   01;35\n*.mpg   01;35\n*.mpeg  01;35\n*.m2v   01;35\n*.mkv   01;35\n*.webm  01;35\n*.ogm   01;35\n*.mp4   01;35\n*.m4v   01;35\n*.mp4v  01;35\n*.vob   01;35\n*.qt    01;35\n*.nuv   01;35\n*.wmv   01;35\n*.asf   01;35\n*.rm    01;35\n*.rmvb  01;35\n*.flc   01;35\n*.avi   01;35\n*.fli   01;35\n*.flv   01;35\n*.gl    01;35\n*.dl    01;35\n*.xcf   01;35\n*.xwd   01;35\n*.yuv   01;35\n*.cgm   01;35\n*.emf   01;35\n*.ogv   01;35\n*.ogx   01;35\n\n# audio formats (dircolors defaults)\n*.aac   00;36\n*.au    00;36\n*.flac  00;36\n*.m4a   00;36\n*.mid   00;36\n*.midi  00;36\n*.mka   00;36\n*.mp3   00;36\n*.mpc   00;36\n*.ogg   00;36\n*.ra    00;36\n*.wav   00;36\n*.oga   00;36\n*.opus  00;36\n*.spx   00;36\n*.xspf  00;36\n"
  },
  {
    "path": "home/_modules/shell/lf/configs/icons",
    "content": "# vim:ft=conf\n\n# These examples require Nerd Fonts or a compatible font to be used.\n# See https://www.nerdfonts.com for more information.\n\n# default values from lf (with matching order)\n# ln      l       # LINK\n# or      l       # ORPHAN\n# tw      t       # STICKY_OTHER_WRITABLE\n# ow      d       # OTHER_WRITABLE\n# st      t       # STICKY\n# di      d       # DIR\n# pi      p       # FIFO\n# so      s       # SOCK\n# bd      b       # BLK\n# cd      c       # CHR\n# su      u       # SETUID\n# sg      g       # SETGID\n# ex      x       # EXEC\n# fi      -       # FILE\n\n# file types (with matching order)\nln             # LINK\nor             # ORPHAN\ntw      t       # STICKY_OTHER_WRITABLE\now             # OTHER_WRITABLE\nst      t       # STICKY\ndi             # DIR\npi      p       # FIFO\nso      s       # SOCK\nbd      b       # BLK\ncd      c       # CHR\nsu      u       # SETUID\nsg      g       # SETGID\nex             # EXEC\nfi             # FILE\n\n# file extensions (vim-devicons)\n*.styl          \n*.sass          \n*.scss          \n*.htm           \n*.html          \n*.slim          \n*.haml          \n*.ejs           \n*.css           \n*.less          \n*.md            \n*.mdx           \n*.markdown      \n*.rmd           \n*.json          \n*.webmanifest   \n*.js            \n*.mjs           \n*.jsx           \n*.rb            \n*.gemspec       \n*.rake          \n*.php           \n*.py            \n*.pyc           \n*.pyo           \n*.pyd           \n*.coffee        \n*.mustache      \n*.hbs           \n*.conf          \n*.ini           \n*.yml           \n*.yaml          \n*.toml          \n*.bat           \n*.mk            \n*.jpg           \n*.jpeg          \n*.bmp           \n*.png           \n*.webp          \n*.gif           \n*.ico           \n*.twig          \n*.cpp           \n*.c++           \n*.cxx           \n*.cc            \n*.cp            \n*.c             \n*.cs            \n*.h             \n*.hh            \n*.hpp           \n*.hxx           \n*.hs            \n*.lhs           \n*.nix           \n*.lua           \n*.java          \n*.sh            \n*.fish          \n*.bash          \n*.zsh           \n*.ksh           \n*.csh           \n*.awk           \n*.ps1           \n*.ml            λ\n*.mli           λ\n*.diff          \n*.db            \n*.sql           \n*.dump          \n*.clj           \n*.cljc          \n*.cljs          \n*.edn           \n*.scala         \n*.go            \n*.dart          \n*.xul           \n*.sln           \n*.suo           \n*.pl            \n*.pm            \n*.t             \n*.rss           \n'*.f#'          \n*.fsscript      \n*.fsx           \n*.fs            \n*.fsi           \n*.rs            \n*.rlib          \n*.d             \n*.erl           \n*.hrl           \n*.ex            \n*.exs           \n*.eex           \n*.leex          \n*.heex          \n*.vim           \n*.ai            \n*.psd           \n*.psb           \n*.ts            \n*.tsx           \n*.jl            \n*.pp            \n*.vue           \n*.elm           \n*.swift         \n*.xcplayground  \n*.tex           ﭨ\n*.r             ﳒ\n*.rproj         鉶\n*.sol           ﲹ\n*.pem           \n\n# file names (vim-devicons) (case-insensitive not supported in lf)\n*gruntfile.coffee       \n*gruntfile.js           \n*gruntfile.ls           \n*gulpfile.coffee        \n*gulpfile.js            \n*gulpfile.ls            \n*mix.lock               \n*dropbox                \n*.ds_store              \n*.gitconfig             \n*.gitignore             \n*.gitattributes         \n*.gitlab-ci.yml         \n*.bashrc                \n*.zshrc                 \n*.zshenv                \n*.zprofile              \n*.vimrc                 \n*.gvimrc                \n*_vimrc                 \n*_gvimrc                \n*.bashprofile           \n*favicon.ico            \n*license                \n*node_modules           \n*react.jsx              \n*procfile               \n*dockerfile             \n*docker-compose.yml     \n*rakefile               \n*config.ru              \n*gemfile                \n*makefile               \n*cmakelists.txt         \n*robots.txt             ﮧ\n\n# file names (case-sensitive adaptations)\n*Gruntfile.coffee       \n*Gruntfile.js           \n*Gruntfile.ls           \n*Gulpfile.coffee        \n*Gulpfile.js            \n*Gulpfile.ls            \n*Dropbox                \n*.DS_Store              \n*LICENSE                \n*React.jsx              \n*Procfile               \n*Dockerfile             \n*Docker-compose.yml     \n*Rakefile               \n*Gemfile                \n*Makefile               \n*CMakeLists.txt         \n\n# file patterns (vim-devicons) (patterns not supported in lf)\n# .*jquery.*\\.js$         \n# .*angular.*\\.js$        \n# .*backbone.*\\.js$       \n# .*require.*\\.js$        \n# .*materialize.*\\.js$    \n# .*materialize.*\\.css$   \n# .*mootools.*\\.js$       \n# .*vimrc.*               \n# Vagrantfile$            \n\n# file patterns (file name adaptations)\n*jquery.min.js          \n*angular.min.js         \n*backbone.min.js        \n*require.min.js         \n*materialize.min.js     \n*materialize.min.css    \n*mootools.min.js        \n*vimrc                  \nVagrantfile             \n\n# archives or compressed (extensions from dircolors defaults)\n*.tar   \n*.tgz   \n*.arc   \n*.arj   \n*.taz   \n*.lha   \n*.lz4   \n*.lzh   \n*.lzma  \n*.tlz   \n*.txz   \n*.tzo   \n*.t7z   \n*.zip   \n*.z     \n*.dz    \n*.gz    \n*.lrz   \n*.lz    \n*.lzo   \n*.xz    \n*.zst   \n*.tzst  \n*.bz2   \n*.bz    \n*.tbz   \n*.tbz2  \n*.tz    \n*.deb   \n*.rpm   \n*.jar   \n*.war   \n*.ear   \n*.sar   \n*.rar   \n*.alz   \n*.ace   \n*.zoo   \n*.cpio  \n*.7z    \n*.rz    \n*.cab   \n*.wim   \n*.swm   \n*.dwm   \n*.esd   \n\n# image formats (extensions from dircolors defaults)\n*.jpg   \n*.jpeg  \n*.mjpg  \n*.mjpeg \n*.gif   \n*.bmp   \n*.pbm   \n*.pgm   \n*.ppm   \n*.tga   \n*.xbm   \n*.xpm   \n*.tif   \n*.tiff  \n*.png   \n*.svg   \n*.svgz  \n*.mng   \n*.pcx   \n*.mov   \n*.mpg   \n*.mpeg  \n*.m2v   \n*.mkv   \n*.webm  \n*.ogm   \n*.mp4   \n*.m4v   \n*.mp4v  \n*.vob   \n*.qt    \n*.nuv   \n*.wmv   \n*.asf   \n*.rm    \n*.rmvb  \n*.flc   \n*.avi   \n*.fli   \n*.flv   \n*.gl    \n*.dl    \n*.xcf   \n*.xwd   \n*.yuv   \n*.cgm   \n*.emf   \n*.ogv   \n*.ogx   \n\n# audio formats (extensions from dircolors defaults)\n*.aac   \n*.au    \n*.flac  \n*.m4a   \n*.mid   \n*.midi  \n*.mka   \n*.mp3   \n*.mpc   \n*.ogg   \n*.ra    \n*.wav   \n*.oga   \n*.opus  \n*.spx   \n*.xspf  \n\n# other formats\n*.pdf   \n"
  },
  {
    "path": "home/_modules/shell/lf/default.nix",
    "content": "{\n  pkgs,\n  lib,\n  config,\n  ...\n}:\nlet\n  myHome = config.myHome;\n  cfg = myHome.shell.lf;\nin\n{\n  options.myHome.shell.lf = {\n    enable = lib.mkEnableOption \"lf\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    myHome.shell.dircolors.enable = true;\n\n    xdg.configFile.\"lf/colors\".source = ./configs/colors;\n    xdg.configFile.\"lf/icons\".source = ./configs/icons;\n    programs.lf = {\n      enable = true;\n      keybindings = {\n        \"D\" = \"delete\";\n        \"n\" = \"\";\n        \"nd\" = \"push :newdir<space>\";\n        \"nf\" = \"push :newfile<space>\";\n        \"gf\" = \":fzf_search\";\n        \"gz\" = \"push :z<space>\";\n      };\n      extraConfig = ''\n        # previews\n        set previewer ${pkgs.ctpv}/bin/ctpv\n        set cleaner ${pkgs.ctpv}/bin/ctpvclear\n        &${pkgs.ctpv}/bin/ctpv -s $id\n        &${pkgs.ctpv}/bin/ctpvquit $id\n\n        cmd newdir %{{\n          ${pkgs.coreutils}/bin/mkdir \"$@\"\n          lf -remote \"send $id select \\\"$@\\\"\"\n        }}\n\n        cmd newfile %{{\n          ${pkgs.coreutils}/bin/touch \"$@\"\n          lf -remote \"send $id select \\\"$@\\\"\"\n        }}\n\n        # zoxide\n        cmd z %{{\n          result=\"$(${pkgs.zoxide}/bin/zoxide query --exclude $PWD $@)\"\n          lf -remote \"send $id cd \\\"$result\\\"\"\n        }}\n\n        # fzf with ripgrep\n        cmd fzf_search ''${{\n          res=\"$( \\\n            RG_PREFIX=\"${pkgs.ripgrep}/bin/rg --column --line-number --no-heading --color=always \\\n              --smart-case \"\n            FZF_DEFAULT_COMMAND=\"$RG_PREFIX '''\" \\\n              ${pkgs.fzf}/bin/fzf --bind \"change:reload:$RG_PREFIX {q} || true\" \\\n              --ansi --layout=reverse --header 'Search in files' \\\n              | cut -d':' -f1\n          )\"\n          [ ! -z \"$res\" ] && lf -remote \"send $id select \\\"$res\\\"\"\n        }}\n\n        # opening files\n        cmd open ''${{\n          case $(${pkgs.file}/bin/file --mime-type -Lb \"$f\") in\n            # text files\n            text/*|application/json)\n              $EDITOR \"$fx\";;\n\n            # archive files\n            application/x-bzip*|application/*zip|application/x-xz|application/x-rar|application/x-*-image)\n              mntdir=\"$f-archivemount\"\n              [ ! -d \"$mntdir\" ] && {\n                mkdir \"$mntdir\"\n                ${pkgs.archivemount}/bin/archivemount \"$f\" \"$mntdir\"\n                echo \"$mntdir\" >> \"/tmp/__lf_archivemount_$id\"\n              }\n              lf -remote \"send $id cd \\\"$mntdir\\\"\"\n              lf -remote \"send $id reload\"\n              ;;\n          esac\n        }}\n\n        # umount archives on quit\n        cmd on-quit ''${{\n          archivemount_dir=\"/tmp/__lf_archivemount_$id\"\n          if [ -f \"$archivemount_dir\" ]; then\n            cat \"$archivemount_dir\" | \\\n              while read -r line; do\n                /run/wrappers/bin/sudo ${pkgs.umount}/bin/umount \"$line\"\n                ${pkgs.coreutils}/bin/rmdir \"$line\"\n              done\n            rm -f \"$archivemount_dir\"\n          fi\n        }}\n\n        # dynamically set number of columns\n        ''${{\n          w=$(${pkgs.ncurses}/bin/tput cols)\n          if [ \"$w\" -le 80 ]; then\n            lf -remote \"send $id set ratios 1:2\"\n          elif [ \"$w\" -le 160 ]; then\n            lf -remote \"send $id set ratios 1:2:3\"\n          else\n            lf -remote \"send $id set ratios 1:2:3:5\"\n          fi\n        }}\n      '';\n      settings = {\n        icons = true;\n        drawbox = true;\n      };\n    };\n\n    home.packages = with pkgs; [\n      chafa\n      poppler-utils\n      ffmpegthumbnailer\n    ];\n  };\n}\n"
  },
  {
    "path": "home/_modules/shell/nix-direnv/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.shell.nix-direnv;\nin\n{\n  options.myHome.shell.nix-direnv = {\n    enable = lib.mkEnableOption \"nix-direnv\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.direnv = {\n      enable = true;\n      nix-direnv.enable = true;\n    };\n    home.sessionVariables.DIRENV_LOG_FORMAT = \"\";\n  };\n}\n"
  },
  {
    "path": "home/_modules/shell/starship/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.shell.starship;\nin\n{\n  options.myHome.shell.starship = {\n    enable = lib.mkEnableOption \"Starship cross-shell prompt\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    # TODO: wait for the IFD issue is fixed\n    # ref: https://github.com/catppuccin/nix/issues/392\n    catppuccin.starship.enable = lib.mkIf (pkgs.stdenv.hostPlatform.system == \"x86_64-linux\") true;\n\n    programs.starship = {\n      enable = true;\n      settings = {\n        add_newline = false;\n        command_timeout = 1000;\n        format = lib.concatStrings [\n          \"$os\"\n          \"$username\"\n          \"$hostname\"\n          \"$directory\"\n          \"$git_branch\"\n          \"$git_status\"\n          \"$fill\"\n          \"$direnv\"\n          \"$nix_shell\"\n          \"$python\"\n          \"$cmd_duration\"\n          \"$status\"\n          \"$line_break\"\n          \"$character\"\n        ];\n\n        os = {\n          disabled = false;\n          format = \"[](fg:blue)[ $symbol ](fg:mantle bg:blue)[](fg:blue bg:surface0)\";\n          symbols = {\n            Arch = \"󰣇 \";\n            NixOS = \"󱄅 \";\n            Ubuntu = \" \";\n          };\n        };\n\n        username = {\n          style_user = \"fg:peach bg:surface0\";\n          style_root = \"fg:red bg:surface0\";\n          format = \"[ $user]($style)\";\n          show_always = false;\n        };\n\n        hostname = {\n          ssh_only = true;\n          format = \"[@$hostname](fg:green bg:surface0)\";\n        };\n\n        directory = {\n          truncation_length = 3;\n          format = \"[ in](fg:text bg:surface0)[ $path ](fg:blue bg:surface0)([$read_only ](fg:red bg:surface0))[](fg:surface0)\";\n          read_only = \"\";\n          truncation_symbol = \"../\";\n          truncate_to_repo = true;\n          fish_style_pwd_dir_length = 1;\n        };\n\n        git_branch = {\n          format = \"[ $symbol$branch ](fg:mauve)\";\n          symbol = \" \";\n        };\n\n        git_status = {\n          format = \"[$all_status$ahead_behind](fg:mauve)\";\n          conflicted = \" \";\n          ahead = \" \";\n          behind = \" \";\n          diverged = \"󰆗 \";\n          up_to_date = \" \";\n          untracked = \" \";\n          stashed = \" \";\n          modified = \" \";\n          staged = \" \";\n          renamed = \" \";\n          deleted = \" \";\n        };\n\n        fill = {\n          symbol = \" \";\n        };\n\n        direnv = {\n          disabled = false;\n          format = \"[$symbol\\\\($loaded/$allowed\\\\) ](fg:blue)\";\n          symbol = \"  \";\n        };\n\n        nix_shell = {\n          format = \"[$symbol(\\\\($name\\\\)) ](fg:blue)\";\n          heuristic = true; # needed to detect `nix shell`\n          symbol = \"󱄅 \"; # the default unicode is causing issue https://github.com/starship/starship/issues/5924\n        };\n\n        python = {\n          format = \"[\\${symbol}\\${pyenv_prefix}(\\${version} )(\\($virtualenv\\) )]($style)\";\n          symbol = \"🐍 \";\n        };\n\n        cmd_duration = {\n          min_time = 0;\n          format = \"[](fg:surface0)[ took](fg:text bg:surface0)[ $duration ](fg:yellow bg:surface0)\";\n        };\n\n        status = {\n          disabled = false;\n          format = \"[](fg:blue bg:surface0)[ $symbol](fg:mantle bg:blue)[](fg:blue)\";\n          symbol = \" \";\n          success_symbol = \" \";\n        };\n\n        character = {\n          success_symbol = \"[](green)\";\n          error_symbol = \"[](green)\";\n          vicmd_symbol = \"[](mauve)\";\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/terminal-emulator/alacritty/default.nix",
    "content": "{\n  lib,\n  config,\n  pkgs,\n  ...\n}:\nwith lib;\nlet\n  cfg = config.myHome.terminal-emulator.alacritty;\nin\n{\n  options.myHome.terminal-emulator.alacritty = {\n    enable = mkEnableOption \"alacritty\";\n  };\n\n  config = mkIf (cfg.enable) {\n    catppuccin.alacritty.enable = true;\n\n    programs.alacritty = {\n      enable = true;\n      package = (config.lib.nixGL.wrap pkgs.alacritty);\n      settings = {\n        general.live_config_reload = true;\n        env.TERM = \"xterm-256color\";\n        window = {\n          padding = {\n            x = 10;\n            y = 10;\n          };\n          dynamic_padding = true;\n          decorations = \"none\";\n          startup_mode = \"Maximized\";\n        };\n        scrolling.history = 10000;\n        font = {\n          size = 12.0;\n          normal = {\n            family = \"UbuntuMono Nerd Font\";\n            style = \"Regular\";\n          };\n        };\n        colors = {\n          draw_bold_text_with_bright_colors = false;\n        };\n        bell.duration = 0;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/terminal-emulator/contour/config/contour.yml",
    "content": "---\non_mouse_select: CopyToSelectionClipboard\nprofiles:\n  main:\n    show_title_bar: false\n    maximized: true\n    environment:\n      TERM: xterm-256color\n      COLORTERM: truecolor\n    margins:\n      horizontal: 10\n      vertical: 10\n    history:\n      limit: 10000\n      auto_scroll_on_update: true\n      scroll_multiplier: 3\n    scrollbar:\n      position: Hidden\n    mouse:\n      hide_while_typing: true\n    font:\n      # using 12 causes the underscore and long characters to cut off\n      # https://github.com/contour-terminal/contour/issues/1603\n      size: 12.1\n      regular:\n        family: UbuntuMono Nerd Font\n        weight: normal\n        slant: normal\n      emoji: emoji\n    draw_bold_text_with_bright_colors: false\n    cursor:\n      shape: rectangle\n      blinking: false\n    vi_mode_highlight_timeout: 300\n    vi_mode_scrolloff: 8\n    status_line:\n      display: none\n    colors: catppuccin_mocha\n    hyperlink_decoration:\n      normal: dotted\n      hover: underline\ncolor_schemes:\n  catppuccin_mocha:\n    default:\n      background: \"#1E1E2E\" # base\n      foreground: \"#CDD6F4\" # text\n    cursor:\n      default: \"#F5E0DC\" # rosewater\n      text: \"#1E1E2E\" # base\n    normal:\n      black: \"#45475A\" # surface1\n      red: \"#F38BA8\" # red\n      green: \"#A6E3A1\" # green\n      yellow: \"#F9E2AF\" # yellow\n      blue: \"#89B4FA\" # blue\n      magenta: \"#F5C2E7\" # pink\n      cyan: \"#94E2D5\" # teal\n      white: \"#BAC2DE\" # subtext1\n    bright:\n      black: \"#585B70\" # surface2\n      red: \"#F38BA8\" # red\n      green: \"#A6E3A1\" # green\n      yellow: \"#F9E2AF\" # yellow\n      blue: \"#89B4FA\" # blue\n      magenta: \"#F5C2E7\" # pink\n      cyan: \"#94E2D5\" # teal\n      white: \"#A6ADC8\" # subtext0\n    vi_mode_highlight:\n      foreground: CellForeground\n      background: \"#1E1E2E\" # base\n    vi_mode_cursorline:\n      foreground: CellForeground\n      background: \"#585B70\" # surface2\n      background_alpha: 0.1\n    selection:\n      foreground: CellForeground\n      background: \"#585B70\" # surface2\n    indicator_statusline:\n      foreground: CellForeground\n      background: \"#89B4FA\" # blue\n\ninput_mapping:\n  - { mods: [Control, Shift], key: Plus, action: IncreaseFontSize }\n  - { mods: [Control, Shift], key: Minus, action: DecreaseFontSize }\n  - { mods: [Control], key: Backspace, action: ResetFontSize }\n  - { mods: [Control, Shift], key: V, action: PasteClipboard, strip: false }\n  - { mods: [Control, Shift], key: C, action: ViNormalMode, mode: \"Insert\" }\n"
  },
  {
    "path": "home/_modules/terminal-emulator/contour/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.terminal-emulator.contour;\nin\n{\n  options.myHome.terminal-emulator.contour = {\n    enable = lib.mkEnableOption \"Contour Terminal Emulator\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    home.packages = [ (config.lib.nixGL.wrap pkgs.contour) ];\n    xdg.configFile.\"contour\".source = ./config;\n  };\n}\n"
  },
  {
    "path": "home/_modules/terminal-emulator/kitty/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.terminal-emulator.kitty;\nin\n{\n  options.myHome.terminal-emulator.kitty = {\n    enable = lib.mkEnableOption \"kitty\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.kitty = {\n      enable = true;\n      package = (config.lib.nixGL.wrap pkgs.kitty);\n      catppuccin.enable = true;\n      font = {\n        name = \"UbuntuMono Nerd Font\";\n        size = 12;\n      };\n      settings = {\n        # Cursor\n        cursor = \"#C0CAF5\";\n        cursor_text_color = \"#202124\";\n        cursor_shape = \"underline\";\n        cursor_blink_interval = \"-1\";\n\n        # Scrollback\n        scrollback_lines = 10000;\n\n        # Mouse\n        mouse_hide_wait = \"-1\";\n        url_color = \"#73DACA\";\n        url_style = \"curly\";\n\n        #Terminal bell\n        enable_audio_bell = \"no\";\n        visual_bell_duration = 0;\n\n        # Window layout\n        window_padding_width = 4;\n        confirm_os_window_close = 0;\n\n        # Advanced\n        allow_remote_control = \"no\";\n        shell_integration = \"disabled\";\n        term = \"xterm-256color\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/terminal-emulator/wezterm/config/wezterm.lua",
    "content": "local wezterm = require 'wezterm'\nlocal act = wezterm.action\n\n-- Start window maximized\nwezterm.on(\"gui-startup\", function(cmd)\n  local _, _, window = wezterm.mux.spawn_window(cmd or {})\n  window:gui_window():maximize()\nend)\n\nreturn {\n  -- Fonts\n  font = wezterm.font(\"UbuntuMono Nerd Font\"),\n  font_size = 12.0,\n\n  -- Appearance\n  color_scheme = \"Catppuccin Mocha\",\n  window_decorations = \"RESIZE\",\n  enable_tab_bar = false,\n  enable_scroll_bar = false,\n  warn_about_missing_glyphs = false,\n  window_close_confirmation = \"NeverPrompt\",\n  enable_wayland = false, -- this fixes weird issues that I don't understand why it's happening\n\n  -- Window layout\n  window_padding = {\n    left = \"1cell\",\n    right = \"1cell\",\n    top = \"0.5cell\",\n    bottom = \"0.5cell\",\n  },\n\n  -- Keyboard shortcuts\n  disable_default_key_bindings = true,\n  keys = {\n    { key = \"C\",         mods = \"CTRL|SHIFT\", action = act.ActivateCopyMode },\n    { key = \"V\",         mods = \"CTRL|SHIFT\", action = act.PasteFrom(\"Clipboard\") },\n    { key = \"+\",         mods = \"CTRL|SHIFT\", action = act.IncreaseFontSize },\n    { key = \"_\",         mods = \"CTRL|SHIFT\", action = act.DecreaseFontSize },\n    { key = \"Backspace\", mods = \"CTRL|SHIFT\", action = act.ResetFontSize },\n  },\n}\n"
  },
  {
    "path": "home/_modules/terminal-emulator/wezterm/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.terminal-emulator.wezterm;\nin\n{\n  options.myHome.terminal-emulator.wezterm = {\n    enable = lib.mkEnableOption \"WezTerm\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.wezterm = {\n      enable = true;\n      package = (config.lib.nixGL.wrap pkgs.wezterm);\n      extraConfig = builtins.readFile ./config/wezterm.lua;\n    };\n    home.packages = [ (config.lib.nixGL.wrap pkgs.contour) ];\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/blueman-applet/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  myHome = config.myHome;\n  cfg = myHome.windowmanager.add-on.blueman-applet;\nin\n{\n  options.myHome.windowmanager.add-on.blueman-applet = {\n    enable = lib.mkEnableOption \"blueman-applet\";\n  };\n\n  config = lib.mkIf (cfg.enable) { services.blueman-applet.enable = true; };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/dunst/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.windowmanager.add-on.dunst;\nin\n{\n  options.myHome.windowmanager.add-on.dunst = {\n    enable = lib.mkEnableOption \"dunst\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    home.packages = [ pkgs.libnotify ];\n    services.dunst = {\n      enable = true;\n      settings = {\n        global = {\n          follow = \"mouse\";\n          width = 300;\n          height = 300;\n          origin = \"top-right\";\n          offset = \"10x10\";\n          scale = 0;\n          notification_limit = 10;\n          progress_bar = true;\n          progress_bar_height = 10;\n          progress_bar_frame_width = 1;\n          progress_bar_min_width = 150;\n          progress_bar_max_width = 300;\n          indicate_hidden = \"yes\";\n          transparency = 0;\n          separator_height = 2;\n          padding = 8;\n          horizontal_padding = 8;\n          text_icon_padding = 0;\n          frame_width = 1;\n          frame_color = \"#C0CAF5\";\n          separator_color = \"frame\";\n          sort = \"yes\";\n          idle_threshold = 120;\n          font = \"UbuntuMono Nerd Font 12\";\n          line_height = 0;\n          markup = \"full\";\n          format = \"<b>%s</b>\\\\n%b\";\n          alignment = \"left\";\n          vertical_alignment = \"center\";\n          show_age_threshold = 60;\n          ellipsize = \"middle\";\n          ignore_newline = \"no\";\n          stack_duplicates = false;\n          hide_duplicate_count = false;\n          show_indicators = \"yes\";\n          icon_position = \"left\";\n          min_icon_size = 16;\n          max_icon_size = 16;\n          icon_path = \"/usr/share/icons/Vimix-Doder-dark/16/actions/:/usr/share/icons/Vimix-Doder-dark/16/devices:/usr/share/icons/Vimix-Doder-dark/places/:/usr/share/icons/Vimix-Doder-dark/16/mimetypes/:/usr/share/icons/Vimix-Doder-dark/16/panel/:/usr/share/icons/Vimix-Doder-dark/16/status/\";\n          sticky_history = \"yes\";\n          history_length = 20;\n          dmenu = \"/usr/bin/dmenu -p dunst:\";\n          browser = \"$BROWSER\";\n          always_run_script = true;\n          title = \"Dunst\";\n          class = \"Dunst\";\n          corner_radius = 5;\n          ignore_dbusclose = false;\n          layer = \"top\";\n          force_xwayland = false;\n          force_xinerama = false;\n          mouse_left_click = \"do_action\";\n          mouse_middle_click = \"close_current\";\n          mouse_right_click = \"close_current\";\n        };\n        experimental = {\n          per_monitor_dpi = false;\n        };\n        urgency_low = {\n          background = \"#1A1B26\";\n          foreground = \"#7AA2F7\";\n          timeout = 10;\n        };\n        urgency_normal = {\n          background = \"#1A1B26\";\n          foreground = \"#9ECE6A\";\n          timeout = 10;\n        };\n        urgency_critical = {\n          background = \"#1A1B26\";\n          foreground = \"#F7768E\";\n          timeout = 0;\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/gtk-theme/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  myPkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.windowmanager.add-on.gtk-theme;\nin\n{\n  options.myHome.windowmanager.add-on.gtk-theme = {\n    enable = lib.mkEnableOption \"gtk-theme\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    home.pointerCursor = {\n      package = pkgs.catppuccin-cursors.mochaMauve;\n      name = \"catppuccin-mocha-mauve-cursors\";\n      size = 24;\n      gtk.enable = true;\n      x11.enable = true;\n    };\n    gtk = {\n      enable = true;\n      font = {\n        name = \"UbuntuMono Nerd Font\";\n        package = pkgs.nerd-fonts.ubuntu-mono;\n        size = 12;\n      };\n      theme = {\n        name = \"Tokyonight-Dark\";\n        package = myPkgs.tokyonight-gtk-theme;\n      };\n      iconTheme = {\n        name = \"Tokyonight-Dark\";\n        package = myPkgs.tokyonight-icon-theme;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/nm-applet/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.windowmanager.add-on.nm-applet;\nin\n{\n  options.myHome.windowmanager.add-on.nm-applet = {\n    enable = lib.mkEnableOption \"nm-applet\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    services.network-manager-applet.enable = true;\n    xsession.preferStatusNotifierItems = true;\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/nwg-bar/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.windowmanager.add-on.nwg-bar;\nin\n{\n  options.myHome.windowmanager.add-on.nwg-bar = {\n    enable = lib.mkEnableOption \"nwg-bar\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    myHome.windowmanager.add-on.swaylock.enable = true;\n\n    home.packages = [ pkgs.nwg-bar ];\n    xdg.configFile = {\n      \"nwg-bar/bar.json\".text = ''\n        [\n          {\n            \"label\": \"_Lock\",\n            \"exec\": \"${config.programs.swaylock.package}/bin/swaylock\",\n            \"icon\": \"${pkgs.nwg-bar}/share/nwg-bar/images/system-lock-screen.svg\"\n          },\n          {\n            \"label\": \"_Exit\",\n            \"exec\": \"${\n              if config.wayland.windowManager.sway.enable then\n                \"swaymsg exit\"\n              else if config.wayland.windowManager.hyprland.enable then\n                \"hyprctl dispatch exit\"\n              else\n                \"\"\n            }\",\n            \"icon\": \"${pkgs.nwg-bar}/share/nwg-bar/images/system-log-out.svg\"\n          },\n          {\n            \"label\": \"_Reboot\",\n            \"exec\": \"systemctl reboot\",\n            \"icon\": \"${pkgs.nwg-bar}/share/nwg-bar/images/system-reboot.svg\"\n          },\n          {\n            \"label\": \"_Shutdown\",\n            \"exec\": \"systemctl -i poweroff\",\n            \"icon\": \"${pkgs.nwg-bar}/share/nwg-bar/images/system-shutdown.svg\"\n          },\n          {\n            \"label\": \"UE_FI\",\n            \"exec\": \"systemctl reboot --firmware-setup\",\n            \"icon\": \"${pkgs.nwg-bar}/share/nwg-bar/images/system-reboot.svg\"\n          }\n        ]\n      '';\n\n      \"nwg-bar/style.css\".text = ''\n        window {\n          border: 1px solid #C0CAF5;\n          border-radius: 10px;\n          background-color: #1A1B26;\n        }\n        button, image {\n          color: #C0CAF5;\n          background: none;\n          border: none;\n          box-shadow: none;\n        }\n        button {\n          padding-left: 10px;\n          padding-right: 10px;\n          margin: 5px;\n        }\n        button:hover {\n          background-color: #16161E\n        }\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/pasystray/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.windowmanager.add-on.pasystray;\nin\n{\n  options.myHome.windowmanager.add-on.pasystray = {\n    enable = lib.mkEnableOption \"pasystray\";\n  };\n\n  config = lib.mkIf (cfg.enable) { services.pasystray.enable = true; };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/picom/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.windowmanager.add-on.picom;\nin\n{\n  options.myHome.windowmanager.add-on.picom = {\n    enable = lib.mkEnableOption \"picom\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    services.picom = {\n      enable = true;\n      settings = {\n        # Shadows\n        shadow = true;\n        shadow-radius = 7;\n        shadow-opacity = 0.7;\n        shadow-offset-x = -7;\n        shadow-offset-y = -7;\n        shadow-red = 0.8;\n        shadow-green = 0;\n        shadow-blue = 0;\n        shadow-exclude = [\n          \"name = 'Notification'\"\n          \"class_g = 'Conky'\"\n          \"_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'\"\n          \"_GTK_FRAME_EXTENTS@:c\"\n          #\"_NET_WM_STATE@:32a *= '_NET_WM_STATE_STICKY'\",\n          \"class_g ?= 'i3-frame'\"\n          \"class_g ?= 'Dunst'\"\n        ];\n        # shadow-exclude-reg = \"\"\n        # xinerama-shadow-crop = false\n        # xinerama-shadow-crop = true;\n\n        # Fading\n        fading = true;\n        fade-in-step = 3.0e-2;\n        fade-out-step = 3.0e-2;\n        # fade-delta = 10\n        fade-exclude = [ \"_NET_WM_STATE@:32a *= '_NET_WM_STATE_FULLSCREEN'\" ];\n        # no-fading-openclose = false\n        # no-fading-destroyed-argb = false\n\n        # Transparency / Opacity\n        inactive-opacity = 0.98;\n        frame-opacity = 1.0;\n        inactive-opacity-override = false;\n        active-opacity = 1.0;\n        inactive-dim = 0.3;\n        focus-exclude = [\n          \"class_g = 'Cairo-clock'\"\n          \"_NET_WM_STATE@:32a *= '_NET_WM_STATE_FULLSCREEN'\"\n        ];\n        inactive-dim-fixed = 1.0;\n        opacity-rule = [\n          \"50:class_g = 'Dmenu'\"\n          \"93:class_g = 'URxvt' && !_NET_WM_STATE@:32a\"\n          \"0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'\"\n          \"93:class_g = 'Gnome-terminal'\"\n          \"93:class_g = 'Thunar'\"\n        ];\n\n        # Corners\n        corner-radius = 0;\n        rounded-corners-exclude = [\n          \"window_type = 'dock'\"\n          \"window_type = 'desktop'\"\n        ];\n\n        # Background-Blurring\n        # blur-method =\n        # blur-size = 12\n        # blur-deviation = false\n        # blur-strength = 5\n        # blur-background = false\n        # blur-background-frame = false\n        # blur-background-fixed = false\n        blur-kern = \"3x3box\";\n        blur-background-exclude = [\n          \"window_type = 'dock'\"\n          \"window_type = 'desktop'\"\n          \"_GTK_FRAME_EXTENTS@:c\"\n        ];\n\n        # General Settings\n        # daemon = false\n        backend = \"xrender\";\n        # vsync = true;\n        # dbus = false\n        mark-wmwin-focused = true;\n        mark-ovredir-focused = true;\n        detect-rounded-corners = true;\n        detect-client-opacity = true;\n        use-ewmh-active-win = true;\n        unredir-if-possible = false;\n        unredir-if-possible-delay = 5000; # miliseconds\n        unredir-if-possible-exclude = [ ];\n        detect-transient = true;\n        detect-client-leader = true;\n        # resize-damage = 1\n        invert-color-include = [ ];\n        glx-no-stencil = true;\n        glx-no-rebind-pixmap = true;\n        use-damage = true;\n        # xrender-sync-fence = false\n        # glx-fshader-win = \"\"\n        # force-win-blend = false\n        # no-ewmh-fullscreen = false\n        # max-brightness = 1.0\n        # transparent-clipping = false\n        log-level = \"info\";\n        # log-file = \"/path/to/your/log/file\"\n        # show-all-xerrors = false\n        # write-pid-path = \"/path/to/your/log/file\"\n        wintypes = {\n          tooltip = {\n            fade = true;\n            shadow = false;\n            opacity = 0.85;\n            focus = true;\n          };\n          dock = {\n            shadow = false;\n          };\n          dnd = {\n            shadow = false;\n          };\n          popup_menu = {\n            opacity = 0.8;\n          };\n          dropdown_menu = {\n            opacity = 0.9;\n            shadow = false;\n          };\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/py3status/config",
    "content": "order += \"frame net\"\norder += \"net_rate\"\norder += \"frame disks\"\norder += \"uname\"\norder += \"frame timedate\"\norder += \"whoami\"\n\ngeneral {\n    separator_block_width = 15\n}\n\nnet_rate {\n    format = \"[\\?color=down  {down}] [\\?color=up  {up}]\"\n    format_value = \"{value:.0f}{unit}\"\n    thresholds = {\n        'down': [\n            (0, \"#7AA2F7\"),\n            (1024, \"#7AA2F7\"),\n            (1048576, \"#7AA2F7\"),\n        ],\n        'up': [\n            (0, \"#F7768E\"),\n            (1024, \"#F7768E\"),\n            (1048576, \"#F7768E\"),\n        ],\n    }\n}\n\nframe disks {\n    static_string {\n        separator = false\n        separator_block_width = 5\n        format = shell(printf \" \" && df -h ~ | tail -n 1 | awk '{$1=\"\";$6=\"\";print $0}' | awk -F ' ' '{print $3}', str)\n        color = \"#9ECE6A\"\n    }\n    static_string {\n        format = shell(printf \" \" && df -h / | tail -n 1 | awk '{$1=\"\";$6=\"\";print $0}' | awk -F ' ' '{print $3}', str)\n        color = \"#F7768E\"\n    }\n}\n\nuname {\n    format = \" {release}\"\n    color = \"#E0AF68\"\n}\n\nframe timedate {\n    clock {\n        format = \" {Asia/Jakarta}\"\n        format_time = \"%a, %d %b %y\"\n        color = \"#BB9AF7\"\n    }\n    clock {\n        format = \" {Asia/Jakarta}\"\n        format_time = \"%I:%M:%S %p\"\n        color = \"#9ECE6A\"\n    }\n}\n\nwhoami {\n    format = \" {username}\"\n    color = \"#7AA2F7\"\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/py3status/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.windowmanager.add-on.py3status;\nin\n{\n  options.myHome.windowmanager.add-on.py3status = {\n    enable = lib.mkEnableOption \"py3status\";\n  };\n\n  config = lib.mkIf (cfg.enable) { xdg.configFile.\"py3status/config\".source = ./config; };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/rofi/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  myHome = config.myHome;\n  cfg = myHome.windowmanager.add-on.rofi;\nin\n{\n  options.myHome.windowmanager.add-on.rofi = {\n    enable = lib.mkEnableOption \"rofi\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.rofi = {\n      enable = true;\n      extraConfig = {\n        font = \"UbuntuMono Nerd Font 12\";\n        display-drun = \" \";\n        show-icons = true;\n        drun-display-format = \"{name}\";\n        disable-history = false;\n        sidebar-mode = false;\n      };\n      theme = ./style.rasi;\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/rofi/style.rasi",
    "content": "* {\n  selected-normal-foreground:  #1A1B26;\n  foreground:                  #C0CAF5;\n  normal-foreground:           @foreground;\n  alternate-normal-background: @background;\n  selected-urgent-foreground:  @selected-normal-foreground;\n  urgent-foreground:           @selected-normal-foreground;\n  alternate-urgent-background: @selected-normal-background;\n  active-foreground:           #C0CAF5;\n  selected-active-foreground:  @active-foreground;\n  alternate-active-background: #1A1B26;\n  background:                  #1A1B26;\n  alternate-normal-foreground: @foreground;\n  normal-background:           @background;\n  selected-normal-background:  #7AA2F7;\n  spacing:                     2px;\n  separatorcolor:              #C0CAF5;\n  urgent-background:           #F7768E;\n  selected-urgent-background:  @selected-normal-background;\n  alternate-urgent-foreground: @urgent-foreground;\n  background-color:            @background;\n  alternate-active-foreground: @active-foreground;\n  active-background:           @background;\n  selected-active-background:  @selected-normal-background;\n  prompt-foreground:           #F7768E;\n}\n\n#window {\n  background-color: @background;\n  border-color: @separatorcolor;\n  border: 1px;\n  border-radius: 5px;\n  padding: 5px;\n  width: 25%;\n}\n\n#mainbox {\n  border: 0;\n  padding: 2px;\n}\n\n#message {\n  border: 2px 0px 0px;\n  border-color: @separatorcolor;\n  padding: 1px;\n}\n\n#textbox {\n  text-color: @foreground;\n}\n\n#listview {\n  fixed-height: 0;\n  border: 1px 0px 0px ;\n  border-color: @separatorcolor;\n  spacing: 5px;\n  scrollbar: false;\n  padding: 10px 0px 0px ;\n  lines: 10;\n}\n\n#element {\n  border: 0;\n  padding: 3px;\n  cursor: pointer;\n  spacing: 5px;\n}\n\n#element.normal.normal {\n  background-color: @normal-background;\n  text-color: @normal-foreground;\n}\n#element.normal.urgent {\n  background-color: @urgent-background;\n  text-color: @urgent-foreground;\n}\n#element.normal.active {\n  background-color: @active-background;\n  text-color: @active-foreground;\n}\n#element.selected.normal {\n  background-color: @selected-normal-background;\n  text-color: @selected-normal-foreground;\n}\n#element.selected.urgent {\n  background-color: @selected-urgent-background;\n  text-color: @selected-urgent-foreground;\n}\n#element.selected.active {\n  background-color: @selected-active-background;\n  text-color: @selected-active-foreground;\n}\n#element.alternate.normal {\n  background-color: @alternate-normal-background;\n  text-color: @alternate-normal-foreground;\n}\n#element.alternate.urgent {\n  background-color: @alternate-urgent-background;\n  text-color: @alternate-urgent-foreground;\n}\n#element.alternate.active {\n  background-color: @alternate-active-background;\n  text-color: @alternate-active-foreground;\n}\n\n#element-text {\n  background-color: transparent;\n  cursor: inherit;\n  highlight: inherit;\n  text-color: inherit;\n}\n\n#element-icon {\n  background-color: transparent;\n  size: 1em;\n  cursor: inherit;\n  text-color: inherit;\n}\n\n#scrollbar {\n  width: 4px ;\n  border: 0;\n  handle-width: 8px ;\n  padding: 0;\n  handle-color: var(normal-foreground);\n}\n\n#sidebar {\n  border-color: @separatorcolor;\n  border: 2px dash 0 0;\n}\n\n#mode-switcher {\n  border: 2px 0px 0px ;\n  border-color: @separatorcolor;\n}\n\n#button {\n  cursor: pointer;\n  spacing: 0;\n  text-color: @normal-foreground;\n}\n\n#button.selected {\n  background-color: @selected-normal-background;\n  text-color: @selected-normal-foreground;\n}\n\n#inputbar {\n  spacing: 5px;\n  text-color: @normal-foreground;\n  padding: 3px ;\n}\n\n#case-indicator {\n  spacing: 0;\n  text-color: @normal-foreground;\n}\n\n#entry {\n  spacing: 0;\n  text-color: @normal-foreground;\n}\n\n#prompt {\n  spacing: 0;\n  text-color: @prompt-foreground;\n}\n\n#textbox-prompt-colon {\n  expand: false;\n  str: \":\";\n  text-color: @prompt-foreground;\n}\n\n#inputbar {\n  children: [prompt,entry,case-indicator];\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/screenshotter/default.nix",
    "content": "{\n  config,\n  pkgs,\n  lib,\n  ...\n}:\nlet\n  cfg = config.myHome.windowmanager.add-on.screenshotter;\n  wl-full-screenshot = pkgs.writeShellScriptBin \"wl-full-screenshot\" ''\n    IMG=~/Desktop/$(date +%Y%m%d_%Hh%mm%Ss).png\n    ${pkgs.grim}/bin/grim $IMG \\\n      && ${pkgs.wl-clipboard}/bin/wl-copy < \"$IMG\" \\\n      && ${pkgs.pipewire}/bin/pw-cat -p ${./camera-shutter.oga} \\\n      && ${pkgs.libnotify}/bin/notify-send \"Screenshot copied to clipboard and saved in your Desktop folder\"\n  '';\n\n  wl-region-screenshot = pkgs.writeShellScriptBin \"wl-region-screenshot\" ''\n    IMG=~/Desktop/$(date +%Y%m%d_%Hh%mm%Ss).png\n    ${pkgs.grim}/bin/grim -g \"$(${pkgs.slurp}/bin/slurp)\" \"$IMG\" \\\n      && ${pkgs.wl-clipboard}/bin/wl-copy < \"$IMG\" \\\n      && ${pkgs.pipewire}/bin/pw-cat -p ${./camera-shutter.oga} \\\n      && ${pkgs.libnotify}/bin/notify-send \"Screenshot copied to clipboard and saved in your Desktop folder\"\n  '';\n\n  x-full-screenshot = pkgs.writeShellScriptBin \"x-full-screenshot\" ''\n    IMG=~/Desktop/$(date +%Y%m%d_%Hh%mm%Ss).png\n    ${pkgs.scrot}/bin/scrot -e '${pkgs.xclip}/bin/xclip -sel clipboard -t image/png -i $f' -F \"$IMG\" \\\n      && ${pkgs.pipewire}/bin/pw-cat -p ${./camera-shutter.oga} \\\n      && ${pkgs.libnotify}/bin/notify-send \"Screenshot copied to clipboard and saved in your Desktop folder\"\n  '';\n\n  x-region-screenshot = pkgs.writeShellScriptBin \"x-region-screenshot\" ''\n    IMG=~/Desktop/$(date +%Y%m%d_%Hh%mm%Ss).png\n    ${pkgs.scrot}/bin/scrot -s -e '${pkgs.xclip}/bin/xclip -sel clipboard -t image/png -i $f' -F \"$IMG\" \\\n      && ${pkgs.pipewire}/bin/pw-cat -p ${./camera-shutter.oga} \\\n      && ${pkgs.libnotify}/bin/notify-send \"Screenshot copied to clipboard and saved in your Desktop folder\"\n  '';\n\n  modeName = \"Screenshot: (Enter) Full screenshot, (s) Select region\";\nin\n{\n  options.myHome.windowmanager.add-on.screenshotter = {\n    enable = lib.mkOption {\n      type = lib.types.bool;\n      default = false;\n      description = ''\n        Enable screenshotter.\n        This module will let you go to `screenshot mode` on `Printscreen` keypress in.\n        Once in the mode, you can press `Enter` to take full screenshot or `r` to take region screenshot with mouse selection.\n        The screenshot will be saved in `~/Desktop` directory and in your clipboard so you can paste the picture in applications.\n\n        Only works in i3, Hyprland, and Sway window manager.\n      '';\n    };\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    wayland.windowManager = {\n      hyprland = {\n        extraConfig = ''\n          submap = ${modeName}\n          bind = , return, execr, ${lib.getExe wl-full-screenshot}\n          bind = , return, submap, reset\n          bind = , s, exec, ${lib.getExe wl-region-screenshot}\n          bind = , s, submap, reset\n          # return to normal mode\n          bind = , escape, submap, reset\n          submap = reset\n        '';\n        settings.bind = [ \", print, submap, ${modeName}\" ];\n      };\n      sway.config = {\n        modes.${modeName} = {\n          Return = \"exec ${lib.getExe wl-full-screenshot}, mode default\";\n          \"--release s\" = \"exec ${lib.getExe wl-region-screenshot}, mode default\";\n          Escape = \"mode default\";\n        };\n        keybindings = {\n          Print = \"mode \\\"${modeName}\\\"\";\n        };\n      };\n    };\n\n    xsession.windowManager.i3.config = {\n      modes.${modeName} = {\n        Return = \"exec ${lib.getExe x-full-screenshot}, mode default\";\n        \"--release s\" = \"exec ${lib.getExe x-region-screenshot}, mode default\";\n        Escape = \"mode default\";\n      };\n      keybindings = {\n        Print = \"mode \\\"${modeName}\\\"\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/swayidle/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.windowmanager.add-on.swayidle;\n  wmCfg = config.wayland.windowManager;\nin\nwith lib;\n{\n  options.myHome.windowmanager.add-on.swayidle = {\n    enable = mkEnableOption \"Swayidle\";\n  };\n\n  config = mkIf (cfg.enable) {\n    services.swayidle = {\n      enable = true;\n      timeouts = [\n        {\n          timeout = 1800;\n          command = (\n            mkMerge [\n              (mkIf wmCfg.sway.enable \"${pkgs.sway}/bin/swaymsg \\\"output * dpms off\\\"\")\n              (mkIf wmCfg.hyprland.enable \"${wmCfg.hyprland.finalPackage}/bin/hyprctl dispatch dpms off\")\n            ]\n          );\n          resumeCommand = (\n            mkMerge [\n              (mkIf wmCfg.sway.enable \"${pkgs.sway}/bin/swaymsg \\\"output * dpms on\\\"\")\n              (mkIf wmCfg.hyprland.enable \"${wmCfg.hyprland.finalPackage}/bin/hyprctl dispatch dpms on\")\n            ]\n          );\n        }\n      ];\n      systemdTarget = mkIf wmCfg.hyprland.enable \"hyprland-session.target\";\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/swaylock/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.myHome.windowmanager.add-on.swaylock;\nin\n{\n  options.myHome.windowmanager.add-on.swaylock = {\n    enable = lib.mkEnableOption \"Swaylock\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.swaylock = {\n      enable = true;\n      package = pkgs.swaylock-effects;\n      settings = {\n        screenshots = true;\n        show-failed-attempts = true;\n        line-uses-inside = true;\n        clock = true;\n        timestr = \"%I:%M%p\";\n        datestr = \"%a, %d %b\";\n        indicator = true;\n        effect-blur = \"7x5\";\n        effect-compose = \"50%,35%;center;${./text.png}\";\n        # Inside colors\n        inside-color = \"1A1B26\";\n        inside-wrong-color = \"1A1B26\";\n        inside-ver-color = \"1A1B26\";\n        inside-clear-color = \"1A1B26\";\n        # Text colors\n        text-color = \"C0CAF5\";\n        text-wrong-color = \"C0CAF5\";\n        text-ver-color = \"C0CAF5\";\n        text-clear-color = \"C0CAF5\";\n        text-caps-lock-color = \"E0AF68\";\n        # Ring colors\n        ring-color = \"9ECE6A\";\n        ring-wrong-color = \"F7768E\";\n        ring-ver-color = \"7AA2F7\";\n        ring-clear-color = \"E0AF68\";\n        # Highlight colors\n        key-hl-color = \"BB9AF7\";\n        bs-hl-color = \"BB9AF7\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/terminal-emulator/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.windowmanager.add-on.terminal-emulator;\n  super = \"Mod4\";\n  inherit (lib)\n    mkOption\n    types\n    mkIf\n    mkMerge\n    ;\n  mkDefaultTerminal = term: {\n    myHome.terminal-emulator.${term}.enable = true;\n    wayland.windowManager = {\n      hyprland.settings.bind = [ \"SUPER, t, exec, ${term}\" ];\n      sway.config.keybindings.\"${super}+t\" = \"exec --no-startup-id ${term}\";\n    };\n    xsession.windowManager.i3.config.keybindings.\"${super}+t\" = \"exec --no-startup-id ${term}\";\n  };\nin\n{\n  options.myHome.windowmanager.add-on.terminal-emulator = {\n    default = mkOption {\n      type = types.nullOr (\n        types.enum [\n          \"alacritty\"\n          \"contour\"\n          \"kitty\"\n          \"wezterm\"\n        ]\n      );\n      default = null;\n    };\n  };\n\n  config = mkIf (cfg.default != null) (mkMerge [\n    (mkIf (cfg.default == \"alacritty\") (mkDefaultTerminal \"alacritty\"))\n    (mkIf (cfg.default == \"contour\") (mkDefaultTerminal \"contour\"))\n    (mkIf (cfg.default == \"kitty\") (mkDefaultTerminal \"kitty\"))\n    (mkIf (cfg.default == \"wezterm\") (mkDefaultTerminal \"wezterm\"))\n  ]);\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/theme/tokyonight/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.windowmanager.add-on.theme.tokyonight;\n  # Tokyonight Night color scheme\n  color = {\n    bg = \"#1a1b26\";\n    bg_dark = \"#16161e\";\n    bg_highlight = \"#292e42\";\n    blue = \"#7aa2f7\";\n    blue0 = \"#3d59a1\";\n    blue1 = \"#2ac3de\";\n    blue2 = \"#0db9d7\";\n    blue5 = \"#89ddff\";\n    blue6 = \"#b4f9f8\";\n    blue7 = \"#394b70\";\n    comment = \"#565f89\";\n    cyan = \"#7dcfff\";\n    dark3 = \"#545c7e\";\n    dark5 = \"#737aa2\";\n    fg = \"#c0caf5\";\n    fg_dark = \"#a9b1d6\";\n    fg_gutter = \"#3b4261\";\n    green = \"#9ece6a\";\n    green1 = \"#73daca\";\n    green2 = \"#41a6b5\";\n    magenta = \"#bb9af7\";\n    magenta2 = \"#ff007c\";\n    orange = \"#ff9e64\";\n    purple = \"#9d7cd8\";\n    red = \"#f7768e\";\n    red1 = \"#db4b4b\";\n    teal = \"#1abc9c\";\n    terminal_black = \"#414868\";\n    yellow = \"#e0af68\";\n  };\nin\n{\n  options.myHome.windowmanager.add-on.theme.tokyonight = {\n    enable = lib.mkOption {\n      type = lib.types.bool;\n      default = false;\n      description = ''\n        Enable Tokyonight Night theme for window manager.\n\n        Only works in i3 and Sway window manager.\n      '';\n    };\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    wayland.windowManager.sway.config.colors = {\n      focused = {\n        border = color.bg;\n        background = color.bg;\n        text = color.red;\n        indicator = color.blue;\n        childBorder = color.fg_dark;\n      };\n      unfocused = {\n        border = color.bg;\n        background = color.bg;\n        text = color.fg_dark;\n        indicator = color.blue;\n        childBorder = color.fg_dark;\n      };\n      focusedInactive = {\n        border = color.bg;\n        background = color.bg;\n        text = color.fg_dark;\n        indicator = color.blue;\n        childBorder = color.fg_dark;\n      };\n      urgent = {\n        border = color.red;\n        background = color.red;\n        text = color.fg_dark;\n        indicator = color.blue;\n        childBorder = color.fg_dark;\n      };\n    };\n    xsession.windowManager.i3.config = {\n      colors = {\n        focused = {\n          border = color.bg;\n          background = color.bg;\n          text = color.red;\n          indicator = color.fg_dark;\n          childBorder = color.bg;\n        };\n        unfocused = {\n          border = color.bg;\n          background = color.bg;\n          text = color.fg_dark;\n          indicator = color.fg_dark;\n          childBorder = color.bg;\n        };\n        focusedInactive = {\n          border = color.bg;\n          background = color.bg;\n          text = color.fg_dark;\n          indicator = color.fg_dark;\n          childBorder = color.bg;\n        };\n        urgent = {\n          border = color.red;\n          background = color.red;\n          text = color.fg;\n          indicator = color.fg_dark;\n          childBorder = color.bg;\n        };\n      };\n      # TODO: use polibar instead\n      bars = [\n        {\n          trayOutput = (builtins.head (builtins.filter (m: m.primary) config.myHardware.monitors)).name;\n          statusCommand = \"py3status\";\n          fonts = {\n            names = [ \"UbuntuMono Nerd Font\" ];\n            size = 12.0;\n          };\n          position = \"top\";\n          colors = {\n            background = color.bg;\n            separator = color.fg;\n            activeWorkspace = {\n              border = color.bg;\n              background = color.bg;\n              text = color.red;\n            };\n            focusedWorkspace = {\n              border = color.bg;\n              background = color.bg;\n              text = color.red;\n            };\n            inactiveWorkspace = {\n              border = color.bg;\n              background = color.bg;\n              text = color.fg;\n            };\n            urgentWorkspace = {\n              border = color.red;\n              background = color.red;\n              text = color.fg;\n            };\n          };\n        }\n      ];\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/waybar/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  myHome = config.myHome;\n  myHardware = config.myHardware;\n  cfg = myHome.windowmanager.add-on.waybar;\n\n  leftModules = (\n    if myHome.windowmanager.sway.enable then\n      [\n        \"sway/workspaces\"\n        \"sway/mode\"\n      ]\n    else if myHome.windowmanager.hyprland.enable then\n      [\n        \"hyprland/workspaces\"\n        \"hyprland/submap\"\n      ]\n    else\n      [ ]\n  );\n  centerModules = [ \"clock\" ];\n  rightModules = [\n    \"network#down\"\n    \"network#up\"\n  ];\n\n  modulesCfg = {\n    \"network#down\" = {\n      format = \"󰁅 {bandwidthDownBits}\";\n      tooltip-format = \"{ifname} {ipaddr}\";\n      interval = 1;\n    };\n    \"network#up\" = {\n      format = \"󰁝 {bandwidthUpBits}\";\n      tooltip-format = \"{ifname} {ipaddr}\";\n      interval = 1;\n    };\n\n    clock = {\n      format = \"{:%A, %d %b %I:%M%p}\";\n      interval = 1;\n      timezone = \"Asia/Jakarta\";\n      tooltip = false;\n    };\n    \"custom/power\" = {\n      format = \"󰤆 \";\n      tooltip = true;\n      on-click = \"${pkgs.nwg-bar}/bin/nwg-bar\";\n    };\n    \"sway/workspaces\" = {\n      format = \"{value}\";\n    };\n    \"sway/mode\" = {\n      format = \"󰔡 {}\";\n      max-length = 100;\n    };\n    \"hyprland/workspaces\" = {\n      format = \"{name}\";\n      on-click = \"activate\";\n      on-scroll-up = \"${config.wayland.windowManager.hyprland.finalPackage}/bin/hyprctl dispatch workspace e+1\";\n      on-scroll-down = \"${config.wayland.windowManager.hyprland.finalPackage}/bin/hyprctl dispatch workspace e-1\";\n    };\n    \"hyprland/submap\" = {\n      format = \"󰔡 {}\";\n      max-length = 100;\n    };\n  };\nin\n{\n  options.myHome.windowmanager.add-on.waybar = {\n    enable = lib.mkEnableOption \"waybar\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.waybar = {\n      enable = true;\n      systemd.enable = true;\n      settings = builtins.map (\n        monitor:\n        {\n          output = monitor.name;\n          layer = \"top\";\n          position = \"top\";\n          margin = \"10 20 -5 20\";\n\n          modules-left = leftModules;\n          modules-center = centerModules;\n          modules-right =\n            rightModules\n            ++ (\n              if monitor.primary then\n                [\n                  \"tray\"\n                  \"custom/power\"\n                ]\n              else\n                [ ]\n            );\n        }\n        // modulesCfg\n      ) myHardware.monitors;\n\n      style = ''\n        * {\n            border: none;\n            border-radius: 0;\n            font-family: \"UbuntuMono Nerd Font\";\n            font-size: 16px;\n            font-weight: normal;\n            padding: 1px;\n        }\n\n        button {\n          min-height: 24px;\n          min-width: 16px;\n        }\n\n        window#waybar {\n            background-color: transparent;\n            color: #C0CAF5;\n            transition-property: background-color;\n            transition-duration: .5s;\n            border: 1px solid #C0CAF5;\n            border-radius: 10px;\n        }\n\n        window#waybar.hidden {\n            opacity: 0.2;\n        }\n\n        #workspaces button {\n            color: #C0CAF5;\n            padding: 0 3px;\n            border-radius: 5px;\n        }\n\n        #workspaces button.focused {\n            color: #7AA2F7;\n        }\n\n        #workspaces button.active {\n            color: #7AA2F7;\n        }\n\n        #workspaces button.urgent {\n            color: #F7768E;\n        }\n\n        #mode {\n            color: #F7768E;\n            padding-left: 2px;\n        }\n\n        #submap {\n            color: #F7768E;\n            padding-left: 2px;\n        }\n\n        #clock {\n            color: #BB9AF7;\n        }\n\n        #network.down {\n            color: #9ECE6A;\n            padding-right: 8px;\n        }\n\n        #network.up {\n            color: #7AA2F7;\n            padding-right: 8px;\n        }\n\n        #tray {\n            color: #C0CAF5;\n            padding-right: 8px;\n        }\n\n        #custom-power {\n            color: #F7768E;\n            padding-right: 8px;\n        }\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/add-on/xdg/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.myHome.windowmanager.add-on.xdg;\nin\n{\n  options.myHome.windowmanager.add-on.xdg = {\n    enable = lib.mkEnableOption \"xdg\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    xdg.userDirs = {\n      enable = true;\n      createDirectories = true;\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/hyprland/config/appearances.nix",
    "content": "{\n  general = {\n    gaps_in = 5;\n    gaps_out = 20;\n    \"col.inactive_border\" = \"rgb(A9B1D6)\";\n    \"col.active_border\" = \"rgb(A9B1D6)\";\n    layout = \"master\";\n  };\n  decoration = {\n    rounding = 10;\n    inactive_opacity = 0.7;\n    dim_inactive = true;\n    dim_strength = 0.3;\n  };\n  master = {\n    new_on_top = true;\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/hyprland/config/default.nix",
    "content": "{\n  config,\n  pkgs,\n  lib,\n  ...\n}:\nlet\n  myHardware = config.myHardware;\n  resizeMode = \"Resize: (h/Left) width- (j/Down) height-, (k/Up) height+, (l/Right) width+\";\nin\n{\n  # Submaps are impossible to be defined in settings\n  extraConfig = ''\n    # Resize window\n    submap = ${resizeMode}\n    binde = , h, resizeactive, -10 0\n    binde = , j, resizeactive, 0 10\n    binde = , k, resizeactive, 0 -10\n    binde = , l, resizeactive, 10 0\n    # Or use arrow keys\n    binde = , left, resizeactive, -10 0\n    binde = , down, resizeactive, 0 10\n    binde = , up, resizeactive, 0 -10\n    binde = , right, resizeactive, 10 0\n    # Return to normal mode\n    bind = , escape, submap, reset\n    submap = reset\n  '';\n\n  settings =\n    {\n      # Monitor placement\n      # [ \"<name>,<width>x<height>,<x>x<y>,1\" ... ]\n      monitor = builtins.map (\n        m: \"${m.name},${toString m.width}x${toString m.height},${toString m.x}x${toString m.y},1\"\n      ) myHardware.monitors;\n\n      # Wallpaper\n      # [ \"swaybg -o <name> -i <path>\" ... ]\n      exec-once = builtins.map (m: \"${pkgs.swaybg}/bin/swaybg -o ${m.name} -i ${m.wallpaper}\") (\n        builtins.filter (m: m.wallpaper != null) myHardware.monitors\n      );\n\n      # General settings\n      general = {\n        resize_on_border = true;\n      };\n      input = {\n        numlock_by_default = true;\n        follow_mouse = 1;\n      };\n      misc = {\n        mouse_move_enables_dpms = true;\n        key_press_enables_dpms = true;\n      };\n      env = lib.mkIf (myHardware.gpuDriver == \"nvidia\") [\n        \"LIBVA_DRIVER_NAME,nvidia\"\n        \"XDG_SESSION_TYPE,wayland\"\n        \"GBM_BACKEND,nvidia-drm\"\n        \"__GLX_VENDOR_LIBRARY_NAME,nvidia\"\n        \"WLR_NO_HARDWARE_CURSORS,1\"\n      ];\n    }\n    // (import ./keybindings.nix { inherit config resizeMode pkgs; })\n    // (import ./ws-outputs.nix { inherit config; })\n    // (import ./window-rules.nix)\n    // (import ./appearances.nix);\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/hyprland/config/keybindings.nix",
    "content": "{\n  config,\n  resizeMode,\n  pkgs,\n}:\n{\n  bindm = [\n    # Super+mouse to drag/resize floating windows\n    \"SUPER, mouse:272, movewindow\"\n    \"SUPER, mouse:273, resizewindow\"\n  ];\n  bind = [\n    # Kill focused window\n    \"SUPER, F4, killactive,\"\n\n    # Change focus around\n    \"SUPER, h, movefocus, l\"\n    \"SUPER, j, movefocus, d\"\n    \"SUPER, k, movefocus, u\"\n    \"SUPER, l, movefocus, r\"\n    # Or use arrow keys\n    \"SUPER, left, movefocus, l\"\n    \"SUPER, down, movefocus, d\"\n    \"SUPER, up, movefocus, u\"\n    \"SUPER, right, movefocus, r\"\n\n    # Move the focused window with the same, but add Shift\n    \"SUPER SHIFT, h, movewindow, l\"\n    \"SUPER SHIFT, j, movewindow, d\"\n    \"SUPER SHIFT, k, movewindow, u\"\n    \"SUPER SHIFT, l, movewindow, r\"\n    # Or use arrow keys\n    \"SUPER SHIFT, left, movewindow, l\"\n    \"SUPER SHIFT, down, movewindow, d\"\n    \"SUPER SHIFT, up, movewindow, u\"\n    \"SUPER SHIFT, right, movewindow, r\"\n\n    # Next/previous workspace\n    \"SUPER, tab, workspace, e+1\"\n    \"SUPER SHIFT, tab, workspace, e-1\"\n\n    # Toggle fullscreen mode\n    \"SUPER SHIFT, f, fullscreen, 1\"\n\n    # Toggle floating mode\n    \"SUPER SHIFT, space, togglefloating,\"\n\n    # Move the currently focused window to the scratchpad\n    \"SUPER SHIFT, p, movetoworkspacesilent, special\"\n\n    # Show the next scratchpad window or hide the focused scratchpad window\n    \"SUPER, p, togglespecialworkspace,\"\n\n    # Switch to workspace\n    \"SUPER, 1, workspace, 1\"\n    \"SUPER, 2, workspace, 2\"\n    \"SUPER, 3, workspace, 3\"\n    \"SUPER, 4, workspace, 4\"\n    \"SUPER, 5, workspace, 5\"\n    \"SUPER, 6, workspace, 6\"\n    \"SUPER, 7, workspace, 7\"\n    \"SUPER, 8, workspace, 8\"\n    \"SUPER, 9, workspace, 9\"\n    \"SUPER, 0, workspace, 10\"\n\n    # Move focused container to workspace\n    \"SUPER SHIFT, 1, movetoworkspace, 1\"\n    \"SUPER SHIFT, 2, movetoworkspace, 2\"\n    \"SUPER SHIFT, 3, movetoworkspace, 3\"\n    \"SUPER SHIFT, 4, movetoworkspace, 4\"\n    \"SUPER SHIFT, 5, movetoworkspace, 5\"\n    \"SUPER SHIFT, 6, movetoworkspace, 6\"\n    \"SUPER SHIFT, 7, movetoworkspace, 7\"\n    \"SUPER SHIFT, 8, movetoworkspace, 8\"\n    \"SUPER SHIFT, 9, movetoworkspace, 9\"\n    \"SUPER SHIFT, 0, movetoworkspace, 10\"\n\n    # Reload the configuration file\n    \"SUPER SHIFT, c, execr, hyprctl reload\"\n\n    # Most used applications\n    \"SUPER, f, exec, thunar\" # use system provided thunar package\n    \"SUPER, c, exec, ${pkgs.gnome-calculator}/bin/gnome-calculator\"\n\n    # Rofi as dmenu replacement\n    \"SUPER, grave, exec, ${config.programs.rofi.package}/bin/rofi -show drun\"\n    \"SUPER, Escape, exec, ${config.programs.rofi.package}/bin/rofi -show drun\"\n\n    # Modes\n    \"SUPER SHIFT, r, submap, ${resizeMode}\"\n\n    # Logout menu\n    \"CTRL SUPER, delete, exec, ${pkgs.nwg-bar}/bin/nwg-bar\"\n  ];\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/hyprland/config/window-rules.nix",
    "content": "{\n  windowrulev2 = [\n    # Window floating and layout\n    \"float, class:^(pavucontrol)$\"\n    \"float, class:^(thunar)$\"\n    \"float, class:^(obs)$\"\n    \"float, class:^(gnome-calculator|org\\.gnome\\.Calculator)$\"\n    \"float, class:^(pamac-manager)$\"\n    \"float, class:^(eog)$\"\n    \"float, class:^(blueman-manager)$\"\n    \"float, class:^(nm-connection-editor)$\"\n    \"float, class:^(rhythmbox)$\"\n    \"size 1000 640, class:^(rhythmbox)$\"\n    \"float, title:^(File Transfer*)$\"\n    \"float, title:^(Lxappearance)$\"\n    \"pin, title:^(Lxappearance)$\"\n    \"float, title:^(VirtualBox)$\"\n    \"float, title:^(VirtualBox)$\"\n    \"idleinhibit focus, fullscreen:1\"\n    \"idleinhibit focus, class:^(org.libretro.RetroArch)$\"\n    \"idleinhibit focus, class:^(com.github.iwalton3.jellyfin-media-player)$\"\n\n    # Window placement\n    \"workspace silent special, title:^(updater)$, class:^(kitty)$\"\n    \"workspace 5, class:^(smplayer)$\"\n    \"workspace 5, class:^(totem)$\"\n    \"workspace 5, class:^(rhythmbox)$\"\n    \"workspace 6, class:^(org.libretro.RetroArch)$\"\n    \"workspace 7, class:^(Gimp)$\"\n    \"workspace 9, class:^(obs)$\"\n    \"workspace 10, class:^(VirtualBox)$\"\n    \"noborder, title:^(Syncthing Tray( \\(.*\\))?)$\"\n    \"size 550 400, title:^(Syncthing Tray( \\(.*\\))?)$\"\n    \"move 1365 30, title:^(Syncthing Tray( \\(.*\\))?)$\"\n    \"move 1465 30, title:^(Nextcloud)$, class:^(Nextcloud)$\"\n  ];\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/hyprland/config/ws-outputs.nix",
    "content": "{ config }:\nlet\n  strBool = b: if b then \"true\" else \"false\";\n  mkWorkspaces =\n    monitors:\n    builtins.concatMap (\n      mon:\n      builtins.map (\n        ws: \"${toString ws}, monitor:${mon.name}, default:${strBool (ws == builtins.head mon.workspaces)}\"\n      ) mon.workspaces\n    ) monitors;\nin\n{\n  # [ \"<ws>, monitor:<name>, default:<bool>\" \"...\" ]\n  workspace = mkWorkspaces config.myHardware.monitors;\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/hyprland/default.nix",
    "content": "{\n  config,\n  osConfig,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  myHardware = config.myHardware;\n  cfg = config.myHome.windowmanager.hyprland;\n  systemEnabled = lib.myLib.systemEnabled \"mySystem.windowmanager.hyprland.enable\" osConfig;\nin\n{\n  options.myHome.windowmanager.hyprland = {\n    enable = lib.mkEnableOption \"Hyprland window manager\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    myHome = {\n      isWayland = true;\n\n      windowmanager.add-on = {\n        blueman-applet.enable = true;\n        dunst.enable = true;\n        gtk-theme.enable = true;\n        nm-applet.enable = true;\n        nwg-bar.enable = true;\n        pasystray.enable = true;\n        rofi.enable = true;\n        swayidle.enable = true;\n        waybar.enable = true;\n        screenshotter.enable = true;\n        terminal-emulator.default = \"alacritty\";\n        xdg.enable = true;\n      };\n    };\n\n    warnings = lib.mkIf (!systemEnabled) [\n      ''\n        You have enabled Hyprland home-manager module but not the NixOS system module.\n        Some things might not work properly.\n      ''\n    ];\n\n    assertions = [\n      {\n        assertion = builtins.length myHardware.monitors > 0;\n        message = ''\n          At least one monitor in the `config.myHardware.monitors` is\n          needed to use Hyprland home-manager module.\n        '';\n      }\n    ];\n\n    wayland.windowManager.hyprland = {\n      enable = true;\n    } // (import ./config { inherit config lib pkgs; });\n\n    systemd.user.startServices = \"sd-switch\";\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/i3/config/commands.nix",
    "content": "[\n  {\n    criteria = {\n      window_role = \"pop-up\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      window_role = \"prefwindow\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"notify\";\n    };\n    command = \"floating enable border pixel 1\";\n  }\n  {\n    criteria = {\n      title = \"File Transfer*\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"Galculator\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"Lxappearance\";\n    };\n    command = \"floating enable sticky enable border normal\";\n  }\n  {\n    criteria = {\n      class = \"Oblogout\";\n    };\n    command = \"fullscreen enable\";\n  }\n  {\n    criteria = {\n      class = \"Pavucontrol\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"VirtualBox\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"Skype\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"(?i)nvidia-settings\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"smplayer\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"Eog\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"Rhythmbox\";\n    };\n    command = \"floating enable resize set 1000 640\";\n  }\n  {\n    criteria = {\n      class = \"obs\";\n    };\n    command = \"floating enable\";\n  }\n]\n"
  },
  {
    "path": "home/_modules/windowmanager/i3/config/default.nix",
    "content": "{\n  config,\n  pkgs,\n  lib,\n}:\nlet\n  myHardware = config.myHardware;\n  modes = {\n    resize = \"Resize: (h/Left) width-, (j/Down) height-, (k/Up) height+, (l/Right) width+\";\n    gaps = \"Gaps (o) outer, (i) inner\";\n    gapsOuter = \"Outer Gaps (k/Up) grow locally, (K/Shift+Up) grow globally\";\n    gapsInner = \"Inner Gaps (k/Up) grow locally, (K/Shift+Up) grow globally\";\n    system = \"System (l) lock, (e) logout, (r) reboot, (s) shutdown, (f) UEFI\";\n  };\nin\n{\n  floating.modifier = \"Mod4\";\n  focus = {\n    wrapping = \"yes\";\n    followMouse = true;\n    newWindow = \"urgent\";\n  };\n  gaps = {\n    inner = 10;\n    outer = 5;\n    smartGaps = true;\n  };\n\n  # [ { output = \"<xname>\"; workspace = \"<ws>\"; } {...} ]\n  workspaceOutputAssign = builtins.concatMap (\n    mon:\n    builtins.map (ws: {\n      workspace = (toString ws);\n      output = mon.xname;\n    }) mon.workspaces\n  ) myHardware.monitors;\n\n  keybindings = import ./keybindings.nix { inherit config pkgs modes; };\n  modes = import ./modes.nix { inherit modes pkgs; };\n  window.commands = import ./commands.nix;\n  startup = import ./startups.nix { inherit config pkgs lib; };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/i3/config/keybindings.nix",
    "content": "{\n  config,\n  pkgs,\n  modes,\n}:\nlet\n  super = \"Mod4\";\nin\n{\n  # kill focused window\n  \"${super}+F4\" = \"kill\";\n  # change focus\n  \"${super}+h\" = \"focus left\";\n  \"${super}+j\" = \"focus down\";\n  \"${super}+k\" = \"focus up\";\n  \"${super}+l\" = \"focus right\";\n  # alternatively, you can use the cursor keys\n  \"${super}+Left\" = \"focus left\";\n  \"${super}+Down\" = \"focus down\";\n  \"${super}+Up\" = \"focus up\";\n  \"${super}+Right\" = \"focus right\";\n  # move focused window\n  \"${super}+Shift+h\" = \"move left\";\n  \"${super}+Shift+j\" = \"move down\";\n  \"${super}+Shift+k\" = \"move up\";\n  \"${super}+Shift+l\" = \"move right\";\n  # alternatively, you can use the cursor keys\n  \"${super}+Shift+Left\" = \"move left\";\n  \"${super}+Shift+Down\" = \"move down\";\n  \"${super}+Shift+Up\" = \"move up\";\n  \"${super}+Shift+Right\" = \"move right\";\n  # split in orientation\n  \"${super}+Control+h\" = \"split h; exec notify-send 'tile horizontally'\";\n  \"${super}+Control+v\" = \"split v; exec notify-send 'tile vertically'\";\n  \"${super}+Control+q\" = \"split toggle\";\n  # next/previous workspace\n  \"${super}+Tab\" = \"workspace next\";\n  \"${super}+Shift+Tab\" = \"workspace prev\";\n  # toggle window border\n  \"${super}+Control+t\" = \"border toggle\";\n  # enter fullscreen mode for the focused container\n  \"${super}+Control+f\" = \"fullscreen toggle\";\n  # toggle gaps on and off, please uncomment if you don't have i3-gaps installed\n  \"${super}+Control+g\" = ''\n    exec --no-startup-id \"if [ `i3-msg -t get_tree | grep -Po \\\n      '.*\\\\\"gaps\\\\\":{\\\\\"inner\\\\\":\\K(-|)[0-9]+(?=.*\\\\\"focused\\\\\":true)'` -eq 0 ]; then \\\n              i3-msg gaps inner current set 0; i3-msg gaps outer current set 0; \\\n      else \\\n              i3-msg gaps inner current set 10; i3-msg gaps outer current set 5; \\\n      fi\n  '';\n  # change container layout (stacked, tabbed, toggle split)\n  \"${super}+Control+s\" = \"layout stacking\";\n  \"${super}+Control+w\" = \"layout tabbed\";\n  \"${super}+Control+e\" = \"layout toggle split\";\n  # toggle floating mode\n  \"${super}+Shift+space\" = \"floating toggle\";\n  # change focus between tiling / floating windows\n  \"${super}+space\" = \"focus mode_toggle\";\n  # switch to workspace\n  \"${super}+1\" = \"workspace 1\";\n  \"${super}+2\" = \"workspace 2\";\n  \"${super}+3\" = \"workspace 3\";\n  \"${super}+4\" = \"workspace 4\";\n  \"${super}+5\" = \"workspace 5\";\n  \"${super}+6\" = \"workspace 6\";\n  \"${super}+7\" = \"workspace 7\";\n  \"${super}+8\" = \"workspace 8\";\n  \"${super}+9\" = \"workspace 9\";\n  \"${super}+0\" = \"workspace 10\";\n  # move focused container to workspace\n  \"${super}+Shift+1\" = \"move container to workspace 1\";\n  \"${super}+Shift+2\" = \"move container to workspace 2\";\n  \"${super}+Shift+3\" = \"move container to workspace 3\";\n  \"${super}+Shift+4\" = \"move container to workspace 4\";\n  \"${super}+Shift+5\" = \"move container to workspace 5\";\n  \"${super}+Shift+6\" = \"move container to workspace 6\";\n  \"${super}+Shift+7\" = \"move container to workspace 7\";\n  \"${super}+Shift+8\" = \"move container to workspace 8\";\n  \"${super}+Shift+9\" = \"move container to workspace 9\";\n  \"${super}+Shift+0\" = \"move container to workspace 10\";\n  # reload the configuration file\n  \"${super}+Shift+c\" = \"reload\";\n  # exit i3 (logs you out of your X session)\n  \"${super}+Shift+e\" = \"exec \\\"i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'\\\"\";\n  # most used application shortcuts\n  \"${super}+f\" = \"exec --no-startup-id thunar\"; # use system provided thunar package\n  \"${super}+c\" = \"exec ${pkgs.gnome-calculator}/bin/gnome-calculator\";\n  # rofi as dmenu replacement\n  \"${super}+grave\" = \"exec ${config.programs.rofi.package}/bin/rofi -show drun\";\n  \"${super}+Escape\" = \"exec ${config.programs.rofi.package}/bin/rofi -show drun\";\n  # modes\n  \"${super}+Shift+r\" = \"mode \\\"${modes.resize}\\\"\";\n  \"${super}+Shift+g\" = \"mode \\\"${modes.gaps}\\\"\";\n  \"${super}+Control+Delete\" = \"mode \\\"${modes.system}\\\"\";\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/i3/config/modes.nix",
    "content": "{ modes, pkgs }:\n{\n  # resize window (you can also use the mouse for that)\n  ${modes.resize} = {\n    h = \"resize shrink width 10 px or 10 ppt\";\n    j = \"resize shrink height 10 px or 10 ppt\";\n    k = \"resize grow height 10 px or 10 ppt\";\n    l = \"resize grow width 10 px or 10 ppt\";\n    # same bindings, but for the arrow keys\n    Left = \"resize shrink width 10 px or 10 ppt\";\n    Down = \"resize shrink height 10 px or 10 ppt\";\n    Up = \"resize grow height 10 px or 10 ppt\";\n    Right = \"resize grow width 10 px or 10 ppt\";\n    # back to normal: Enter or Escape\n    Return = \"mode default\";\n    Escape = \"mode default\";\n  };\n  # resize gaps\n  ${modes.gaps} = {\n    o = \"mode \\\"$mode_gaps_outer\\\"\";\n    i = \"mode \\\"$mode_gaps_inner\\\"\";\n    Return = \"mode \\\"${modes.gaps}\\\"\";\n    Escape = \"mode default\";\n  };\n  ${modes.gapsOuter} = {\n    k = \"gaps outer current plus 5\";\n    j = \"gaps outer current minus 5\";\n    # same bindings, but for the arrow keys\n    Up = \"gaps outer current plus 5\";\n    Down = \"gaps outer current minus 5\";\n    \"Shift+k\" = \"gaps outer all plus 5\";\n    \"Shift+j\" = \"gaps outer all minus 5\";\n    # same keybindings, but for the arrow keys\n    \"Shift+Up\" = \"gaps outer all plus 5\";\n    \"Shift+Down\" = \"gaps outer all minus 5\";\n    Return = \"mode \\\"${modes.gaps}\\\"\";\n    Escape = \"mode default\";\n  };\n  ${modes.gapsInner} = {\n    k = \"gaps inner current plus 5\";\n    j = \"gaps inner current minus 5\";\n    # same bindings, but for the arrow keys\n    Up = \"gaps inner current plus 5\";\n    Down = \"gaps inner current minus 5\";\n    \"Shift+k\" = \"gaps inner all plus 5\";\n    \"Shift+j\" = \"gaps inner all minus 5\";\n    # same keybindings, but for the arrow keys\n    \"Shift+Up\" = \"gaps inner all plus 5\";\n    \"Shift+Down\" = \"gaps inner all minus 5\";\n    Return = \"mode \\\"${modes.gaps}\\\"\";\n    Escape = \"mode default\";\n  };\n  # Press Ctrl+Alt+Delete will show log out menu\n  ${modes.system} = {\n    l = \"exec --no-startup-id ${pkgs.i3lock}/bin/i3lock && sleep 1, mode default\";\n    e = \"exec --no-startup-id i3-msg exit, mode default\";\n    r = \"exec --no-startup-id systemctl reboot, mode default\";\n    s = \"exec --no-startup-id systemctl poweroff -i, mode default\";\n    f = \"exec --no-startup-id systemctl reboot --firmware-setup, mode default\";\n    # back to normal: Enter or Escape\n    Return = \"mode default\";\n    Escape = \"mode default\";\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/i3/config/startups.nix",
    "content": "{\n  config,\n  pkgs,\n  lib,\n}:\nlet\n  wallpapers = builtins.map (m: m.wallpaper) (\n    builtins.filter (m: m.wallpaper != null) config.myHardware.monitors\n  );\nin\n[\n  {\n    # \"xrandr --pos <x>x<y> --mode <width>x<height> ...\"\n    command =\n      \"${pkgs.xorg.xrandr}/bin/xrandr\"\n      + (lib.concatMapStrings (\n        mon:\n        \" --output ${mon.xname} --pos ${toString mon.x}x${toString mon.y} --mode ${toString mon.width}x${toString mon.height}\"\n        + (lib.optionalString (mon.primary) \" --primary\")\n      ) config.myHardware.monitors);\n    notification = false;\n    always = true;\n  }\n  {\n    # \"feh --bg-fill <path> ...\"\n    command = \"${pkgs.feh}/bin/feh\" + (lib.concatMapStrings (wp: \" --bg-fill ${wp}\")) wallpapers;\n    notification = false;\n    always = true;\n  }\n  {\n    command = \"${pkgs.xfce.xfce4-power-manager}/bin/xfce4-power-manager\";\n    notification = false;\n  }\n  {\n    command = \"${pkgs.numlockx}/bin/numlockx\";\n    notification = false;\n  }\n]\n"
  },
  {
    "path": "home/_modules/windowmanager/i3/default.nix",
    "content": "{\n  config,\n  lib,\n  osConfig,\n  pkgs,\n  ...\n}:\nlet\n  myHardware = config.myHardware;\n  cfg = config.myHome.windowmanager.i3;\n  systemEnabled = lib.myLib.systemEnabled \"mySystem.windowmanager.i3.enable\" osConfig;\nin\n{\n  options.myHome.windowmanager.i3 = {\n    enable = lib.mkEnableOption \"i3\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    myHome = {\n      isWayland = false;\n\n      windowmanager.add-on = {\n        blueman-applet.enable = true;\n        dunst.enable = true;\n        gtk-theme.enable = true;\n        nm-applet.enable = true;\n        rofi.enable = true;\n        theme.tokyonight.enable = true;\n        screenshotter.enable = true;\n        pasystray.enable = true;\n        picom.enable = true;\n        py3status.enable = true;\n        terminal-emulator.default = \"wezterm\";\n        xdg.enable = true;\n      };\n    };\n\n    warnings = lib.mkIf (!systemEnabled) [\n      ''\n        You have enabled i3 home-manager module but not the NixOS system module.\n        Some things might not work properly.\n      ''\n    ];\n\n    assertions = [\n      {\n        assertion = builtins.length myHardware.monitors > 0;\n        message = ''\n          At least one monitor in the `config.myHardware.monitors` is\n          needed to use i3 home-manager module.\n        '';\n      }\n    ];\n\n    xsession.windowManager.i3 = {\n      enable = true;\n      package = pkgs.i3-gaps;\n      config = import ./config { inherit config pkgs lib; };\n    };\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/sway/config/commands.nix",
    "content": "[\n  # Window title formatting\n  {\n    criteria = {\n      class = \".*\";\n    };\n    command = \"title_format \\\"<b> %title</b>\\\"\";\n  }\n  # window floating and layout\n  {\n    criteria = {\n      window_role = \"pop-up\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      window_role = \"prefwindow\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"pavucontrol\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"thunar\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"obs\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"(gnome-calculator)|(org\\.gnome\\.Calculator)\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"eog\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"blueman-manager\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"nm-connection-editor\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      app_id = \"rhythmbox\";\n    };\n    command = \"floating enable, resize set 1000 640\";\n  }\n  {\n    criteria = {\n      title = \"File Transfer*\";\n    };\n    command = \"floating enable\";\n  }\n  {\n    criteria = {\n      class = \"Lxappearance\";\n    };\n    command = \"floating enable, sticky enable\";\n  }\n  {\n    criteria = {\n      class = \"VirtualBox\";\n    };\n    command = \"floating enable\";\n  }\n\n  # window placement\n  {\n    criteria = {\n      app_id = \"retroarch\";\n    };\n    command = \"move container to workspace 6, focus\";\n  }\n  {\n    criteria = {\n      class = \"Gimp\";\n    };\n    command = \"move container to workspace 7, focus\";\n  }\n  {\n    criteria = {\n      app_id = \"obs\";\n    };\n    command = \"move container to workspace 9, focus\";\n  }\n  {\n    criteria = {\n      class = \"VirtualBox\";\n    };\n    command = \"move container to workspace 10, focus\";\n  }\n  {\n    criteria = {\n      title = \"^Syncthing Tray( \\(.*\\))?$\";\n    };\n    command = \"floating enable, border none, resize set 550 400, move position 1350 0\";\n  }\n]\n"
  },
  {
    "path": "home/_modules/windowmanager/sway/config/default.nix",
    "content": "{\n  config,\n  pkgs,\n  lib,\n}:\nlet\n  myHardware = config.myHardware;\n  modes = {\n    resize = \"Resize: (h/Left) width-, (j/Down) height-, (k/Up) height+, (l/Right) width+\";\n    gaps = \"Gaps (o) outer, (i) inner\";\n    gapsOuter = \"Outer Gaps (k/Up) grow locally, (K/Shift+Up) grow globally\";\n    gapsInner = \"Inner Gaps (k/Up) grow locally, (K/Shift+Up) grow globally\";\n  };\nin\n{\n  floating = {\n    modifier = \"Mod4\";\n    titlebar = false;\n    border = 0;\n  };\n  window.border = 1;\n  fonts = {\n    names = [ \"pango:UbuntuMono Nerd Font\" ];\n    size = 12.0;\n  };\n  focus = {\n    wrapping = \"yes\";\n    followMouse = true;\n    newWindow = \"smart\";\n  };\n  gaps.inner = 10;\n  bars = [ ];\n\n  # { <name> = { bg = \"<path> fill\"; pos = \"<x> <y>\"; }; ... = {...}; }\n  output = builtins.listToAttrs (\n    builtins.map (m: {\n      name = m.name;\n      value = {\n        bg = \"${m.wallpaper} fill\";\n        pos = \"${toString m.x} ${toString m.y}\";\n      };\n    }) (builtins.filter (m: m.wallpaper != null) myHardware.monitors)\n  );\n\n  # [ { output = \"<xname>\"; workspace = \"<ws>\"; } {...} ]\n  workspaceOutputAssign = builtins.concatMap (\n    mon:\n    builtins.map (ws: {\n      workspace = (toString ws);\n      output = mon.name;\n    }) mon.workspaces\n  ) myHardware.monitors;\n\n  keybindings = import ./keybindings.nix { inherit config modes pkgs; };\n  modes = import ./modes.nix { inherit modes pkgs; };\n  window.commands = import ./commands.nix;\n  startup = import ./startups.nix { inherit pkgs; };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/sway/config/keybindings.nix",
    "content": "{\n  config,\n  modes,\n  pkgs,\n}:\nlet\n  super = \"Mod4\";\nin\n{\n  # Kill focused window\n  \"${super}+F4\" = \"kill\";\n  # Change focus around\n  \"${super}+h\" = \"focus left\";\n  \"${super}+j\" = \"focus down\";\n  \"${super}+k\" = \"focus up\";\n  \"${super}+l\" = \"focus right\";\n  # Or use arrow keys\n  \"${super}+Left\" = \"focus left\";\n  \"${super}+Down\" = \"focus down\";\n  \"${super}+Up\" = \"focus up\";\n  \"${super}+Right\" = \"focus right\";\n  # Move the focused window with the same, but add Shift\n  \"${super}+Shift+h\" = \"move left\";\n  \"${super}+Shift+j\" = \"move down\";\n  \"${super}+Shift+k\" = \"move up\";\n  \"${super}+Shift+l\" = \"move right\";\n  # Or use arrow keys\n  \"${super}+Shift+Left\" = \"move left\";\n  \"${super}+Shift+Down\" = \"move down\";\n  \"${super}+Shift+Up\" = \"move up\";\n  \"${super}+Shift+Right\" = \"move right\";\n  # Split current container\n  \"${super}+Control+h\" = \"splith; exec notify-send 'tile horizontally'\";\n  \"${super}+Control+v\" = \"splitv; exec notify-send 'tile vertically'\";\n  \"${super}+Control+q\" = \"split toggle\";\n  # Next/previous workspace\n  \"${super}+Tab\" = \"workspace next\";\n  \"${super}+Shift+Tab\" = \"workspace prev\";\n  # Toggle window border\n  \"${super}+Control+t\" = \"border toggle\";\n  # toggle gaps on and off\n  \"${super}+Control+g\" = ''\n    exec \"if [ `swaymsg -t get_tree | grep -Po \\\n      '.*\\\\\"gaps\\\\\":{\\\\\"inner\\\\\":\\K(-|)[0-9]+(?=.*\\\\\"focused\\\\\":true)'` -eq 0 ]; then \\\n              swaymsg gaps inner current set 0; swaymsg gaps outer current set 0; \\\n      else \\\n              swaymsg gaps inner current set 10; swaymsg gaps outer current set 5; \\\n      fi\n  '';\n  # Toggle fullscreen mode\n  \"${super}+Shift+f\" = \"fullscreen toggle\";\n  # Change container layout\n  \"${super}+Control+s\" = \"layout stacking\";\n  \"${super}+Control+w\" = \"layout tabbed\";\n  \"${super}+Control+e\" = \"layout toggle split\";\n  # Toggle floating mode\n  \"${super}+Shift+space\" = \"floating toggle\";\n  # Swap focus between tiling / floating windows\n  \"${super}+space\" = \"focus mode_toggle\";\n  # Focus the parent container\n  #\"${super}+a\" = \"focus parent\";\n  # Focus the child container\n  #\"${super}+d\" = \"focus child\";\n  # Move the currently focused window to the scratchpad\n  \"${super}+Shift+p\" = \"move scratchpad\";\n  # Show the next scratchpad window or hide the focused scratchpad window.\n  \"${super}+p\" = \"scratchpad show\";\n  # Switch to workspace\n  \"${super}+1\" = \"workspace 1\";\n  \"${super}+2\" = \"workspace 2\";\n  \"${super}+3\" = \"workspace 3\";\n  \"${super}+4\" = \"workspace 4\";\n  \"${super}+5\" = \"workspace 5\";\n  \"${super}+6\" = \"workspace 6\";\n  \"${super}+7\" = \"workspace 7\";\n  \"${super}+8\" = \"workspace 8\";\n  \"${super}+9\" = \"workspace 9\";\n  \"${super}+0\" = \"workspace 10\";\n  # Move focused container to workspace\n  \"${super}+Shift+1\" = \"move container to workspace 1\";\n  \"${super}+Shift+2\" = \"move container to workspace 2\";\n  \"${super}+Shift+3\" = \"move container to workspace 3\";\n  \"${super}+Shift+4\" = \"move container to workspace 4\";\n  \"${super}+Shift+5\" = \"move container to workspace 5\";\n  \"${super}+Shift+6\" = \"move container to workspace 6\";\n  \"${super}+Shift+7\" = \"move container to workspace 7\";\n  \"${super}+Shift+8\" = \"move container to workspace 8\";\n  \"${super}+Shift+9\" = \"move container to workspace 9\";\n  \"${super}+Shift+0\" = \"move container to workspace 10\";\n  # Reload the configuration file\n  \"${super}+Shift+c\" = \"reload\";\n  # Exit sway (logs you out of your Wayland session)\n  \"${super}+Shift+e\" = \"exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'\";\n  # Most used applications\n  \"${super}+f\" = \"exec thunar\"; # use system provided thunar package\n  \"${super}+c\" = \"exec ${pkgs.gnome-calculator}/bin/gnome-calculator\";\n  # Rofi as dmenu replacement\n  \"${super}+grave\" = \"exec ${config.programs.rofi.package}/bin/rofi -show drun\";\n  \"${super}+Escape\" = \"exec ${config.programs.rofi.package}/bin/rofi -show drun\";\n  # Modes\n  \"${super}+Shift+r\" = \"mode \\\"${modes.resize}\\\"\";\n  \"${super}+Shift+g\" = \"mode \\\"${modes.gaps}\\\"\";\n  # Logout menu\n  \"${super}+Control+Delete\" = \"exec ${pkgs.nwg-bar}/bin/nwg-bar\";\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/sway/config/modes.nix",
    "content": "{ modes, pkgs }:\n{\n  # Resize  window\n  ${modes.resize} = {\n    h = \"resize shrink width\";\n    j = \"resize grow height\";\n    k = \"resize shrink height\";\n    l = \"resize grow width\";\n    # Or use arrow keys\n    Left = \"resize shrink width\";\n    Down = \"resize grow height\";\n    Up = \"resize shrink height\";\n    Right = \"resize grow width\";\n    # Return to normal mode\n    Return = \"mode default\";\n    Escape = \"mode default\";\n  };\n  # Resize gaps\n  ${modes.gaps} = {\n    o = \"mode \\\"$gaps_outer\\\"\";\n    i = \"mode \\\"$gaps_inner\\\"\";\n    # Return to normal mode\n    Return = \"mode default\";\n    Escape = \"mode default\";\n  };\n  ${modes.gapsOuter} = {\n    k = \"gaps outer current plus 5\";\n    j = \"gaps outer current minus 5\";\n    \"Shift+k\" = \"gaps outer all plus 5\";\n    \"Shift+j\" = \"gaps outer all minus 5\";\n    # Or use arrow keys\n    Up = \"gaps outer current plus 5\";\n    Down = \"gaps outer current minus 5\";\n    \"Shift+Up\" = \"gaps outer all plus 5\";\n    \"Shift+Down\" = \"gaps outer all minus 5\";\n    # Return to gaps/normal mode\n    Return = \"mode \\\"${modes.gaps}\\\"\";\n    Escape = \"mode default\";\n  };\n  ${modes.gapsInner} = {\n    k = \"gaps inner current plus 5\";\n    j = \"gaps inner current minus 5\";\n    \"Shift+k\" = \"gaps inner all plus 5\";\n    \"Shift+j\" = \"gaps inner all minus 5\";\n    # Or use arrow keys\n    Up = \"gaps inner current plus 5\";\n    Down = \"gaps inner current minus 5\";\n    \"Shift+Up\" = \"gaps inner all plus 5\";\n    \"Shift+Down\" = \"gaps inner all minus 5\";\n    # Return to gaps/normal mode\n    Return = \"mode \\\"${modes.gaps}\\\"\";\n    Escape = \"mode default\";\n  };\n}\n"
  },
  {
    "path": "home/_modules/windowmanager/sway/config/startups.nix",
    "content": "{ pkgs }:\n[\n  {\n    command = \"${pkgs.sway-contrib.inactive-windows-transparency}/bin/inactive-windows-transparency.py -o 0.9\";\n    always = true;\n  }\n]\n"
  },
  {
    "path": "home/_modules/windowmanager/sway/default.nix",
    "content": "{\n  config,\n  osConfig,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  myHardware = config.myHardware;\n  cfg = config.myHome.windowmanager.sway;\n  systemEnabled = lib.myLib.systemEnabled \"mySystem.windowmanager.sway.enable\" osConfig;\nin\n{\n  options.myHome.windowmanager.sway = {\n    enable = lib.mkEnableOption \"sway\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    myHome = {\n      isWayland = true;\n\n      windowmanager.add-on = {\n        blueman-applet.enable = true;\n        dunst.enable = true;\n        gtk-theme.enable = true;\n        nm-applet.enable = true;\n        nwg-bar.enable = true;\n        pasystray.enable = true;\n        rofi.enable = true;\n        theme.tokyonight.enable = true;\n        screenshotter.enable = true;\n        swayidle.enable = true;\n        terminal-emulator.default = \"alacritty\";\n        waybar.enable = true;\n        xdg.enable = true;\n      };\n    };\n\n    warnings = lib.mkIf (!systemEnabled) [\n      ''\n        You have enabled Sway home-manager module but not the NixOS system module.\n        Some things might not work properly.\n      ''\n    ];\n\n    assertions = [\n      {\n        assertion = builtins.length myHardware.monitors > 0;\n        message = ''\n          At least one monitor in the `config.myHardware.monitors` is\n          needed to use Sway home-manager module.\n        '';\n      }\n    ];\n\n    wayland.windowManager.sway = {\n      enable = true;\n      package = null; # use the system provided package\n      config = import ./config { inherit config pkgs lib; };\n    };\n\n    home.sessionVariables = {\n      XDG_CURRENT_DESKTOP = \"sway\";\n      GTK_USE_PORTAL = \"1\";\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/gitcommit-message",
    "content": "\n\nSigned-off-by: budimanjojo <budimanjojo@gmail.com>\n# <type>[optional scope]: <description>                                  (not more than 72 chars)\n# <BLANK NEWLINE>\n# [optional body]                                                        (not more than 72 chars)\n# <BLANK NEWLINE>\n# [optional footer(s)]                                                   (not more than 72 chars)\n#-------------------------------EXAMPLE----------------------------------\n# feat!(frontend): new button added in dashboard\n#\n# A new \"Follow Us\" button is added in your Account dashboard.\n# If you don't see it, please delete your browser cache and reload the\n# webpage.\n#\n# BREAKING CHANGE: this button replaces the custom button you have,\n# you need to reconfigure your custom button.\n"
  },
  {
    "path": "home/budiman/config/neovim/appearance.nix",
    "content": "{ pkgs, ... }:\n{\n  config = {\n    ## Highlght on yank\n    autoGroups.yankhighlight.clear = true;\n    autoCmd = [\n      {\n        event = [ \"TextYankPost\" ];\n        pattern = [ \"*\" ];\n        group = \"yankhighlight\";\n        callback = {\n          __raw = \"function()\n            vim.hl.on_yank()\n          end\";\n        };\n      }\n    ];\n\n    opts = {\n      background = \"dark\";\n      termguicolors = true;\n      signcolumn = \"yes\";\n      cursorcolumn = true;\n    };\n\n    colorschemes.catppuccin = {\n      enable = true;\n      package = pkgs.unstable.vimPlugins.catppuccin-nvim;\n      settings = {\n        flavor = \"mocha\";\n        float.transparent = true;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/autocmds.nix",
    "content": "{\n  config = {\n    autoGroups.disabledeindenthastag.clear = true;\n    autoGroups.disableautocomment.clear = true;\n    autoGroups.ftdetection.clear = true;\n    autoGroups.ftconfiguration.clear = true;\n    autoGroups.closewithq.clear = true;\n    autoGroups.autoimportformatgo.clear = true;\n    autoGroups.autoformatnix.clear = true;\n\n    autoCmd = [\n      ## Disable removing indentation on files when first letter is # except some filetypes\n      {\n        event = [ \"FileType\" ];\n        pattern = [ \"*\\\\(^c$\\\\|^cpp$\\\\)\\\\@<!\" ];\n        group = \"disabledeindenthastag\";\n        callback = {\n          __raw = \"function()\n            vim.opt.cinkeys:remove({ '0#' })\n          end\";\n        };\n      }\n\n      ## Disable autocomment on enter\n      {\n        event = [\n          \"BufEnter\"\n          \"CmdLineLeave\"\n        ];\n        pattern = [ \"*\" ];\n        group = \"disableautocomment\";\n        callback = {\n          __raw = \"function()\n            vim.opt.formatoptions:remove({ 'c', 'r','o' })\n          end\";\n        };\n      }\n\n      ## Filetype detection\n      {\n        event = [\n          \"BufRead\"\n          \"BufNewFile\"\n        ];\n        pattern = [ \"*\" ];\n        group = \"ftdetection\";\n        callback = {\n          __raw = \"function()\n            vim.schedule(detectFt)\n          end\";\n        };\n      }\n\n      ## Filetype configuration\n      {\n        event = [ \"FileType\" ];\n        pattern = [ \"*\" ];\n        group = \"ftconfiguration\";\n        callback = {\n          __raw = \"function(arg)\n            local ft = arg.match\n            if ft == 'go' then\n              vim.opt_local.shiftwidth = 4\n              vim.opt_local.tabstop = 4\n              vim.opt_local.softtabstop = 4\n              vim.opt_local.expandtab = false\n            elseif ft == 'gitcommit' then\n              vim.opt_local.colorcolumn = { 73 }\n            end\n          end\";\n        };\n      }\n\n      ## Close some filetypes with <q>\n      {\n        event = [ \"FileType\" ];\n        pattern = [\n          \"PlenaryTestPopup\"\n          \"help\"\n          \"lspinfo\"\n          \"man\"\n          \"notify\"\n          \"qf\"\n          \"query\"\n          \"spectre_panel\"\n          \"startuptime\"\n          \"tsplayground\"\n          \"neotest-output\"\n          \"checkhealth\"\n          \"neotest-summary\"\n          \"neotest-output-panel\"\n        ];\n        group = \"closewithq\";\n        callback = {\n          __raw = \"function(event)\n            vim.bo[event.buf].buflisted = false\n            vim.keymap.set('n', 'q', '<cmd>close<CR>', { buffer = event.buf, silent = true })\n          end\";\n        };\n      }\n\n      ## Auto imports and format on save for golang\n      {\n        event = [ \"BufWritePre\" ];\n        pattern = \"*.go\";\n        group = \"autoimportformatgo\";\n        callback = {\n          __raw = \"function()\n            local params = vim.lsp.util.make_range_params()\n            params.context = { only = { 'source.organizeImports' } }\n            local result = vim.lsp.buf_request_sync(0, 'textDocument/codeAction', params)\n            for cid, res in pairs(result or {}) do\n              for _, r in pairs(res.result or {}) do\n                if r.edit then\n                  local enc = (vim.lsp.get_client_by_id(cid) or {}).offset_encoding or 'utf-16'\n                  vim. lsp.util.apply_workspace_edit(r.edit, enc)\n                end\n              end\n            end\n            vim.lsp.buf.format({ async = false })\n          end\";\n        };\n      }\n\n      ## Auto format on save for nix files\n      {\n        event = [ \"BufWritePre\" ];\n        pattern = \"*.nix\";\n        group = \"autoformatnix\";\n        callback = {\n          __raw = \"function()\n            vim.lsp.buf.format({ async = false })\n          end\";\n        };\n      }\n    ];\n    extraConfigLua = ''\n      -- Filetype detection function\n      local detectFt = function()\n        local fpath = vim.fn.expand '%:p'\n        if string.match(fpath, '.*/playbooks/.*%.yaml') or string.match(fpath, '.*/playbooks/.*%.yaml') then\n          vim.o.filetype = 'yaml.ansible'\n        elseif string.match(fpath, '.*/inventory/hosts%.yaml') or string.match(fpath, '.*/inventory/hosts%.yaml') then\n          vim.o.filetype = 'yaml.ansible'\n        elseif string.match(fpath, '.*/%.chezmoiscripts/.*%.sh.tmpl') then\n          vim.o.filetype = 'sh.chezmoitmpl'\n        elseif string.match(fpath, '.*/%.chezmoiscripts/.*%.fish.tmpl') then\n          vim.o.filetype = 'fish.chezmoitmpl'\n        end\n      end\n    '';\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/default.nix",
    "content": "{\n  imports = [\n    ./lsp.nix\n    ./plugins\n    ./general.nix\n    ./appearance.nix\n    ./autocmds.nix\n    ./diagnostic.nix\n    ./keymaps.nix\n  ];\n  config = {\n    extraFiles = {\n      \"lua/utils.lua\".source = ./lua/utils.lua;\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/diagnostic.nix",
    "content": "{ lib, ... }:\n{\n  config = {\n    diagnostic.settings = {\n      virtual_text = false;\n      update_in_insert = true;\n      float = {\n        source = \"if_many\";\n        focusable = false;\n        zindex = 1;\n      };\n      signs =\n        let\n          signs = {\n            Error = \"󰅚 \";\n            Warn = \"󰗖 \";\n            Hint = \"󰌶 \";\n            Info = \" \";\n          };\n\n          mkDiagnosticWithValue =\n            valueFn:\n            lib.nixvim.toRawKeys (\n              lib.mapAttrs' (\n                n: v: lib.nameValuePair \"vim.diagnostic.severity.${lib.toUpper n}\" (valueFn n v)\n              ) signs\n            );\n        in\n        {\n          # { \"__rawKey__vim.diagnostic.severity.ERROR\" = \"󰅚 \"; ... }\n          text = mkDiagnosticWithValue (_: v: v);\n          # { \"__rawKey__vim.diagnostic.severity.ERROR\" = \"DiagnosticSignError\"; ... }\n          texthl = mkDiagnosticWithValue (n: _: \"DiagnosticSign${n}\");\n          numhl = mkDiagnosticWithValue (n: _: \"DiagnosticSign${n}\");\n        };\n    };\n\n    # set updatetime to 150ms\n    opts.updatetime = 150;\n    autoCmd = [\n      {\n        desc = \"Show diagnostics on hover\";\n        event = \"CursorHold\";\n        pattern = \"*\";\n        callback = {\n          __raw = \"function()\n            vim.diagnostic.open_float()\n          end\";\n        };\n      }\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/general.nix",
    "content": "{\n  config = {\n    extraConfigLua = ''\n      -- General options\n      vim.cmd(\"filetype plugin indent on\")\n      vim.cmd(\"syntax enable\")\n\n      -- Temporary undodir\n      local undodir = \"/tmp/.vim-undodir-\" .. vim.env.USER\n      if vim.fn.isdirectory(undodir) ~= 1 then\n        vim.fn.mkdir(undodir, \"p\", \"0700\")\n      end\n      vim.o.undodir = undodir\n      vim.o.undofile = true\n    '';\n    opts = {\n      mouse = \"a\"; # # Enable mouse support in all modes\n      number = true; # # Show line numbers\n      relativenumber = true; # # Relative line numbers\n      modelines = 0; # # Turn off modelines\n      cursorline = true; # # Highlight current line\n      list = true; # # Display whitespace chars\n      listchars = \"tab:...,trail:_,extends:>,precedes:<,nbsp:~\"; # # Format of list\n      wrap = true; # # Wrap lines on small screen\n      whichwrap = \"b,s,<,>,h,l,[,]\"; # # Able to move line using these keys\n      backspace = \"indent,eol,start\"; # # Fixes common backspace problem\n      scrolloff = 5; # # Display 5 lines above/below the cursor when scrolling\n      matchpairs = \"(:),{:},[:],<:>\"; # # Highlight matching pairs, use % to jump between\n      hlsearch = true; # # Incremental search highlight\n      incsearch = true; # # Incremental search highlight\n      ignorecase = true; # # Ignore case while searching\n      smartcase = true; # # Override ignorecase if using uppercase words\n      history = 1000; # # History lines to be remembered\n      wildmenu = true; # # Better commandline completion\n      cmdheight = 0; # # Height of commandline bar\n      hidden = true; # # Hide current buffer when opening new file to new buffer\n      backup = false; # # Disable creating backup file\n      writebackup = false; # # Disable creating backup file\n      swapfile = false; # # Disable creating swap file\n      showmode = false; # # Don't show current mode\n      showmatch = true; # # Show matching brackets when cursor is over them\n      splitbelow = true; # # Open new split below\n      splitright = true; # # Open new vertical split on the right\n      expandtab = true; # # Tabs are spaces\n      smarttab = true; # # Smarter tabs\n      shiftwidth = 2; # # 1 tab is 2 spaces\n      tabstop = 2; # # 1 tab is 2 spaces\n      autoindent = true; # # Copy indentation from previous line when starting new line\n      cindent = true; # # Copy indentation from file being edited\n      spell = true; # # Enable spelling\n      spelllang = \"en_us\"; # # Spelling language\n    };\n    clipboard = {\n      register = \"unnamedplus\"; # # Use unnamedplus register\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/keymaps.nix",
    "content": "{\n  config = {\n    extraConfigLuaPre = ''\n      -- Space is <Leader>\n      vim.keymap.set(\"\", \"<Space>\", \"<Nop>\", { silent = true })\n      vim.g.mapleader = \" \"\n      vim.g.maplocalleader = \" \"\n    '';\n    keymaps = [\n      ## Fast save, save quit, force exit\n      {\n        mode = \"n\";\n        key = \"<Leader>w\";\n        action = \":w!<CR>\";\n        options = {\n          desc = \"Write!\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>x\";\n        action = \":x<CR>\";\n        options = {\n          desc = \"Write and exit\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>qq\";\n        action = \":q<CR>\";\n        options = {\n          desc = \"Quit\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>qa\";\n        action = \":qa!<CR>\";\n        options = {\n          desc = \"Quit all!\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>wq\";\n        action = \":wq!<CR>\";\n        options = {\n          desc = \"Write and quit!\";\n          silent = true;\n        };\n      }\n\n      ## Better cursor movement on wrapped line\n      ## NOTE: we use \"x\" instead of \"v\" because luasnip uses select\n      ## mode for snippet node and I don't want it to do weird stuffs\n      {\n        mode = [\n          \"n\"\n          \"x\"\n        ];\n        key = \"k\";\n        action = \"gk\";\n      }\n      {\n        mode = [\n          \"n\"\n          \"x\"\n        ];\n        key = \"j\";\n        action = \"gj\";\n      }\n      {\n        mode = [\n          \"n\"\n          \"v\"\n        ];\n        key = \"<Up>\";\n        action = \"gk\";\n      }\n      {\n        mode = [\n          \"n\"\n          \"v\"\n        ];\n        key = \"<Down>\";\n        action = \"gj\";\n      }\n      {\n        mode = \"i\";\n        key = \"<Up>\";\n        action = \"<C-o>gk\";\n      }\n      {\n        mode = \"i\";\n        key = \"<Down>\";\n        action = \"<C-o>gj\";\n      }\n\n      ## Y to yank from cursor to end of line\n      {\n        mode = \"n\";\n        key = \"Y\";\n        action = \"y$\";\n        options.desc = \"Yank to end of line\";\n      }\n\n      ## Move selected lines up and down with updated indentation\n      {\n        mode = \"v\";\n        key = \"J\";\n        action = \":m '>+1<CR>gv=gv\";\n      }\n      {\n        mode = \"v\";\n        key = \"K\";\n        action = \":m '<-2<CR>gv=gv\";\n      }\n\n      ## Better search movement\n      {\n        mode = \"n\";\n        key = \"n\";\n        action = \"nzzzv\";\n        options.desc = \"Go to the next search result and center\";\n      }\n      {\n        mode = \"n\";\n        key = \"N\";\n        action = \"Nzzzv\";\n        options.desc = \"Go to the previous search result and center\";\n      }\n\n      ## Move between splits\n      {\n        mode = \"n\";\n        key = \"<C-k>\";\n        action = \"<C-w><C-k>\";\n        options.desc = \"Move focus to split above\";\n      }\n      {\n        mode = \"n\";\n        key = \"<C-j>\";\n        action = \"<C-w><C-j>\";\n        options.desc = \"Move focus to split below\";\n      }\n      {\n        mode = \"n\";\n        key = \"<C-h>\";\n        action = \"<C-w><C-h>\";\n        options.desc = \"Move focus to left split\";\n      }\n      {\n        mode = \"n\";\n        key = \"<C-l>\";\n        action = \"<C-w><C-l>\";\n        options.desc = \"Move focus to right split\";\n      }\n\n      ## Open new split\n      {\n        mode = \"n\";\n        key = \"<Leader>s\";\n        action = \":new<CR>\";\n        options = {\n          desc = \"Open new horizontal split\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>v\";\n        action = \":vnew<CR>\";\n        options = {\n          desc = \"Open new vertical split\";\n          silent = true;\n        };\n      }\n\n      ## Tab navigation\n      {\n        mode = \"n\";\n        key = \"<Leader>tn\";\n        action = \":tabnew<CR>\";\n        options = {\n          desc = \"Create new tab\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>tq\";\n        action = \":tabclose<CR>\";\n        options = {\n          desc = \"Close current tab\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>th\";\n        action = \":tabprev<CR>\";\n        options = {\n          desc = \"Go to the previous tab\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>tl\";\n        action = \":tabnext<CR>\";\n        options = {\n          desc = \"Go to the next tab\";\n          silent = true;\n        };\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>te\";\n        action = \":tabedit <C-r>=expand('%:p:h')<CR>/\";\n        options.desc = \"Open new tab with current buffer's path\";\n      }\n\n      ## Indent or de-indent\n      {\n        mode = \"n\";\n        key = \"<Tab>\";\n        action.__raw = \"function() return require('utils').always_working_indent_line() end\";\n        options.desc = \"Add indentation\";\n      }\n      {\n        mode = \"n\";\n        key = \"<S-Tab>\";\n        action = \"<<\";\n        options.desc = \"De-indentation\";\n      }\n      {\n        mode = \"v\";\n        key = \"<Tab>\";\n        action = \">gv\";\n        options.desc = \"Add indentation\";\n      }\n      {\n        mode = \"v\";\n        key = \"<S-Tab>\";\n        action = \"<gv\";\n        options.desc = \"De-indentation\";\n      }\n\n      ## Jumping\n      {\n        mode = \"n\";\n        key = \"<Leader>hh\";\n        action = \"<C-o>\";\n        options.desc = \"Jump to older cursor position\";\n      }\n      {\n        mode = \"n\";\n        key = \"<Leader>ll\";\n        action = \"<C-i>\";\n        options.desc = \"Jump to newer cursor position\";\n      }\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/lsp.nix",
    "content": "{ pkgs, config, ... }:\nlet\n  snacksPickerEnabled =\n    let\n      snacks = config.plugins.snacks;\n    in\n    snacks.settings != { } && builtins.hasAttr \"picker\" snacks.settings;\nin\n{\n  config = {\n    plugins = {\n      # make lua_ls works with neovim API\n      lazydev = {\n        enable = true;\n        settings.library = [\n          {\n            path = \"\\${3rd}/luv/library\";\n            words = [ \"vim%.uv\" ];\n          }\n        ];\n      };\n\n      lspconfig.enable = true; # this provides sane defaults for LSP servers\n      schemastore.enable = true;\n      lsp-signature.enable = true;\n      trouble.enable = true;\n      web-devicons.enable = true;\n    };\n\n    lsp = {\n      keymaps = [\n        {\n          key = \"<Leader>rn\";\n          lspBufAction = \"rename\";\n          mode = [ \"n\" ];\n          options.desc = \"Do LSP rename action\";\n        }\n        {\n          key = \"<Leader>ca\";\n          lspBufAction = \"code_action\";\n          mode = [ \"n\" ];\n          options.desc = \"Do LSP code action\";\n        }\n        {\n          key = \"gd\";\n          action.__raw =\n            if snacksPickerEnabled then\n              \"function() Snacks.picker.lsp_definitions() end\"\n            else\n              \"function() vim.lsp.buf.definition() end\";\n          mode = [ \"n\" ];\n          options.desc = \"Do LSP get definition action\";\n        }\n        {\n          key = \"gD\";\n          action.__raw =\n            if snacksPickerEnabled then\n              \"function() Snacks.picker.lsp_declarations() end\"\n            else\n              \"function() vim.lsp.buf.declaration() end\";\n          mode = [ \"n\" ];\n          options.desc = \"Do LSP get declaration action\";\n        }\n        {\n          key = \"gh\";\n          action.__raw = ''\n            function()\n              return require('utils').fix_buf_hover()\n            end\n          '';\n          mode = [ \"n\" ];\n          options.desc = \"Do LSP hover action\";\n        }\n        {\n          key = \"gr\";\n          action.__raw =\n            if snacksPickerEnabled then\n              \"function() Snacks.picker.lsp_references() end\"\n            else\n              \"function() vim.lsp.buf.references() end\";\n          mode = [ \"n\" ];\n          options.desc = \"Do LSP get references action\";\n        }\n        {\n          key = \"gi\";\n          action.__raw =\n            if snacksPickerEnabled then\n              \"function() Snacks.picker.lsp_implementations() end\"\n            else\n              \"function() vim.lsp.buf.implementation() end\";\n          mode = [ \"n\" ];\n          options.desc = \"Do LSP get implementation action\";\n        }\n        {\n          key = \"<Leader>pp\";\n          lspBufAction = \"format\";\n          mode = [\n            \"n\"\n            \"v\"\n          ];\n          options.desc = \"Do LSP format action\";\n        }\n        {\n          key = \"<Leader>lr\";\n          action = \":LspRestart<CR>\";\n          mode = [ \"n\" ];\n          options.desc = \"Restart LSP\";\n        }\n      ];\n\n      servers = {\n        # ansiblels.enable = true;\n        bashls.enable = true;\n        cssls.enable = true;\n        dockerls.enable = true;\n\n        gopls = {\n          enable = true;\n          package = pkgs.unstable.gopls;\n          config.settings.gopls.gofumpt = true;\n        };\n\n        jsonls = {\n          enable = true;\n          config.filetypes = [\n            \"json\"\n            \"jsonc\"\n            \"json5\"\n          ];\n        };\n\n        nil_ls = {\n          enable = true;\n          config.settings.nil.nix.flake.autoArchive = true;\n        };\n\n        pyright.enable = true;\n        lua_ls.enable = true;\n        taplo.enable = true;\n        ts_ls.enable = true;\n\n        yamlls = {\n          enable = true;\n          config.settings.yaml = {\n            customTags = [\n              \"!include_dir_list\"\n              \"!include_dir_named\"\n              \"!include_dir_merge_list\"\n              \"!include_dir_merge_named\"\n              \"!secret\"\n              \"!env_var\"\n            ];\n            schemas = {\n              kubernetes = [\n                \"namespace.yaml\"\n                \"deployment.yaml\"\n                \"daemonset.yaml\"\n                \"statefulset.yaml\"\n                \"service.yaml\"\n                \"pv.yaml\"\n                \"pvc.yaml\"\n                \"configmmap.yaml\"\n                \"secret.yaml\"\n                \"rbac.yaml\"\n                \"crd.yaml\"\n                \"storageclass.yaml\"\n                \"cronjob.yaml\"\n              ];\n              \"https://raw.githubusercontent.com/docker/compose/master/compose/config/compose_spec.json\" = [\n                \"docker-compose.yml\"\n                \"docker-compose.yaml\"\n              ];\n              \"https://json.schemastore.org/github-workflow\" = [\n                \".github/workflows/**.yml\"\n                \".github/workflows/**.yaml\"\n              ];\n              \"https://json.schemastore.org/github-actions\" = [\n                \"action.yml\"\n                \"action.yaml\"\n              ];\n              \"https://json.schemastore.org/gitlab-ci\" = \".gitlab-ci.yml\";\n              \"https://json.schemastore.org/kustomization\" = [\n                \"kustomization.yml\"\n                \"kustomization.yaml\"\n              ];\n              \"https://json.schemastore.org/pre-commit-config\" = [\n                \".pre-commit-config.yml\"\n                \".pre-commit-config.yaml\"\n              ];\n            };\n          };\n        };\n      };\n    };\n\n    extraPackages = [\n      pkgs.shellcheck # dependency for bashls\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/lua/utils.lua",
    "content": "local M = {}\n\n-- Returns `cwd` or path of current buffer depending on whether the current window is terminal or nothing\nfunction M.current_path()\n  local current_win_info = vim.fn[\"getwininfo\"](vim.api.nvim_get_current_win())\n  if current_win_info == nil or #current_win_info == 0 then\n    return vim.fn[\"getcwd\"]()\n  end\n  return vim.fn.expand(\"%:p:h\")\nend\n\n-- Workaround for hover not working when there is diagnostic float\n-- https://www.reddit.com/r/neovim/comments/pg1o6k/neovim_lsp_hover_window_is_hidden_behind_line\nfunction M.fix_buf_hover()\n  vim.o.eventignore = 'CursorHold'\n  vim.lsp.buf.hover()\n  vim.cmd([[autocmd CursorMoved <buffer> ++once set eventignore=\"\"]])\nend\n\n-- LuaSnip popup when there are choices\n-- https://github.com/L3MON4D3/LuaSnip/wiki/Misc#choicenode-popup\nlocal current_nsid = vim.api.nvim_create_namespace('LuaSnipChoiceListSelections')\nlocal current_win = nil\n\nlocal function window_for_choiceNode(choiceNode)\n  local buf = vim.api.nvim_create_buf(false, true)\n  local buf_text = {}\n  local row_selection = 0\n  local row_offset = 0\n  local text\n  for _, node in ipairs(choiceNode.choices) do\n      text = node:get_docstring()\n      -- find one that is currently showing\n      if node == choiceNode.active_choice then\n        -- current line is starter from buffer list which is length usually\n        row_selection = #buf_text\n        -- finding how many lines total within a choice selection\n        row_offset = #text\n      end\n      vim.list_extend(buf_text, text)\n  end\n  vim.api.nvim_buf_set_text(buf, 0, 0, 0, 0, buf_text)\n  local w, h = vim.lsp.util._make_floating_popup_size(buf_text)\n  -- adding highlight so we can see which one is being selected\n  local extmark = vim.api.nvim_buf_set_extmark(buf, current_nsid, row_selection, 0, {\n    hl_group = 'incsearch',\n    end_line = row_selection + row_offset\n  })\n  -- shows window at a beginning of choiceNode\n  local win = vim.api.nvim_open_win(buf, false, {\n    relative = \"win\",\n    width = w,\n    height = h,\n    bufpos = choiceNode.mark:pos_begin_end(),\n    style = \"minimal\",\n    border = 'rounded'\n  })\n  -- return with 3 main important so we can use them again\n  return { win_id = win, extmark = extmark, buf = buf }\nend\n\nfunction M.choice_popup(choiceNode)\n  -- build stack for nested choiceNodes.\n  if current_win then\n    vim.api.nvim_win_close(current_win.win_id, true)\n    vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)\n  end\n  local create_win = window_for_choiceNode(choiceNode)\n  current_win = {\n    win_id = create_win.win_id,\n    prev = current_win,\n    node = choiceNode,\n    extmark = create_win.extmark,\n    buf = create_win.buf\n  }\nend\n\nfunction M.update_choice_popup(choiceNode)\n  vim.api.nvim_win_close(current_win.win_id, true)\n  vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)\n  local create_win = window_for_choiceNode(choiceNode)\n  current_win.win_id = create_win.win_id\n  current_win.extmark = create_win.extmark\n  current_win.buf = create_win.buf\nend\n\nfunction M.choice_popup_close()\n  vim.api.nvim_win_close(current_win.win_id, true)\n  vim.api.nvim_buf_del_extmark(current_win.buf, current_nsid, current_win.extmark)\n  -- now we are checking if we still have previous choice we were in after exit nested choice\n  current_win = current_win.prev\n  if current_win then\n    -- reopen window further down in the stack.\n    local create_win = window_for_choiceNode(current_win.node)\n    current_win.win_id = create_win.win_id\n    current_win.extmark = create_win.extmark\n    current_win.buf = create_win.buf\n  end\nend\n\n-- Make sure indenting `#` works even if `smartindent` and `cindent` is on\nfunction M.always_working_indent_line()\n  -- get the current settings\n  local smartindent = vim.o.smartindent\n  local cindent = vim.o.cindent\n\n  -- set smartindent and cindent off\n  vim.o.smartindent = false\n  vim.o.cindent = false\n\n  -- indent the line\n  vim.api.nvim_feedkeys('>>', 'x', true)\n\n  -- reset it back to previous settings\n  vim.o.smartindent = smartindent\n  vim.o.cindent = cindent\nend\n\nreturn M\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/blink-cmp/default.nix",
    "content": "{\n  pkgs,\n  lib,\n  config,\n  ...\n}:\n{\n  config.plugins = {\n    blink-cmp = {\n      enable = true;\n      package = pkgs.unstable.vimPlugins.blink-cmp;\n      settings = {\n        appearance.nerd_font_variant = \"normal\"; # UbuntuMono Nerd Font is not a mono font?\n        snippets.preset = \"luasnip\";\n        sources = {\n          providers = {\n            path = {\n              score_offset = 40;\n              opts.trailing_slash = false;\n            };\n            lsp.score_offset = 30;\n            snippets = {\n              score_offset = 20;\n              opts.use_label_description = true;\n            };\n            buffer.score_offset = 10;\n          };\n          default = [\n            \"lsp\"\n            \"path\"\n            \"snippets\"\n            \"buffer\"\n          ];\n        };\n\n        keymap = {\n          preset = \"none\";\n          \"<C-j>\" = [\n            \"select_next\"\n            \"fallback\"\n          ];\n          \"<C-k>\" = [\n            \"select_prev\"\n            \"fallback\"\n          ];\n          \"<Up>\" = [\n            \"scroll_documentation_up\"\n            \"fallback\"\n          ];\n          \"<Down>\" = [\n            \"scroll_documentation_down\"\n            \"fallback\"\n          ];\n          \"<CR>\" = [\n            \"accept\"\n            \"fallback\"\n          ];\n          \"<C-y>\" = [\n            \"select_and_accept\"\n            \"fallback\"\n          ];\n        };\n\n        completion = {\n          keyword.range = \"full\";\n          list.selection.preselect = false;\n          menu.draw.columns = [\n            { __raw = \"{'label', 'label_description', gap = 1}\"; }\n            { __raw = \"{'kind_icon', 'kind'}\"; }\n            { __raw = \"{'source_name'}\"; }\n          ];\n          documentation.auto_show = true;\n          ghost_text = {\n            enabled = true;\n            show_without_selection = true;\n          };\n        };\n\n        fuzzy.sorts = [\n          \"exact\"\n          \"score\"\n          \"sort_text\"\n        ];\n\n        cmdline = {\n          keymap = {\n            preset = \"none\";\n            \"<Tab>\" = [\n              \"show_and_insert_or_accept_single\"\n              \"select_next\"\n            ];\n            \"<S-Tab>\" = [\n              \"show_and_insert_or_accept_single\"\n              \"select_prev\"\n            ];\n            \"<C-j>\" = [\n              \"select_next\"\n              \"fallback\"\n            ];\n            \"<C-k>\" = [\n              \"select_prev\"\n              \"fallback\"\n            ];\n            \"<CR>\" = [\n              \"accept\"\n              \"fallback\"\n            ];\n            \"<C-y>\" = [\n              \"select_accept_and_enter\"\n              \"fallback\"\n            ];\n          };\n          completion = {\n            menu.auto_show = lib.mkIf config.plugins.noice.enable {\n              __raw = ''\n                function(ctx)\n                  return vim.fn.getcmdtype() == ':'\n                end\n              '';\n            };\n            list.selection.preselect = false;\n          };\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/chezmoi-vim/default.nix",
    "content": "{ myPkgs, ... }:\n{\n  config = {\n    extraPlugins = [\n      myPkgs.nvim-plugins.chezmoi-vim\n    ];\n\n    globals.\"chezmoi#use_tmp_buffer\" = true;\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/cord/default.nix",
    "content": "{\n  config.plugins.cord = {\n    enable = true;\n    settings.display.theme = \"catppuccin\";\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/default.nix",
    "content": "{\n  imports = [\n    ./blink-cmp\n    ./chezmoi-vim\n    ./cord\n    ./endec\n    ./gitsigns\n    ./lualine\n    ./grug-far\n    ./luasnip\n    ./mini\n    ./noice\n    ./none-ls\n    ./nvim-autopairs\n    ./oil\n    ./snacks\n    ./toggleterm\n    ./treesitter\n  ];\n\n  config.plugins = {\n    web-devicons.enable = true;\n    colorizer.enable = true;\n    ts-autotag.enable = true;\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/endec/default.nix",
    "content": "{ pkgs, ... }:\n{\n  config = {\n    extraPlugins = [ pkgs.unstable.vimPlugins.endec-nvim ];\n\n    extraConfigLua = ''\n      require('endec').setup({\n        keymaps = {\n          defaults = false,\n          decode_base64_popup = \"<Leader>bd\",\n          vdecode_base64_popup = \"<Leader>bd\",\n          encode_base64_inplace = \"<Leader>be\",\n          vencode_base64_inplace = \"<Leader>be\",\n        }\n      })\n    '';\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/gitsigns/default.nix",
    "content": "{\n  config.plugins.gitsigns = {\n    enable = true;\n    settings = {\n      linehl = true;\n      current_line_blame = true;\n\n      signs = {\n        add.text = \"󰐕\";\n        change.text = \"󰐕\";\n        delete.text = \"_\";\n        topdelete.text = \"‾\";\n        changedelete.text = \"~\";\n        untracked.text = \"┆\";\n      };\n\n      signs_staged_enable = true;\n      signs_staged = {\n        add.text = \"󰐕\";\n        change.text = \"󰐕\";\n        delete.text = \"_\";\n        topdelete.text = \"‾\";\n        changedelete.text = \"~\";\n        untracked.text = \"┆\";\n      };\n\n      on_attach.__raw = ''\n        function(bufnr)\n          local gitsigns = require('gitsigns')\n\n          local function map(mode, lhs, rhs, opts)\n            opts = opts or {}\n            opts.buffer = bufnr\n            vim.keymap.set(mode, lhs, rhs, opts)\n          end\n\n          map('n', '<C-n>', function() gitsigns.nav_hunk('next') end, { desc = \"Go to next hunk\" })\n          map('n', '<C-p>', function() gitsigns.nav_hunk('prev') end, { desc = \"Go to prev hunk\" })\n\n          map('n', '<Leader>ga', gitsigns.stage_hunk, { desc = \"Git stage hunk\" })\n          map('n', '<Leader>gr', gitsigns.reset_hunk, { desc = \"Git reset hunk\" })\n\n          map('v', '<Leader>ga', function()\n            gitsigns.stage_hunk({ vim.fn.line('.'), vim.fn.line('v') })\n          end, { desc = \"Git stage hunk\" })\n          map('v', '<Leader>gr', function()\n            gitsigns.reset_hunk({ vim.fn.line('.'), vim.fn.line('v') })\n          end, { desc = \"Git reset hunk\" })\n\n          map('n', '<Leader>gaa', gitsigns.stage_buffer, { desc = \"Git stage buffer\" })\n          map('n', '<Leader>gra', gitsigns.reset_buffer, { desc = \"Reset git actions in buffer\" })\n          map('n', '<Leader>gh', gitsigns.preview_hunk, { desc = \"Preview git actions in hunk\" })\n          map('n', '<Leader>gd', gitsigns.diffthis, { desc = \"Git diff this hunk\" })\n        end\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/grug-far/default.nix",
    "content": "{\n  config = {\n    plugins.grug-far = {\n      enable = true;\n      settings.keymaps = {\n        replace.n = \"<localleader>W\";\n        syncLocations.n = \"<localleader>w\";\n        syncLine.n = \"<localleader><space>\";\n        applyNext.n = \"<localleader>j\";\n        applyPrev.n = \"<localleader>k\";\n        openLocation.n = \"<enter>\";\n        previewLocation.n = \"<localleader>i\";\n        refresh.n = \"<localleader>f\";\n        close.n = \"q\";\n        help.n = \"g?\";\n        abort.n = \"<localleader>qq\";\n        qflist = false;\n        historyOpen = false;\n        historyAdd = false;\n        openNextLocation = false;\n        openPrevLocation = false;\n        pickHistoryEntry = false;\n        toggleShowCommand = false;\n        swapEngine = false;\n        swapReplacementInterpreter = false;\n        syncNext = false;\n        syncPrev = false;\n        syncFile = false;\n        nextInput = false;\n        prevInput = false;\n      };\n    };\n\n    keymaps = [\n      {\n        mode = [\n          \"n\"\n          \"v\"\n        ];\n        key = \"<Leader>sr\";\n        action.__raw = ''\n          function()\n            return require('grug-far').open({\n              prefills = { search = vim.api.nvim_get_current_line() };\n            })\n          end\n        '';\n        options.desc = \"Open grug-far search and replace window with current line under cursor prefilled\";\n      }\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/lualine/default.nix",
    "content": "{\n  config.plugins.lualine = {\n    enable = true;\n    settings = {\n      options = {\n        theme = \"catppuccin-nvim\";\n        globalstatus = false;\n        icons_enabled = true;\n        section_separators = {\n          left = \"\";\n          right = \"\";\n        };\n        component_separators = \"\";\n        disabled_filetypes = [ \"oil\" ];\n      };\n\n      sections = {\n        lualine_a = [\n          {\n            __unkeyed-1 = \"mode\";\n            separator = {\n              left = \"\";\n              right = \"\";\n            };\n          }\n        ];\n        lualine_b = [\n          {\n            __unkeyed-1 = \"branch\";\n            separator = {\n              left = \"\";\n              right = \"\";\n            };\n          }\n        ];\n        lualine_c = [\n          {\n            __unkeyed-1 = \"diff\";\n            symbols = {\n              added = \" \";\n              modified = \" \";\n              removed = \" \";\n            };\n          }\n          {\n            __unkeyed-1 = \"diagnostics\";\n            sources = [ \"nvim_diagnostic\" ];\n          }\n          {\n            __unkeyed-1 = \"filename\";\n            path = 1;\n            shorting_target = 30;\n            symbols = {\n              modified = \" 󰎜\";\n              readonly = \" 󰈡\";\n              unnamed = \" 󰎞\";\n            };\n          }\n        ];\n        lualine_x = [ \"filetype\" ];\n        lualine_y = [ \"progress\" ];\n        lualine_z = [\n          {\n            __unkeyed-1 = \"location\";\n            separator = {\n              left = \"\";\n              right = \"\";\n            };\n          }\n        ];\n      };\n\n      tabline = {\n        lualine_b = [\n          {\n            __unkeyed-1 = \"buffers\";\n            separator = {\n              left = \"\";\n              right = \"\";\n            };\n            show_modified_status = false;\n            buffers_color = {\n              active = \"lualine_b_normal\";\n              inactive = \"lualine_c_inactive\";\n            };\n          }\n        ];\n        lualine_z = [\n          {\n            # display macro recording\n            cond.__raw = \"function() return vim.fn.reg_recording() ~= '' end\";\n            __unkeyed-1.__raw = \"function()\n              return 'Recording @' .. vim.fn.reg_recording()\n            end\";\n            separator = {\n              left = \"\";\n              right = \"\";\n            };\n          }\n        ];\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/luasnip/default.nix",
    "content": "{\n  pkgs,\n  myPkgs,\n  lib,\n  ...\n}:\nlet\n  vimPlugins = pkgs.vimPlugins // myPkgs.nvim-plugins;\nin\n{\n  config = {\n    extraPlugins = with vimPlugins; [\n      friendly-snippets\n      k8s-snippets\n    ];\n\n    plugins.luasnip = {\n      enable = true;\n      fromLua = [ { paths = ./lua-snippets; } ];\n      fromVscode = [ { exclude = [ \"gitcommit\" ]; } ];\n\n      # Hint snippet node-type with virtual text\n      # https://github.com/L3MON4D3/LuaSnip/wiki/Nice-Configs#hint-node-type-with-virtual-text\n      settings.ext_opts = lib.nixvim.toRawKeys {\n        \"require('luasnip.util.types').choiceNode\".active.virt_text = [\n          [\n            \"● \"\n            \"DiagnosticVirtualTextWarn\"\n          ]\n        ];\n        \"require('luasnip.util.types').insertNode\".active.virt_text = [\n          [\n            \"● \"\n            \"DiagnosticVirtualTextInfo\"\n          ]\n        ];\n      };\n    };\n\n    keymaps = [\n      {\n        mode = [\n          \"i\"\n          \"s\"\n        ];\n        key = \"<C-j>\";\n        action.__raw = ''\n          function()\n            if require('luasnip').choice_active() then\n              require('luasnip').change_choice(1)\n            else\n              vim.api.nvim_input(\"<Down>\")\n            end\n          end'';\n        options.desc = \"Select choice node in snippet or move cursor down\";\n      }\n      {\n        mode = [\n          \"i\"\n          \"s\"\n        ];\n        key = \"<C-k>\";\n        action.__raw = ''\n          function()\n            if require('luasnip').choice_active() then\n              require('luasnip').change_choice(-1)\n            else\n              vim.api.nvim_input(\"<Up>\")\n            end\n          end'';\n        options.desc = \"Select choice node in snippet or move cursor up\";\n      }\n      {\n        mode = [\n          \"i\"\n          \"s\"\n        ];\n        key = \"<C-l>\";\n        action.__raw = ''\n          function()\n            if require('luasnip').expand_or_locally_jumpable() then\n              require('luasnip').jump(1)\n            else\n              vim.api.nvim_feedkeys(vim.keycode(\"<Right>\"), \"i\", false)\n            end\n          end'';\n        options.desc = \"Go to next snippet or move cursor right\";\n      }\n      {\n        mode = [\n          \"i\"\n          \"s\"\n        ];\n        key = \"<C-h>\";\n        action.__raw = ''\n          function()\n            if require('luasnip').locally_jumpable() then\n              require('luasnip').jump(-1)\n            else\n              vim.api.nvim_feedkeys(vim.keycode(\"<Left>\"), \"i\", false)\n            end\n          end'';\n        options.desc = \"Go to next snippet or move cursor left\";\n      }\n    ];\n\n    # ChoiceNode popup in snippet\n    autoGroups.choicepopup.clear = true;\n    autoCmd = [\n      {\n        event = [ \"User\" ];\n        pattern = [\n          \"LuasnipChoiceNodeEnter\"\n          \"LuasnipChoiceNodeLeave\"\n          \"LuasnipChangeChoice\"\n        ];\n        group = \"choicepopup\";\n        callback.__raw = ''\n          function(arg)\n            local match = arg.match\n            if match == 'LuasnipChoiceNodeEnter' then\n              require('utils').choice_popup(require('luasnip').session.event_node)\n            elseif match == 'LuasnipChoiceNodeLeave' then\n              require('utils').choice_popup_close()\n            elseif match == \"LuasnipChangeChoice\" then\n              require('utils').update_choice_popup(require('luasnip').session.event_node)\n            end\n          end\n        '';\n      }\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/luasnip/lua-snippets/gitcommit.lua",
    "content": "local ls = require('luasnip')\nlocal s = ls.snippet\nlocal text = ls.text_node\nlocal insert = ls.insert_node\nlocal choice = ls.choice_node\nlocal nonempty = require('luasnip.extras').nonempty\n\nreturn {\n  s({\n    trig = 'cc',\n    name = 'conventional commit',\n    dscr = 'Conventional commit',\n  }, {\n    choice(1, {\n      text('feat'), text('fix'), text('doc'), text('chore'),\n    }),\n    nonempty(2, '(', ''),\n    insert(2, 'scope'),\n    nonempty(2, ')', ''),\n    text(': '),\n    insert(3, 'title'),\n    insert(0),\n  }),\n\n  s({\n    trig = 'fix',\n    name = 'fix conventional commit',\n    dscr = 'Fix conventional commit',\n  }, {\n    text('fix'),\n    nonempty(1, '(', ''),\n    insert(1, 'scope'),\n    nonempty(1, ')', ''),\n    text(': '),\n    insert(2, 'title'),\n    insert(0),\n  }),\n\n  s({\n    trig = 'feat',\n    name = 'feat conventional commit',\n    dscr = 'Feat conventional commit',\n  }, {\n    text('feat'),\n    nonempty(1, '(', ''),\n    insert(1, 'scope'),\n    nonempty(1, ')', ''),\n    text(': '),\n    insert(2, 'title'),\n    insert(0),\n  }),\n\n  s({\n    trig = 'chore',\n    name = 'chore conventional commit',\n    dscr = 'Chore conventional commit',\n  }, {\n    text('chore'),\n    nonempty(1, '(', ''),\n    insert(1, 'scope'),\n    nonempty(1, ')', ''),\n    text(': '),\n    insert(2, 'title'),\n    insert(0),\n  }),\n\n  s({\n    trig = 'docs',\n    name = 'docs conventional commit',\n    dscr = 'Docs conventional commit',\n  }, {\n    text('docs'),\n    nonempty(1, '(', ''),\n    insert(1, 'scope'),\n    nonempty(1, ')', ''),\n    text(': '),\n    insert(2, 'title'),\n    insert(0),\n  }),\n\n  s({\n    trig = 'cc!',\n    name = 'conventional commit breaking',\n    dscr = 'Conventional commit with breaking change(s)',\n  }, {\n    choice(1, {\n      text('feat'), text('fix'),\n    }),\n    nonempty(2, '(', ''),\n    insert(2, 'scope'),\n    nonempty(2, ')', ''),\n    text('!: '),\n    insert(3, 'title'),\n    text({'', '', 'BREAKING CHANGE: '}),\n    insert(0),\n  }),\n\n  s({\n    trig = 'feat!',\n    name = 'feat conventional commit breaking',\n    dscr = 'feat conventional commit with breaking change(s)',\n  }, {\n    text('feat'),\n    nonempty(1, '(', ''),\n    insert(1, 'scope'),\n    nonempty(1, ')', ''),\n    text('!: '),\n    insert(2, 'title'),\n    text({'', '', 'BREAKING CHANGE: '}),\n    insert(0),\n  }),\n\n  s({\n    trig = 'fix!',\n    name = 'fix conventional commit breaking',\n    dscr = 'fix conventional commit with breaking change(s)',\n  }, {\n    text('fix'),\n    nonempty(1, '(', ''),\n    insert(1, 'scope'),\n    nonempty(1, ')', ''),\n    text('!: '),\n    insert(2, 'title'),\n    text({'', '', 'BREAKING CHANGE: '}),\n    insert(0),\n  }),\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/mini/comment.nix",
    "content": "{ plugins.mini.modules.comment = { }; }\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/mini/default.nix",
    "content": "{\n  imports = [\n    ./comment.nix\n    ./icons.nix\n    ./indentscope.nix\n    ./surround.nix\n    ./trailspace.nix\n  ];\n\n  config.plugins.mini.enable = true;\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/mini/icons.nix",
    "content": "{\n  plugins.mini = {\n    mockDevIcons = true;\n    modules.icons = { };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/mini/indentscope.nix",
    "content": "{\n  plugins.mini.modules.indentscope = {\n    draw.delay = 0;\n    symbol = \"▎\";\n    options.try_as_border = true;\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/mini/surround.nix",
    "content": "{ plugins.mini.modules.surround = { }; }\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/mini/trailspace.nix",
    "content": "{\n  plugins.mini.modules.trailspace = { };\n\n  # custom `trim_trailing_lastline` editorconfig property\n  # that will remove trailing last lines from file\n  # based on the `trim_trailing_whitespace`\n  editorconfig.properties.trim_trailing_lastline = ''\n    function(bufnr, val)\n      assert(\n        val == 'true' or val == 'false',\n        'trim_trailing_lastline must be either \"true\" or \"false\"'\n      )\n\n      local group = vim.api.nvim_create_augroup(\n        'nvim.editorconfig.trim_trailing_ll',\n        {}\n      )\n\n      if val == 'true' then\n        vim.api.nvim_create_autocmd('BufWritePre', {\n          group = group,\n          buffer = bufnr,\n          callback = function()\n            MiniTrailspace.trim_last_lines()\n          end,\n        })\n      else\n        vim.api.nvim_clear_autocmds({\n          event = 'BufWritePre',\n          group = group,\n        })\n      end\n    end\n  '';\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/noice/default.nix",
    "content": "{\n  config = {\n    colorschemes.catppuccin.settings.integrations = {\n      noice = true;\n      notify = true;\n    };\n\n    plugins = {\n      # dependencies\n      notify.enable = true;\n      nui.enable = true;\n\n      noice = {\n        enable = true;\n        settings = {\n          lsp = {\n            signature.enabled = false;\n            # override markdown rendering to use treesitter\n            override = {\n              \"vim.lsp.util.convert_input_to_markdown_lines\" = true;\n              \"vim.lsp.util.stylize_markdown\" = true;\n            };\n          };\n\n          messages.view_history = \"popup\";\n          popupmenu.enabled = false;\n\n          routes = [\n            # use mini notification for :w messages\n            {\n              filter = {\n                event = \"msg_show\";\n                kind = \"\";\n                find = \"written\";\n              };\n              view = \"mini\";\n            }\n            # hide search virtual text\n            {\n              filter = {\n                event = \"msg_show\";\n                kind = \"search_count\";\n              };\n              opts.skip = true;\n            }\n\n            # hide `vim.tbl_islist` is deprecated message\n            {\n              filter = {\n                event = \"msg_show\";\n                find = \"vim.tbl_islist is deprecated\";\n              };\n              opts.skip = true;\n            }\n\n            # route any messages with more than 10 lines to popup view\n            {\n              filter = {\n                event = \"msg_show\";\n                min_height = 10;\n              };\n              view = \"popup\";\n            }\n          ];\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/none-ls/default.nix",
    "content": "{ pkgs, ... }:\n{\n  config.plugins.none-ls = {\n    enable = true;\n    settings.diagnostics_format = \"[#{c}] #{m} (#{s})\";\n    sources = {\n      formatting = {\n        nixfmt = {\n          enable = true;\n          package = pkgs.nixfmt-rfc-style;\n        };\n        prettier = {\n          enable = true;\n          disableTsServerFormatter = true;\n        };\n        shfmt = {\n          enable = true;\n          settings.extra_args = [\n            \"-i\"\n            \"2\"\n            \"-ci\"\n          ];\n        };\n      };\n      diagnostics = {\n        ansiblelint = {\n          enable = true;\n          # TODO: ansible-lint is currently broken on stable\n          # ref: https://github.com/NixOS/nixpkgs/issues/460422\n          package = pkgs.unstable.ansible-lint;\n          settings.filetypes = [ \"yaml.ansible\" ];\n        };\n        golangci_lint = {\n          enable = true;\n          package = pkgs.unstable.golangci-lint;\n        };\n        markdownlint.enable = true;\n        write_good.enable = true;\n        yamllint.enable = true;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/nvim-autopairs/_rules.lua",
    "content": "local rule = require('nvim-autopairs.rule')\nlocal cond = require('nvim-autopairs.conds')\nlocal ts_conds = require('nvim-autopairs.ts-conds')\nlocal npairs = require('nvim-autopairs')\n\nlocal function mkSpaceBracketExitRule(t)\n  return rule(t[1] .. ' ', ' ' .. t[2])\n    :with_pair(cond.none())\n    -- this is needed so the `with_del()` method\n    -- for the rule: \"(|) press space => ( | )\" works\n    :with_del(cond.none())\n    :with_move(function(opts)\n      return opts.char == t[2]\n    end)\n    :use_key(t[2])\n    -- removes the trailing whitespaces on <CR>\n    :replace_map_cr(function(_)\n      return '<C-c>2xi<CR><C-c>O'\n    end)\nend\n\nnpairs.add_rules{\n  -- (|) press space => ( | )\n  -- {|} press space => { | }\n  -- [|] press space => [ | ]\n  rule(' ', ' ')\n    :with_pair(function (opts)\n      local pair = opts.line:sub(opts.col - 1, opts.col)\n      return vim.tbl_contains({ '()', '{}', '[]' }, pair)\n    end)\n    -- we only want to delete the pair of spaces\n    :with_del(function(opts)\n      local col = vim.api.nvim_win_get_cursor(0)[2]\n      local context = opts.line:sub(col - 1, col + 2)\n      return vim.tbl_contains({ '(  )', '{  }', '[  ]' }, context)\n    end),\n\n  -- ( | ) press ) => ( )|\n  mkSpaceBracketExitRule({'(', ')'}),\n  -- { | } press } => { }|\n  mkSpaceBracketExitRule({'{', '}'}),\n  -- [ | ] press ] => [ ]|\n  mkSpaceBracketExitRule({'[', ']'}),\n\n  -- |; press ; => ;|\n  rule('', ';')\n    :with_move(function(opts) return opts.char == ';' end)\n    :with_pair(cond.none())\n    :with_del(cond.none())\n    :with_cr(cond.none())\n    :use_key(';'),\n\n  -- a =| press <SPC> => a = |;\n  -- for nix filetype\n  rule('= ', ';', 'nix')\n    :with_pair(function(opts)\n      local prev_char = opts.line:sub(opts.col - 2, opts.col - 2)\n      return prev_char:match('[^=<>!]') ~= nil\n        and ts_conds.is_not_ts_node('source')(opts)\n    end),\n\n    -- with | press <SPC> => with |;\n    -- for nix filetype\n    rule('with ', ';', 'nix')\n      :with_pair(function(opts)\n        local prev_char = opts.line:sub(opts.col - 5, opts.col - 5)\n        return prev_char:match('[^%w_-]') ~= nil\n          and ts_conds.is_not_ts_node('source')(opts)\n      end),\n\n    -- {| press ' => {'|',},\n    -- for lua filetype\n    rule(\"'\", \"',\", 'lua')\n      :with_pair(ts_conds.is_ts_node({ 'table_constructor' })),\n\n    -- {| press \" => {\"|\",},\n    -- for lua filetype\n    rule('\"', '\",', 'lua')\n      :with_pair(ts_conds.is_ts_node({ 'table_constructor' })),\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/nvim-autopairs/default.nix",
    "content": "{\n  config = {\n    extraFiles.\"lua/nvim-autopairs/_rules.lua\".source = ./_rules.lua;\n    plugins.nvim-autopairs = {\n      enable = true;\n      luaConfig = {\n        post = \"require('nvim-autopairs._rules')\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/oil/default.nix",
    "content": "{\n  config = {\n    plugins.oil = {\n      enable = true;\n      settings = {\n        columns = [\n          \"icon\"\n          \"size\"\n        ];\n        use_default_keymaps = false;\n\n        keymaps = {\n          \"?\" = \"actions.show_help\";\n          \"<CR>\" = \"actions.select\";\n          \"<C-h>\" = \"actions.parent\";\n          \"<C-l>\" = \"actions.select\";\n          \"`\" = \"actions.open_cwd\";\n          \"<C-s>\" = \"actions.select_split\";\n          \"<C-v>\" = \"actions.select_vsplit\";\n          \"q\" = \"actions.close\";\n          \"<C-r>\" = \"actions.refresh\";\n          \".\" = \"actions.toggle_hidden\";\n        };\n\n        float = {\n          max_width = 50;\n          max_height = 40;\n          border = \"rounded\";\n          win_options.winblend = 10;\n        };\n      };\n    };\n\n    keymaps = [\n      {\n        mode = \"n\";\n        key = \"<C-f>\";\n        action.__raw = \"function()\n          return require('oil').toggle_float()\n        end\";\n        options.desc = \"Toggle floating file explorer\";\n      }\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/snacks/default.nix",
    "content": "{ pkgs, ... }:\n{\n  imports = [\n    ./picker.nix\n    ./input.nix\n  ];\n\n  config = {\n    plugins.snacks = {\n      enable = true;\n      # TODO: wait for the stable version to get commit 9ad5d53\n      package = pkgs.unstable.vimPlugins.snacks-nvim;\n    };\n    colorschemes.catppuccin.settings.integrations.snacks.enabled = true;\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/snacks/input.nix",
    "content": "{ lib, config, ... }:\n{\n  plugins.snacks.settings = {\n    styles.input = {\n      relative = \"cursor\";\n      row = -3;\n      col = 0;\n      b.completion = lib.mkIf config.plugins.blink-cmp.enable true;\n    };\n    input = {\n      enabled = true;\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/snacks/picker.nix",
    "content": "{ pkgs, ... }:\n{\n  plugins.snacks.settings.picker = {\n    layout = \"telescope\";\n\n    layouts.telescope = {\n      reverse = false;\n      cycle = true;\n    };\n\n    # TODO: very long title is being truncated with defaults\n    layouts.select.layout.min_width = 120;\n\n    formatters.file.min_width = 60;\n\n    sources = {\n      files.hidden = true;\n      help.win.preview.wo.wrap = true;\n      buffers.current = false; # don't show current buffer\n    };\n\n    win.input.keys = {\n      \"<Esc>\" = {\n        __unkeyed-1 = \"close\";\n        mode = [\n          \"i\"\n          \"n\"\n        ];\n      };\n      \"<C-h>\" = {\n        __unkeyed-1 = \"toggle_hidden\";\n        mode = [\n          \"i\"\n          \"n\"\n        ];\n      };\n    };\n  };\n\n  extraPackages = with pkgs; [\n    ripgrep\n    fd\n    git\n  ];\n\n  keymaps = [\n    {\n      mode = \"n\";\n      key = \"<Leader>fz\";\n      action.__raw = \"function() Snacks.picker() end\";\n      options.desc = \"Find everything\";\n    }\n    {\n      mode = \"n\";\n      key = \"<Leader>ff\";\n      action.__raw = \"function() Snacks.picker.files() end\";\n      options.desc = \"Find files\";\n    }\n    {\n      mode = \"n\";\n      key = \"<Leader>fg\";\n      action.__raw = \"function() Snacks.picker.grep() end\";\n      options.desc = \"Grep from project\";\n    }\n    {\n      mode = \"n\";\n      key = \"<Leader>fc\";\n      action.__raw = \"function() Snacks.picker.git_log() end\";\n      options.desc = \"Git commits log\";\n    }\n    {\n      mode = \"n\";\n      key = \"<Leader>fb\";\n      action.__raw = \"function() Snacks.picker.buffers() end\";\n      options.desc = \"Find from buffers\";\n    }\n    {\n      mode = \"n\";\n      key = \"<Leader>fh\";\n      action.__raw = \"function() Snacks.picker.help() end\";\n      options.desc = \"Find help pages\";\n    }\n    {\n      mode = \"n\";\n      key = \"<Leader>fk\";\n      action.__raw = \"function() Snacks.picker.keymaps() end\";\n      options.desc = \"Find keymaps\";\n    }\n    {\n      mode = \"n\";\n      key = \"<Leader>fe\";\n      action.__raw = \"function() Snacks.picker.diagnostics_buffer() end\";\n      options.desc = \"Find buffer diagnostics\";\n    }\n  ];\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/toggleterm/default.nix",
    "content": "{\n  config = {\n    plugins.toggleterm = {\n      enable = true;\n      settings = {\n        open_mapping = \"[[<C-t>]]\";\n        direction = \"float\";\n        insert_mapping = false;\n        shade_terminals = false;\n        highlights = {\n          FloatBorder.link = \"FloatBorder\";\n          NormalFloat.link = \"NormalFloat\";\n        };\n        float_opts = {\n          border = \"curved\";\n          width.__raw = ''\n            function()\n              return math.ceil(vim.o.columns * 0.8)\n            end\n          '';\n          height.__raw = ''\n            function()\n              return math.ceil((vim.o.lines - 2) * 0.8)\n            end\n          '';\n        };\n      };\n    };\n\n  };\n}\n"
  },
  {
    "path": "home/budiman/config/neovim/plugins/treesitter/default.nix",
    "content": "{\n  config.plugins.treesitter = {\n    enable = true;\n    nixvimInjections = true;\n    settings = {\n      highlight = {\n        enable = true;\n        disable.__raw = ''\n          function(_, bufnr)\n            if string.find(vim.bo.filetype, 'chezmoitmpl') then\n              return true\n            end\n\n            return vim.api.nvim_buf_line_count(bufnr) > 50000\n          end\n        '';\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/default.nix",
    "content": "{\n  hostname,\n  myPkgs,\n  config,\n  ...\n}:\n{\n  # host specific config\n  imports = [ ./hosts/${hostname}.nix ];\n\n  # global config applied to all hosts\n  config = {\n    myHome = {\n      editor.neovim = {\n        enable = true;\n        package = myPkgs.neovim;\n      };\n\n      shell.git = {\n        enable = true;\n        config = {\n          commit = {\n            template = \"${./config/gitcommit-message}\";\n            gpgSign = true;\n          };\n          user = {\n            name = \"budimanjojo\";\n            email = \"budimanjojo@gmail.com\";\n            signingKey = \"${config.home.homeDirectory}/.ssh/id_rsa.pub\";\n          };\n          gpg = {\n            format = \"ssh\";\n          };\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/hosts/budimanjojo-firewall.nix",
    "content": "{ ... }:\n{\n  imports = [ ../profiles/server.nix ];\n}\n"
  },
  {
    "path": "home/budiman/hosts/budimanjojo-main.nix",
    "content": "{ pkgs, ... }:\n{\n  imports = [\n    ../profiles/workstation-hyprland.nix\n    ../profiles/extra-gaming.nix\n    ../profiles/extra-graphics.nix\n    ../profiles/extra-utilities.nix\n  ];\n\n  config = {\n    myHome = {\n      browser.firefox.profiles = {\n        \"budiman\" = {\n          id = 0;\n          name = \"budiman\";\n          isDefault = true;\n          extensions.packages = with pkgs.nur.repos.rycee.firefox-addons; [\n            bitwarden\n            tokyo-night-v2\n          ];\n        };\n        \"lina\" = {\n          id = 1;\n          name = \"lina\";\n          isDefault = false;\n          extensions.packages = with pkgs.nur.repos.rycee.firefox-addons; [\n            bitwarden\n            tokyo-night-v2\n          ];\n        };\n      };\n      programs.qmk.enable = true;\n    };\n\n    home.packages = [ pkgs.hugo ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/hosts/budimanjojo-nas.nix",
    "content": "{ ... }:\n{\n  imports = [ ../profiles/server.nix ];\n}\n"
  },
  {
    "path": "home/budiman/hosts/budimanjojo-oracle.nix",
    "content": "{ ... }:\n{\n  imports = [ ../profiles/server.nix ];\n}\n"
  },
  {
    "path": "home/budiman/hosts/budimanjojo-ubuntu.nix",
    "content": "{ pkgs, inputs, ... }:\n{\n  config = {\n    myHome = {\n      programs = {\n        chezmoi.enable = true;\n        go.enable = true;\n        qmk.enable = true;\n      };\n      multiplexer.tmux.enable = true;\n      homelab.kubernetes.enable = true;\n      shell.lf.enable = true;\n      shell.fish.enable = true;\n      terminal-emulator.contour.enable = true;\n      terminal-emulator.alacritty.enable = true;\n    };\n\n    home.packages = [ pkgs.hugo ];\n\n    targets.genericLinux = {\n      # this add nix installed desktop files to be shown in application menu\n      enable = true;\n      nixGL.packages = inputs.nixgl.packages;\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/profiles/extra-gaming.nix",
    "content": "{ pkgs, ... }:\n{\n  config = {\n    home.packages = with pkgs; [\n      (retroarch.withCores (\n        cores: with cores; [\n          mesen\n          fceumm\n        ]\n      ))\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/profiles/extra-graphics.nix",
    "content": "{ pkgs, ... }:\n{\n  config.home.packages = with pkgs; [\n    gimp\n    inkscape\n  ];\n}\n"
  },
  {
    "path": "home/budiman/profiles/extra-utilities.nix",
    "content": "{ pkgs, ... }:\n{\n  config.home.packages = with pkgs; [\n    gparted\n    eog\n    totem\n    rhythmbox\n    jellyfin-media-player\n  ];\n}\n"
  },
  {
    "path": "home/budiman/profiles/server.nix",
    "content": "{ ... }:\n{\n  config = {\n    myHome = {\n      shell = {\n        fish.enable = true;\n      };\n      multiplexer.tmux.enable = true;\n    };\n  };\n}\n"
  },
  {
    "path": "home/budiman/profiles/workstation-common.nix",
    "content": "{ pkgs, ... }:\n{\n  config = {\n    myHome = {\n      shell = {\n        fish.enable = true;\n        lf.enable = true;\n      };\n      multiplexer.tmux.enable = true;\n      browser.firefox.enable = true;\n      services.opencloud-client.enable = true;\n      homelab.kubernetes.enable = true;\n      programs.go.enable = true;\n    };\n\n    home.packages = with pkgs; [\n      geany\n      libreoffice-fresh\n      discord\n    ];\n  };\n}\n"
  },
  {
    "path": "home/budiman/profiles/workstation-hyprland.nix",
    "content": "{ ... }:\n{\n  imports = [ ./workstation-common.nix ];\n\n  config.myHome.windowmanager.hyprland.enable = true;\n}\n"
  },
  {
    "path": "home/budiman/profiles/workstation-i3.nix",
    "content": "{ ... }:\n{\n  imports = [ ./workstation-common.nix ];\n\n  config.myHome.windowmanager.i3.enable = true;\n}\n"
  },
  {
    "path": "home/budiman/profiles/workstation-sway.nix",
    "content": "{ ... }:\n{\n  imports = [ ./workstation-common.nix ];\n\n  config.myHome.windowmanager.sway.enable = true;\n}\n"
  },
  {
    "path": "lib/default.nix",
    "content": "{ lib, ... }:\nrec {\n  # systemEnabled returns the value of `osConfig.module`\n  # returns false if `module` doesn't exist in the `osConfig`\n  # will panic if `osConfig.module` is not type of bool\n  # I use this to determine if the home module should be enabled depending on the system module\n  # for example: `systemEnabled \"programs.hyprland.enable\" osConfig`\n  systemEnabled =\n    module: osConfig:\n    if (isNixos osConfig) then\n      let\n        splitted = lib.splitString \".\" module;\n        val = lib.attrByPath splitted false osConfig;\n      in\n      if (builtins.isBool val) then val else builtins.abort \"osConfig.${module} is not type of bool\"\n    else\n      false;\n\n  # copyFromSystem returns the value of `osConfig.module`\n  # returns empty attrset if system is not NixOS\n  # I use this to set shared system and home modules automatically\n  # for example: `config.mySharedModule = copyFromSystem \"mySharedModule\" osConfig`\n  copyFromSystem =\n    module: osConfig:\n    if (isNixos osConfig) then\n      let\n        splitted = lib.splitString \".\" module;\n        val = lib.getAttrFromPath splitted osConfig;\n      in\n      val\n    else\n      { };\n\n  # isAdminUser returns true if the username is equal to `osConfig.mySystem.adminUser`\n  isAdminUser =\n    username: osConfig: if (isNixos osConfig) then osConfig.mySystem.adminUser == username else false;\n\n  # isNixos returns true if the provided `osConfig` is not an empty attrset\n  isNixos = set: set != { };\n}\n"
  },
  {
    "path": "overlays/default.nix",
    "content": "{ inputs, config, ... }:\n{\n  # NUR pkgs set (declared in the flake inputs) will be accessible\n  # through `pkgs.nur`\n  nur = inputs.nur.overlays.default;\n\n  # nixGL pkgs set (declared in the flake inputs) will be accessible\n  # through `pkgs.nixgl`\n  nixgl = inputs.nixgl.overlay;\n\n  # The unstable nixpkgs set (declared in the flake inputs) will\n  # be accessible through `pkgs.unstable`\n  unstable-packages = final: prev: {\n    unstable = import inputs.nixpkgs-unstable {\n      localSystem = final.stdenv.hostPlatform;\n      config = config;\n      overlays = [\n        # overlays of unstable packages are declared here\n      ];\n    };\n  };\n\n  # Your own overlays for stable nixpkgs should be declared here\n  nixpkgs-overlays = final: prev: {\n    # vimPlugins = prev.vimPlugins // {\n    #   # this version have fzf integration added\n    #   catppuccin-nvim = prev.vimPlugins.catppuccin-nvim.overrideAttrs (oldAttrs: {\n    #     version = \"2024-08-20\";\n    #     src = prev.fetchFromGitHub {\n    #       owner = \"catppuccin\";\n    #       repo = \"nvim\";\n    #       rev = \"4fd72a9ab64b393c2c22b168508fd244877fec96\";\n    #       sha256 = \"sha256-aNmnn7Ym3+OnuvSgpke6rw4AkoVfNCpbjV71JF1c9rs=\";\n    #     };\n    #   });\n    # };\n    # talosctl = prev.talosctl.override {\n    #   buildGoModule =\n    #     args:\n    #     prev.buildGoModule (\n    #       args\n    #       // {\n    #         version = \"1.5.1\";\n    #         src = prev.fetchFromGitHub {\n    #           owner = \"siderolabs\";\n    #           repo = \"talos\";\n    #           rev = \"v1.5.1\";\n    #           hash = \"sha256-HYIk1oZbtcnHLap+4AMwoQN0k44zjiiwDzGcNW+9qqM=\";\n    #         };\n    #         vendorHash = \"sha256-Aefwa8zdKWV9TE9rwNA4pzKZekTurkD0pTDm3QfKdUQ=\";\n    #       }\n    #     );\n    # };\n    # nil = prev.nil.overrideAttrs (old: rec {\n    #   pname = \"nil\";\n    #   version = \"2023-03-01\";\n    #   src = prev.fetchFromGitHub {\n    #     owner = \"oxalica\";\n    #     repo = \"nil\";\n    #     rev = \"2023-03-01\";\n    #     hash = \"sha256-HGd/TV8ZHVAVBx+ndrxAfS/Nz+VHOQjNWjtKkkgYkqA=\";\n    #   };\n    #   CFG_RELEASE = version;\n    #\n    #   cargoDeps = old.cargoDeps.overrideAttrs (_: {\n    #     name = \"${pname}-vendor.tar.gz\";\n    #     inherit src CFG_RELEASE;\n    #     outputHash = \"sha256-0OEX4XxC2lkccFng4bQUoAyFbv1snJT2Ku7tUVbx4BU=\";\n    #   });\n    # });\n    # wlroots_0_16 = prev.wlroots_0_16.overrideAttrs (old: {\n    #   src = prev.fetchFromGitLab {\n    #     domain = \"gitlab.freedesktop.org\";\n    #     owner = \"wlroots\";\n    #     repo = \"wlroots\";\n    #     rev = \"0.16.1\";\n    #     hash = \"sha256-UyPN7zmytre4emwx/ztZ4JefXHwixPV6UEEqnhSLbIY=\";\n    #   };\n    # });\n  };\n}\n"
  },
  {
    "path": "packages/_sources/generated.json",
    "content": "{\n    \"abbreviation-tips\": {\n        \"cargoLock\": null,\n        \"date\": null,\n        \"extract\": null,\n        \"name\": \"abbreviation-tips\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"gazorby\",\n            \"repo\": \"fish-abbreviation-tips\",\n            \"rev\": \"v0.7.0\",\n            \"sha256\": \"sha256-F1t81VliD+v6WEWqj1c1ehFBXzqLyumx5vV46s/FZRU=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"v0.7.0\"\n    },\n    \"chezmoi-vim\": {\n        \"cargoLock\": null,\n        \"date\": \"2025-10-31\",\n        \"extract\": null,\n        \"name\": \"chezmoi-vim\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"alker0\",\n            \"repo\": \"chezmoi.vim\",\n            \"rev\": \"73b30df35c6b645ebd2e6a440eea8463ef3c3f47\",\n            \"sha256\": \"sha256-4gnY60CZUrVgqYhmyBdE4mTD7D4iqphpSgKWxl+UlzA=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"73b30df35c6b645ebd2e6a440eea8463ef3c3f47\"\n    },\n    \"fish-completion-sync\": {\n        \"cargoLock\": null,\n        \"date\": \"2025-03-09\",\n        \"extract\": null,\n        \"name\": \"fish-completion-sync\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"iynaix\",\n            \"repo\": \"fish-completion-sync\",\n            \"rev\": \"4f058ad2986727a5f510e757bc82cbbfca4596f0\",\n            \"sha256\": \"sha256-kHpdCQdYcpvi9EFM/uZXv93mZqlk1zCi2DRhWaDyK5g=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"4f058ad2986727a5f510e757bc82cbbfca4596f0\"\n    },\n    \"guihua-lua\": {\n        \"cargoLock\": null,\n        \"date\": \"2026-04-28\",\n        \"extract\": null,\n        \"name\": \"guihua-lua\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"ray-x\",\n            \"repo\": \"guihua.lua\",\n            \"rev\": \"7c364432c2f9153ed068f4eab1989edd9f3fd302\",\n            \"sha256\": \"sha256-rgxqLQf7psUtXwnFOiDBt6CpMyaAMdz2pg3PKj12IzE=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"7c364432c2f9153ed068f4eab1989edd9f3fd302\"\n    },\n    \"k8s-snippets\": {\n        \"cargoLock\": null,\n        \"date\": \"2025-11-13\",\n        \"extract\": null,\n        \"name\": \"k8s-snippets\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"budimanjojo\",\n            \"repo\": \"k8s-snippets\",\n            \"rev\": \"d253da00664caa43584d8e245d25992650827dee\",\n            \"sha256\": \"sha256-cq9ZCFwbMrxkdIrxuZwXN+KLfr2BYX+RdcPBkB7FIRQ=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"d253da00664caa43584d8e245d25992650827dee\"\n    },\n    \"luasnip\": {\n        \"cargoLock\": null,\n        \"date\": null,\n        \"extract\": null,\n        \"name\": \"luasnip\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"L3MON4D3\",\n            \"repo\": \"LuaSnip\",\n            \"rev\": \"v2.5.0\",\n            \"sha256\": \"sha256-diZO1on0rlSp6XuNGN2lNa85rhkNe1QQOejJD+LKkZk=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"v2.5.0\"\n    },\n    \"mason-lspconfig-nvim\": {\n        \"cargoLock\": null,\n        \"date\": \"2026-04-23\",\n        \"extract\": null,\n        \"name\": \"mason-lspconfig-nvim\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"williamboman\",\n            \"repo\": \"mason-lspconfig.nvim\",\n            \"rev\": \"0c2823e0418f3d9230ff8b201c976e84de1cb401\",\n            \"sha256\": \"sha256-wWoRUg2nvmqaEWxjYEOk1q+jQyKupgJi2LubhewcVCw=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"0c2823e0418f3d9230ff8b201c976e84de1cb401\"\n    },\n    \"mason-tool-installer-nvim\": {\n        \"cargoLock\": null,\n        \"date\": \"2026-01-22\",\n        \"extract\": null,\n        \"name\": \"mason-tool-installer-nvim\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"WhoIsSethDaniel\",\n            \"repo\": \"mason-tool-installer.nvim\",\n            \"rev\": \"443f1ef8b5e6bf47045cb2217b6f748a223cf7dc\",\n            \"sha256\": \"sha256-OZGq2TKZx1+GSzrQAdk2fAUv3052NMfTkm5QdO+EXXk=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"443f1ef8b5e6bf47045cb2217b6f748a223cf7dc\"\n    },\n    \"oil-nvim\": {\n        \"cargoLock\": null,\n        \"date\": \"2026-02-23\",\n        \"extract\": null,\n        \"name\": \"oil-nvim\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"stevearc\",\n            \"repo\": \"oil.nvim\",\n            \"rev\": \"0fcc83805ad11cf714a949c98c605ed717e0b83e\",\n            \"sha256\": \"sha256-hoTQoNEsCbZ0aZMUUUvgkC9NYjovjUUirw2FN9b9dn0=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"0fcc83805ad11cf714a949c98c605ed717e0b83e\"\n    },\n    \"tmux-fish\": {\n        \"cargoLock\": null,\n        \"date\": \"2025-04-07\",\n        \"extract\": null,\n        \"name\": \"tmux-fish\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"budimanjojo\",\n            \"repo\": \"tmux.fish\",\n            \"rev\": \"db0030b7f4f78af4053dc5c032c7512406961ea5\",\n            \"sha256\": \"sha256-rRibn+FN8VNTSC1HmV05DXEa6+3uOHNx03tprkcjjs8=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"db0030b7f4f78af4053dc5c032c7512406961ea5\"\n    },\n    \"tokyonight-gtk-theme\": {\n        \"cargoLock\": null,\n        \"date\": \"2025-10-23\",\n        \"extract\": null,\n        \"name\": \"tokyonight-gtk-theme\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"Fausto-Korpsvart\",\n            \"repo\": \"Tokyo-Night-GTK-Theme\",\n            \"rev\": \"6c340e058e84c1975a038a8e5d1e384477225dc0\",\n            \"sha256\": \"sha256-7H2n9wTaW8Db1RejWK071ITV1j5KIuzfql0Tx9WT6zM=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"6c340e058e84c1975a038a8e5d1e384477225dc0\"\n    },\n    \"tokyonight-icon-theme\": {\n        \"cargoLock\": null,\n        \"date\": \"2025-10-23\",\n        \"extract\": null,\n        \"name\": \"tokyonight-icon-theme\",\n        \"passthru\": null,\n        \"pinned\": false,\n        \"src\": {\n            \"deepClone\": false,\n            \"fetchSubmodules\": false,\n            \"leaveDotGit\": false,\n            \"name\": null,\n            \"owner\": \"Fausto-Korpsvart\",\n            \"repo\": \"Tokyo-Night-GTK-Theme\",\n            \"rev\": \"6c340e058e84c1975a038a8e5d1e384477225dc0\",\n            \"sha256\": \"sha256-7H2n9wTaW8Db1RejWK071ITV1j5KIuzfql0Tx9WT6zM=\",\n            \"sparseCheckout\": [],\n            \"type\": \"github\"\n        },\n        \"version\": \"6c340e058e84c1975a038a8e5d1e384477225dc0\"\n    }\n}"
  },
  {
    "path": "packages/_sources/generated.nix",
    "content": "# This file was generated by nvfetcher, please do not modify it manually.\n{\n  fetchgit,\n  fetchurl,\n  fetchFromGitHub,\n  dockerTools,\n}:\n{\n  abbreviation-tips = {\n    pname = \"abbreviation-tips\";\n    version = \"v0.7.0\";\n    src = fetchFromGitHub {\n      owner = \"gazorby\";\n      repo = \"fish-abbreviation-tips\";\n      rev = \"v0.7.0\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-F1t81VliD+v6WEWqj1c1ehFBXzqLyumx5vV46s/FZRU=\";\n    };\n  };\n  chezmoi-vim = {\n    pname = \"chezmoi-vim\";\n    version = \"73b30df35c6b645ebd2e6a440eea8463ef3c3f47\";\n    src = fetchFromGitHub {\n      owner = \"alker0\";\n      repo = \"chezmoi.vim\";\n      rev = \"73b30df35c6b645ebd2e6a440eea8463ef3c3f47\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-4gnY60CZUrVgqYhmyBdE4mTD7D4iqphpSgKWxl+UlzA=\";\n    };\n    date = \"2025-10-31\";\n  };\n  fish-completion-sync = {\n    pname = \"fish-completion-sync\";\n    version = \"4f058ad2986727a5f510e757bc82cbbfca4596f0\";\n    src = fetchFromGitHub {\n      owner = \"iynaix\";\n      repo = \"fish-completion-sync\";\n      rev = \"4f058ad2986727a5f510e757bc82cbbfca4596f0\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-kHpdCQdYcpvi9EFM/uZXv93mZqlk1zCi2DRhWaDyK5g=\";\n    };\n    date = \"2025-03-09\";\n  };\n  guihua-lua = {\n    pname = \"guihua-lua\";\n    version = \"7c364432c2f9153ed068f4eab1989edd9f3fd302\";\n    src = fetchFromGitHub {\n      owner = \"ray-x\";\n      repo = \"guihua.lua\";\n      rev = \"7c364432c2f9153ed068f4eab1989edd9f3fd302\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-rgxqLQf7psUtXwnFOiDBt6CpMyaAMdz2pg3PKj12IzE=\";\n    };\n    date = \"2026-04-28\";\n  };\n  k8s-snippets = {\n    pname = \"k8s-snippets\";\n    version = \"d253da00664caa43584d8e245d25992650827dee\";\n    src = fetchFromGitHub {\n      owner = \"budimanjojo\";\n      repo = \"k8s-snippets\";\n      rev = \"d253da00664caa43584d8e245d25992650827dee\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-cq9ZCFwbMrxkdIrxuZwXN+KLfr2BYX+RdcPBkB7FIRQ=\";\n    };\n    date = \"2025-11-13\";\n  };\n  luasnip = {\n    pname = \"luasnip\";\n    version = \"v2.5.0\";\n    src = fetchFromGitHub {\n      owner = \"L3MON4D3\";\n      repo = \"LuaSnip\";\n      rev = \"v2.5.0\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-diZO1on0rlSp6XuNGN2lNa85rhkNe1QQOejJD+LKkZk=\";\n    };\n  };\n  mason-lspconfig-nvim = {\n    pname = \"mason-lspconfig-nvim\";\n    version = \"0c2823e0418f3d9230ff8b201c976e84de1cb401\";\n    src = fetchFromGitHub {\n      owner = \"williamboman\";\n      repo = \"mason-lspconfig.nvim\";\n      rev = \"0c2823e0418f3d9230ff8b201c976e84de1cb401\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-wWoRUg2nvmqaEWxjYEOk1q+jQyKupgJi2LubhewcVCw=\";\n    };\n    date = \"2026-04-23\";\n  };\n  mason-tool-installer-nvim = {\n    pname = \"mason-tool-installer-nvim\";\n    version = \"443f1ef8b5e6bf47045cb2217b6f748a223cf7dc\";\n    src = fetchFromGitHub {\n      owner = \"WhoIsSethDaniel\";\n      repo = \"mason-tool-installer.nvim\";\n      rev = \"443f1ef8b5e6bf47045cb2217b6f748a223cf7dc\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-OZGq2TKZx1+GSzrQAdk2fAUv3052NMfTkm5QdO+EXXk=\";\n    };\n    date = \"2026-01-22\";\n  };\n  oil-nvim = {\n    pname = \"oil-nvim\";\n    version = \"0fcc83805ad11cf714a949c98c605ed717e0b83e\";\n    src = fetchFromGitHub {\n      owner = \"stevearc\";\n      repo = \"oil.nvim\";\n      rev = \"0fcc83805ad11cf714a949c98c605ed717e0b83e\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-hoTQoNEsCbZ0aZMUUUvgkC9NYjovjUUirw2FN9b9dn0=\";\n    };\n    date = \"2026-02-23\";\n  };\n  tmux-fish = {\n    pname = \"tmux-fish\";\n    version = \"db0030b7f4f78af4053dc5c032c7512406961ea5\";\n    src = fetchFromGitHub {\n      owner = \"budimanjojo\";\n      repo = \"tmux.fish\";\n      rev = \"db0030b7f4f78af4053dc5c032c7512406961ea5\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-rRibn+FN8VNTSC1HmV05DXEa6+3uOHNx03tprkcjjs8=\";\n    };\n    date = \"2025-04-07\";\n  };\n  tokyonight-gtk-theme = {\n    pname = \"tokyonight-gtk-theme\";\n    version = \"6c340e058e84c1975a038a8e5d1e384477225dc0\";\n    src = fetchFromGitHub {\n      owner = \"Fausto-Korpsvart\";\n      repo = \"Tokyo-Night-GTK-Theme\";\n      rev = \"6c340e058e84c1975a038a8e5d1e384477225dc0\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-7H2n9wTaW8Db1RejWK071ITV1j5KIuzfql0Tx9WT6zM=\";\n    };\n    date = \"2025-10-23\";\n  };\n  tokyonight-icon-theme = {\n    pname = \"tokyonight-icon-theme\";\n    version = \"6c340e058e84c1975a038a8e5d1e384477225dc0\";\n    src = fetchFromGitHub {\n      owner = \"Fausto-Korpsvart\";\n      repo = \"Tokyo-Night-GTK-Theme\";\n      rev = \"6c340e058e84c1975a038a8e5d1e384477225dc0\";\n      fetchSubmodules = false;\n      sha256 = \"sha256-7H2n9wTaW8Db1RejWK071ITV1j5KIuzfql0Tx9WT6zM=\";\n    };\n    date = \"2025-10-23\";\n  };\n}\n"
  },
  {
    "path": "packages/configure-gtk/default.nix",
    "content": "{ pkgs }:\npkgs.writeTextFile {\n  name = \"configure-gtk\";\n  destination = \"/bin/configure-gtk\";\n  executable = true;\n  text =\n    let\n      schema = pkgs.gsettings-desktop-schemas;\n      datadir = \"${schema}/share/gsettings-schemas/${schema.name}\";\n    in\n    \"\n    export XDG_DATA_DIRS=${datadir}:$XDG_DATA_DIRS\n    gsettings set org.gnome.desktop.interface gtk-theme 'Tokyonight-Dark-B'\n    gsettings set org.gnome.desktop.interface icon-theme 'Tokyonight-Dark'\n    gsettings set org.gnome.desktop.interface cursor-theme 'Vimix Cursors'\n    gsettings set org.gnome.desktop.interface font-name 'UbuntuMono Nerd Font 12'\n    gsettings set org.gnome.desktop.wm.preferences button-layout ':appmenu'\n  \";\n}\n"
  },
  {
    "path": "packages/default.nix",
    "content": "{\n  pkgs,\n  inputs',\n  self',\n  ...\n}:\n{\n  talhelper = inputs'.talhelper.packages.default;\n  configure-gtk = pkgs.callPackage ./configure-gtk/default.nix { };\n  tokyonight-gtk-theme = pkgs.callPackage ./tokyonight-gtk-theme/default.nix { };\n  tokyonight-icon-theme = pkgs.callPackage ./tokyonight-icon-theme/default.nix { };\n  nvim-plugins = pkgs.callPackage ./nvim-plugins/default.nix { };\n  fish-plugins = pkgs.fishPlugins.callPackage ./fish-plugins/default.nix { };\n  # krr = pkgs.callPackage ./krr/default.nix { };\n  kubectl-rook-ceph = pkgs.callPackage ./kubectl-rook-ceph/default.nix { };\n  neovim = inputs'.nixvim.legacyPackages.makeNixvimWithModule {\n    # make nixvim use the same pkgs with my overlays added\n    inherit pkgs;\n    extraSpecialArgs = {\n      myPkgs = self'.legacyPackages;\n    };\n    module.imports = [ ../home/budiman/config/neovim ];\n  };\n}\n"
  },
  {
    "path": "packages/fish-plugins/default.nix",
    "content": "{ callPackage, buildFishPlugin }:\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\nin\n{\n  abbreviation-tips = buildFishPlugin { inherit (sourceData.abbreviation-tips) pname src version; };\n  tmux-fish = buildFishPlugin {\n    inherit (sourceData.tmux-fish) pname src;\n    version = sourceData.tmux-fish.date;\n  };\n  fish-completion-sync = buildFishPlugin {\n    inherit (sourceData.fish-completion-sync) pname src version;\n  };\n}\n"
  },
  {
    "path": "packages/krr/about-time.nix",
    "content": "{\n  lib,\n  python3,\n  callPackage,\n  ...\n}:\n\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\nin\n\npython3.pkgs.buildPythonPackage {\n  inherit (sourceData.about-time) pname version src;\n\n  doCheck = false;\n\n  meta = with lib; {\n    description = \"Easily measure timing and throughput of code blocks, with beautiful human friendly representations\";\n    homepage = \"https://github.com/rsalmei/about-time\";\n    licence = licences.mit;\n  };\n}\n"
  },
  {
    "path": "packages/krr/alive-progress.nix",
    "content": "{\n  lib,\n  python3,\n  pkgs,\n  callPackage,\n  ...\n}:\n\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\n  about-time = callPackage ./about-time.nix { };\nin\n\npython3.pkgs.buildPythonPackage {\n  inherit (sourceData.alive-progress) pname version src;\n\n  doCheck = false;\n\n  propagatedBuildInputs = with pkgs.python3Packages; [\n    about-time\n    grapheme\n  ];\n\n  meta = with lib; {\n    description = \"A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!\";\n    homepage = \"https://github.com/rsalmei/alive-progress\";\n    licence = licences.mit;\n  };\n}\n"
  },
  {
    "path": "packages/krr/default.nix",
    "content": "{\n  lib,\n  python3,\n  callPackage,\n  pkgs,\n}:\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\n  prometheus-api-client = callPackage ./prometheus-api-client.nix { };\n  alive-progress = callPackage ./alive-progress.nix { };\nin\n\npython3.pkgs.buildPythonPackage {\n  inherit (sourceData.krr) pname version src;\n\n  format = \"pyproject\";\n\n  postPatch = ''\n    substituteInPlace pyproject.toml \\\n      --replace \"pydantic = \\\"1.10.7\\\"\" \"pydantic = \\\">=1.10.7\\\"\" \\\n      --replace \"typer = {extras = [\\\"all\\\"], version = \\\"^0.7.0\\\"}\" \"typer = {extras = [\\\"all\\\"], version = \\\">=0.7.0\\\"}\"\n  '';\n\n  propagatedBuildInputs = with pkgs.python3Packages; [\n    alive-progress\n    cachetools\n    certifi\n    charset-normalizer\n    click\n    colorama\n    commonmark\n    contourpy\n    cycler\n    dateparser\n    fonttools\n    google-auth\n    httmock\n    idna\n    kiwisolver\n    kubernetes\n    matplotlib\n    numpy\n    oauthlib\n    packaging\n    pandas\n    pillow\n    prometheus-api-client\n    poetry-core\n    pyasn1-modules\n    pyasn1\n    pydantic\n    pygments\n    pyparsing\n    python-dateutil\n    pytz-deprecation-shim\n    pytz\n    pyyaml\n    regex\n    requests-oauthlib\n    requests\n    rich\n    rsa\n    setuptools\n    shellingham\n    six\n    typer\n    typing-extensions\n    tzdata\n    tzlocal\n    urllib3\n    websocket-client\n  ];\n\n  meta = with lib; {\n    description = \"Prometheus-based Kubernetes Resource Recommendations\";\n    homepage = \"https://github.com/robusta-dev/krr\";\n    licence = licences.mit;\n  };\n}\n"
  },
  {
    "path": "packages/krr/prometheus-api-client.nix",
    "content": "{\n  lib,\n  python3,\n  pkgs,\n  callPackage,\n  ...\n}:\n\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\nin\n\npython3.pkgs.buildPythonPackage {\n  inherit (sourceData.prometheus-api-client) pname version src;\n\n  doCheck = false;\n\n  propagatedBuildInputs = with pkgs.python3Packages; [\n    matplotlib\n    numpy\n    pandas\n    requests\n    dateparser\n    httmock\n  ];\n\n  meta = with lib; {\n    description = \"A python wrapper for the prometheus http api\";\n    homepage = \"https://github.com/4n4nd/prometheus-api-client-python\";\n    licence = licences.mit;\n  };\n}\n"
  },
  {
    "path": "packages/kubectl-rook-ceph/default.nix",
    "content": "{\n  buildGoModule,\n  fetchFromGitHub,\n  installShellFiles,\n  lib,\n}:\n\nbuildGoModule rec {\n  pname = \"kubectl-rook-ceph\";\n  version = \"v0.5.2\";\n\n  src = fetchFromGitHub {\n    owner = \"rook\";\n    repo = pname;\n    rev = version;\n    sha256 = \"sha256-fRkC1rr+jFZ6xp1aU1vxqLqW1OTeZgyJPwC+FIeKFcc=\";\n  };\n\n  vendorHash = \"sha256-D1k4+1PsBMtGwYVDacrLOSKUhWLOIY/KIooIt7Qo/QE=\";\n\n  nativeBuildInputs = [ installShellFiles ];\n\n  subPackages = [ \"cmd\" ];\n\n  postInstall = ''\n    mv $out/bin/cmd $out/bin/kubectl-rook_ceph\n\n    # Shell completion for `kubectl rook-ceph`\n    # Quite ugly but it works, see: https://github.com/kubernetes/kubernetes/pull/105867\n    cat <<EOF >$out/bin/kubectl_complete-rook_ceph\n    #!/usr/bin/env sh\n\n    kubectl rook-ceph __complete \"\\$@\"\n    EOF\n\n    chmod u+x $out/bin/kubectl_complete-rook_ceph\n\n    # This is the more elegant way of doing it but it doesn't work\n    # for shell in bash fish zsh; do\n    #   $out/bin/kubectl-rook_ceph completion $shell > kubectl-rook_ceph.$shell\n    #   installShellCompletion  kubectl-rook_ceph.$shell\n    # done\n  '';\n\n  meta = with lib; {\n    description = \"kubectl plugin to run kubectl commands with rook-ceph\";\n    homepage = \"https://github.com/rook/kubectl-rook-ceph\";\n    licence = licences.asl20;\n  };\n}\n"
  },
  {
    "path": "packages/nvfetcher.toml",
    "content": "[tokyonight-gtk-theme]\nsrc.git = \"https://github.com/Fausto-Korpsvart/Tokyo-Night-GTK-Theme\"\nfetch.github = \"Fausto-Korpsvart/Tokyo-Night-GTK-Theme\"\n[tokyonight-icon-theme]\nsrc.git = \"https://github.com/Fausto-Korpsvart/Tokyo-Night-GTK-Theme\"\nfetch.github = \"Fausto-Korpsvart/Tokyo-Night-GTK-Theme\"\n[chezmoi-vim]\nsrc.git = \"https://github.com/alker0/chezmoi.vim\"\nfetch.github = \"alker0/chezmoi.vim\"\n[mason-lspconfig-nvim]\nsrc.git = \"https://github.com/williamboman/mason-lspconfig.nvim\"\nfetch.github = \"williamboman/mason-lspconfig.nvim\"\n[mason-tool-installer-nvim]\nsrc.git = \"https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim\"\nfetch.github = \"WhoIsSethDaniel/mason-tool-installer.nvim\"\n[oil-nvim]\nsrc.git = \"https://github.com/stevearc/oil.nvim\"\nfetch.github = \"stevearc/oil.nvim\"\n[luasnip]\nsrc.github = \"L3MON4D3/LuaSnip\"\nfetch.github = \"L3MON4D3/LuaSnip\"\n[k8s-snippets]\nsrc.git = \"https://github.com/budimanjojo/k8s-snippets\"\nfetch.github = \"budimanjojo/k8s-snippets\"\n[guihua-lua]\nsrc.git = \"https://github.com/ray-x/guihua.lua\"\nfetch.github = \"ray-x/guihua.lua\"\n[abbreviation-tips]\nsrc.github_tag = \"gazorby/fish-abbreviation-tips\"\nfetch.github = \"gazorby/fish-abbreviation-tips\"\n[tmux-fish]\nsrc.git = \"https://github.com/budimanjojo/tmux.fish\"\nfetch.github = \"budimanjojo/tmux.fish\"\n[fish-completion-sync]\nsrc.git = \"https://github.com/iynaix/fish-completion-sync\"\nfetch.github = \"iynaix/fish-completion-sync\"\n"
  },
  {
    "path": "packages/nvim-plugins/default.nix",
    "content": "{\n  fetchFromGitHub,\n  pkgs,\n  callPackage,\n}:\n\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\nin\n\n{\n  chezmoi-vim = pkgs.vimUtils.buildVimPlugin {\n    inherit (sourceData.chezmoi-vim) pname src;\n    version = sourceData.chezmoi-vim.date;\n  };\n  mason-lspconfig-nvim = pkgs.vimUtils.buildVimPlugin {\n    inherit (sourceData.mason-lspconfig-nvim) pname src;\n    version = sourceData.mason-lspconfig-nvim.date;\n  };\n  mason-tool-installer-nvim = pkgs.vimUtils.buildVimPlugin {\n    inherit (sourceData.mason-tool-installer-nvim) pname src;\n    version = sourceData.mason-tool-installer-nvim.date;\n  };\n  oil-nvim = pkgs.vimUtils.buildVimPlugin {\n    inherit (sourceData.oil-nvim) pname src;\n    version = sourceData.oil-nvim.date;\n  };\n  luasnip = pkgs.vimUtils.buildVimPlugin { inherit (sourceData.luasnip) pname src version; };\n  k8s-snippets = pkgs.vimUtils.buildVimPlugin {\n    inherit (sourceData.k8s-snippets) pname src;\n    version = sourceData.k8s-snippets.date;\n  };\n  guihua-lua = pkgs.vimUtils.buildVimPlugin {\n    inherit (sourceData.guihua-lua) pname src;\n    version = sourceData.guihua-lua.date;\n    buildPhase = ''\n      (\n        cd lua/fzy\n        make\n      )\n    '';\n    # TODO: check failing and Idk why\n    nvimSkipModules = [ \"fzy.fzy-lua-native\" ];\n  };\n}\n"
  },
  {
    "path": "packages/tokyonight-gtk-theme/default.nix",
    "content": "{\n  stdenvNoCC,\n  lib,\n  callPackage,\n  pkgs,\n}:\n\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\nin\nstdenvNoCC.mkDerivation {\n  inherit (sourceData.tokyonight-gtk-theme) pname version src;\n\n  nativeBuildInputs = [\n    pkgs.gnome-shell\n    pkgs.sassc\n  ];\n\n  builtInputs = [ pkgs.gnome-themes-extra ];\n\n  dontBuild = true;\n\n  postPatch = ''\n    patchShebangs themes/install.sh\n  '';\n\n  installPhase = ''\n    runHook preInstall\n\n    mkdir -p $out/share/themes\n    cd themes\n    ./install.sh --dest $out/share/themes --name Tokyonight\n\n    runHook postInstall\n  '';\n\n  meta = with lib; {\n    description = \"A GTK theme based on the Tokyo Night colour palette\";\n    longDescription = ''\n            A GTK theme based on the colours of Folke's great theme: Tokyonight for Neovim, the VinceLiuice's awesome: Magnetic GTK theme and the creativity of Gusbemacbe's: Suru Plus Icon Theme.\n      Great to combine in your Gnome Desktop Environment and TWMs like: XmonadWM, AwesomeWM, BSPWM, etc... With support also for the desktop environments Cinnamon and XFCE.\n    '';\n    homepage = \"https://github.com/Fausto-Korpsvart/Tokyo-Night-GTK-Theme\";\n    license = licenses.gpl3Only;\n  };\n}\n"
  },
  {
    "path": "packages/tokyonight-icon-theme/default.nix",
    "content": "{\n  stdenvNoCC,\n  hicolor-icon-theme,\n  gtk3,\n  lib,\n  callPackage,\n}:\n\nlet\n  sourceData = callPackage ../_sources/generated.nix { };\nin\n\nstdenvNoCC.mkDerivation {\n  inherit (sourceData.tokyonight-icon-theme) pname version src;\n\n  nativeBuildInputs = [ gtk3 ];\n\n  propagatedBuildInputs = [ hicolor-icon-theme ];\n\n  dontDropIconThemeCache = true;\n\n  # These fixup steps are slow and unnecessary for this package\n  dontPatchELF = true;\n  dontRewriteSymlinks = true;\n\n  installPhase = ''\n    runHook preInstall\n\n    mkdir -p $out/share/icons\n    cp -r ./icons/Tokyonight-* $out/share/icons/\n\n    for theme in $out/share/icons/*; do\n      gtk-update-icon-cache $theme\n    done\n\n    runHook postInstall\n  '';\n\n  meta = with lib; {\n    description = \"A GTK theme based on the Tokyo Night colour palette\";\n    longDescription = ''\n      Tokyonight is a GTK theme based on the colour palette of the Tokyonight for Neovim by @Folke, the Graphite GTK theme by @VinceLiuice and the Suru Plus icons by @gusbemacbe.\n      The idea was born from the need for GTK themes that match the most prominent colour palettes of Neovim code editor and Tiling Window Manager, such as Xmonad, Awesome, DWM, etc, which use these colour schemes to give a uniform and unique look to working environments. See on Reddit: r/unixporn.\n      The colour palettes in this series of themes are the ones I have used the most in my setup for Neovim, Xmonad and Gnome DE, so creating themes started as something personal that I then decided to share thanks to several people asking me to share them because they seemed good, I hope you find them useful and make your desktops look good too.\n    '';\n    homepage = \"https://github.com/Fausto-Korpsvart/Tokyo-Night-GTK-Theme\";\n    license = licenses.gpl3Only;\n  };\n}\n"
  },
  {
    "path": "shell.nix",
    "content": "{ pkgs, ... }:\nwith pkgs;\nmkShell {\n  buildInputs = [\n    pkgs.just\n  ];\n}\n"
  },
  {
    "path": "system/_modules/_default/default.nix",
    "content": "{\n  config,\n  hostname,\n  pkgs,\n  lib,\n  ...\n}:\nlet\n  mySystem = config.mySystem;\nin\n{\n  imports = [\n    ./nix.nix\n    ./sops.nix\n    ./users.nix\n  ];\n\n  config = {\n    networking.hostName = hostname;\n\n    time.timeZone = \"Asia/Jakarta\";\n\n    security = {\n      sudo.wheelNeedsPassword = false;\n      polkit.enable = true;\n    };\n\n    services.displayManager.autoLogin.user = mySystem.adminUser;\n\n    catppuccin = {\n      flavor = \"mocha\";\n      accent = \"mauve\";\n    };\n\n    # do not change unless you know what you are doing\n    system.stateVersion = \"23.11\";\n    documentation.nixos.enable = false;\n  };\n}\n"
  },
  {
    "path": "system/_modules/_default/nix.nix",
    "content": "{ inputs, hostname, ... }:\n{\n  nix = {\n    # make `nix run` and `nix shell` use the same nixpkgs as the one used by this flake\n    # for example, to run `talosctl` from the `unstable` branch, we can run `nix shell unstable#talosctl`\n    registry = {\n      stable.flake = inputs.nixpkgs;\n      unstable.flake = inputs.nixpkgs-unstable;\n    };\n    channel.enable = false; # remove nix-channel related tools & configs, we use flakes instead\n\n    settings = {\n      # NIX_PATH is still used by many useful tools, so we set it to the same value as the one used by this flake\n      # make `nix repl '<nixpkgs>'` use the same nixpkgs as the one used by this flake\n      nix-path = \"nixpkgs=${inputs.nixpkgs.outPath}\";\n      experimental-features = [\n        \"nix-command\"\n        \"flakes\"\n      ];\n      substituters = [\n        \"https://viperml.cachix.org\"\n        \"https://budimanjojo.cachix.org\"\n      ];\n      trusted-public-keys = [\n        \"viperml.cachix.org-1:qZhKBMTfmcLL+OG6fj/hzsMEedgKvZVFRRAhq7j8Vh8=\"\n        \"budimanjojo.cachix.org-1:S0gy6IKTFXis9fFqEbVAS2zsvnZw/30NV2bWvGiN1YQ=\"\n      ];\n      auto-optimise-store = true;\n      keep-outputs = true;\n      keep-derivations = false;\n      # this make sure we always check for new commit when fetching source\n      tarball-ttl = 0;\n      trusted-users = [ \"@wheel\" ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/_default/secret.sops.yaml",
    "content": "adminPassword: ENC[AES256_GCM,data:sjzzXiIU7VbV9kc+f4npI90SFIfY0jAww4j414ZtfAUzwURgr3x0nA3+mou7MGeLKW3ZEl0qs3bEVUxU9mYm137Cyu4HP1wprw==,iv:wXqhbAtvoo7nnH8PKzqWLbEArmPa800Vh00guYb7yuU=,tag:Vsxe8B4CCXn+/5iSNWEHew==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvMWdiczk5OTlFVEs0Z3Mr\n            UEFvNkhxNDVYVUtCUTJzOTNIakk0M25xZERrCkkxQ1RuYnQ1TDRQRmVEbjVMcTBX\n            OStkb2N2cTRVLzNsMUdHWU8zYUNtV0UKLS0tIEJpWTBrS1lJZ2U4cEdxM3h1N0Rr\n            ZEE4OENBSGo5b280RTQyZTVuVUppWVEKwhWnxqdvHNpEVOc5Wy9kzRcdL7NlIdqp\n            dyQOdgNIRIpv6hg7TOtW5wzMI1FZpZXVil0JE0MIvwUk/oDpAFf3Aw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzSzNSRzMvRUFRMDE5OVR5\n            NlR6c2ZYdlk0RmgzL3ZNNFpJZk1RaVFHZFRjCjBMZlh3ZXhKN1lOczM3WGVIUDFD\n            cnhZR3Noc1loQ1NQZGVjaGY3VG1jWlEKLS0tIEVWcUwrTkJWVzBYR2ZnbTVkOWo3\n            MFNZUTBkS3J3MGNxR0Q3MlVyOVhHVG8KKJQE1nBZeQCktuEYOMDZVe4ybce7b9g/\n            Qkv23Qtdh5TLIeRiTxSTqktHOHLWKTT4V+Lsf5s9TylL4oYXJ0EngA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZEs4UWJpQXdGbFE0MTQ4\n            VmZ4T1VHV3VPUkhzcERhbDh5MXlObVJJRTNRClVodlc0TW15cGUvdTA5TGpOZkI0\n            UzdwOFpBb1VORFJhTUpWRi8wWmppUjQKLS0tIHdxYmFpREtnc2c3cC9MVXhNTXBM\n            d2ZBSVRxTG9icEh3eXh1M1hpR3dKK0EKc7jzKMYIlIobeoeB1JW0M4Jm0pGB/5MV\n            +2RU0tTK6fcgB7lP+E53HG5tG4pauPm87D7g046OxG9AVsEI+f0sBQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlbXJVcWRqa0ROQXNQdG5t\n            aUVEQ0hLbUxsWEg5dWNOZEtxbG90cmMxdVYwCkpoeWVrM2EvbHJuMGFHcFA0MEs0\n            RWFQWHhFaXdycHZoUzkyeDBFaTRwbm8KLS0tIHNUR2YvS0Q4YkRkOFpSelQ3RkQ0\n            MlNyMUtpQmk1ekllVTRwMm9sLzlOUGcKcS8D8ni7aiM79ahgr2WZF8wxU5hcX7y3\n            Jm2XIHfuiaCAptgwr0lsHDfV1iF89l1zK3IUsNvBajmJFNsNPcMBWg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXRFRDTk5vTEh6Rm9EWkl1\n            alYzcmYrazRsOG9hYXJoaWZTUkh0b0N4OHpJCnRIWktFNUVOZFV5L0Q3bFBhemZm\n            UkdESk15YXFJYzJ0cWtObGRzZzJDWkEKLS0tIFBHd2lLNWhTZ1FZWGttWlR4OC9i\n            Tk1PeG1jS3MwRk5jY0YvRFlkMU84T1EKFIzjYAKlUKfLZJNCN+skxOLwU1316dBB\n            RicAuX1l5UTBIbeZGdIG+QdckxC+NoKZ91tZyRNRseVteN7nORgQow==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFcnhVcW5iZ3dGaGFHUlF3\n            MFpBT3p6Z04rK1hkeWJPOUU3bjlxN0plUkg4Ck8wSlp4YUxCZjhaTDhaekU3NHRP\n            K0psb3ZjZ21nZEwvWmpzK1FGVWRIVUkKLS0tIDZZWUhsTjRkL1V3ZFBrTjV0ckZo\n            WDlhSXphRmFxM2kzWWU0MDBrc2tVdUEKOhJOI+1BfBOkh3HIcAM19d9jp6e7JkEG\n            DR5lZej75bO5R7mvJ2v7rwj074cbWywRiC2kbEo4Cp1CHREsgktjCQ==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2024-07-07T04:54:05Z\"\n    mac: ENC[AES256_GCM,data:9lMToM2IcNVqgaLL+ww+6ZnmuEr0Ve8NCuzevpuli1MhkLOEq/rFw2w6BPvlQYspRha7ovu9mJJsnRxPHIPAJV0lNjmj4oOCliewPtCWDN+tVyYbMwLpcN6CNvwm1/cH5IXeDG2lMzbYDoCcmyavJp9gZclcN6esy0FeSxJnUK0=,iv:9W3NU1DB6BnJhhGplxMY38zmHHVd1sjWQf4qv6vLr94=,tag:zU1oQmYU8UDnb3P+TwJGiA==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.8.1\n"
  },
  {
    "path": "system/_modules/_default/sops.nix",
    "content": "{ config, pkgs, ... }:\nlet\n  mySystem = config.mySystem;\nin\n{\n  config = {\n    environment.systemPackages = [\n      pkgs.sops\n      pkgs.age\n    ];\n    sops.age = {\n      keyFile = \"${config.users.users.${mySystem.adminUser}.home}/.config/sops/age/keys.txt\";\n      generateKey = true; # generate the key if it doesn't exist\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/_default/users.nix",
    "content": "{ config, pkgs, ... }:\n{\n  sops.secrets = {\n    adminPassword = {\n      sopsFile = ./secret.sops.yaml;\n      neededForUsers = true;\n    };\n  };\n\n  users = {\n    mutableUsers = false;\n    users.${config.mySystem.adminUser} = {\n      isNormalUser = true;\n      uid = 1000;\n      extraGroups = [ \"wheel\" ];\n      shell = pkgs.fish;\n      hashedPasswordFile = config.sops.secrets.adminPassword.path;\n    };\n  };\n\n  programs.fish.enable = true;\n}\n"
  },
  {
    "path": "system/_modules/containers/beeaccounting/default.nix",
    "content": "{ lib, config, ... }:\nwith lib;\nlet\n  mySystem = config.mySystem;\n  cfg = mySystem.containers.beeaccounting;\n  ghcr-login = {\n    username = \"budimanjojo\";\n    registry = \"ghcr.io\";\n    passwordFile = config.sops.secrets.budimanjojo-ghcr-pull-token.path;\n  };\n  userCfg = config.users.users.${mySystem.adminUser};\nin\n{\n  options.mySystem.containers.beeaccounting = {\n    enable = mkEnableOption \"Beeaccounting\";\n  };\n\n  config = mkIf cfg.enable {\n    sops.secrets.budimanjojo-ghcr-pull-token.sopsFile = ./secret.sops.yaml;\n\n    networking.firewall.allowedTCPPorts = [\n      5432\n      631\n      5353\n    ];\n\n    users.users.${mySystem.adminUser}.extraGroups = [ \"docker\" ];\n\n    virtualisation = {\n      oci-containers = {\n        ## because https://github.com/NixOS/nixpkgs/issues/259770\n        backend = \"docker\";\n        containers = {\n          beeaccounting-db = {\n            image = \"ghcr.io/budimanjojo/beeaccounting/beeplat-database:v1.1.1\";\n            autoStart = true;\n            login = ghcr-login;\n            ports = [ \"5432:5432\" ];\n            volumes = [ \"beeaccounting-db:/data\" ];\n          };\n          beeaccounting-app = {\n            image = \"ghcr.io/budimanjojo/beeaccounting/beeplat-client:v1.1.1\";\n            autoStart = true;\n            login = ghcr-login;\n            extraOptions = [\n              \"--device=/dev/bus/usb\"\n              \"--ulimit\"\n              \"nofile=1024:524288\"\n            ];\n            ports = [\n              \"631:631\"\n              \"5353:5353/udp\"\n            ];\n            volumes = [\n              \"/tmp/.X11-unix:/tmp/.X11-unix\"\n              \"beeaccounting-app:/app\"\n              \"beeaccounting-cups:/etc/cups\"\n              \"/home/${mySystem.adminUser}/OpenCloud:/opencloud\"\n            ];\n            environment = {\n              ## There's no good way to get $DISPLAY from the host and it's unpure\n              DISPLAY = \":0\";\n              TZ = \"Asia/Jakarta\";\n              _JAVA_AWT_WM_NONREPARENTING = \"1\";\n              PUID = \"${toString userCfg.uid}\";\n              PGID = \"${toString config.users.groups.${userCfg.group}.gid}\";\n              TYPE = \"server\";\n            };\n          };\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/containers/beeaccounting/secret.sops.yaml",
    "content": "budimanjojo-ghcr-pull-token: ENC[AES256_GCM,data:skSIMsRrCX2qS+ATi6Aip3H8l2ySlipp+JArEmeOWuVbarGVKo/7dQ==,iv:GS7XIYLzttfTiEIrlkE5c6LR5GRkKaxI94VY+SXgNfU=,tag:5vbWMWVeZP5J/rh5qca/EA==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBidWRWZTNZcXlHSllwWmI0\n            alpVOHB3Y0U5ajJOMFg3ZUFZTnVnQy9TQlNRCitOZXhCK291UE9RNDNpKzdsaHBu\n            L2RFQjgrMWNrMHgzRldTdnRTY1Q3OGMKLS0tIDU0MCtVZFhDdFNNRk1VMVdlOXRO\n            S3g1TWNXTDlWU1orblNyTU9iMzJFdkUKA4mY34QeWtdkApVSyEvLGs1UM5ALcHvz\n            G8T82c92ml+EJ40zPAv/eOB6lsQt0KyWwIC2UuoaEr/ruJVfC/qeMQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1WWdsbThVTTJkMHpFSnRk\n            K1JWQ1lGNnk1eVNDSG50RklIdFc0dTBPZ3pFCmhDVEhueEk4SVRkU2xJT0dmNkZC\n            RFhZbm5SY0ZONlRCbytXc2xOV0pNUlUKLS0tIHBBZXZFeG9PVmxMNlFDRWZHMTNB\n            aDlYYUUwVnFJYk5mbEZPOVJuWVpFbW8Kbx65BPMDUOlg9HAsa4cra8nHfv+6rGNK\n            rSCKxvoj3KmBASeeCT5PnHEMJlA3lZAKINScRrCd0NOjuykhbYZvtA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1V3hITGIrYlZlZk15dlYz\n            V2RLRFdVTkFabUV0QmpZOWZzcWNZdmUxMnpzCmd2U1REUVJCZWdrNjBGbGJGT2Rm\n            Y09VSk1MTGpzcHFoelJMZ0FyR3U3R28KLS0tIGhGdjFmVS8yYmZsWXdLZjdWYWdH\n            RnZGOGZFT1psZUNiVEMveXc2WUFGMGsKDnf2V+KKrPG+uAPNMDz8YW/HgdbCNoT8\n            tojEN0ww1gF7EuVpXfZASQRzgAMqpl6f0CtKVbB/VfjezBqF287npg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZejNoaUFjcGJCRGVuTWR3\n            ZG9ZeDBPS3JQa0srdWVNQmpBd2owY1IzYzBzCkpJY0hCajhTS2IzbkNVZ24zbnln\n            ODNiL3BuQUJqalZSNEoxL3RwUjZYVzgKLS0tIHlZMko5eFFGbERTRFk4b2lvV3A4\n            QzA3L1JTdUE1RVYvUnJMMnl3eWpabVkK4i57i7wlFKDGGlA+LxKreRI7F5d2UVjA\n            wWjR9CkJonzjKs2gYWK+sCaansJ0fWJrIcXxq1rHcpmpbQ7fyqMSEg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoN0RvandwNExpOG9IRXRw\n            RW9Oc3AwQ3BYM1VENzdvcE9jUzlLS1lkemc0Cmp2T1ZmYmMrV1ZuSElGdldVbmlG\n            Z2x0ZGduN0tuUnhQL2pFTlAzTW1zdTgKLS0tIDdQQzNGb1h2ck1kVytjRzU0Wngr\n            YVp4Ym5xb0pmcWlkUjlPMDdGaXh4R0kKQ3wCMwIFb1nEsT4iFiFEmYs4KHwd2vxq\n            e8p/+0x9FG+xDxig3nqOS4ybYrbpv5fFj6bWyNOEhzWiE1VKEaOKUg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzTDZENlVqSm1GeitqamdV\n            Nmd6QXBlTEFMMkRWU0VBeU14RDdpKzYwT1FzCkl1VGxwd1Q4QTNRSHJHbHZDbWQz\n            SXlZWmYrTjNxd2lSRzFQVG8xQXpGanMKLS0tIHRRZi85V1lrUnZuZlpCS29QRWNS\n            dWF5QWVVTmZiWkVwVVhadTN5WkJMSWcKh206JMuZtMm5yNH+bHC1PggmgODgJZHy\n            WaICxUv6QEF4Ye6EKSVDL1pmby/jtieebG1zFzmYnYe8coLPNB7QUg==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2024-07-06T08:59:54Z\"\n    mac: ENC[AES256_GCM,data:bcs6qSknLX7TOJH74y10vC9trINHYaZ57H6OFrFyI+la8/ChzXwv9Hin9G4ynbuL7OHsRL1BBAiTiwFGL6wSt54ryaz1gkeltHXbXBc++qAWIM4YXoHKP2AU6zHOghJqswcEiedHxSCapX59eRylddx/49bWPmXjIgQn8dqMUVI=,iv:kaVtvyjAtFu80IV4Gdu/IETPEZzOWVEn+TABmM+P/Q4=,tag:42iHTAfSFQjodznjXQc8+w==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.8.1\n"
  },
  {
    "path": "system/_modules/default.nix",
    "content": "{ lib, ... }:\n{\n  imports = [\n    # contains config.myHardware options\n    # in a separate file because this module is shared with home-manager\n    ./myHardware.nix\n\n    # base module enabled for all hosts\n    ./_default\n\n    ./containers/beeaccounting\n\n    ./displaymanager/sddm\n\n    ./monitoring/node-exporter\n    ./monitoring/smartctl-exporter\n\n    ./programs/adb\n    ./programs/hugo\n    ./programs/msmtp\n    ./programs/nh\n    ./programs/qmk\n\n    ./services/btrfs-autoscrub\n    ./services/grafana\n    ./services/openssh\n    ./services/prometheus\n    ./services/restic-backup\n\n    ./system/autoupgrade\n    ./system/bootloader\n    ./system/cpu\n    ./system/font\n    ./system/sound\n    ./system/video\n\n    ./windowmanager/add-on/blueman\n    ./windowmanager/add-on/gnome-keyring\n    ./windowmanager/add-on/networkmanager\n    ./windowmanager/add-on/polkit-gnome\n    ./windowmanager/add-on/thunar\n    ./windowmanager/hyprland\n    ./windowmanager/i3\n    ./windowmanager/sway\n  ];\n\n  options.mySystem = with lib; {\n    adminUser = mkOption {\n      type = types.str;\n      default = \"\";\n    };\n    isWayland = mkOption {\n      type = types.bool;\n      default = false;\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/displaymanager/sddm/default.nix",
    "content": "{\n  config,\n  pkgs,\n  lib,\n  options,\n  ...\n}:\nlet\n  mySystem = config.mySystem;\n  myHardware = config.myHardware;\n  cfg = mySystem.displaymanager.sddm;\n\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    concatMapStrings\n    ;\nin\n{\n  options.mySystem.displaymanager.sddm = {\n    enable = mkEnableOption \"SDDM display manager\";\n    wallpaper = mkOption {\n      type = types.nullOr types.path;\n      default = null;\n    };\n    defaultSession = mkOption {\n      type = options.services.displayManager.defaultSession.type;\n      default = options.services.displayManager.defaultSession.default;\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    catppuccin.sddm = {\n      enable = true;\n      background = cfg.wallpaper;\n      loginBackground = true;\n    };\n\n    services = {\n      displayManager = {\n        sddm = {\n          enable = true;\n          wayland.enable = mySystem.isWayland;\n          package = pkgs.kdePackages.sddm;\n        };\n        defaultSession = cfg.defaultSession;\n      };\n\n      # turn off non primary monitors on X11 because sddm weirdly displays prompt on all screens\n      xserver.displayManager.setupCommands = mkIf (!mySystem.isWayland) (\n        \"${pkgs.xorg.xrandr}/bin/xrandr\"\n        + (concatMapStrings (\n          mon:\n          \" --output ${mon.xname}\"\n          + (\n            if (mon.primary) then\n              \" --pos ${toString mon.x}x${toString mon.y} --mode ${toString mon.width}x${toString mon.height}\"\n            else\n              \" --off\"\n          )\n        ) myHardware.monitors)\n      );\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/monitoring/node-exporter/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.monitoring.node-exporter;\n  inherit (lib) mkEnableOption mkIf;\nin\n{\n  options.mySystem.monitoring.node-exporter = {\n    enable = mkEnableOption \"Prometheus node exporter\";\n  };\n\n  config = mkIf (cfg.enable) {\n    services = {\n      prometheus.exporters.node = {\n        enable = true;\n        enabledCollectors = [ \"systemd\" ];\n        openFirewall = true;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/monitoring/smartctl-exporter/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.monitoring.smartctl-exporter;\n  inherit (lib) mkEnableOption mkIf;\nin\n{\n  options.mySystem.monitoring.smartctl-exporter = {\n    enable = mkEnableOption \"Prometheus smartctl exporter\";\n  };\n\n  config = mkIf (cfg.enable) {\n    services = {\n      prometheus.exporters.smartctl = {\n        enable = true;\n        openFirewall = true;\n      };\n\n      # `/dev/nvme*` devices are not in `disk` group by default so `smartctl-exporter` won't work on them\n      # workaround until https://github.com/NixOS/nixpkgs/pull/205165/commits/36256aeaa2fded21257127a9c1d17d6299ef9395 is merged\n      udev.extraRules = ''\n        SUBSYSTEM==\"nvme\", KERNEL==\"nvme[0-9]*\", GROUP=\"disk\"\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/myHardware.nix",
    "content": "{ lib, config, ... }:\nlet\n  inherit (lib) mkOption types;\nin\n{\n  options.myHardware = {\n    cpu = mkOption {\n      type = types.nullOr (\n        types.enum [\n          \"amd\"\n          \"intel\"\n        ]\n      );\n      default = null;\n    };\n    gpuDriver = mkOption {\n      type = types.nullOr (\n        types.enum [\n          \"amd\"\n          \"intel\"\n          \"nvidia\"\n          \"nouveau\"\n        ]\n      );\n      default = null;\n    };\n    isUEFI = mkOption {\n      type = types.bool;\n      default = true;\n    };\n    monitors = mkOption {\n      type = types.listOf (\n        types.submodule (\n          { config, ... }:\n          {\n            options = {\n              name = mkOption { type = types.str; };\n              xname = mkOption {\n                type = types.str;\n                default = config.name;\n                description = ''\n                  Name of the monitor on X11 session.\n                  `xrandr` doesn't use the name reported in `/sys/class/drm`, it's also different depending on graphic driver.\n                '';\n              };\n              primary = mkOption {\n                type = types.bool;\n                default = false;\n              };\n              width = mkOption {\n                type = types.int;\n                default = 1920;\n              };\n              height = mkOption {\n                type = types.int;\n                default = 1080;\n              };\n              x = mkOption {\n                type = types.int;\n                default = 0;\n              };\n              y = mkOption {\n                type = types.int;\n                default = 0;\n              };\n              wallpaper = mkOption {\n                type = types.nullOr types.path;\n                default = null;\n              };\n              workspaces = mkOption {\n                type = types.listOf types.int;\n                default = [ ];\n              };\n            };\n          }\n        )\n      );\n      default = [ ];\n    };\n  };\n\n  config = {\n    assertions = [\n      (\n        let\n          monitors = config.myHardware.monitors;\n          primary = builtins.filter (m: m.primary) monitors;\n        in\n        {\n          assertion = monitors == [ ] || builtins.length primary == 1;\n          message =\n            \"Must have exactly one primary monitor in `config.myHardware.monitors` but found \"\n            + toString (builtins.length primary);\n        }\n      )\n    ];\n  };\n}\n"
  },
  {
    "path": "system/_modules/programs/adb/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.programs.adb;\n  mySystem = config.mySystem;\nin\n{\n  options.mySystem.programs.adb = {\n    enable = lib.mkEnableOption \"adb\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs.adb.enable = true;\n    users.users.${mySystem.adminUser}.extraGroups = [ \"adbusers\" ];\n  };\n}\n"
  },
  {
    "path": "system/_modules/programs/hugo/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.programs.hugo;\nin\n{\n  options.mySystem.programs.hugo = {\n    enable = lib.mkEnableOption \"Hugo\";\n  };\n\n  config = lib.mkIf cfg.enable { networking.firewall.allowedTCPPorts = [ 1313 ]; };\n}\n"
  },
  {
    "path": "system/_modules/programs/msmtp/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  mySystem = config.mySystem;\n  cfg = mySystem.programs.msmtp;\nin\n{\n  options.mySystem.programs.msmtp = {\n    enable = lib.mkEnableOption \"msmtp\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    sops.secrets.gmail-password = {\n      sopsFile = ./secret.sops.yaml;\n      owner = \"${mySystem.adminUser}\";\n      group = \"${config.users.users.${mySystem.adminUser}.group}\";\n    };\n\n    programs.msmtp = {\n      enable = true;\n      accounts = {\n        gmail = {\n          auth = true;\n          host = \"smtp.gmail.com\";\n          port = 587;\n          tls = true;\n          tls_starttls = true;\n          from = \"budimanjojo@gmail.com\";\n          user = \"budimanjojo\";\n          passwordeval = \"${pkgs.coreutils}/bin/cat ${config.sops.secrets.gmail-password.path}\";\n        };\n      };\n      extraConfig = ''\n        account default: gmail\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/programs/msmtp/secret.sops.yaml",
    "content": "gmail-password: ENC[AES256_GCM,data:CUz7IBEHhXfOanT9aSO5Gw==,iv:/r2hzqsEaqGBtnlzIXUk+ghNEm+0Hx1dRa5RFWqJbNQ=,tag:DcTF0ClRmy3so1lnJk6geA==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxMkZyeUJ5bFNHVE1Fd3Zp\n            cGwzSHRMeUtiZGU4VzUySFptY3V5RGpJajFFCnJZZFpERFBMeDI3NkpScTVsenJl\n            clNlVWJnQXhQYWUwR1BiSEM0ZWI3aEUKLS0tIDBxL2lYaU9UMyt5ZkhieDFqU2Uv\n            TlBRS0hRbWZFN3pCWHI0Y1IxL0k0Nm8Km0YYRz6cccUooE26iB7zmZTs3r/exCTf\n            Lb/bIRWrST5JEk3QGDBMdvOBcbs5/a77NhLqGrSV0uL0bnpTf+s/aA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxUWNlS1Zka2xzVXBoT0lt\n            V0tWNDZmRUUxZlJPeWw0eUIzOXlTdkNSVnpnCk1CeVBPbnBZNU9WZFIvNXdYaWFz\n            eDgyRlgvQlY2K0tibGlIQzNtVEJCQnMKLS0tIEd0UmNDMHloRGlqMkZxd1Z0czdu\n            SHZrU3R0TWJTU0Z4eGNCUklTaUFIMm8KwDUFdBohEbjqHDYA4ipruKN9gTJezOLp\n            VR7vmdlCfDxDk/L513uF8NpgBe1WoPG/7caiZJsC1gBk4NvbVY4Ksg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFQTB2UllwNFp0V2xqaXBO\n            UmdKUFNOdXM4R2s4MFZLQVIrNmlMdmowQ0ZjCkpLbjRubXpYanU0QXFkM1JsY0Rn\n            RnBocTYycjVzUGVsWFpnNTVtWVI0NVUKLS0tIDBqaURSWGk1aThYUEV6ckI5SHVt\n            Y1ozTnlTSWtUL3JpOXc1WjZEU001QTgK7/8+eTvxxU+pWiJI/MbT3YieClLU25tA\n            l7j7W3EfAI6/uzI511r6ZzNPmnI3o8IY7V+B6MOcz+Td8FAQmjXqAQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6WU5PbU5hTS8ySjBTdEF4\n            WXprUEV2ZXV6VHdtRld3N292QW9jYjltaUVNCkNSQWlrelF5NVEwc3duVUhYTDhY\n            Q3VFVkora01kME5ock9uREh1N2kyak0KLS0tIEE5R0xLMUMvV2VUUW9UUXpVN3ky\n            dmQ2Zml6dEtxbTFOMS9HZXZGYWRsWVUK3PW6krDcslUCf5Wj0HIFSXaD4znrfn5/\n            MvUAp9RQ3DRjE1YdjLrzmqadxTG7yPvboCny4ej5Ke93NKxVeCuTbQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0NUhVclk0b1ZteGhkQVAy\n            a0pRVE16NlBINzZ4eUw4WEVUQnBQTlhxaUhjCnNGb0kxeVhpUkNhWUhodldhdllX\n            WDJpdHg5OG1RM3QzRDBoSnpCOHQ3V0UKLS0tIFpXYy9ndFU2Y0Izc1FvcGpuLzFa\n            OG5UWmIxZnhvQ25kdnBPMVRZRlZkalUK02NaG8sfqVq1afKsrsaG76OX0LSAfyCX\n            1muVgAuT8xUfd/YY8aOeahWgK3N7uRu2Z03uKFwM04OvaH7vevtPrA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4bXdaVmt1dzhrY2VnNzJi\n            Y1YzeHEyOFdtMEwyVDE1SDBaaFQ3cVVKTFJNCm9MKzMxZklnTUl2c3V1amRmL1NL\n            dE9uaUc2dGgyQ1RZSjd2MHVWREFBVm8KLS0tIElOWjlnQUlCSWRpeWU1ZVVzc2lK\n            SlExSjRlT1JleGdSOHg3Um5mREJ4TWMKR97SREF+I+sBcZthPFaG2x4c641Cz1lc\n            gfEk7fT43eK/vft7wxRrQu7RvUPbZi/Jvt230uqFx67Lwakx8MRKgw==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2023-05-11T08:51:55Z\"\n    mac: ENC[AES256_GCM,data:wPGzbL5U6O/W1BFu58ChwfsvC2x8Tg/+XjoH8MXvYZqzqB1TJbAW+ke6ivQ2HwSOdTXUEhv+Q2G//CXHnR1P4X0gjfdKJfP+puIaxAL8vqG51f5y518JYl9B5XUQtj3oEaC+ZZw2KOTec+PfZqXL8d9HY4030gkbeqloiCbN/R4=,iv:ghpIlpo9AWMRV62syST8F8PvazRZDGy4zpuVrDSNtxQ=,tag:9iPFzhaW7gbfgrwcO4J8NA==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.7.3\n"
  },
  {
    "path": "system/_modules/programs/nh/default.nix",
    "content": "{\n  config,\n  lib,\n  options,\n  ...\n}:\nlet\n  cfg = config.mySystem.programs.nh;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    mkMerge\n    mkIf\n    ;\nin\n{\n  options.mySystem.programs.nh = {\n    enable = mkEnableOption \"nh Nix CLI helper\";\n    flake = mkOption {\n      type = options.programs.nh.flake.type;\n      default = options.programs.nh.flake.default;\n    };\n  };\n\n  config = mkMerge [\n    (mkIf (cfg.enable) {\n      programs.nh = {\n        enable = true;\n        flake = cfg.flake;\n        clean = {\n          enable = true;\n          dates = \"weekly\";\n        };\n      };\n      programs.fish.shellAbbrs = {\n        nos = \"git -C $FLAKE pull; nh os switch\";\n      };\n    })\n    (mkIf (!cfg.enable) {\n      nix.gc = {\n        automatic = true;\n        dates = \"weekly\";\n      };\n    })\n  ];\n}\n"
  },
  {
    "path": "system/_modules/programs/qmk/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  mySystem = config.mySystem;\n  cfg = mySystem.programs.qmk;\nin\n{\n  options.mySystem.programs.qmk = {\n    enable = lib.mkEnableOption \"QMK\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    # needed for qmk udev rule\n    users = {\n      groups.plugdev = { };\n      users.${mySystem.adminUser}.extraGroups = [ \"plugdev\" ];\n    };\n    hardware.keyboard.qmk.enable = true;\n  };\n}\n"
  },
  {
    "path": "system/_modules/services/btrfs-autoscrub/default.nix",
    "content": "{\n  config,\n  options,\n  pkgs,\n  lib,\n  utils,\n  ...\n}:\nlet\n  cfg = config.mySystem.services.btrfs-autoscrub;\n  parentOpt = options.services.btrfs.autoScrub;\n\n  mail-started = pkgs.writeShellScript \"mail.sh\" ''\n    DIRNAME=$1\n    HOSTNAME=${config.networking.fqdnOrHostName}\n    TO=budimanjojo@gmail.com\n\n    /run/wrappers/bin/sendmail -i -- $TO << EOF\n    From: btrfs on $HOSTNAME <budimanjojo@gmail.com>\n    To: $TO\n    Subject: BTRFS scrub status\n\n    BTRFS scrub for \"$DIRNAME\" on \"$HOSTNAME\" started.\n    EOF\n  '';\n\n  mail-stopped = pkgs.writeShellScript \"mail.sh\" ''\n    DIRNAME=$1\n    HOSTNAME=${config.networking.fqdnOrHostName}\n    TO=budimanjojo@gmail.com\n\n    /run/wrappers/bin/sendmail -i -- $TO << EOF\n    From: btrfs on $HOSTNAME <budimanjojo@gmail.com>\n    To: $TO\n    Subject: BTRFS scrub status\n\n    BTRFS scrub for \"$DIRNAME\" on \"$HOSTNAME\" unexpectedly stopped.\n    EOF\n  '';\nin\n{\n  options.mySystem.services.btrfs-autoscrub = {\n    enable = lib.mkEnableOption \"btrfs auto scrub\";\n    interval = lib.mkOption {\n      type = parentOpt.interval.type;\n      default = parentOpt.interval.default;\n    };\n    fileSystems = lib.mkOption {\n      type = parentOpt.fileSystems.type;\n      default = parentOpt.fileSystems.default;\n    };\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    mySystem.programs.msmtp.enable = true;\n\n    services.btrfs.autoScrub = {\n      enable = true;\n      interval = cfg.interval;\n      fileSystems = cfg.fileSystems;\n    };\n\n    systemd.services = builtins.listToAttrs (\n      map (\n        fs:\n        (lib.nameValuePair \"btrfs-scrub-${utils.escapeSystemdPath fs}\" {\n          postStart = \"${mail-started} ${fs}\";\n          postStop = \"(${pkgs.btrfs-progs}/bin/btrfs scrub status ${fs} | ${pkgs.gnugrep}/bin/grep finished) || ${mail-stopped} ${fs}\";\n        })\n      ) cfg.fileSystems\n    );\n  };\n}\n"
  },
  {
    "path": "system/_modules/services/grafana/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.services.grafana;\n  prometheusCfg = config.mySystem.services.prometheus;\nin\n{\n  options.mySystem.services.grafana = {\n    enable = lib.mkEnableOption \"Grafana\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    sops.secrets.grafana-password = {\n      sopsFile = ./secret.sops.yaml;\n      owner = config.users.users.grafana.name;\n    };\n\n    services = {\n      grafana = {\n        enable = true;\n        settings = {\n          security = {\n            admin_password = \"$__file{${config.sops.secrets.grafana-password.path}}\";\n          };\n          server = {\n            http_addr = \"0.0.0.0\";\n          };\n        };\n\n        provision = lib.mkIf prometheusCfg.enable {\n          enable = true;\n          datasources.settings.datasources = [\n            {\n              name = \"Prometheus\";\n              type = \"prometheus\";\n              url = \"http://localhost:${toString config.services.prometheus.port}\";\n              isDefault = true;\n            }\n          ];\n        };\n      };\n    };\n\n    networking.firewall.allowedTCPPorts = [ config.services.grafana.settings.server.http_port ];\n  };\n}\n"
  },
  {
    "path": "system/_modules/services/grafana/secret.sops.yaml",
    "content": "grafana-password: ENC[AES256_GCM,data:T+AgSaSWbLc+KB8=,iv:UBnSF5xWdSPR/zTCYA2CArvJnQexE7uIKBIFUNTPNY4=,tag:9fUrNlgotTaJTu8pyC+LAQ==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6T2pVNGZEdWdlaEh4Ty9v\n            dHBIU2g5clJmR0I3cEx3Z256cXN3UjVYZVhVCis2L2hjRHFlTHVkeW5zcGEydDI4\n            Nktqc3RzU21DWDRwLzgzbDFWcjBLUzQKLS0tIGY2UEtmVklRVlZxZnk3Y2NiRUNy\n            YkFINTdiemQrYXUzNmg2ZDQ5RmdINmcKGAhrDrVzqFNLtwxH//T49XhfRs31kXQA\n            kJFgyd0RplorB+ML2sk9KsDtcu6w3AMtVhhdkLQAi6I/WSOsldnpFA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOSDA5UHE4NE9vN0IvQTFX\n            OFo2dFNuSFRkWEx2YVhlMXFtU2RVMmJaOVRnCmV0bVRUU1MyYXZlY0o5cFlsOERF\n            WUY1dFBtZWs1Q3B3ZkxZNThwbkhBMDAKLS0tIGEzTUM5dkhqZkJJeExVQmlJVUxs\n            eXFTaGhhOGVZN1BEbDZvcGFseW5FNHcKV8ADjs9O9jS/mkOfkoMgSxz6GgDePa2Z\n            OTR7ax+oLbbjX0zy7UIA62HbULcwCuENlbMwopiZbkSRryXfgljaOg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRZ2VUNXQ3aTJ4Z2hPZXVo\n            c1d3dDMzNTh2ZThIQnJ3Mm5zR2ZxMmhKdVRJCjF3Ylg1LzBsa1JabnJ6KytEWGdS\n            b2ZFRlByY1k1a0hJdHpIcWFCbCtzUVUKLS0tIDMrSFI4RFlYREsyQm9LQ1ExdUI0\n            aEdFZDQ1Mm9jRk5ySGp3d21ZUUVRSzQKkOu3hzEQ0ax1OSP16JFHfOm5mWGi6whL\n            vFmu7zkS7sBbBdX39pUbMbJz8hZ4nVirnTmRJtObxXX+2hTJN9KbHQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkc3VNRmw1enFKVUNaVnRt\n            QXpvSnFrc2NNNWExUkU4eWViei9nT2M4TlhNCjhKbGplL0E5b2lFN1orY1E3c2Qr\n            QmIrT1VKb20ybldWVTEwZDZYNjVtVDQKLS0tIGN2VlZsVlVjKzJkNTVFYnZjMVU5\n            eFBFWjdnNzFFcGNVUTRiUkhFZlpPbHcKJ/xiu6/EwbOMISliRzRwJpKwxxgZVWiK\n            AkJz9NoRJiMCvVHQywCcKa2R49lY2tjbhxCt0Wvzco6w2t2LYCyZdw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3SnA4ME1WUmtsUXpVWWdJ\n            UFNnSkFLWEdoNEFhK3BGb1RuTW43THJmcFE4CnFIU2V6Y3BVNjI0ckh4bUMzUGRK\n            TXVhL2JHYk1CWHJodzFuSEZkQzdRWmsKLS0tIDZYZStsc0NnSFNGYW1sRVdhcWxh\n            YzFSVXdRY1dnOENzWk5rOTBrN085T3MKKuv/Lvv1wxW++WQycnymscJnbZ1v2qfk\n            jmYBLEWMHDqEy1Cd8lUErg1WxASOrgloVBwrsOTbkKfDq3bNNzPIbA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFc2ZEUElTckdjdnpWN2x3\n            L2w3cVNXY0ttczBqUkpHTVJxZ1pEUTQyd0gwCnFWN3JuckoxU3Y1Ty9nNmlSU3Jw\n            cXVXd05Vb2RGdDN0TzFKK2ZzZWZtbk0KLS0tIGk1UUdxeUF0WTlNd0VJVzRpSVRi\n            V0FkQk5sWVB2bWFLbjVqSFoxcklrRTAKUfnvSKSkVC2Q0XvW4VqwuWNVDGBpWuz0\n            DtXLoGOCj6dAhVuZPjV46mqQINbYIlWJq7zC0cmAvP14wQkh/FAI1w==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2023-05-11T08:44:33Z\"\n    mac: ENC[AES256_GCM,data:yZViZY+PSxsli5z9+nScdWqpwwHr9kwkT1/LhOin4ygLVGFDe7SQBvu4rEflpkkIKjm2jHUAZ0i6D5OVH9QXSV8q9sdlqcc+WqhxSYcTUmpLl2KNR2r/DHq3RZNhElX30byLjUAzfHYW8Ld7XtVH2hx/sWF/Qr697Q5Qu4cG0sc=,iv:HNA684Q5Z2jQBlV69njWUrDIl6EGuwA0iZI8nhflcG0=,tag:DSomFZOJIKD2k+EfjtNnnw==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.7.3\n"
  },
  {
    "path": "system/_modules/services/openssh/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  mySystem = config.mySystem;\n  cfg = mySystem.services.openssh;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    ;\nin\n{\n  options.mySystem.services.openssh = {\n    enable = mkEnableOption \"openssh\";\n    authorizedKeys = mkOption {\n      type = types.listOf types.singleLineStr;\n      default = [ ];\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    services.openssh = {\n      enable = true;\n      settings = {\n        PermitRootLogin = \"no\";\n        PasswordAuthentication = false;\n      };\n    };\n\n    users.users.${mySystem.adminUser}.openssh.authorizedKeys.keys = cfg.authorizedKeys;\n  };\n}\n"
  },
  {
    "path": "system/_modules/services/prometheus/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.services.prometheus;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    ;\nin\n{\n  options.mySystem.services.prometheus = {\n    enable = mkEnableOption \"Prometheus\";\n    discordWHKey = mkOption {\n      type = types.nullOr types.str;\n      default = null;\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    sops.secrets.alertmanager-secret = { };\n    services = {\n      prometheus = {\n        enable = true;\n\n        ruleFiles = [\n          ./rules/embedded-exporter.yaml\n          ./rules/node-exporter.yaml\n        ];\n\n        exporters = {\n          node = {\n            enable = true;\n            enabledCollectors = [ \"systemd\" ];\n          };\n        };\n\n        alertmanager = {\n          enable = true;\n          environmentFile = mkIf (cfg.discordWHKey != null) (\n            builtins.toFile \"env\" \"DISCORD_WEBHOOK_KEY=${cfg.discordWHKey}\"\n          );\n          configuration = {\n            global = {\n              resolve_timeout = \"5m\";\n            };\n            receivers = [\n              {\n                name = \"discord\";\n                discord_configs = mkIf (cfg.discordWHKey != null) [\n                  {\n                    webhook_url = \"https://discord.com/api/webhooks/$DISCORD_WEBHOOK_KEY\";\n                    title = ''\n                      [{{ .Status | toUpper }}:{{ if eq .Status \"firing\" }}{{ .Alerts.Firing | len }}{{ else }}{{ .Alerts.Resolved | len }}{{ end }}]\n                    '';\n                    message = ''\n                      {{- range .Alerts }}\n                        **{{ .Labels.alertname }} {{ if ne .Labels.severity \"\" }}({{ .Labels.severity | title }}){{ end }} **\n                        {{- if ne .Annotations.description \"\" }}\n                         **Description:** {{ .Annotations.description }}\n                        {{- else if ne .Annotations.summary \"\" }}\n                         **Summary:** {{ .Annotations.summary }}\n                        {{- else if ne .Annotations.message \"\" }}\n                         **Message:** {{ .Annotations.message }}\n                        {{- else }}\n                         **Description:** N/A\n                        {{- end }}\n                      {{- end }}\n                    '';\n                  }\n                ];\n              }\n            ];\n            route = {\n              group_by = [ \"alertname\" ];\n              group_interval = \"5m\";\n              group_wait = \"30s\";\n              receiver = \"discord\";\n              repeat_interval = \"12h\";\n            };\n          };\n        };\n\n        alertmanagers = [\n          {\n            static_configs = [\n              { targets = [ \"localhost:${toString config.services.prometheus.alertmanager.port}\" ]; }\n            ];\n          }\n        ];\n\n        scrapeConfigs = [\n          {\n            job_name = \"prometheus\";\n            static_configs = [ { targets = [ \"localhost:${toString config.services.prometheus.port}\" ]; } ];\n          }\n          {\n            job_name = \"node\";\n            static_configs = [\n              { targets = [ \"localhost:${toString config.services.prometheus.exporters.node.port}\" ]; }\n            ];\n          }\n        ];\n      };\n    };\n\n    networking.firewall.allowedTCPPorts = [\n      config.services.prometheus.port\n      config.services.prometheus.exporters.node.port\n      config.services.prometheus.alertmanager.port\n    ];\n  };\n}\n"
  },
  {
    "path": "system/_modules/services/prometheus/rules/embedded-exporter.yaml",
    "content": "groups:\n  - name: EmbeddedExporter\n    rules:\n      - alert: PrometheusJobMissing\n        expr: 'absent(up{job=\"prometheus\"})'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus job missing (instance {{ $labels.instance }})\n          description: \"A Prometheus job has disappeared\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTargetMissing\n        expr: 'up == 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus target missing (instance {{ $labels.instance }})\n          description: \"A Prometheus target has disappeared. An exporter might be crashed.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusAllTargetsMissing\n        expr: 'sum by (job) (up) == 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus all targets missing (instance {{ $labels.instance }})\n          description: \"A Prometheus job does not have living target anymore.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTargetMissingWithWarmupTime\n        expr: 'sum by (instance, job) ((up == 0) * on (instance) group_right(job) (node_time_seconds - node_boot_time_seconds > 600))'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus target missing with warmup time (instance {{ $labels.instance }})\n          description: \"Allow a job time to start up (10 minutes) before alerting that it's down.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusConfigurationReloadFailure\n        expr: 'prometheus_config_last_reload_successful != 1'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus configuration reload failure (instance {{ $labels.instance }})\n          description: \"Prometheus configuration reload error\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTooManyRestarts\n        expr: 'changes(process_start_time_seconds{job=~\"prometheus|pushgateway|alertmanager\"}[15m]) > 2'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus too many restarts (instance {{ $labels.instance }})\n          description: \"Prometheus has restarted more than twice in the last 15 minutes. It might be crashlooping.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusAlertmanagerConfigurationReloadFailure\n        expr: 'alertmanager_config_last_reload_successful != 1'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus AlertManager configuration reload failure (instance {{ $labels.instance }})\n          description: \"AlertManager configuration reload error\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusAlertmanagerConfigNotSynced\n        expr: 'count(count_values(\"config_hash\", alertmanager_config_hash)) > 1'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus AlertManager config not synced (instance {{ $labels.instance }})\n          description: \"Configurations of AlertManager cluster instances are out of sync\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusNotConnectedToAlertmanager\n        expr: 'prometheus_notifications_alertmanagers_discovered < 1'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus not connected to alertmanager (instance {{ $labels.instance }})\n          description: \"Prometheus cannot connect the alertmanager\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusRuleEvaluationFailures\n        expr: 'increase(prometheus_rule_evaluation_failures_total[3m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus rule evaluation failures (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} rule evaluation failures, leading to potentially ignored alerts.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTemplateTextExpansionFailures\n        expr: 'increase(prometheus_template_text_expansion_failures_total[3m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus template text expansion failures (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} template text expansion failures\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusRuleEvaluationSlow\n        expr: 'prometheus_rule_group_last_duration_seconds > prometheus_rule_group_interval_seconds'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus rule evaluation slow (instance {{ $labels.instance }})\n          description: \"Prometheus rule evaluation took more time than the scheduled interval. It indicates a slower storage backend access or too complex query.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusNotificationsBacklog\n        expr: 'min_over_time(prometheus_notifications_queue_length[10m]) > 0'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus notifications backlog (instance {{ $labels.instance }})\n          description: \"The Prometheus notification queue has not been empty for 10 minutes\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusAlertmanagerNotificationFailing\n        expr: 'rate(alertmanager_notifications_failed_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus AlertManager notification failing (instance {{ $labels.instance }})\n          description: \"Alertmanager is failing sending notifications\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTargetEmpty\n        expr: 'prometheus_sd_discovered_targets == 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus target empty (instance {{ $labels.instance }})\n          description: \"Prometheus has no target in service discovery\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTargetScrapingSlow\n        expr: 'prometheus_target_interval_length_seconds{quantile=\"0.9\"} / on (interval, instance, job) prometheus_target_interval_length_seconds{quantile=\"0.5\"} > 1.05'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus target scraping slow (instance {{ $labels.instance }})\n          description: \"Prometheus is scraping exporters slowly since it exceeded the requested interval time. Your Prometheus server is under-provisioned.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusLargeScrape\n        expr: 'increase(prometheus_target_scrapes_exceeded_sample_limit_total[10m]) > 10'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus large scrape (instance {{ $labels.instance }})\n          description: \"Prometheus has many scrapes that exceed the sample limit\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTargetScrapeDuplicate\n        expr: 'increase(prometheus_target_scrapes_sample_duplicate_timestamp_total[5m]) > 0'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus target scrape duplicate (instance {{ $labels.instance }})\n          description: \"Prometheus has many samples rejected due to duplicate timestamps but different values\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTsdbCheckpointCreationFailures\n        expr: 'increase(prometheus_tsdb_checkpoint_creations_failed_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus TSDB checkpoint creation failures (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} checkpoint creation failures\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTsdbCheckpointDeletionFailures\n        expr: 'increase(prometheus_tsdb_checkpoint_deletions_failed_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus TSDB checkpoint deletion failures (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} checkpoint deletion failures\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTsdbCompactionsFailed\n        expr: 'increase(prometheus_tsdb_compactions_failed_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus TSDB compactions failed (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} TSDB compactions failures\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTsdbHeadTruncationsFailed\n        expr: 'increase(prometheus_tsdb_head_truncations_failed_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus TSDB head truncations failed (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} TSDB head truncation failures\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTsdbReloadFailures\n        expr: 'increase(prometheus_tsdb_reloads_failures_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus TSDB reload failures (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} TSDB reload failures\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTsdbWalCorruptions\n        expr: 'increase(prometheus_tsdb_wal_corruptions_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus TSDB WAL corruptions (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} TSDB WAL corruptions\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTsdbWalTruncationsFailed\n        expr: 'increase(prometheus_tsdb_wal_truncations_failed_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Prometheus TSDB WAL truncations failed (instance {{ $labels.instance }})\n          description: \"Prometheus encountered {{ $value }} TSDB WAL truncation failures\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: PrometheusTimeserieCardinality\n        expr: 'label_replace(count by(__name__) ({__name__=~\".+\"}), \"name\", \"$1\", \"__name__\", \"(.+)\") > 10000'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Prometheus timeserie cardinality (instance {{ $labels.instance }})\n          description: \"The \\\"{{ $labels.name }}\\\" timeserie cardinality is getting very high: {{ $value }}\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n"
  },
  {
    "path": "system/_modules/services/prometheus/rules/node-exporter.yaml",
    "content": "groups:\n  - name: NodeExporter\n    rules:\n      - alert: HostOutOfMemory\n        expr: 'node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100 < 10'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host out of memory (instance {{ $labels.instance }})\n          description: \"Node memory is filling up (< 10% left)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostMemoryUnderMemoryPressure\n        expr: 'rate(node_vmstat_pgmajfault[1m]) > 1000'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host memory under memory pressure (instance {{ $labels.instance }})\n          description: \"The node is under heavy memory pressure. High rate of major page faults\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostMemoryIsUnderUtilized\n        expr: '100 - (rate(node_memory_MemAvailable_bytes[30m]) / node_memory_MemTotal_bytes * 100) < 20'\n        for: 1w\n        labels:\n          severity: info\n        annotations:\n          summary: Host Memory is under utilized (instance {{ $labels.instance }})\n          description: \"Node memory is < 20% for 1 week. Consider reducing memory space.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostUnusualNetworkThroughputIn\n        expr: 'sum by (instance) (rate(node_network_receive_bytes_total[2m])) / 1024 / 1024 > 100'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host unusual network throughput in (instance {{ $labels.instance }})\n          description: \"Host network interfaces are probably receiving too much data (> 100 MB/s)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostUnusualNetworkThroughputOut\n        expr: 'sum by (instance) (rate(node_network_transmit_bytes_total[2m])) / 1024 / 1024 > 100'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host unusual network throughput out (instance {{ $labels.instance }})\n          description: \"Host network interfaces are probably sending too much data (> 100 MB/s)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostUnusualDiskReadRate\n        expr: 'sum by (instance) (rate(node_disk_read_bytes_total[2m])) / 1024 / 1024 > 50'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host unusual disk read rate (instance {{ $labels.instance }})\n          description: \"Disk is probably reading too much data (> 50 MB/s)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostUnusualDiskWriteRate\n        expr: 'sum by (instance) (rate(node_disk_written_bytes_total[2m])) / 1024 / 1024 > 50'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host unusual disk write rate (instance {{ $labels.instance }})\n          description: \"Disk is probably writing too much data (> 50 MB/s)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostOutOfDiskSpace\n        expr: '(node_filesystem_avail_bytes * 100) / node_filesystem_size_bytes < 10 and ON (instance, device, mountpoint) node_filesystem_readonly == 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host out of disk space (instance {{ $labels.instance }})\n          description: \"Disk is almost full (< 10% left)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostDiskWillFillIn24Hours\n        expr: '(node_filesystem_avail_bytes * 100) / node_filesystem_size_bytes < 10 and ON (instance, device, mountpoint) predict_linear(node_filesystem_avail_bytes{fstype!~\"tmpfs\"}[1h], 24 * 3600) < 0 and ON (instance, device, mountpoint) node_filesystem_readonly == 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host disk will fill in 24 hours (instance {{ $labels.instance }})\n          description: \"Filesystem is predicted to run out of space within the next 24 hours at current write rate\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostOutOfInodes\n        expr: 'node_filesystem_files_free / node_filesystem_files * 100 < 10 and ON (instance, device, mountpoint) node_filesystem_readonly == 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host out of inodes (instance {{ $labels.instance }})\n          description: \"Disk is almost running out of available inodes (< 10% left)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostInodesWillFillIn24Hours\n        expr: 'node_filesystem_files_free / node_filesystem_files * 100 < 10 and predict_linear(node_filesystem_files_free[1h], 24 * 3600) < 0 and ON (instance, device, mountpoint) node_filesystem_readonly == 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host inodes will fill in 24 hours (instance {{ $labels.instance }})\n          description: \"Filesystem is predicted to run out of inodes within the next 24 hours at current write rate\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostUnusualDiskReadLatency\n        expr: 'rate(node_disk_read_time_seconds_total[1m]) / rate(node_disk_reads_completed_total[1m]) > 0.1 and rate(node_disk_reads_completed_total[1m]) > 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host unusual disk read latency (instance {{ $labels.instance }})\n          description: \"Disk latency is growing (read operations > 100ms)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostUnusualDiskWriteLatency\n        expr: 'rate(node_disk_write_time_seconds_total[1m]) / rate(node_disk_writes_completed_total[1m]) > 0.1 and rate(node_disk_writes_completed_total[1m]) > 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host unusual disk write latency (instance {{ $labels.instance }})\n          description: \"Disk latency is growing (write operations > 100ms)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostHighCpuLoad\n        expr: 'sum by (instance) (avg by (mode, instance) (rate(node_cpu_seconds_total{mode!=\"idle\"}[2m]))) > 0.8'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host high CPU load (instance {{ $labels.instance }})\n          description: \"CPU load is > 80%\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostCpuIsUnderUtilized\n        expr: '100 - (rate(node_cpu_seconds_total{mode=\"idle\"}[30m]) * 100) < 20'\n        for: 1w\n        labels:\n          severity: info\n        annotations:\n          summary: Host CPU is under utilized (instance {{ $labels.instance }})\n          description: \"CPU load is < 20% for 1 week. Consider reducing the number of CPUs.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostCpuStealNoisyNeighbor\n        expr: 'avg by(instance) (rate(node_cpu_seconds_total{mode=\"steal\"}[5m])) * 100 > 10'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host CPU steal noisy neighbor (instance {{ $labels.instance }})\n          description: \"CPU steal is > 10%. A noisy neighbor is killing VM performances or a spot instance may be out of credit.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostCpuHighIowait\n        expr: 'avg by (instance) (rate(node_cpu_seconds_total{mode=\"iowait\"}[5m])) * 100 > 10'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host CPU high iowait (instance {{ $labels.instance }})\n          description: \"CPU iowait > 10%. A high iowait means that you are disk or network bound.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostUnusualDiskIo\n        expr: 'rate(node_disk_io_time_seconds_total[1m]) > 0.5'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host unusual disk IO (instance {{ $labels.instance }})\n          description: \"Time spent in IO is too high on {{ $labels.instance }}. Check storage for issues.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostContextSwitching\n        expr: '(rate(node_context_switches_total[5m])) / (count without(cpu, mode) (node_cpu_seconds_total{mode=\"idle\"})) > 1000'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host context switching (instance {{ $labels.instance }})\n          description: \"Context switching is growing on node (> 1000 / s)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostSwapIsFillingUp\n        expr: '(1 - (node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes)) * 100 > 80'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host swap is filling up (instance {{ $labels.instance }})\n          description: \"Swap is filling up (>80%)\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostSystemdServiceCrashed\n        expr: 'node_systemd_unit_state{state=\"failed\"} == 1'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host systemd service crashed (instance {{ $labels.instance }})\n          description: \"systemd service crashed\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostPhysicalComponentTooHot\n        expr: 'node_hwmon_temp_celsius * ignoring(label) group_left(instance, job, node, sensor) node_hwmon_sensor_label{label!=\"tctl\"} > 75'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host physical component too hot (instance {{ $labels.instance }})\n          description: \"Physical hardware component too hot\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostNodeOvertemperatureAlarm\n        expr: 'node_hwmon_temp_crit_alarm_celsius == 1'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Host node overtemperature alarm (instance {{ $labels.instance }})\n          description: \"Physical node temperature alarm triggered\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostRaidArrayGotInactive\n        expr: 'node_md_state{state=\"inactive\"} > 0'\n        for: 0m\n        labels:\n          severity: critical\n        annotations:\n          summary: Host RAID array got inactive (instance {{ $labels.instance }})\n          description: \"RAID array {{ $labels.device }} is in degraded state due to one or more disks failures. Number of spare drives is insufficient to fix issue automatically.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostRaidDiskFailure\n        expr: 'node_md_disks{state=\"failed\"} > 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host RAID disk failure (instance {{ $labels.instance }})\n          description: \"At least one device in RAID array on {{ $labels.instance }} failed. Array {{ $labels.md_device }} needs attention and possibly a disk swap\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostKernelVersionDeviations\n        expr: 'count(sum(label_replace(node_uname_info, \"kernel\", \"$1\", \"release\", \"([0-9]+.[0-9]+.[0-9]+).*\")) by (kernel)) > 1'\n        for: 6h\n        labels:\n          severity: warning\n        annotations:\n          summary: Host kernel version deviations (instance {{ $labels.instance }})\n          description: \"Different kernel versions are running\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostOomKillDetected\n        expr: 'increase(node_vmstat_oom_kill[1m]) > 0'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host OOM kill detected (instance {{ $labels.instance }})\n          description: \"OOM kill detected\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostEdacCorrectableErrorsDetected\n        expr: 'increase(node_edac_correctable_errors_total[1m]) > 0'\n        for: 0m\n        labels:\n          severity: info\n        annotations:\n          summary: Host EDAC Correctable Errors detected (instance {{ $labels.instance }})\n          description: \"Host {{ $labels.instance }} has had {{ printf \\\"%.0f\\\" $value }} correctable memory errors reported by EDAC in the last 5 minutes.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostEdacUncorrectableErrorsDetected\n        expr: 'node_edac_uncorrectable_errors_total > 0'\n        for: 0m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host EDAC Uncorrectable Errors detected (instance {{ $labels.instance }})\n          description: \"Host {{ $labels.instance }} has had {{ printf \\\"%.0f\\\" $value }} uncorrectable memory errors reported by EDAC in the last 5 minutes.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostNetworkReceiveErrors\n        expr: 'rate(node_network_receive_errs_total[2m]) / rate(node_network_receive_packets_total[2m]) > 0.01'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host Network Receive Errors (instance {{ $labels.instance }})\n          description: \"Host {{ $labels.instance }} interface {{ $labels.device }} has encountered {{ printf \\\"%.0f\\\" $value }} receive errors in the last two minutes.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostNetworkTransmitErrors\n        expr: 'rate(node_network_transmit_errs_total[2m]) / rate(node_network_transmit_packets_total[2m]) > 0.01'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host Network Transmit Errors (instance {{ $labels.instance }})\n          description: \"Host {{ $labels.instance }} interface {{ $labels.device }} has encountered {{ printf \\\"%.0f\\\" $value }} transmit errors in the last two minutes.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostNetworkInterfaceSaturated\n        expr: '(rate(node_network_receive_bytes_total{device!~\"^tap.*|^vnet.*|^veth.*|^tun.*\"}[1m]) + rate(node_network_transmit_bytes_total{device!~\"^tap.*|^vnet.*|^veth.*|^tun.*\"}[1m])) / node_network_speed_bytes{device!~\"^tap.*|^vnet.*|^veth.*|^tun.*\"} > 0.8 < 10000'\n        for: 1m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host Network Interface Saturated (instance {{ $labels.instance }})\n          description: \"The network interface \\\"{{ $labels.device }}\\\" on \\\"{{ $labels.instance }}\\\" is getting overloaded.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostNetworkBondDegraded\n        expr: '(node_bonding_active - node_bonding_slaves) != 0'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host Network Bond Degraded (instance {{ $labels.instance }})\n          description: \"Bond \\\"{{ $labels.device }}\\\" degraded on \\\"{{ $labels.instance }}\\\".\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostConntrackLimit\n        expr: 'node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.8'\n        for: 5m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host conntrack limit (instance {{ $labels.instance }})\n          description: \"The number of conntrack is approaching limit\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostClockSkew\n        expr: '(node_timex_offset_seconds > 0.05 and deriv(node_timex_offset_seconds[5m]) >= 0) or (node_timex_offset_seconds < -0.05 and deriv(node_timex_offset_seconds[5m]) <= 0)'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host clock skew (instance {{ $labels.instance }})\n          description: \"Clock skew detected. Clock is out of sync. Ensure NTP is configured correctly on this host.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostClockNotSynchronising\n        expr: 'min_over_time(node_timex_sync_status[1m]) == 0 and node_timex_maxerror_seconds >= 16'\n        for: 2m\n        labels:\n          severity: warning\n        annotations:\n          summary: Host clock not synchronising (instance {{ $labels.instance }})\n          description: \"Clock not synchronising. Ensure NTP is configured on this host.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n\n      - alert: HostRequiresReboot\n        expr: 'node_reboot_required > 0'\n        for: 4h\n        labels:\n          severity: info\n        annotations:\n          summary: Host requires reboot (instance {{ $labels.instance }})\n          description: \"{{ $labels.instance }} requires a reboot.\\n  VALUE = {{ $value }}\\n  LABELS = {{ $labels }}\"\n"
  },
  {
    "path": "system/_modules/services/restic-backup/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.services.restic-backup;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    ;\nin\n{\n  options.mySystem.services.restic-backup = {\n    enable = mkEnableOption \"restic-backup\";\n    location = mkOption {\n      type = types.str;\n      description = \"restic repository location\";\n      default = \"s3:http://192.168.15.15:9000/restic-repository\";\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    # function to create attrset that can be consumed by `services.restic.backups` module\n    # options needs to be an attrset that has `app`, `paths`, and optional `excludePaths` keys\n    lib.mySystem.mkRestic = options: {\n      \"${options.app}\" = {\n        paths = options.paths;\n        initialize = true;\n        repository = \"${cfg.location}/${options.app}\";\n        passwordFile = config.sops.secrets.\"restic/password\".path;\n        environmentFile = config.sops.secrets.\"restic/env\".path;\n        exclude = if builtins.hasAttr \"excludePaths\" options then options.excludePaths else [ ];\n        timerConfig = {\n          OnCalendar = \"02:00\";\n          Persistent = true;\n          RandomizedDelaySec = \"3h\";\n        };\n        pruneOpts = [\n          \"--keep-daily 10\"\n          \"--keep-monthly 2\"\n        ];\n        backupPrepareCommand = ''\n          # remove stale locks - this avoids some occasional annoyance\n          ${pkgs.restic}/bin/restic unlock --remove-all || true\n        '';\n      };\n    };\n\n    sops.secrets = {\n      \"restic/password\".sopsFile = ./secret.sops.yaml;\n      \"restic/env\".sopsFile = ./secret.sops.yaml;\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/services/restic-backup/secret.sops.yaml",
    "content": "restic:\n    password: ENC[AES256_GCM,data:vcAJ+V2A2FHalb38LAFj3A==,iv:kvWV7+dOD9q0LpxuaYSePRH1HUiBwMvSVvhtUUguUkw=,tag:FLa0efRhc/EIT2fTSNIk4A==,type:str]\n    env: ENC[AES256_GCM,data:vUxWjYVXFRpXxfxwGvu0qbKRX7MpyiJCLPb2Lb0t3Qt9bntXv0BeKipZXTiPDx/MrAd3ackVdJc/+B7/YzzkC8FH7IOeq04Q/KgCcGp2gC+A/PJNdD9VttUmZNMIMHVmYJcjJH8u,iv:nhGTDrwQVDYDQSVq9gWpTYLqOjje2DCzWLhp9DbECJI=,tag:FngpwkPC+HTbSucoIFXnuQ==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4RlNweTFLNnJmdGdCdzl4\n            WFNFaUxXVE5tb2dENFFkOVd4REZGeTU4MkZBCjA0Uit0L2Jzckl6cklOQkw2dzhO\n            cFZvMW9oUnVFTHVNSFJYLzRkbGxUbm8KLS0tIGVKZzNVcnJDRWNGZGY2UG5oT1pu\n            WHA3a2lGQ2xOU3ZGankzSzFpTFIvWm8KNulntM1qdaitQx54lQb3bUXyTQsFWstz\n            4z3zH+6LltnVmNMXBgJMIVWvpof4IwaFrIIRdN15zzYrVjqrz3awaA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYSzdmdzd1UHI0aDlWWHht\n            UmwrcmJieStTckJhMWNFdTYvNDRDNnRoWmk0CkpLYTRGZ0g0WDA3RjR5bVhvQjFQ\n            U1NOVlU5WEZFTFZGMWlhaUl0MjV6NVUKLS0tIGxMWnI0QnVWMit2MElIV20yK1BF\n            enRWSE85d1dTVWFJYyt5aWJmUytTMzAKGD9croAIzxIEFjYDY6jiyVrbZqnG7rVA\n            3jV1Q00xc/kzpZ48GLH4plHEDsFphJARagYViRvA57MVvgGImhXplw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5QXdOSWQwOU9qK3ppZllL\n            SjVjSGNJb1ZyTjE3aW10R3hHQUFVdHdUSjNrCklzcjlMWFRxSDdVVHQzck1hQXdP\n            WkVtTlFOUTdFbHBOdmhMcTB1M2FHa3cKLS0tIElQejkrWTc2MGxjd3lrdWN2cnBV\n            YnRLc2ZRNFRSOVFYcGRxckx5WUNxRFEKNZNy2cqpNURcjvyGxsTisPcIs4Mqpn25\n            ao2TpzARPNv5wg5fHwao5lJQG8vTuMqBvrwtVvxwznlqrl7G1wI/kQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiWDRubzhKTFlaVzFCSnZH\n            eTR2aTVJSTNybUhLSkpBQ2swczRIQzkzTURnCjJseVlUZk5hcDg0SDF0YkpIdUxL\n            KzRwQzJZWHdqMlJaZ1kwQW1ZRWNzbE0KLS0tIFhVYU9XTzVhNWNad1BxdUx3U0Fp\n            STJUbGp4ZHkzOXhiT2ZGQkpTdHpheUUKASN7GU6nImNIw4YknBFhjCIAQnnlqKv3\n            X6w9Jqsx/lIjFP5Yue+QpeXaeNPR+Spw1QGqtTYjlhLmyfTc2IDSLQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyN0hJREJsaTNDaDB2b2Vs\n            ZUl2U3BmNzkyV2dNRlpIejB3ckZZeXFaSldFCm5KbURUaWxFN251ZVR0dmQwWEJ3\n            K2lHWUhZZFNPUUJ1NzEzSTJ6b0FpU1EKLS0tIFNtQTc3NjJ4OEVrQ1oxQVltdG00\n            a05pcEZJVkJkbDU1K3lZNzdIY1VxdUEKZ12N9I7PJ6a49SvrpsAL8OSY4G0GEXhC\n            yw0+xfdR84EjSezgxFteqHbE6MhmFkLzCBZpAnlvk2rv+CUriCg70Q==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZc28xYlE2Sy9OY1ZHK3RS\n            RXVkd09obHp4aStSaWhtUUVrT1RXTzVnUFM4CksrN1FwZHdxckRER3RaRUFucEJO\n            Yk1EYWxPajdVQi82SDBnTzBOOG1Ca00KLS0tIGpjUGFjQW5JejNZOXVkQThrVjFm\n            dEI1dkdWRkhTNDExOTZYakoyRlJkU28Kzk4oPwyfgSQsOtkrXAkn0kTCjJuK7RWj\n            KhG2R/HRpDIwX5M0dlDhlpbmwgZFibNbokeEHdF73sNVqQfzd45ExQ==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2024-12-06T04:37:50Z\"\n    mac: ENC[AES256_GCM,data:TqtEz8eA8WkAwKD7Ju1KNRGHZEK5Lod5ihKL9EjvM9SxiXxLb1Lyzi9IxfKjoA1dnIHpQYl7Pn0WOg7mz0tOg+UxLRjQJCj+3vzQhyF+CmStqKRyoPeA84vUMv2dIFhHHP0w5+wFK+P/uXX52qRuUizhRNCu4u/cvX0iy8NUZfs=,iv:qW1CvW1M7Lv/9O8ISfhaMlMeaJ//zqRe31BPwFaJgOg=,tag:IUImY5FadTgPYdJMHzljUg==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.8.1\n"
  },
  {
    "path": "system/_modules/system/autoupgrade/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.system.autoupgrade;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    ;\nin\n{\n  options.mySystem.system.autoupgrade = {\n    enable = mkEnableOption \"system autoUpgrade\";\n    dates = mkOption {\n      type = types.str;\n      default = \"Sun 13:00\";\n    };\n  };\n\n  config.system.autoUpgrade = mkIf (cfg.enable) {\n    enable = true;\n    flake = \"github:budimanjojo/nix-config\";\n    flags = [\n      \"-L\" # print build logs\n    ];\n    dates = cfg.dates;\n  };\n}\n"
  },
  {
    "path": "system/_modules/system/bootloader/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  myHardware = config.myHardware;\nin\n{\n  config = lib.mkMerge [\n    {\n      boot = {\n        initrd.verbose = false;\n        consoleLogLevel = 0;\n        kernelParams = [\n          \"quiet\"\n          \"rd.udev.log_level=3\"\n        ];\n        loader.timeout = 1;\n      };\n    }\n\n    (lib.mkIf (myHardware.isUEFI) {\n      boot.loader = {\n        systemd-boot = {\n          enable = true;\n          # memtest86 is not available in aarch64-linux\n          memtest86.enable = lib.mkIf (pkgs.stdenv.hostPlatform.system != \"aarch64-linux\") true;\n          configurationLimit = 10;\n        };\n        efi.canTouchEfiVariables = true;\n      };\n    })\n\n    (lib.mkIf (!myHardware.isUEFI) {\n      boot.loader = {\n        grub = {\n          enable = true;\n          memtest86.enable = true;\n          configurationLimit = 10;\n        };\n      };\n    })\n  ];\n}\n"
  },
  {
    "path": "system/_modules/system/cpu/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  myHardware = config.myHardware;\nin\n{\n  config = lib.mkMerge [\n    (lib.mkIf (myHardware.cpu == \"amd\") {\n      hardware.cpu.amd.updateMicrocode = true;\n      boot.kernelModules = [ \"kvm-amd\" ];\n    })\n    (lib.mkIf (myHardware.cpu == \"intel\") {\n      hardware.cpu.intel.updateMicrocode = true;\n      boot.kernelModules = [ \"kvm-intel\" ];\n    })\n  ];\n}\n"
  },
  {
    "path": "system/_modules/system/font/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.system.font;\nin\n{\n  options.mySystem.system.font = {\n    enable = lib.mkEnableOption \"font\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    fonts.packages = with pkgs; [\n      nerd-fonts.ubuntu-mono\n      unifont\n    ];\n  };\n}\n"
  },
  {
    "path": "system/_modules/system/sound/default.nix",
    "content": "{\n  lib,\n  config,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.system.sound;\nin\n{\n  options.mySystem.system.sound = {\n    enable = lib.mkEnableOption \"sound\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    # security.rtkit.enable = true;\n    services.pipewire = {\n      enable = true;\n      alsa.enable = true;\n      alsa.support32Bit = true;\n      pulse.enable = true;\n      wireplumber.configPackages = [\n        (pkgs.writeTextDir \"share/wireplumber/bluetooth.lua.d/51-bluez-config.lua\" ''\n          bluez_monitor.properties = {\n            [\"bluez5.enable-sbc-xq\"] = true,\n            [\"bluez5.enable-msbc\"] = true,\n            [\"bluez5.enable-hw-volume\"] = true,\n            [\"bluez5.headset-roles\"] = \"[ hsp_hs hsp_ag hfp_hf hfp_ag ]\"\n          }\n        '')\n      ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/system/video/default.nix",
    "content": "{\n  pkgs,\n  lib,\n  config,\n  ...\n}:\nlet\n  cfg = config.mySystem.system.video;\n  myHardware = config.myHardware;\n  mySystem = config.mySystem;\n  inherit (lib) mkEnableOption mkIf mkMerge;\nin\n{\n  options.mySystem.system.video = {\n    enable = mkEnableOption \"video\";\n  };\n\n  config = mkIf (cfg.enable) (mkMerge [\n    {\n      hardware = {\n        graphics = {\n          enable = true;\n          enable32Bit = true;\n        };\n      };\n      users.users.${mySystem.adminUser}.extraGroups = [ \"video\" ];\n    }\n\n    (mkIf (myHardware.gpuDriver == \"nouveau\") {\n      # enable the nouveau kernel module\n      boot.initrd.kernelModules = [ \"nouveau\" ];\n      services.xserver.videoDrivers = [ \"nouveau\" ];\n      environment.variables = {\n        VDPAU_DRIVER = \"nouveau\";\n        LIBVA_DRIVER_NAME = \"nouveau\";\n      };\n    })\n\n    (mkIf (myHardware.gpuDriver == \"nvidia\") {\n      # enable nvidia kernel module\n      boot.initrd.kernelModules = [ \"nvidia\" ];\n      boot.extraModulePackages = [ config.boot.kernelPackages.nvidia_x11 ];\n      services.xserver.videoDrivers = [ \"nvidia\" ];\n      # hardware acceleration\n      hardware.graphics.extraPackages = [\n        pkgs.libva-vdpau-driver\n        pkgs.libvdpau-va-gl\n      ];\n      hardware.nvidia = {\n        open = false;\n        modesetting.enable = true;\n        # enable Nvidia settings menu\n        nvidiaSettings = true;\n      };\n      environment.variables = {\n        VDPAU_DRIVER = \"va_gl\";\n        LIBVA_DRIVER_NAME = \"nvidia\";\n      };\n    })\n\n    (mkIf (myHardware.gpuDriver == \"amd\") {\n      # enable amdgpu kernel module\n      boot.initrd.kernelModules = [ \"amdgpu\" ];\n      services.xserver.videoDrivers = [ \"amdgpu\" ];\n      # enables AMDVLK & OpenCL support\n      hardware.graphics.extraPackages = with pkgs; [\n        amdvlk\n        rocm-opencl-icd\n        rocm-opencl-runtime\n      ];\n      hardware.graphics.extraPackages32 = with pkgs; [ driversi686Linux.amdvlk ];\n      # force use of RADV, can be unset if AMDVLK should be used\n      environment.variables.AMD_VULKAN_ICD = \"RADV\";\n    })\n\n    (mkIf (myHardware.gpuDriver == \"intel\") {\n      # enable the i915 kernel module\n      boot.initrd.kernelModules = [ \"i915\" ];\n      # better performance than the actual Intel driver\n      services.xserver.videoDrivers = [ \"modesetting\" ];\n      # OpenCL support and VAAPI\n      hardware.graphics.extraPackages = [\n        pkgs.intel-compute-runtime\n        pkgs.intel-media-driver\n        pkgs.vaapiIntel\n        pkgs.vaapiVdpau\n        pkgs.libvdpau-va-gl\n      ];\n      environment.variables.VDPAU_DRIVER = \"va_gl\";\n    })\n  ]);\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/add-on/blueman/default.nix",
    "content": "{ lib, config, ... }:\nlet\n  mySystem = config.mySystem;\n  cfg = mySystem.windowmanager.add-on.blueman;\nin\n{\n  options.mySystem.windowmanager.add-on.blueman = {\n    enable = lib.mkEnableOption \"Blueman\";\n  };\n\n  config = lib.mkIf (cfg.enable) { services.blueman.enable = true; };\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/add-on/gnome-keyring/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.windowmanager.add-on.gnome-keyring;\nin\n{\n  options.mySystem.windowmanager.add-on.gnome-keyring = {\n    enable = lib.mkEnableOption \"gnome-keyring\";\n  };\n\n  config = lib.mkIf (cfg.enable) { services.gnome.gnome-keyring.enable = true; };\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/add-on/networkmanager/default.nix",
    "content": "{ config, lib, ... }:\nlet\n  cfg = config.mySystem.windowmanager.add-on.networkmanager;\n  mySystem = config.mySystem;\nin\n{\n  options.mySystem.windowmanager.add-on.networkmanager = {\n    enable = lib.mkEnableOption \"Network Manager\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    networking.networkmanager.enable = true;\n    users.users.${mySystem.adminUser}.extraGroups = [ \"networkmanager\" ];\n  };\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/add-on/polkit-gnome/default.nix",
    "content": "{\n  lib,\n  config,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.windowmanager.add-on.polkit-gnome;\nin\n{\n  options.mySystem.windowmanager.add-on.polkit-gnome = {\n    enable = lib.mkEnableOption \"GNOME Polkit\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    security.polkit.enable = true;\n    systemd.user.services.polkit-gnome-authentication-agent-1 = {\n      description = \"polkit-gnome-authentication-agent-1\";\n      wantedBy = [ \"graphical-session.target\" ];\n      wants = [ \"graphical-session.target\" ];\n      after = [ \"graphical-session.target\" ];\n      serviceConfig = {\n        Type = \"simple\";\n        ExecStart = \"${pkgs.polkit_gnome}/libexec/polkit-gnome-authentication-agent-1\";\n        Restart = \"on-failure\";\n        RestartSec = 1;\n        TimeoutStopSec = 10;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/add-on/thunar/default.nix",
    "content": "{\n  lib,\n  config,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.windowmanager.add-on.thunar;\nin\n{\n  options.mySystem.windowmanager.add-on.thunar = {\n    enable = lib.mkEnableOption \"Thunar file manager\";\n  };\n\n  config = lib.mkIf (cfg.enable) {\n    programs = {\n      thunar = {\n        enable = true;\n        plugins = with pkgs.xfce; [\n          thunar-volman\n          thunar-archive-plugin\n          thunar-media-tags-plugin\n        ];\n      };\n    };\n    services = {\n      tumbler.enable = true;\n      gvfs = {\n        enable = true;\n        package = pkgs.gvfs;\n      };\n    };\n    environment.systemPackages = with pkgs; [\n      ffmpegthumbnailer\n      file-roller\n    ];\n  };\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/hyprland/default.nix",
    "content": "{\n  pkgs,\n  lib,\n  config,\n  myPkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.windowmanager.hyprland;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    ;\nin\n{\n  options.mySystem.windowmanager.hyprland = {\n    enable = mkEnableOption \"Hyprland window manager\";\n    isDefaultSession = mkOption {\n      type = types.bool;\n      default = true;\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    mySystem = {\n      isWayland = true;\n\n      windowmanager.add-on = {\n        blueman.enable = true;\n        gnome-keyring.enable = true;\n        networkmanager.enable = true;\n        polkit-gnome.enable = true;\n        thunar.enable = true;\n      };\n\n      displaymanager.sddm = {\n        enable = true;\n        defaultSession = mkIf (cfg.isDefaultSession) \"hyprland\";\n      };\n      system = {\n        font.enable = true;\n        sound.enable = true;\n        video.enable = true;\n      };\n    };\n\n    environment.systemPackages = with pkgs; [\n      at-spi2-core\n      libappindicator-gtk3\n      libappindicator-gtk2\n      libappindicator\n      wl-clipboard\n      xdg-utils\n      myPkgs.configure-gtk\n    ];\n\n    programs.hyprland = {\n      enable = true;\n      xwayland.enable = true;\n    };\n\n    services.dbus.enable = true;\n    networking.networkmanager.enable = true;\n    # Needed for swaylock to work\n    security.pam.services.swaylock = { };\n  };\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/i3/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.windowmanager.i3;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    ;\nin\n{\n  options.mySystem.windowmanager.i3 = {\n    enable = mkEnableOption \"i3 window manager\";\n    isDefaultSession = mkOption {\n      type = types.bool;\n      default = true;\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    mySystem = {\n      isWayland = false;\n\n      windowmanager.add-on = {\n        blueman.enable = true;\n        networkmanager.enable = true;\n        polkit-gnome.enable = true;\n        thunar.enable = true;\n      };\n\n      system = {\n        font.enable = true;\n        sound.enable = true;\n        video.enable = true;\n      };\n\n      displaymanager.sddm = {\n        enable = true;\n        defaultSession = mkIf (cfg.isDefaultSession) \"none+i3\";\n      };\n    };\n\n    services.xserver = {\n      enable = true;\n      desktopManager = {\n        xterm.enable = false;\n        runXdgAutostartIfNone = true;\n      };\n      windowManager.i3 = {\n        enable = true;\n        package = pkgs.i3-gaps;\n        extraPackages = with pkgs; [\n          picom\n          (python3Packages.py3status.overrideAttrs (oldAttrs: {\n            propagatedBuildInputs =\n              with python3Packages;\n              [\n                pytz\n                tzlocal\n              ]\n              ++ oldAttrs.propagatedBuildInputs;\n          }))\n          xdg-utils\n        ];\n      };\n      xrandrHeads = builtins.map (mon: {\n        output = mon.xname;\n        primary = mon.primary;\n        monitorConfig = ''\n          Option \"PreferredMode\" \"${toString mon.width}x${toString mon.height}\"\n        '';\n      }) config.myHardware.monitors;\n      exportConfiguration = true;\n    };\n    programs.dconf.enable = true;\n  };\n}\n"
  },
  {
    "path": "system/_modules/windowmanager/sway/default.nix",
    "content": "{\n  pkgs,\n  lib,\n  config,\n  myPkgs,\n  ...\n}:\nlet\n  cfg = config.mySystem.windowmanager.sway;\n  myHardware = config.myHardware;\n  inherit (lib)\n    mkEnableOption\n    mkOption\n    types\n    mkIf\n    ;\nin\n{\n  options.mySystem.windowmanager.sway = {\n    enable = mkEnableOption \"Sway window manager\";\n    isDefaultSession = mkOption {\n      type = types.bool;\n      default = true;\n    };\n  };\n\n  config = mkIf (cfg.enable) {\n    mySystem = {\n      isWayland = true;\n\n      windowmanager.add-on = {\n        blueman.enable = true;\n        gnome-keyring.enable = true;\n        networkmanager.enable = true;\n        polkit-gnome.enable = true;\n        thunar.enable = true;\n      };\n\n      system = {\n        font.enable = true;\n        sound.enable = true;\n        video.enable = true;\n      };\n      displaymanager.sddm = {\n        enable = true;\n        defaultSession = mkIf (cfg.isDefaultSession) \"sway\";\n      };\n    };\n\n    environment.systemPackages = with pkgs; [\n      at-spi2-core\n      libappindicator-gtk3\n      libappindicator-gtk2\n      libappindicator\n      wl-clipboard\n      xdg-utils\n      myPkgs.configure-gtk\n    ];\n\n    programs.sway = {\n      enable = true;\n      wrapperFeatures.gtk = true;\n      extraOptions = mkIf (myHardware.gpuDriver == \"nvidia\") [ \"--unsupported-gpu\" ];\n      extraSessionCommands = mkIf (myHardware.gpuDriver == \"nvidia\") ''\n        export LIBVA_DRIVER_NAME=nvidia\n        export XDG_SESSION_TYPE=wayland\n        export GBM_BACKEND=nvidia-drm\n        export __GLX_VENDOR_LIBRARY_NAME=nvidia\n        export WLR_NO_HARDWARE_CURSORS=1\n      '';\n    };\n\n    services.dbus.enable = true;\n    xdg.portal = {\n      enable = true;\n      wlr.enable = true;\n      extraPortals = [\n        pkgs.xdg-desktop-portal-wlr\n        pkgs.xdg-desktop-portal-gtk\n      ];\n    };\n\n    networking.networkmanager.enable = true;\n    programs.dconf.enable = true;\n    programs.xwayland.enable = true;\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/default.nix",
    "content": "{ ... }:\n{\n  imports = [\n    ./firewall\n\n    ./services/adguardhome\n    ./services/chrony\n    ./services/fireqos\n    ./services/frr\n    ./services/kea\n    ./services/omada-controller\n    ./services/powerdns\n    ./services/rsyslogd\n    ./services/tdarr\n\n    ./network.nix\n    ./podman.nix\n    ./wireguard.nix\n  ];\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/firewall/config/sets.nft",
    "content": "set broadlink_plugs {\n  type ipv4_addr\n  elements = { 192.168.69.10 }\n}\n\nset android_livingroom_tv {\n  type ipv4_addr\n  elements = { 192.168.69.30 }\n}\n\nset dns_server {\n  type ipv4_addr\n  elements = { 192.168.10.1 }\n}\n\nset k8s_api {\n  type ipv4_addr\n  elements = { 192.168.200.20 }\n}\n\nset k8s_ingress {\n  type ipv4_addr\n  elements = { 192.168.15.1, 192.168.15.20, 192.168.15.21 }\n}\n\nset k8s_nodes {\n  type ipv4_addr\n  flags interval\n  auto-merge\n  elements = { 192.168.200.20-192.168.200.23 }\n}\n\nset k8s_minio {\n  type ipv4_addr\n  elements = { 192.168.15.15 }\n}\n\nset k8s_services {\n  type ipv4_addr\n  flags interval\n  auto-merge\n  elements = {\n    192.168.10.200-192.168.10.210,\n    192.168.15.0-192.168.15.254,\n    192.168.200.200-192.168.200.210\n  }\n}\n\nset k8s_vector_aggregator {\n  type ipv4_addr\n  elements = { 192.168.15.5 }\n}\n\nset omada_controller {\n  type ipv4_addr\n  elements = { 10.5.0.10 }\n}\n\nset trusted_devices {\n  type ipv4_addr\n  flags interval\n  auto-merge\n  elements = {\n    192.168.50.10,\n    192.168.50.11,\n    192.168.50.49,\n    10.10.10.12,\n    10.10.10.13\n  }\n}\n\nset wall_displays {\n  type ipv4_addr\n  elements = { 192.168.50.40 }\n}\n\nset omada_tcp {\n  type inet_service\n  flags interval\n  auto-merge\n  elements = { 443, 8043, 29811-29817 }\n}\n\nset omada_udp {\n  type inet_service\n  elements = { 29810 }\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/firewall/config/zone-directions.nft",
    "content": "chain ZONE_INPUT {\n  type filter hook input priority filter + 1; policy accept;\n  jump STATE_POLICY\n  counter jump ZONE_TO_LOCAL\n}\n\nchain ZONE_FORWARD {\n  type filter hook forward priority filter + 1; policy accept;\n  jump STATE_POLICY\n  oifname \"lan0\" counter jump ZONE_TO_LAN0\n  oifname \"lan0.50\" counter jump ZONE_TO_HOME\n  oifname \"lan0.69\" counter jump ZONE_TO_IOT\n  oifname \"lan0.200\" counter jump ZONE_TO_SERVER\n  oifname \"lan0.250\" counter jump ZONE_TO_GUEST\n  oifname \"ctr0\" counter jump ZONE_TO_CONTAINERS\n  oifname \"wg0\" counter jump ZONE_TO_WIREGUARD\n  oifname \"wan0\" counter jump ZONE_TO_WAN\n}\n\nchain ZONE_OUTPUT {\n  type filter hook output priority filter + 1; policy accept;\n  jump STATE_POLICY\n  counter jump ZONE_FROM_LOCAL\n}\n\nchain ZONE_TO_LOCAL {\n  iifname \"lo\" counter return\n  iifname \"lan0\" counter jump LAN0-LOCAL\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-LOCAL\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.69\" counter jump IOT-LOCAL\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.200\" counter jump SERVER-LOCAL\n  iifname \"lan0.200\" counter return\n  iifname \"lan0.250\" counter jump GUEST-LOCAL\n  iifname \"lan0.250\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-LOCAL\n  iifname \"ctr0\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-LOCAL\n  iifname \"wg0\" counter return\n  iifname \"wan0\" counter jump WAN-LOCAL\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_LOCAL default-action drop\"\n}\n\nchain ZONE_TO_LAN0 {\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-LAN0\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.69\" counter jump IOT-LAN0\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.200\" counter jump SERVER-LAN0\n  iifname \"lan0.200\" counter return\n  iifname \"lan0.250\" counter jump GUEST-LAN0\n  iifname \"lan0.250\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-LAN0\n  iifname \"ctr0\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-LAN0\n  iifname \"wg0\" counter return\n  iifname \"wan0\" counter jump WAN-LAN0\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_LAN0 default-action drop\"\n}\n\nchain ZONE_TO_HOME {\n  iifname \"lan0.50\" counter return\n  iifname \"lan0\" counter jump LAN0-HOME\n  iifname \"lan0\" counter return\n  iifname \"lan0.69\" counter jump IOT-HOME\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.200\" counter jump SERVER-HOME\n  iifname \"lan0.200\" counter return\n  iifname \"lan0.250\" counter jump GUEST-HOME\n  iifname \"lan0.250\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-HOME\n  iifname \"ctr0\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-HOME\n  iifname \"wg0\" counter return\n  iifname \"wan0\" counter jump WAN-HOME\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_HOME default-action drop\"\n}\n\nchain ZONE_TO_IOT {\n  iifname \"lan0.69\" counter return\n  iifname \"lan0\" counter jump LAN0-IOT\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-IOT\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.200\" counter jump SERVER-IOT\n  iifname \"lan0.200\" counter return\n  iifname \"lan0.250\" counter jump GUEST-IOT\n  iifname \"lan0.250\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-IOT\n  iifname \"ctr0\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-IOT\n  iifname \"wg0\" counter return\n  iifname \"wan0\" counter jump WAN-IOT\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_IOT default-action drop\"\n}\n\nchain ZONE_TO_SERVER {\n  iifname \"lan0.200\" counter return\n  iifname \"lan0\" counter jump LAN0-SERVER\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-SERVER\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.69\" counter jump IOT-SERVER\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.250\" counter jump GUEST-SERVER\n  iifname \"lan0.250\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-SERVER\n  iifname \"ctr0\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-SERVER\n  iifname \"wg0\" counter return\n  iifname \"wan0\" counter jump WAN-SERVER\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_SERVER default-action drop\"\n}\n\nchain ZONE_TO_GUEST {\n  iifname \"lan0.250\" counter return\n  iifname \"lan0\" counter jump LAN0-GUEST\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-GUEST\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.69\" counter jump IOT-GUEST\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.200\" counter jump SERVER-GUEST\n  iifname \"lan0.200\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-GUEST\n  iifname \"ctr0\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-GUEST\n  iifname \"wg0\" counter return\n  iifname \"wan0\" counter jump WAN-GUEST\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_GUEST default-action drop\"\n}\n\nchain ZONE_TO_CONTAINERS {\n  iifname \"ctr0\" counter return\n  iifname \"lan0\" counter jump LAN0-CONTAINERS\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-CONTAINERS\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.69\" counter jump IOT-CONTAINERS\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.200\" counter jump SERVER-CONTAINERS\n  iifname \"lan0.200\" counter return\n  iifname \"lan0.250\" counter jump GUEST-CONTAINERS\n  iifname \"lan0.250\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-CONTAINERS\n  iifname \"wg0\" counter return\n  iifname \"wan0\" counter jump WAN-CONTAINERS\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_CONTAINERS default-action drop\"\n}\n\nchain ZONE_TO_WIREGUARD {\n  iifname \"wg0\" counter return\n  iifname \"lan0\" counter jump LAN0-WIREGUARD\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-WIREGUARD\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.69\" counter jump IOT-WIREGUARD\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.200\" counter jump SERVER-WIREGUARD\n  iifname \"lan0.200\" counter return\n  iifname \"lan0.250\" counter jump GUEST-WIREGUARD\n  iifname \"lan0.250\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-WIREGUARD\n  iifname \"ctr0\" counter return\n  iifname \"wan0\" counter jump WAN-WIREGUARD\n  iifname \"wan0\" counter return\n  counter drop comment \"ZONE_TO_WIREGUARD default-action drop\"\n}\n\nchain ZONE_TO_WAN {\n  iifname \"wan0\" counter return\n  iifname \"lan0\" counter jump LAN0-WAN\n  iifname \"lan0\" counter return\n  iifname \"lan0.50\" counter jump HOME-WAN\n  iifname \"lan0.50\" counter return\n  iifname \"lan0.69\" counter jump IOT-WAN\n  iifname \"lan0.69\" counter return\n  iifname \"lan0.200\" counter jump SERVER-WAN\n  iifname \"lan0.200\" counter return\n  iifname \"lan0.250\" counter jump GUEST-WAN\n  iifname \"lan0.250\" counter return\n  iifname \"ctr0\" counter jump CONTAINERS-WAN\n  iifname \"ctr0\" counter return\n  iifname \"wg0\" counter jump WIREGUARD-WAN\n  iifname \"wg0\" counter return\n  counter drop comment \"ZONE_TO_WAN default-action drop\"\n}\n\nchain ZONE_FROM_LOCAL {\n  oifname \"lo\" counter return\n  oifname \"lan0\" counter jump LOCAL-LAN0\n  oifname \"lan0\" counter return\n  oifname \"lan0.50\" counter jump LOCAL-HOME\n  oifname \"lan0.50\" counter return\n  oifname \"lan0.69\" counter jump LOCAL-IOT\n  oifname \"lan0.69\" counter return\n  oifname \"lan0.200\" counter jump LOCAL-SERVER\n  oifname \"lan0.200\" counter return\n  oifname \"lan0.250\" counter jump LOCAL-GUEST\n  oifname \"lan0.250\" counter return\n  oifname \"ctr0\" counter jump LOCAL-CONTAINERS\n  oifname \"ctr0\" counter return\n  oifname \"wg0\" counter jump LOCAL-WIREGUARD\n  oifname \"wg0\" counter return\n  oifname \"wan0\" counter jump LOCAL-WAN\n  oifname \"wan0\" counter return\n  counter drop comment \"ZONE_FROM_LOCAL default-action drop\"\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/firewall/config/zone-rules.nft",
    "content": "#################\n# ZONE_TO_LOCAL #\n#################\nchain LAN0-LOCAL {\n  udp dport 123 counter return comment \"allow access to ntp server\"\n  meta l4proto { tcp, udp } th dport { 53, 853 } ip daddr @dns_server counter return comment \"allow access to dns server\"\n  ct state invalid counter log prefix \"[LAN0-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LAN0-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\nchain HOME-LOCAL {\n  udp dport 123 counter return comment \"allow access to ntp server\"\n  meta l4proto { tcp, udp } th dport { 53, 853 } ip daddr @dns_server counter return comment \"allow access to dns server\"\n  ip saddr @trusted_devices tcp dport 22 counter return comment \"allow access to ssh server from trusted_devices\"\n  ip saddr @trusted_devices meta l4proto icmp counter return comment \"allow icmp request from trusted_devices\"\n  ct state invalid counter log prefix \"[HOME-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[HOME-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\nchain IOT-LOCAL {\n  udp dport 123 counter return comment \"allow access to ntp server\"\n  meta l4proto { tcp, udp } th dport { 53, 853 } ip daddr @dns_server counter return comment \"allow access to dns server\"\n  ct state invalid counter log prefix \"[IOT-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[IOT-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\nchain SERVER-LOCAL {\n  udp dport 123 counter return comment \"allow access to ntp server\"\n  meta l4proto { tcp, udp } th dport { 53, 853 } ip daddr @dns_server counter return comment \"allow access to dns server\"\n  tcp dport 179 counter return comment \"allow access to bgp server\"\n  udp dport 69 counter return comment \"allow access to tftp server\"\n  ip saddr @k8s_nodes tcp dport { 80, 3000 } counter return comment \"allow access to adguardhome from k8s_nodes\"\n  ip saddr @k8s_nodes meta l4proto icmp counter return comment \"allow icmp request from k8s_nodes\"\n  ip saddr @k8s_nodes tcp dport 9100 counter return comment \"allow access to node-exporter from k8s_nodes\"\n  ip saddr @k8s_nodes tcp dport 9633 counter return comment \"allow access to smartctl-exporter from k8s_nodes\"\n  ct state invalid counter log prefix \"[SERVER-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[SERVER-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\nchain GUEST-LOCAL {\n  udp dport 123 counter return comment \"allow access to ntp server\"\n  meta l4proto { tcp, udp } th dport { 53, 853 } ip daddr @dns_server counter return comment \"allow access to dns server\"\n  ct state invalid counter log prefix \"[GUEST-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[GUEST-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\nchain CONTAINERS-LOCAL {\n  udp dport 123 counter return comment \"allow access to ntp server\"\n  meta l4proto { tcp, udp } th dport { 53, 853 } ip daddr @dns_server counter return comment \"allow access to dns server\"\n  ct state invalid counter log prefix \"[CONTAINERS-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[CONTAINERS-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WIREGUARD-LOCAL {\n  udp dport 123 counter return comment \"allow access to ntp server\"\n  meta l4proto { tcp, udp } th dport { 53, 853 } ip daddr @dns_server counter return comment \"allow access to dns server\"\n  ip saddr @trusted_devices tcp dport 22 counter return comment \"allow access to ssh server from trusted_devices\"\n  ct state invalid counter log prefix \"[WIREGUARD-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WIREGUARD-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-LOCAL {\n  ct state invalid counter log prefix \"[WAN-LOCAL-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-LOCAL-default-D]\" drop comment \"default-action drop\"\n}\n\n################\n# ZONE_TO_LAN0 #\n################\nchain HOME-LAN0 {\n  ip saddr @trusted_devices counter return comment \"accept from trusted_devices\"\n  ct state invalid counter log prefix \"[HOME-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[HOME-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\nchain IOT-LAN0 {\n  ct state invalid counter log prefix \"[IOT-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[IOT-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\nchain SERVER-LAN0 {\n  ct state invalid counter log prefix \"[SERVER-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[SERVER-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\nchain GUEST-LAN0 {\n  ct state invalid counter log prefix \"[GUEST-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[GUEST-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\nchain CONTAINERS-LAN0 {\n  ct state invalid counter log prefix \"[CONTAINERS-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[CONTAINERS-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WIREGUARD-LAN0 {\n  ct state invalid counter log prefix \"[WIREGUARD-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WIREGUARD-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-LAN0 {\n  ct state invalid counter log prefix \"[WAN-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\n################\n# ZONE_TO_HOME #\n################\nchain LAN0-HOME {\n  ct state invalid counter log prefix \"[LAN0-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LAN0-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\nchain IOT-HOME {\n  ct state invalid counter log prefix \"[IOT-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[IOT-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\nchain SERVER-HOME {\n  ip saddr @k8s_nodes ip daddr @wall_displays tcp dport 2323 counter return comment \"allow access to fullykioskbrowser from k8s_nodes\"\n  ip saddr @k8s_nodes tcp dport 9100 counter return comment \"allow access to node-exporter from k8s_nodes\"\n  ip saddr @k8s_nodes tcp dport 9633 counter return comment \"allow access to smartctl-exporter from k8s_nodes\"\n  ct state invalid counter log prefix \"[SERVER-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[SERVER-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\nchain GUEST-HOME {\n  ct state invalid counter log prefix \"[GUEST-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[GUEST-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\nchain CONTAINERS-HOME {\n  ct state invalid counter log prefix \"[CONTAINERS-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[CONTAINERS-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WIREGUARD-HOME {\n  ip saddr @trusted_devices return comment \"allow access from trusted_devices\"\n  ct state invalid counter log prefix \"[WIREGUARD-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WIREGUARD-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-HOME {\n  ct state invalid counter log prefix \"[WAN-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\n###############\n# ZONE_TO_IOT #\n###############\nchain LAN0-IOT {\n  ct state invalid counter log prefix \"[LAN0-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LAN0-IOT-default-D]\" drop comment \"default-action drop\"\n}\n\nchain HOME-IOT {\n  ct state invalid counter log prefix \"[HOME-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[HOME-IOT-default-A]\" return comment \"default-action accept\"\n}\n\nchain SERVER-IOT {\n  ct state invalid counter log prefix \"[SERVER-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[SERVER-IOT-default-A]\" return comment \"default-action accept\"\n}\n\nchain GUEST-IOT {\n  ct state invalid counter log prefix \"[GUEST-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[GUEST-IOT-default-D]\" drop comment \"default-action drop\"\n}\n\nchain CONTAINERS-IOT {\n  ct state invalid counter log prefix \"[CONTAINERS-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[CONTAINERS-IOT-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WIREGUARD-IOT {\n  ip saddr @trusted_devices return comment \"allow access from trusted_devices\"\n  ct state invalid counter log prefix \"[WIREGUARD-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WIREGUARD-IOT-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-IOT {\n  ct state invalid counter log prefix \"[WAN-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-IOT-default-D]\" drop comment \"default-action drop\"\n}\n\n##################\n# ZONE_TO_SERVER #\n##################\nchain LAN0-SERVER {\n  tcp dport { 80, 443 } ip daddr @k8s_ingress counter return comment \"allow access to k8s_ingress\"\n  ct state invalid counter log prefix \"[LAN0-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LAN0-SERVER-default-D]\" drop comment \"default-action drop\"\n}\n\nchain HOME-SERVER {\n  tcp dport { 80, 443 } ip daddr @k8s_ingress counter return comment \"allow access to k8s_ingress\"\n  ip saddr @trusted_devices tcp dport 22 counter return comment \"allow access to ssh from trusted_devices\"\n  ip saddr @trusted_devices ip daddr @k8s_nodes counter return comment \"allow access to k8s nodes from trusted_devices\"\n  ip saddr @trusted_devices tcp dport 50000 counter return comment \"allow access to talos api port from trusted_devices\"\n  ct state invalid counter log prefix \"[HOME-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[HOME-SERVER-default-D]\" drop comment \"default-action drop\"\n}\n\nchain IOT-SERVER {\n  ip saddr @broadlink_plugs ip daddr @k8s_nodes counter return comment \"allow broadlink_plugs to access k8s_nodes\"\n  ip saddr @android_livingroom_tv tcp dport { 80, 443 } ip daddr @k8s_ingress return comment \"allow access to k8s_ingress from android_livingroom_tv\"\n  ct state invalid counter log prefix \"[IOT-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[IOT-SERVER-default-D]\" drop comment \"default-action drop\"\n}\n\nchain GUEST-SERVER {\n  ct state invalid counter log prefix \"[GUEST-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[GUEST-SERVER-default-D]\" drop comment \"default-action drop\"\n}\n\nchain CONTAINERS-SERVER {\n  ct state invalid counter log prefix \"[CONTAINERS-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[CONTAINERS-SERVER-default-A]\" accept comment \"default-action accept\"\n}\n\nchain WIREGUARD-SERVER {\n  tcp dport { 80, 443 } ip daddr @k8s_ingress counter return comment \"allow access to k8s_ingress\"\n  ip saddr @trusted_devices return comment \"allow access from trusted_devices\"\n  ct state invalid counter log prefix \"[WIREGUARD-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WIREGUARD-SERVER-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-SERVER {\n  ct state invalid counter log prefix \"[WAN-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-SERVER-default-D]\" drop comment \"default-action drop\"\n}\n\n#################\n# ZONE_TO_GUEST #\n#################\nchain LAN0-GUEST {\n  ct state invalid counter log prefix \"[LAN0-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LAN0-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\nchain HOME-GUEST {\n  ct state invalid counter log prefix \"[HOME-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[HOME-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\nchain IOT-GUEST {\n  ct state invalid counter log prefix \"[IOT-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[IOT-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\nchain SERVER-GUEST {\n  ct state invalid counter log prefix \"[SERVER-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[SERVER-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\nchain CONTAINERS-GUEST {\n  ct state invalid counter log prefix \"[CONTAINERS-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[CONTAINERS-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WIREGUARD-GUEST {\n  ct state invalid counter log prefix \"[WIREGUARD-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WIREGUARD-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-GUEST {\n  ct state invalid counter log prefix \"[WAN-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\n######################\n# ZONE_TO_CONTAINERS #\n######################\nchain LAN0-CONTAINERS {\n  ip daddr @omada_controller udp dport @omada_udp counter return comment \"accept omada_controller udp ports\"\n  ip daddr @omada_controller tcp dport @omada_tcp counter return comment \"accept omada_controller tcp ports\"\n  ct state invalid counter log prefix \"[LAN0-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LAN0-CONTAINERS-default-A]\" return comment \"default-action accept\"\n}\n\nchain HOME-CONTAINERS {\n  ip saddr @trusted_devices counter return comment \"allow access from trusted_devices\"\n  ct state invalid counter log prefix \"[HOME-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[HOME-CONTAINERS-default-D]\" drop comment \"default-action drop\"\n}\n\nchain IOT-CONTAINERS {\n  ct state invalid counter log prefix \"[IOT-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[IOT-CONTAINERS-default-D]\" drop comment \"default-action drop\"\n}\n\nchain SERVER-CONTAINERS {\n  ct state invalid counter log prefix \"[SERVER-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[SERVER-CONTAINERS-default-A]\" return comment \"default-action accept\"\n}\n\nchain GUEST-CONTAINERS {\n  tcp dport 8088 ip daddr @omada_controller counter return comment \"allow access to guest portal\"\n  ct state invalid counter log prefix \"[GUEST-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[GUEST-CONTAINERS-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WIREGUARD-CONTAINERS {\n  ip saddr @trusted_devices return comment \"allow access from trusted_devices\"\n  ct state invalid counter log prefix \"[WIREGUARD-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WIREGUARD-CONTAINERS-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-CONTAINERS {\n  ct state invalid counter log prefix \"[WAN-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-CONTAINERS-default-D]\" drop comment \"default-action drop\"\n}\n\n#####################\n# ZONE_TO_WIREGUARD #\n#####################\nchain LAN0-WIREGUARD {\n  ct state invalid counter log prefix \"[LAN0-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LAN0-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\nchain HOME-WIREGUARD {\n  ip saddr @trusted_devices tcp dport 22 counter return comment \"allow access to ssh from trusted_devices\"\n  ip saddr @trusted_devices ip daddr 10.10.10.10 tcp dport 8080 counter return comment \"allow access to seedbox webuiPort from trusted_devices\"\n  tcp dport 5432 ip daddr 10.10.10.13 counter return comment \"allow access to postgres database on work-pc\"\n  tcp dport 631 ip daddr 10.10.10.13 counter return comment \"allow access to printer on work-pc\"\n  ct state invalid counter log prefix \"[HOME-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[HOME-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\nchain IOT-WIREGUARD {\n  ct state invalid counter log prefix \"[IOT-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[IOT-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\nchain SERVER-WIREGUARD {\n  meta l4proto icmp counter return comment \"allow icmp request\"\n  tcp dport 22 counter return comment \"allow access to ssh\"\n  ct state invalid counter log prefix \"[SERVER-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[SERVER-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\nchain GUEST-WIREGUARD {\n  ct state invalid counter log prefix \"[GUEST-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[GUEST-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\nchain CONTAINERS-WIREGUARD {\n  ct state invalid counter log prefix \"[CONTAINERS-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[CONTAINERS-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\nchain WAN-WIREGUARD {\n  ct state invalid counter log prefix \"[WAN-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[WAN-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\n###############\n# ZONE_TO_WAN #\n###############\nchain LAN0-WAN {\n  counter return comment \"default-action accept\"\n}\n\nchain HOME-WAN {\n  counter return comment \"default-action accept\"\n}\n\nchain IOT-WAN {\n  counter return comment \"default-action accept\"\n}\n\nchain SERVER-WAN {\n  counter return comment \"default-action accept\"\n}\n\nchain GUEST-WAN {\n  counter return comment \"default-action accept\"\n}\n\nchain CONTAINERS-WAN {\n  counter return comment \"default-action accept\"\n}\n\nchain WIREGUARD-WAN {\n  counter return comment \"default-action accept\"\n}\n\n###################\n# ZONE_FROM_LOCAL #\n###################\nchain LOCAL-LAN0 {\n  ct state invalid counter log prefix \"[LOCAL-LAN0-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LOCAL-LAN0-default-D]\" drop comment \"default-action drop\"\n}\n\nchain LOCAL-HOME {\n  ct state invalid counter log prefix \"[LOCAL-HOME-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LOCAL-HOME-default-D]\" drop comment \"default-action drop\"\n}\n\nchain LOCAL-IOT {\n  ct state invalid counter log prefix \"[LOCAL-IOT-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LOCAL-IOT-default-D]\" drop comment \"default-action drop\"\n}\n\nchain LOCAL-SERVER {\n  tcp dport 179 counter return comment \"allow access to bgp server\"\n  tcp dport 6000 ip daddr @k8s_vector_aggregator counter return comment \"allow access to k8s_vector_aggregator\"\n  tcp dport 9000 ip daddr @k8s_minio counter return comment \"allow access to k8s_minio\"\n  tcp dport 2049 ip daddr 192.168.200.30 counter return comment \"allow access to NFS server\"\n  ct state invalid counter log prefix \"[LOCAL-SERVER-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LOCAL-SERVER-default-D]\" drop comment \"default-action drop\"\n}\n\nchain LOCAL-GUEST {\n  ct state invalid counter log prefix \"[LOCAL-GUEST-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LOCAL-GUEST-default-D]\" drop comment \"default-action drop\"\n}\n\nchain LOCAL-CONTAINERS {\n  ct state invalid counter log prefix \"[LOCAL-CONTAINERS-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LOCAL-CONTAINERS-default-D]\" drop comment \"default-action drop\"\n}\n\nchain LOCAL-WIREGUARD {\n  ct state invalid counter log prefix \"[LOCAL-WIREGUARD-invalid-D]\" drop comment \"drop invalid\"\n  counter log prefix \"[LOCAL-WIREGUARD-default-D]\" drop comment \"default-action drop\"\n}\n\nchain LOCAL-WAN {\n  counter return comment \"default-action accept\"\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/firewall/default.nix",
    "content": "{ ... }:\n{\n  boot.kernel.sysctl = {\n    \"net.ipv4.conf.all.forwarding\" = true;\n    \"net.ipv6.conf.all.forwarding\" = false;\n  };\n  networking = {\n    firewall.enable = false;\n    nat.enable = false;\n    nftables = {\n      enable = true;\n      flushRuleset = true;\n      tables = {\n        home_nat = {\n          family = \"ip\";\n          content = ''\n            chain PREROUTING {\n              type nat hook prerouting priority dstnat; policy accept;\n              iifname {\n                lan0.69, lan0.250\n              } meta l4proto { tcp, udp } th dport 53 ip daddr != 192.168.10.1 counter dnat to 192.168.10.1:53 comment \"force DNS for IOT and GUEST VLAN\"\n              iifname {\n                lan0, lan0.50, lan0.69, lan0.200, lan0,250, wg0\n              } udp dport 123 ip daddr != 192.168.10.1 counter dnat to 192.168.10.1:123 comment \"force NTP for all interfaces\"\n            }\n            chain POSTROUTING {\n              type nat hook postrouting priority srcnat; policy accept;\n              oifname \"wan0\" ip daddr 0.0.0.0/0 counter masquerade comment \"outbound will use the public IP so I can browse internet\"\n            }\n          '';\n        };\n        home_ip_filter = {\n          family = \"ip\";\n          content = ''\n            chain STATE_POLICY {\n              ct state established counter accept\n              ct state related counter accept\n              return\n            }\n\n            ${builtins.readFile ./config/sets.nft}\n            ${builtins.readFile ./config/zone-rules.nft}\n            ${builtins.readFile ./config/zone-directions.nft}\n          '';\n        };\n        home_ip6_filter = {\n          family = \"ip6\";\n          content = ''\n            chain STATE_POLICY {\n              ct state established counter accept\n              ct state related counter accept\n              return\n            }\n\n            chain ZONE_INPUT {\n              type filter hook input priority filter + 1; policy accept;\n              jump STATE_POLICY\n              iifname \"lo\" counter return\n              counter drop comment \"default-action drop\"\n            }\n\n            # ZONE_FORWARD is disabled for ipv6 in sysctl above\n\n            chain ZONE_OUTPUT {\n              type filter hook output priority filter + 1; policy accept;\n              jump STATE_POLICY\n              oifname \"lo\" counter return\n              counter drop comment \"default-action drop\"\n            }\n          '';\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/network.nix",
    "content": "{ ... }:\n{\n  networking = {\n    domain = \"home.arpa\";\n    nameservers = [\n      \"1.1.1.1\"\n      \"8.8.8.8\"\n    ];\n    useDHCP = false;\n  };\n  systemd.network = {\n    enable = true;\n    wait-online = {\n      anyInterface = false;\n      ignoredInterfaces = [\n        \"wan0\"\n        \"wan1\"\n        \"lan1\"\n        \"ctr0\"\n        \"wg0\"\n      ];\n    };\n    links = {\n      # rename all interface names to be easier to identify\n      \"10-wan0\" = {\n        matchConfig.Path = \"pci-0000:01:00.0\";\n        linkConfig.Name = \"wan0\";\n      };\n      \"10-wan1\" = {\n        matchConfig.Path = \"pci-0000:02:00.0\";\n        linkConfig.Name = \"wan1\";\n      };\n      \"10-lan0\" = {\n        matchConfig.Path = \"pci-0000:03:00.0\";\n        linkConfig.Name = \"lan0\";\n      };\n      \"10-lan1\" = {\n        matchConfig.Path = \"pci-0000:04:00.0\";\n        linkConfig.Name = \"lan1\";\n      };\n    };\n\n    netdevs = {\n      # VLANs\n      \"20-lan0.50\" = {\n        netdevConfig = {\n          Name = \"lan0.50\";\n          Description = \"HOME\";\n          Kind = \"vlan\";\n        };\n        vlanConfig.Id = 50;\n      };\n      \"20-lan0.69\" = {\n        netdevConfig = {\n          Name = \"lan0.69\";\n          Description = \"IOT\";\n          Kind = \"vlan\";\n        };\n        vlanConfig.Id = 69;\n      };\n      \"20-lan0.200\" = {\n        netdevConfig = {\n          Name = \"lan0.200\";\n          Description = \"SERVER\";\n          Kind = \"vlan\";\n        };\n        vlanConfig.Id = 200;\n      };\n      \"20-lan0.250\" = {\n        netdevConfig = {\n          Name = \"lan0.250\";\n          Description = \"GUEST\";\n          Kind = \"vlan\";\n        };\n        vlanConfig.Id = 250;\n      };\n    };\n\n    networks = {\n      # Disabled interfaces\n      \"30-wan1\" = {\n        matchConfig.Name = \"wan1\";\n        networkConfig.ConfigureWithoutCarrier = true;\n        linkConfig.ActivationPolicy = \"always-down\";\n      };\n      \"30-lan1\" = {\n        matchConfig.Name = \"lan1\";\n        networkConfig.ConfigureWithoutCarrier = true;\n        linkConfig.ActivationPolicy = \"always-down\";\n      };\n\n      # WAN0\n      \"30-wan0\" = {\n        matchConfig.Name = \"wan0\";\n        networkConfig.DHCP = \"yes\";\n        linkConfig = {\n          MTUBytes = \"1500\";\n          RequiredForOnline = \"routable\";\n        };\n      };\n\n      # LAN0\n      \"30-lan0\" = {\n        matchConfig.Name = \"lan0\";\n        address = [ \"192.168.10.1/24\" ];\n        linkConfig.RequiredForOnline = \"carrier\";\n        vlan = [\n          \"lan0.50\" # HOME\n          \"lan0.69\" # IOT\n          \"lan0.200\" # SERVER\n          \"lan0.250\" # GUEST\n        ];\n      };\n\n      # HOME VLAN\n      \"30-lan0.50\" = {\n        matchConfig.Name = \"lan0.50\";\n        address = [ \"192.168.50.1/24\" ];\n        linkConfig.RequiredForOnline = \"routable\";\n      };\n\n      # IOT VLAN\n      \"30-lan0.69\" = {\n        matchConfig.Name = \"lan0.69\";\n        address = [ \"192.168.69.1/24\" ];\n        linkConfig.RequiredForOnline = \"routable\";\n      };\n\n      # SERVER VLAN\n      \"30-lan0.200\" = {\n        matchConfig.Name = \"lan0.200\";\n        address = [ \"192.168.200.1/24\" ];\n        linkConfig.RequiredForOnline = \"routable\";\n      };\n\n      # GUEST VLAN\n      \"30-lan0.250\" = {\n        matchConfig.Name = \"lan0.250\";\n        address = [ \"192.168.250.1/24\" ];\n        linkConfig.RequiredForOnline = \"routable\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/podman.nix",
    "content": "{ ... }:\n{\n  virtualisation = {\n    containers.enable = true;\n    oci-containers.backend = \"podman\";\n    podman = {\n      enable = true;\n      autoPrune.enable = true;\n      defaultNetwork.settings = {\n        network_interface = \"ctr0\";\n        # setting this to true ironically breaks the dns because dns server is not running in the gateway ip\n        # even setting `dns_name_servers` here doesn't work with this set to true\n        dns_enabled = false;\n        subnets = [\n          {\n            subnet = \"10.5.0.0/24\";\n            gateway = \"10.5.0.1\";\n            lease_range = {\n              start_ip = \"10.5.0.50\";\n              end_ip = \"10.5.0.200\";\n            };\n          }\n        ];\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/secret.sops.yaml",
    "content": "wireguard:\n    privatekey: ENC[AES256_GCM,data:kfz+WjdwfDu158B8IvbsfFFC9XxEhf5e4Y5iwbWSuXHZeYXKo4vfAklndho=,iv:JmgiY1+xBY6em0IFdV0X6fciac7sAFMuGtkNVICqPWc=,tag:axHQxloK++lEH8RFjkK4qw==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEZFFESHd4aXJQbG9kTVNN\n            ekhpYmRnUWJuSk5PZ3N2S1dYSVl6U1UxbndJCmFmdzVqVjZGVzdhdHozOUpNVFFV\n            eWZyYlByRDFUZk9KalRvSjZPWTBPYlEKLS0tIGtTZTNLUVNpOTdHLzdISFlpMFU5\n            SHl3ZVF4T0NmdmxqeWp4a2N0SjFGVEkKFrRXfAPARjvFhcYN2IQqeidxGQAOrbMi\n            iY5qnbzfGuOlp78Z5QXmktFe2SkzNh+gxMvt7SzvjsZVpHQoOgrCKQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3Uis1M2QvOUkvcmpyNXlW\n            VWRWL1RnUlVGS3liWFltSUk0bVpHdkkzblU4CmpxU3Y1bmtzTlpvdDF0RGh2U3VQ\n            RHRydnZDUmhiZkhKN1o2MWxOQlhPancKLS0tIEJJT0tWYWNFUTl1WkhKSElRcm5T\n            SUJGOUVSZW9ENkRCcTRWZjI2c2FOQUEKlm2abH3M0cRJCN5wuhXBKjmmMLp/OLPR\n            PkOxTPzKZtBvnYpZ7ODorLV+0TXekJYYbd43J1phyIlSmPsSlCzI5w==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUamx1dUh4V2pUSjVyNnRu\n            Q21WM3NDaU95MFRHSlFmaVp0WEVOc3NSK1JVClA4RHAyZ3ZuZjhBMk5hY05yYnZX\n            N3VJcW5iRnk0OWgxVXArb0RGOGZaMTgKLS0tIGg5aW0zNkNud3ROemVwbnA3NndI\n            cVk2T1ZXTGtOVEJUb0JVQXowNlNoa0EK87Zdn7rEGTRl291rLxqe655gGgYJOEJ9\n            Fr0q0i0fq5NyrQtXFAisYOCvtLnfXT5dsCf5wAEMyLg3wpG1gSr3lQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMdkIwaDB0VEg3RlFTTi82\n            QW9KQ2JUSVo3dElwUldnNnVISFo1QkVsK0JjCm1YUldLOFpRTmJKNy9wNDBrc3Nz\n            RXo0V2FVM3d0dFF3WGVuSjRSMDBBTm8KLS0tIDVQVnFod3pJWVd2bHY5NjBacjJu\n            d3lUZ3FlNXluYXR3TTN2Y3NSK0JQZjgKVM87AZrFUatOBODRpzRhE2V5AZp/wTOK\n            3rD6lPRw19JPazNWFBL7GMTIjGfyVNINcSMzpYHbL3Hzzi5z4/TM7w==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3c0RKMU93SjcyNndYcjZo\n            eVJJS2JYVDB0MElOa0IwTVVaeVN1blJrcml3Ci9LSy9HUUg1UUVyNDRGTkFKQkVs\n            OE84cXdTUi8vTXk4N3BGWCtPQXJ1aDQKLS0tIEFGNVVxUzNLMUFyOTVndGNLSjVy\n            V243ZUdBT1BNQmUvdEdtc080UVlLVG8KXbW/9iga/O9Sbl2zPWW8fvA1Bf4vBNk5\n            7RjJmCn+8hdAb1xT5D8+91HvNUMVLl8JtQDb/VbVf27Ait8F5SmZeA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqb0hDakdXc2x5MU8wMUhs\n            cnByQ0tmZkdrN25odG5DRXc5STRJUEc0WlRRCkN6NjhnNjdoWDR2VGQ2OUNvcjg1\n            UFFmM0tHbUovZm4veC96ZzIyZi9tcFkKLS0tIGlLeXNzekw4YnhYZWFpVDhRa0dL\n            Y2hZSjk1VHNTV3lBeFFUTExKMTZWcFUK64I4wjtFm2V4egss9dF0IxVIfhCXUcGL\n            L3iOS1c1M8/lbvLls3EeOP1UDiiy8i4rmddYCRUwXIsbsFKU8LBaug==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2024-11-19T05:20:23Z\"\n    mac: ENC[AES256_GCM,data:etRAnN6AQibybMNEXiQ/v86/gm5YcStDiQSMw5b3XQ07w54llUklz0X7ALp65byPC48LFMgWpOF8w/YTWoJuAd7byxxx/iUgBZUMsHXT/c47Se/cgr0I4x5lZS8bsI3tmyNeEpmg2qndVgt+XgU3gFL/60mrUwNyzkDTJKIaFC0=,iv:g3n9TNg7QD1DmMH1oPoQm7ae9m0PKbCEIx/Xa2tPPv4=,tag:osIKhCCp+3QZ03Hzpo83LQ==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.8.1\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/adguardhome/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  adguardUser = \"adguardhome\";\nin\n{\n  sops.secrets = {\n    \"adguardhome/password\" = {\n      sopsFile = ./secret.sops.yaml;\n      owner = adguardUser;\n      restartUnits = [ \"adguardhome.service\" ];\n    };\n    \"adguardhome/env\" = {\n      sopsFile = ./secret.sops.yaml;\n      owner = adguardUser;\n      restartUnits = [ \"adguardhome.service\" ];\n    };\n  };\n\n  # add user, needed to access the secret\n  users = {\n    users.${adguardUser} = {\n      isSystemUser = true;\n      group = adguardUser;\n    };\n    groups.${adguardUser} = { };\n  };\n\n  systemd.services.adguardhome = {\n    # see: https://github.com/AdguardTeam/AdGuardHome/issues/4880\n    wants = [ \"time-sync.target\" ];\n    after = [ \"time-sync.target\" ];\n    serviceConfig = {\n      EnvironmentFile = \"${config.sops.secrets.\"adguardhome/env\".path}\";\n      User = adguardUser;\n    };\n    # we do `envsubst` to substitute string like ${VAR} using the environment secrets\n    # and also `bcrypt` the substituted unencrypted password with `htpasswd` as per config requirement\n    preStart = lib.mkAfter ''\n      ${pkgs.envsubst}/bin/envsubst -no-unset -i \"$STATE_DIRECTORY/AdGuardHome.yaml\" -o \"$STATE_DIRECTORY/AdGuardHome.yaml\"\n\n      PASSWORD=$(cat ${config.sops.secrets.\"adguardhome/password\".path})\n      HASHED_PASSWORD=$(${pkgs.apacheHttpd}/bin/htpasswd -nbB \"\" $PASSWORD | cut -c 2-)\n      ${pkgs.gnused}/bin/sed -i \"s,ADGUARDHOMEPASSWORD,$HASHED_PASSWORD,\" \"$STATE_DIRECTORY/AdGuardHome.yaml\"\n    '';\n  };\n\n  services.adguardhome = {\n    enable = true;\n    host = \"192.168.10.1\";\n    port = 3000;\n    mutableSettings = true;\n    settings = {\n      schema_version = 29; # the default is pkgs.adguardhome.schema_version\n      users = [\n        {\n          name = \"budiman\";\n          password = \"ADGUARDHOMEPASSWORD\"; # placeholder\n        }\n      ];\n      auth_attempts = 5;\n      block_auth_min = 15;\n      theme = \"auto\";\n      http = {\n        pprof.enabled = false;\n        session_ttl = \"720h\";\n      };\n      dhcp.enabled = false;\n      dns = {\n        bind_hosts = [ \"192.168.10.1\" ];\n        port = 53;\n        anonymize_client_ip = false;\n        ratelimit = 20;\n        refuse_any = true;\n        upstream_dns = [\n          \"[/home.arpa/]192.168.10.1:8853\"\n          \"[/internal.\\${SECRET_DOMAIN_1}/]192.168.10.1\"\n          \"[/external.\\${SECRET_DOMAIN_1}/]192.168.10.1\"\n          \"quic://dns.adguard-dns.com\"\n        ];\n        bootstrap_dns = [\n          \"1.1.1.1\"\n          \"8.8.8.8\"\n          \"2606:4700:4700::1111\"\n          \"2001:4860:4860::8888\"\n        ];\n        upstream_mode = \"parallel\";\n        blocked_hosts = [\n          \"version.bind\"\n          \"id.server\"\n          \"hostname.bind\"\n        ];\n        trusted_proxies = [\n          \"127.0.0.0/8\"\n          \"::1/128\"\n        ];\n        enable_dnssec = true;\n        edns_client_subnet = {\n          custom_ip = \"\";\n          enabled = true;\n          use_custom = false;\n        };\n        handle_ddr = true;\n        use_private_ptr_resolvers = true;\n        local_ptr_upstreams = [ \"192.168.10.1:8853\" ];\n      };\n      filtering = {\n        blocking_mode = \"default\";\n        protection_enabled = true;\n        safe_search.enabled = false;\n        safebrowsing_enabled = true;\n        parental_enabled = false;\n        filtering_enabled = true;\n        filters_update_interval = 24;\n      };\n      tls.enabled = false;\n      querylog = {\n        interval = \"168h\";\n        enabled = true;\n      };\n      statistics = {\n        enabled = true;\n        interval = \"24h\";\n      };\n      filters =\n        let\n          urls = [\n            {\n              name = \"AdGuard DNS filter\";\n              url = \"https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt\";\n            }\n            {\n              name = \"IDN: ABPindo\";\n              url = \"https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt\";\n            }\n            {\n              name = \"GoodbyAds\";\n              url = \"https://raw.githubusercontent.com/jerryn70/GoodbyeAds/master/Hosts/GoodbyeAds.txt\";\n            }\n          ];\n          buildList = id: url: {\n            enabled = true;\n            inherit id;\n            inherit (url) name url;\n          };\n        in\n        lib.imap1 buildList urls;\n      user_rules = [\n        \"@@||s2.youtube.com^\"\n        \"@@||graph.facebook.com^\"\n        \"@@||i.instagram.com^\"\n        \"@@||fonts.gstatic.com^\"\n        \"@@||click.redditmail.com^\"\n        \"@@||aypbpr.tokopedia.com^\"\n        \"@@||plenty.vidio.com^\"\n        \"@@||i.sgsnssdk.com^\"\n      ];\n      clients = {\n        runtime_sources = {\n          whois = true;\n          arp = true;\n          rdns = true;\n          dhcp = true;\n          hosts = true;\n        };\n        persistent = [\n          {\n            name = \"GUEST\";\n            uid = \"2e755b04-5463-4f69-93a2-6d620f42c6e1\";\n            tags = [ ];\n            ids = [ \"192.168.250.0/24\" ];\n            use_global_settings = true;\n            use_global_blocked_services = true;\n            blocked_services.ids = [ ];\n            upstreams = [ \"quic://dns.adguard-dns.com\" ];\n          }\n          {\n            name = \"IOT\";\n            uid = \"7a8b3ec5-5915-4bdc-b368-92c1c123bfb1\";\n            tags = [ ];\n            ids = [ \"192.168.69.0/24\" ];\n            use_global_settings = false;\n            use_global_blocked_services = true;\n            blocked_services.ids = [ ];\n            upstreams = [\n              \"1.1.1.1\"\n              \"8.8.8.8\"\n            ];\n          }\n          {\n            name = \"Android TV\";\n            uid = \"3635575c-a9bb-4303-a2e2-75ffd1d0a967\";\n            tags = [ \"device_tv\" ];\n            ids = [ \"192.168.69.30\" ];\n            use_global_settings = true;\n            use_global_blocked_services = true;\n            blocked_services.ids = [ ];\n          }\n          {\n            name = \"Children\";\n            uid = \"27337a9f-d874-497c-9037-39519f1384a5\";\n            tags = [ \"user_child\" ];\n            ids = [\n              \"192.168.10.12\"\n              \"192.168.10.13\"\n              \"10.10.0.4\"\n            ];\n            use_global_settings = false;\n            blocked_services.ids = [\n              \"reddit\"\n              \"tiktok\"\n              \"facebook\"\n              \"instagram\"\n              \"snapchat\"\n              \"tinder\"\n            ];\n            upstreams = [ ];\n            safe_search = {\n              enabled = true;\n              bing = true;\n              duckduckgo = true;\n              google = true;\n              pixabay = true;\n              yandex = true;\n              youtube = true;\n            };\n            filtering_enabled = true;\n            parental_enabled = true;\n            use_global_blocked_services = false;\n            ignore_querylog = false;\n            ignore_statistics = false;\n            safebrowsing_enabled = true;\n          }\n        ];\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/adguardhome/secret.sops.yaml",
    "content": "adguardhome:\n    password: ENC[AES256_GCM,data:2Ksc+o8/FtAP4fk=,iv:+IqBK/hH9/Mdok3D6MZ6IywchOUx1VOv0nHpgTsBGj4=,tag:HqA+KyhdLAwLBtU8u1Uchw==,type:str]\n    env: ENC[AES256_GCM,data:r94YfggYU+nSfKCbMWSPeJ5mMM5zE2oIluEx32QvcOX8Yx2kU5Be0vHDZ2TBQZzb/NJ4OTX1ef2Txtbhp7dI/t1qWzWwQ2o6FOO+MgNdTBTcCjNekZvxpPoiw8mFPSWx3yaFaPkLabjn7+XjYNGcXAlzSWPq28o=,iv:pHL7YBPAy4pEzKz9FULZSl62To7dONbunCyzvI9LCiQ=,tag:p6FVLQZZMZVbmOzGJ/adwA==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwczVkQzlNaWl2MFBXdHE2\n            M2RDajVqTGh6ZXpNZE50VlBuaUlvVllBQWlZCklXMFVHVTBXVnNpYklmaTNqSTF5\n            R3lSa21OWnE3TVMwNHY4Vzk0QVdkUDgKLS0tIFZ2cElLV2ExNjcxbTBkaDZzcTVm\n            Yi9mNURPV05hVC96dzZhSUoxMlB1dWsKwLOTlyTnQzIkZPL3gZ36GyMGxM3v12vJ\n            0qGnpEtsA2zjU9brGNW9n71NsuROCCohumaH9rNXJv4N0YJNPuDc+A==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMczRYNzlpMUJ2eW5tMDF6\n            UnlPMUNlTmZ1YnZSMWZlQVYrNG5TNnJOUGdNClVjSzdVOXpWOURCem9aNVMwRmdL\n            bUZLVjEwaWlINGxLT01MYVJzeHRqdWcKLS0tIHhEZUVUOEV6cVl3alpjK25KK3RX\n            S0QyQkhtdXZHZTkzRzRLdUd0dG1UdHcKikeZfd2zZCYmbqdCumNcR2VTRt0UPp2j\n            qoOcRyqh/BAiKD7paD6LGtSltAYm+J7eFJMCb5J3+YuL9mhT+oUnXA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGaVluV0xZdS9lMGNGbEVD\n            Yjl5blJUUEtNNjNkSmpySnFXcFJla2tRYzFjCmkxMUZnQmQvNVNZa2g3Yy9qcWpG\n            VFoxQ3JqTUs2RHgvbjFvdEwrY0dKU0kKLS0tIHY0MTh4d2tvY1FFKzNtTjJabUJN\n            Z0lUMk5vcFlDVHM0ZXFkYzA1aU00a3MKUnBHocxa4bCTma8ML7Mssd/ItyW5lOGq\n            Dnt/o1EWtKOE9Z6XptUJ2mfb+lsvWPrhbMchEn59fdNV7xxyncrT4g==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUbFA0a1RxYVpMQXl5ME1z\n            cXpoWDNTWU5PQnpZalJxTFliaFJId2hlU0g0CkFtTVNiTWp2eFJtdk5oQ3ByNGRJ\n            ZEJZVWU0VjhSNERGR0E0RVRoY2xic2cKLS0tIHpoWVlpU0x4NkRIbjVHWUVpWTFx\n            TUNKbytGODlBSk54MnFwRUp4Z3l1T2sKw5PUFIVLlQLfD/YPG9A2EvQ/Qu1VGijm\n            eypGBUCKsgzPTt0CjkasVZdNod3GLaFLXTyzlYL0DB3UMbU75c4Jag==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQbHlmeHYzK0F0c2VLR09l\n            VDBOalRLOUVHZEEvUnVkazV0bnN6V3dSOUhnCmlMQzhFWjNIbFROOWQ1L2I5ZjM3\n            MSs1SThaUjBWbHRVODJKMm1jd3BiemMKLS0tICtaOHQvb3hQdFVIdHdxVzdMRzFu\n            WVljaG9KZnJiaDBVaUhJWWRDaEhMdEUKjOulyHID1DeXZlOkaEpwxoi7bVBO1I6B\n            Y9U0z2+sVu8v8B4JcBAO0uvje+FQ7vJsifhgs+NMpuzDvwCWHQu+lA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjTm1NSEpyUnhMTHA3WWM3\n            K0tHR3g3Z1crdjBpcVFVZWh2ZXlvZUkrRjNNCjRYSVFsckw1a1k5UFN4K2NZRXJv\n            YzBNdzM5anprL2l5dnQvRFBOZnpHNTgKLS0tIGdHbWRsU1ZVUUlDbm5nSm9WaDhU\n            Qi82SHIzUFdYS2FBWEJZY3VobDZUaW8K6JEiqewLluyyfHaT3BT1Pl35uNlFaGws\n            +vuwgSkOeGrJYH1zzJ9OIJvSL/c3ee3GTg1Bz1r64nfnYjFRwAiJpw==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2024-11-10T05:38:21Z\"\n    mac: ENC[AES256_GCM,data:0xgKqW7SWB8CVkxWcWlxIzibNk9DyUv+owdPFw56QNxo5c1Lvuf38/o4uWJOuXS1nnJZuP3oVPcq8dxoBnw2Jlsn4E07lKoZMwC5kdaZIiaBOMmp447E81mh4CrbuOhni94KGaEnSDhzOie5cvlpfqo6Fcpe+sWOosJVZ3b+yPU=,iv:gqgq3S7ps/FaFw7oCVIB+K//uimBzPYYxgH+cH0WPd8=,tag:3Jz0qtuef0lg0zDDnm8RVg==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.8.1\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/chrony/default.nix",
    "content": "{ ... }:\n{\n  services.chrony = {\n    enable = true;\n    servers = [ \"id.pool.ntp.org\" ];\n    serverOption = \"iburst\";\n    extraConfig = ''\n      allow 127.0.0.0/8\n      allow 10.0.0.0/8\n      allow 172.16.0.0/12\n      allow 192.168.0.0/16\n    '';\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/fireqos/default.nix",
    "content": "{ ... }:\n{\n  services.fireqos = {\n    enable = true;\n    config = ''\n      interface wan0 world-in input rate 200mbit\n        class calls commit 300kbit\n          match ports 3478:3497,16384:16387,16393:16402\n\n        class default\n\n        class torrents\n          match port 50413\n\n      interface wan0 world-out output rate 65mbit\n        class calls commit 300kbit\n          match ports 3478:3497,16384:16387,16393:16402\n\n        class default\n\n        class torrents\n          match port 50413\n    '';\n  };\n\n  systemd.services.fireqos = {\n    after = [ \"network-online.target\" ];\n    wants = [ \"network-online.target\" ];\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/frr/default.nix",
    "content": "{ ... }:\n{\n  # TODO: everytime networkd is being reconfigured, frr will be broken because networkd deletes nexthops it doesn't manage\n  systemd.network.config.networkConfig.ManageForeignNextHops = false;\n\n  services.frr = {\n    bgpd.enable = true;\n    config = ''\n      router bgp 65400\n        bgp router-id 192.168.200.1\n        no bgp ebgp-requires-policy\n        no bgp network import-check\n        neighbor 192.168.200.21 remote-as 65401\n        neighbor 192.168.200.21 description kmaster1\n        neighbor 192.168.200.21 update-source lan0.200\n        neighbor 192.168.200.22 remote-as 65401\n        neighbor 192.168.200.22 description kmaster2\n        neighbor 192.168.200.22 update-source lan0.200\n        neighbor 192.168.200.23 remote-as 65401\n        neighbor 192.168.200.23 description kmaster3\n        neighbor 192.168.200.23 update-source lan0.200\n      exit\n    '';\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/kea/ddns.nix",
    "content": "{ config, ... }:\nlet\n  keaddnsUser = \"kea\";\nin\n{\n  # add user, needed to access the secret\n  users = {\n    users.${keaddnsUser} = {\n      isSystemUser = true;\n      group = keaddnsUser;\n    };\n    groups.${keaddnsUser} = { };\n  };\n\n  sops.secrets.\"kea/tsig-key\" = {\n    sopsFile = ./secret.sops.yaml;\n    owner = keaddnsUser;\n    group = keaddnsUser;\n  };\n\n  services.kea = {\n    dhcp4.settings = {\n      dhcp-ddns.enable-updates = true;\n      ddns-replace-client-name = \"when-not-present\";\n      ddns-update-on-renew = true; # always update when a lease is renewed, in case I lost the DNS server database\n      ddns-override-client-update = true; # always generate ddns update request ignoring the client's wishes not to\n      ddns-override-no-update = true; # same as above but for different client's wishes\n      ddns-qualifying-suffix = \"home.arpa\";\n    };\n    dhcp-ddns = {\n      enable = true;\n      settings =\n        let\n          pdnsServer = [\n            {\n              ip-address = \"192.168.10.1\";\n              port = 8853;\n            }\n          ];\n        in\n        {\n          tsig-keys = [\n            {\n              name = \"kea\";\n              algorithm = \"hmac-sha512\";\n              secret-file = \"${config.sops.secrets.\"kea/tsig-key\".path}\";\n            }\n          ];\n          forward-ddns = {\n            ddns-domains = [\n              {\n                name = \"home.arpa.\";\n                key-name = \"kea\";\n                dns-servers = pdnsServer;\n              }\n            ];\n          };\n          reverse-ddns = {\n            ddns-domains = [\n              {\n                name = \"168.192.in-addr.arpa.\";\n                key-name = \"kea\";\n                dns-servers = pdnsServer;\n              }\n              {\n                name = \"10.in-addr.arpa\";\n                key-name = \"kea\";\n                dns-servers = pdnsServer;\n              }\n            ];\n          };\n        };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/kea/default.nix",
    "content": "{ ... }:\n{\n  imports = [\n    ./ddns.nix\n    ./dhcp.nix\n  ];\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/kea/dhcp.nix",
    "content": "{ ... }:\nlet\n  leaseOption = {\n    valid-lifetime = 86400;\n    renew-timer = 43200; # 50% of valid lifetime\n    rebind-timer = 75600; # 87.5% of valid lifetime\n  };\n  commonDhcpOptions = [\n    {\n      name = \"domain-name-servers\";\n      data = \"192.168.10.1\";\n    }\n    {\n      name = \"time-servers\";\n      data = \"192.168.10.1\";\n    }\n    {\n      name = \"domain-name\";\n      data = \"home.arpa\";\n    }\n    {\n      name = \"domain-search\";\n      data = \"home.arpa\";\n    }\n  ];\nin\n{\n  services.kea.dhcp4 = {\n    enable = true;\n    settings = {\n      interfaces-config = {\n        interfaces = [\n          \"lan0\"\n          \"lan0.50\"\n          \"lan0.69\"\n          \"lan0.200\"\n          \"lan0.250\"\n        ];\n      };\n      subnet4 = [\n        (\n          {\n            id = 1;\n            interface = \"lan0\";\n            subnet = \"192.168.10.0/24\";\n            pools = [ { pool = \"192.168.10.50 - 192.168.10.199\"; } ];\n            option-data = [\n              {\n                name = \"routers\";\n                data = \"192.168.10.1\";\n              }\n              {\n                # this allows clients to be discovered by omada-controller\n                name = \"capwap-ac-v4\";\n                data = \"10.5.0.10\";\n              }\n            ] ++ commonDhcpOptions;\n          }\n          // leaseOption\n        )\n\n        (\n          {\n            id = 2;\n            interface = \"lan0.50\";\n            subnet = \"192.168.50.0/24\";\n            pools = [ { pool = \"192.168.50.50 - 192.168.50.199\"; } ];\n            option-data = [\n              {\n                name = \"routers\";\n                data = \"192.168.10.1\";\n              }\n            ] ++ commonDhcpOptions;\n            reservations = [\n              {\n                hostname = \"jojo-poco\";\n                ip-address = \"192.168.50.10\";\n                hw-address = \"e8:5f:b4:2d:ce:5d\";\n              }\n              {\n                hostname = \"lina-samsung\";\n                ip-address = \"192.168.50.11\";\n                hw-address = \"ae:9c:e8:65:f4:76\";\n              }\n              {\n                hostname = \"eunice-tablet\";\n                ip-address = \"192.168.50.12\";\n                hw-address = \"00:03:f7:bd:70:d0\";\n              }\n              {\n                hostname = \"eugene-oneplus\";\n                ip-address = \"192.168.50.13\";\n                hw-address = \"98:09:cf:0c:70:af\";\n              }\n              {\n                hostname = \"firehd-8-livingroom\";\n                ip-address = \"192.168.50.40\";\n                hw-address = \"40:a9:cf:3b:a3:7d\";\n              }\n              {\n                hostname = \"budimanjojo-main\";\n                ip-address = \"192.168.50.49\";\n                hw-address = \"b4:2e:99:62:8d:06\";\n              }\n            ];\n          }\n          // leaseOption\n        )\n\n        (\n          {\n            id = 3;\n            interface = \"lan0.69\";\n            subnet = \"192.168.69.0/24\";\n            pools = [ { pool = \"192.168.69.50 - 192.168.69.199\"; } ];\n            option-data = [\n              {\n                name = \"routers\";\n                data = \"192.168.10.1\";\n              }\n            ] ++ commonDhcpOptions;\n            reservations = [\n              {\n                hostname = \"broadlink-livingroom-plug\";\n                ip-address = \"192.168.69.10\";\n                hw-address = \"34:ea:34:79:f0:91\";\n              }\n              {\n                hostname = \"broadlink-bedroom-rm4c\";\n                ip-address = \"192.168.69.11\";\n                hw-address = \"24:df:a7:4f:9a:8e\";\n              }\n              {\n                hostname = \"ezviz-bedroom-camera\";\n                ip-address = \"192.168.69.21\";\n                hw-address = \"a0:ff:0c:9f:A7:7a\";\n              }\n              {\n                hostname = \"android-livingroom-tv\";\n                ip-address = \"192.168.69.30\";\n                hw-address = \"8c:90:2d:cd:00:56\";\n              }\n            ];\n          }\n          // leaseOption\n        )\n\n        (\n          {\n            id = 4;\n            interface = \"lan0.200\";\n            subnet = \"192.168.200.0/24\";\n            pools = [ { pool = \"192.168.200.50 - 192.168.200.199\"; } ];\n            option-data = [\n              {\n                name = \"routers\";\n                data = \"192.168.10.1\";\n              }\n            ] ++ commonDhcpOptions;\n            reservations = [\n              {\n                hostname = \"budimanjojo-nas\";\n                ip-address = \"192.168.200.30\";\n                hw-address = \"d0:50:99:25:88:91\";\n              }\n            ];\n          }\n          // leaseOption\n        )\n\n        (\n          {\n            id = 5;\n            interface = \"lan0.250\";\n            subnet = \"192.168.250.0/24\";\n            pools = [ { pool = \"192.168.250.50 - 192.168.250.199\"; } ];\n            option-data = [\n              {\n                name = \"routers\";\n                data = \"192.168.10.1\";\n              }\n            ] ++ commonDhcpOptions;\n          }\n          // leaseOption\n        )\n      ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/kea/secret.sops.yaml",
    "content": "kea:\n    tsig-key: ENC[AES256_GCM,data:2nJqnNww0MinOpvJnmYrQuJTM1bQbU1LaohkjpoIRZ64f7Szzr6rFHXaRfVuOmi6w5aVSzQxlG+w23v4Oowjgx6fdkzbrJ5vtEkQS/nDdoQJY8EWPsE8vw==,iv:1eOakicUjRrG3D7HoVlqsuHPO8R+UGqBN3I8kSCUSso=,tag:0IMYOaMzBi32xM1MAptOBA==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaSWtWK2ZqODNlemxodFRx\n            UHlRVXZGUUlRaUVaR3IvNkhXbk1TRVhJc21rCk9qTC9QQkxFNW1Hd3RIanBvbjdK\n            SVZQZmFiRjcwS3BpQjdEWWJ2b2wwTUkKLS0tIGFRL3ZIdS9CMkVPOUlERENDWDl5\n            SkNSc0RSdmdOWUdocWxBS3d6VnF5c1UKgRZZXve1WWVGJAylQJ79d7Ousql/8kGM\n            5IKDkz1ouT/Eipq/x1HSOjxy9fBEdr6RFBgX0quKZdLUTH4yzHUCwA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoQjk4VFV1MTAzaHBXRk15\n            cTF1dXNXVmQxY2J4SWpDa0xndXNqblZhRG1RCmRyY2VWaGc0eGppTlB1b3llbDd1\n            b2lQeU96aWNpT0dUTTJJK2YvaE5qQnMKLS0tIFdtS0p1dmpZRnBQRmlrMWpXamJt\n            WXB2ZlNSVDYyNVJPNWZzSzJIR3llV28KsXEK+i4VRs6qzPQPsQek9Epv8XZFWRub\n            VqiwxICvwHxQZnfTcqGP5+kJTeDKK9xZfU3jAedWN9Cbd7SRvC16nw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGZGZTek9BQUFzMkZwcE1V\n            R29VVU5hVjU3TUFmbjU1NUx2cXlnL1krdndFCk5lNWZFWUJSdVluN0dHU3o3dkZy\n            NVlKaEcwR0pjRGJtdEhrUm1CbFk2cUUKLS0tIDYxSXdOL2hHMm11cGc1ZTJXTlhQ\n            R2piZVpIVHcyVkRmK0NGSktOcmNtaTQK7ntSeO3mmidDCTcvFuu4aqfZmbUPPBy6\n            sXP9ORhec1q17x3xpsjjrQaUu0zvUqWqF8rh7P+G3THeeEnAM/Ph8Q==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKZ2krdUd2UWJ4Y3J3cm1D\n            dlBJcmZSbHhrTEZzUTJQVnliWU9pOGo5T2c0CktMOTdmcFd3ZHJLRElpVnNCSVJZ\n            RDlPZ21Rd2psb0MvLzFaMmlkQ2wzWGcKLS0tIFZTZW1ZRUx0VndBeGtMQnpZYlB2\n            a2dPUlVsZkoyNzZpV3o1cmszRmNIbEkK2+bQ1+8VtIAm6dhzwFO57AOW+ibHVawI\n            dliB0wO7/Ec/dFI9ZibdoON9zAHUB0a0+FnYsPAocdsd+881OIUfcQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwK0Y2MFFoL3dBZkVHNXY4\n            dExkR1pCbnlIdFlLTEFNcW9ZOTVCcGxGMng0Cldnd0VVR09GckFrVTVaYUZWTklQ\n            czdEUFJ4ajQyOVdJKzI1QWtxd1hXVTAKLS0tIEZ3c2xZbGN3Zlc0L05LeG11YzdL\n            ODB2a1RiaitXVDRuUWJCbzJIeWFNSzAKDJoMC4WayAESZrBNraVMSJZYP3uk/aSv\n            sNypRghLSX/H7YDcopHoUeQ8nzOtAxWj6ylNhXFHaRjw3eNp2TqLqw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsOVdaSncyMmhUQmkzZmdx\n            bzQ4cmU3bWVaLzZJRTRJMDIyR2UrbmxKbnhZCnJTZ3ZubE41SExQZ3NUWUF5cmxn\n            SjZGTnErRUlCL2RDOUFRb1pibW94UU0KLS0tIDd3RDJQMEFIV3JFRVFVa3A5VGZP\n            ZEo0OVFpRGFJT3Z6aXNkTXIvMjVtRncKc3/C2eeN/zfTzVXhTFeg7vTJTTlH+HVm\n            jLa8HYNuSfAIrNiaAiQTBP5RGvedthas8u40nkjcDXTNqBpaPm8y5g==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2024-12-10T03:10:14Z\"\n    mac: ENC[AES256_GCM,data:yLPGIAwDdYBTOvuDveTUowX9A9u3nQ5TaHStY5q4zxL0Z7ZOOHbir9bPlDjSYhCHroMR3yCrqZDGG85OuQhfLf4RnP3AU8nXO0gOz2fXNOfpe4xMuSvklKjncEAXSOBtZ/KPy+xYbU4W2DpuCl4o5NmWvsn3u3+I0O0niuWttH8=,iv:05WNFpUjEaXBsXmmOXze8qDkfEAs7PACZOVO8imt+rE=,tag:brMF5pxGC45ofH4Q59Fj7w==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.8.1\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/omada-controller/default.nix",
    "content": "{ config, ... }:\nlet\n  app = \"omada-controller\";\n  dataDir = \"/var/lib/omada-controller-data\";\n  logsDir = \"/var/lib/omada-controller-logs\";\nin\n{\n  sops.secrets.\"omada-controller/sslkey\" = {\n    sopsFile = ./secret.sops.yaml;\n    restartUnits = [ \"podman-omada-controller.service\" ];\n  };\n  systemd.tmpfiles.rules = [\n    \"d ${dataDir} 0755 508 508\"\n    \"d ${logsDir} 0755 508 508\"\n  ];\n  virtualisation.oci-containers.containers.omada-controller = {\n    image = \"docker.io/mbentley/omada-controller:6.2\";\n    extraOptions = [\n      \"--ip=10.5.0.10\"\n      \"--stop-timeout=30\"\n    ];\n    volumes = [\n      \"${dataDir}:/opt/tplink/EAPController/data:rw\"\n      \"${logsDir}:/opt/tplink/EAPController/logs:rw\"\n      \"${config.sops.secrets.\"omada-controller/sslkey\".path}:/cert/tls.key:ro\"\n      \"${./ssl.crt}:/cert/tls.crt:ro\"\n    ];\n    environment = {\n      TZ = \"Asia/Jakarta\";\n    };\n  };\n\n  services.restic.backups = config.lib.mySystem.mkRestic {\n    inherit app;\n    paths = [\n      \"${dataDir}\"\n      \"${logsDir}\"\n    ];\n    excludePaths = [ \"autobackup\" ];\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/omada-controller/secret.sops.yaml",
    "content": "omada-controller:\n    sslkey: ENC[AES256_GCM,data:Rv9uowvCXyB/tNKcdDFyckaiVr0IIi/UZBU6T3/qeM3ZmjdJJcPtlmu2UhsspXp8mfWUhOzHLlRUpAhBm7N+htwVyZXDQfGGpWMwgebgW61fTf63FCm1Tik77vrhNf7iSfKo7PnV+CIwnbrbQgBo54r6ob56RQ5R8qjbFY/8DJitx3JxjPuwMhU8dphlA2R4OtAdYzBdt6Td1eofOiIdIbAhtSdDoy+IIvdzknXS3xy7bBJ/ItrU/PhGD2g3ePErZSvVDXbv+fOBwbfFI33iJfze1TuMk4q0yhNrBRppwJB8SuCM8K4xxmhqOxwxdgomEoyPiHQjAflwJiELtxfsdDzCO4Bs5Z2RWO9jc95OiP3QYAncTvG7nn/Xe/qxezku7P0DnXCG7Nzfu66IrQ//Swr8MTf6tFzAKn58ZNJV5s6+xoWh6PzNwaIsXNRnrOZPF5ul5fh9L8D27Em+4IyGLH1H97QvLKKJK5eFs8P/Nyk4JJOqUmZObAgnPC1lNHr4ZVM6HoUZglFLHwR8ul2LpJW42WNWLjW0Hfn9R87Vmrangv8nNvsSVV/m4ABr6TEvbzXUraWYe3Cl4SY4Q1aHkW3vwBLM1vskO1LYEoTlNNIs0TB6YjEXsZmbbh2+HPcysQ+qLWU9szQfMmy74CKfH7RjLT5nZufINsl8yrzIwhtp6cHfbaK5hB9hmg4lO4rj9Fdqni3cPvaVbC5OpQRL2l5SdYjQyG1CF2kYVPGf7la5P21hdEf9DxM2iOjh8j7wa7lC0IoHen90Z1psrVnzPVq/Hw+++516SA7zzOvdEFvzGWetz8Ir57RalznITVVQ+h2qcMhDOupO26URbC3PyjmQwjB5pnVgfTqUbnN/fc52OSFSGHoU1v9iJNxNvyGvVg0DFlBO5sg8VMCIusrn+qwmMj94gWuwlIWdSMBS3rn7lE/X0kmmWK16FBoMn33zD8by+rUDgqCZzJXdQ5rJk8ryVni/7/KXs0foZXNJCptfXH7rM4WhvywesTQ/mfRlbo1Fy5oQnT2nzoXYYkFcUIpYoqCT/1T7nATL44kDY704EQOC0ydhW14mWS8qhza3dlQOabgIaros27FgIDYGaV6lsGGx1VItV5zVSs/KlWtbPgsJ5nLLQ3Z3GK1YKn3QxZ3P/34BZ3+zxbZXN59Hc2C7WGZ5Qmpg7LiUe9tcKWz6Zo0vbFRHpou1aTLWsbjgzB0dfQ89NvMTItfqpVQSe+nT/z9QeOfAk9Lj5qsxQvMYcN1CATDLhNB3upNw1tfcnAiFmYmF0T8umU+bcX7O0qKeh702DuDb5KojUrJUty3YcYV4bVJ16O7BR2riiOpgqLgwiTQSPZA1CPB/aUQ57OhZXBuptHsixn0iVBFnPP9K2tIaTVwdWz92JIZZLMD5aSF82c/7+N2JuLXqMj/rp2xyceXddz0CJHrKRLuC/W3ni0plfhfUg7jJuRQ+ZdW9rSpHS+zv+maeuSGYDyS5tg2OM9/d7rI5kPzEuwC9KM2RqpaLwzrVAlxfAC9bJjfJqF2AjUnTZqimT3/ScXijxZOGFFnK0JBZCsQmAjDmDwZ5qIvl0FEVeuEKO4smXSjbqh5Yb9Rt2usfP5pfd32gSRwN5aCAjYQWLSOytJlfyQu88Y543pea5pICUnjXT8ghR2w4fPB/GFPOaMRfGVNJPityPYo8P+tluyhX+0nbBPgll2ppP91dCdWCcy04jPQVTqjCg9EccZrPtFVTaVK1eK4H2DEgs1cu4pEOnYlheyUeFYAzuAqeiR/YSRN1vsjW4hqp2ALnJdgs08ta/Ec4bikKPJc18CPxOAJ6DZGoh5sE5AAzP8mtilxBjUgHdEssTrLsV8U9sF6q6osqyN5ueK4hbJU6Vq7eF8gKDY9QMJlmAGHutUpMZE+RNO2AQ+oqJk9lFMaaCdryQePKYHFHYGKyG41HSMKH0qC1GPPRQ8z8N/N7lllQP1y/i7Bsq0VrgzXkzksDuZwcPHU4cX2YIGy9kHmpY4UDOCZaEqTtcQLTuBEuIKPaDso5Tm3QO7vnAT/4/8gf5qO65qPw+/Dn9CDuvElrlI8PqW98gOxfY5oLBUVEPjU7WoHyn97cvT5AB9ZlYFEmYtURaGfpBuTUljAtfX080u2+Zrwh4aD2EHSr4gY0NLpSEhMScgrZ+RR1rVC7UknLe7b7XXK1L/GoCRrutsgGJe6ZsF57e+cbx3dspeo9zK0NWk0br1zPV8MN9dJ9orNS9UB2iRyRrHcA3URiXfASvl9aimxYTUyY2btIFW/2SUEaLgF006/gE8FcVXKm14areOyDwfXt7opLMxGy56YSPnWVP2r15Z3nPbJi6Ea/XDhHGSqMdwK+wNFiVb/y/dwuSSjVDOJD8QAkAbR+QRLO8bsA+8FaBeV9TJoonqpeloQLPGS4+hbygjQvS8Tgirj1o+XwgzIMRfLblkgUbQ+6d+9IpjB4fcN+F1sPAGuk2/Ek6Oees/HVHd+2oaYkEClbGC7RtFlsE5K6Yqgs6YLnkVbvlZccTWkYtSIV0mHpeR2kwcHIodcs+NfsgYgP/sqoR2TNi3v9uzV7H821kLR1RTjs7FhcPz1fyv2vluFYcswb4UWrdiLUBMe58s740NLrYVLN8n5l6AkcfW/vXSssijQSnPsCF9yJfS5wnCMXeiMF8iq1XOSzatzRT20LyFABpnykW5gD2Q+AhbbrYtL5wZNZA4mGLmAm5dIE0gpfqpcaHqxjHs488rgQCd4Y1PP4Tld/oZ4h3Pjc2PgZ9fUg9RXbn72GbaPhQ9AJOIAo53vr8PEJs5cfVUjaLOV/A/ZhZ+swfvyIkXR+4o5zCB5t7M5B0DamWZQ4bBiFoC3r9777gYJJRm8uViIhqVvGDv4I0OHGeQC3AAh9H3Fo/Sf5y+dqk5a1kAZdqNiPp+moYEtSFlrWyd6GGyIkKndV/MnXye/nhuq8UF50MPsQ8J1YlZhhdO+UCFoyetb4AW2txeAV4Oy6grvRTh8TonHX948jbyzTfnkzAnXpjVUlSRjH40va3MT/SRutmRnbP1E8sDU2UtfUKy43DoqEJp4JPB8aEr+uys+sVqc8XK3GWJuwOXV53+flZTma3LIU3OAFbFUWTn7C+x8FOI2WWkIcxP+iitmDHIStFVany6je1eBeXEwqmttP1AcPxEj8zeuit1uk3uZHrN2Tn6wyNBEuh1xl3h5kLalP9urs5NL9lo095/Iwa6gm2Zf5t4A4arrgkCiDXkPofMPNNQGkc11s81k5Dsri29gPk/stYpPAL2YWnhXHGHmq1kzO8fGmBi2ykkv6QPT1ml28xVB4xuB9VwS7069CzHQRXzV341Vg93kphz9beNcFLZG3HZyYWkbKqCMyzeheVpPVFOh7SgO8VFFnKYclQebxbDvAj8udc3uw1CPLknX01YLbjBSL7XGMoE+pdhllrVycVpT+/G5IM9tgrMY5YIFZMVUpyi4cckDsTh2LOyPi+T5bRuDZHniu11h5zicglyKAJVMLavPzYjNE3fUgKCL5VtiQIBPecAq7u2WsRwE2Q269o4ervYTyeulvr6B7wcQ/bb5ZGgxTtE96FWScqoy12fWrKyvqAjI2q9r8AAWIMxrdvIvXfgl20j6WBtgeaMw0purkwFTL5nT1jMBweD2HE4tgbGWOqcsdX6yRvRGmPuTH7Rwn4cJsSol+umfwig4RKWVYhe9dxxquGYe5WqZnmoT+VK7LcTgaNKzLDyeWtZzR/2dbS0/lBfEfsK9/2dKO5qs1wvagJfWHT5KP5+APZqCWUOnuwN1/HDTWVEst+IikvqQsN3Fs0fyIRF3DQBPd1W9dH1+qHkfgIk9T8TRUZZVDz9mj7NL9LbDrMOE8mwr32IAQX9SWGm/Weo0i4ZniotmHSyAid27iUTeKlholCltnadud3I8Cl7wUZLPJxPjRTua7b1Ih8HJ9bOEA0Kbfyzwcc8r8hXX99iiXsY8YO98Qj13HDlCXnxmWP7Z1GSJuNkvXun4qyRAtfJGR84QgXkI3EqURmyWRD+Ykh2VnFa7xfESPKwRSn6wMGQXR+7udE4toXwNKV/wqwBbCTD67iZrkZ3shv+lBCei6OIXlnJilajuTnXoJz6sXjcsNOs9PAgShbXYhg9uuhMYddSSuKXCr5AcosKvibmSzMOJrJER0CQ53MO3h2r4jWarfsO/IiJDraBl2x31XN37MtdpUT5tvrSqgKV+21GN5cg13JoPI9u03d3N6zdRUKoKXKG9n31W/nQxNdjhSuUY+lCPZ2n1KxtbUEtSqKSLS7DieSLSmkDxkZHir3zCazv3QYo1Q/uP/RmoljxaV5D/poYwDrjzUe94ewSwRyg==,iv:vEOj5rAyPSrCSrTazDnOF3Hl5jEAjJ24Ex09lf9kBIo=,tag:j96Vhj1wUmXnld25MLgObw==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwR01PSVg0UkxDcm1HMzdO\n            d3dlSm4rWVVyMGpnSWI2NDFsM3lYNkxMWjBNCndJbm80b3NSakFubjdMQmQzVlMr\n            UTlKZjdIYnV2NmtBMEMxWDVVVXFRZnMKLS0tIERxcDJIUkVqSGg3VDJRZC9SZ2hP\n            dHBRWXZjZHJQckYrV1FaOXI3YmVkd0EKRfvy+yU0IoXp7sGY8QjW86ZESc4dzhQd\n            5Y+iCRYr03D8qJIEFYiL2IbaezNpOKgeCaf+96ceU9pvxqyrKfH3Ug==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjK3F0eTk4YnZ3UFFzdGVD\n            elBVQmgvODYyMDRoY2lSOGdlWGcxZndiTGhzCm9wd0dYOUtlRHpUdjhiYUxiTlNK\n            WTV5MGovL3pNZ29PK216YzBGT3JKMGsKLS0tIFc4a3lGSFlueStNSjFEZGhXZ3E1\n            MUQrTmo4Y3BZS1NLTC93dlgzU1ZiOTQKOLs3xXiPGEtcyc+Y0/QkoQvofIMnbBDc\n            kjS81Wyaor46ZevIgu2g1zAyk2GIjItflyqZIMfxC3IZmm+bPzuOWg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHYlZUS0IzUVM1MDA4MTd6\n            c0cyaGV1UUJIMStkZWIvcFlXbWNHVzE1aUQ0CmZvdjZ2Z3dIRkMrcTFYTmFvRWtD\n            cDhBUmN0NEczMnYzdVpOZkZObG8wMEUKLS0tIFVXYy9yZDhqNS90SFgwUmtnSnh2\n            VytreXNRRDkwSG5KNUhaY2k0bndHSEUKGykRA6HLXItCoxcx69EuzGyAOtsqlPcd\n            yHx+7MiVfvZvnotEftKyhDDQhqTA4NJB2gDK1JG7e+zJD6qeNa8QDA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJS3VFdEFGSWpZQUdkK1VC\n            czBYYXJrNE1SSXhYc3diYXNPWGJwT1ZTT0hVCjNiSGU3WFdvVjl5WEorY3NMa2ht\n            bWVIYy9SUkt6V3lrUUZWZDhqNHVqc0kKLS0tIGRGWEg4Sm1xTlFONStuSFl3NG40\n            VGhtUlY1dmE4cnlwcU4vVUkyRGJERXcKPFzN+o5MO7OVvVv8cEBUWsFV4YlKxHhx\n            3BPWBcMgnT6v89BdmB1k69FrgOKzPVCU7V1UdBcNp2dwqgeD3Ddayw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRRXlUUXFkUHBGSjhaRkVK\n            azMvMjM1ZXdpMVlOWGZaaUlLbzRBM1hjMUNnCjl6SisyODArYmJxaE5iaEFydXpN\n            UG02a2dWVzlBZjk0Wnh6bHlGSnlPZWcKLS0tIERkaG1TSS8vY0I2cWJEWkl6QzlL\n            NmZKNTR4K1gwT082Mmt4aWpJT3ZlYzgKCQPEjXhRi2SH8jK03R7qc5ADaGwLH/ik\n            oCxW6pnBI1Ql56vJhyMufuRodyYjxebqBDnTDi12m83IBR8w4IzwYA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkK1F2U204Zjl0QWc5SGFI\n            TjY1bmRJVVVKT2czTURyajFuTjFhQnF6TUZRCndiSzZqSzUyQyswMXppZFAvREFD\n            cHY3RXk1RnJUSUxOWkVMVjdHR0ZRS1UKLS0tIEJ5MDlpRjNXVnN5WHFzL3F6Y0pV\n            RVEvL0M3ZGtURTg4R09XZXFZN0p4OVUKArbhntaN2b/U/QyseGEETldosAi1OgzL\n            AFcxfso0usyMBPhr5WUHeq4vr2PX3sVbEM2t9SPIgAM0NbFD/OwqbQ==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2025-04-28T05:02:22Z\"\n    mac: ENC[AES256_GCM,data:zBpUQjd5y6NK6WdRw2O+sat+fQD+4HDY+XrouAuZIPg7xwzvY3latIxA7qRFSsNCzkjzANTRAgGerSGSRxd6H2GRO2FQYA2BWL2FgdVLvh9VKjeHfza42OaSAJl3/BrE4U3g41+iGxD4SVTevbw9m34klzFhXOykm7ofXcTPVsQ=,iv:CBWPLhK9wQGt06GwGsZUXfFk0Hnf7GTVbXTXrXBD104=,tag:K8bpIFXqS5Fu+XSlqmRh9A==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.9.4\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/omada-controller/ssl.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFkzCCA3ugAwIBAgIUXIiKRTlvV52W0XJaom+pAOYCiCAwDQYJKoZIhvcNAQEL\nBQAwQzEdMBsGA1UEAwwUYnVkaW1hbmpvam8tZmlyZXdhbGwxEDAOBgNVBAoMB1RQ\nLUxpbmsxEDAOBgNVBAsMB1RQLUxpbmswHhcNMjUwNDI4MDQxNTQ2WhcNMzUwNDI2\nMDQxNTQ2WjBDMR0wGwYDVQQDDBRidWRpbWFuam9qby1maXJld2FsbDEQMA4GA1UE\nCgwHVFAtTGluazEQMA4GA1UECwwHVFAtTGluazCCAiIwDQYJKoZIhvcNAQEBBQAD\nggIPADCCAgoCggIBAMkQecvSK44Xmx/KQCxYuqtZwWmdmZFQy8ZVTnz/HhQUdsyR\nn+dgPxLIIK3PXY8Iv747REIRNQMKLMmKzuC1DkGJ5/P+DdFdWT/ga3Z4B+F9ADY5\n25Sqbk/MKF/CMh3kigTAu7GdgewHXXhQ6CfWI6RqQgiYNvuWTbCzzYVftZzVtqlI\nWCF7oqDJKThu3ks/+5EU/L0IIM99QWRJ8TJjAHW/xmKNM/mzCiw4FyG597sRoMQw\nmj850nXksultKCdRdsV33iX6EczDBqZJ9oXt9e7AxJfSFDRUrsSkQkZB0dRbqZWj\nCwZC18mQmuz73KFgTqP/v+JNVbSE2nAs6dXdtliXsDVMONFaGAnvX7Lhy3/NwlXp\nAYtenoiT5xPNpUBTHKaXWjoTHMcaO7lSnD+jGPaFVDUBYFdUWBwucZqe9ndZnvIs\nRv6ugOnIZkItBatAe6I2irHWjGc61xgYfN2LccldZoAa8hgWTSFxkBTXhM1wycKc\nFaFTF9GquRJtwz1fmWIHqQ6KMis6Mcjn6qmFR3zMFOLCFS7J4xAw5Hc7J81iXSHZ\npk89o0TFwyKeGGiKynNCh6JCKFdgGh341TumRdpvDKAOG3/+iutPRCJGnJtz8W7z\nX2Q6SXvKf8d/hOXrWt5Gj20hhQCNz3HG2VqaBW+IJch0ByqFJCoSkyOXGGzbAgMB\nAAGjfzB9MB0GA1UdDgQWBBRSLyLwhK3oV8a7Xh+9kkrHPZDo+jAfBgNVHSMEGDAW\ngBRSLyLwhK3oV8a7Xh+9kkrHPZDo+jAPBgNVHRMBAf8EBTADAQH/MCoGA1UdEQQj\nMCGCCWxvY2FsaG9zdIIUYnVkaW1hbmpvam8tZmlyZXdhbGwwDQYJKoZIhvcNAQEL\nBQADggIBAGBp34RrFxNlnNfHA5l1MAuFmUc5bBa6evBt9TwIAMMw/6sRy6y+HC/N\nsnuCZQp+ubjUcbG8ebTDcynGsBMKTThn0LhQOCNxNEbOAClN7DKquYoWlWgCoy3K\ng139O9/w4JqVCMo0GeiNW6bFSaJvK3JKEZ2x49ZywI6PH4MSNC6S5l8mqieY6nDB\ngQXJ364TeE3L/XK3YyyKoQ8g8jDAr7TtZq9Wl2LDX77mPIfOqr9YaoeubBO9ERkm\n9waQz32FTzNS1RJqktkxzQtx9t5h9xbstBrvF/RFtN7ip/F5doFeXrQg0+m3Fihm\n6u+7BXNh/r9UbzbKVqpeiG9zd8zvCQ0hJG9R2QlrRGA94E2CIeEgwGjuFq/sNuMZ\n1y6054ySR6VStstHRAIb1zrqIzMOFSXiwNuOKuXd1UJDtOfsrJh2nyUSUJEzhYdh\nzLWB9+BbtozNYhhFP3uXP6d3qItj9emideNCfGFXo2x7Z+/JPWpYnWVOVJ9kJokg\n5urgqWDVnm+gsDE31aGcH9GtrS0lmnqHGOjncdusCFGiD/GJ9DuZgerNSJzRKsjh\nTmJXoOo2l+V7URrP2FzzgkYPSXpts2jD/eBJ0YuNIByrkscSChdNaEC4VVHVPUQA\n0cvsnmGHLUpDtbYA+X79PI2qUnvpXQSC7SF5fW8JRvqIpJ4UErVu\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/powerdns/default.nix",
    "content": "{\n  config,\n  lib,\n  pkgs,\n  ...\n}:\nlet\n  directory = \"/var/lib/pdns\";\n  user = \"pdns\";\n  group = \"pdns\";\nin\n{\n  systemd.tmpfiles.rules = [ \"d ${directory} 0750 ${user} ${group}\" ];\n\n  sops.secrets.\"powerdns/env\" = {\n    sopsFile = ./secret.sops.yaml;\n    owner = user;\n    group = group;\n  };\n\n  services.powerdns = {\n    enable = true;\n    extraConfig = ''\n      local-address=192.168.10.1:8853\n      launch=gsqlite3\n      gsqlite3-database=${directory}/pdns.sqlite3\n      dnsupdate=yes\n      allow-dnsupdate-from=192.168.10.1/32\n      default-soa-content=@ gateway.home.arpa. 0 7200 3600 120960 3600\n    '';\n    secretFile = config.sops.secrets.\"powerdns/env\".path;\n  };\n\n  systemd.services.pdns.serviceConfig = {\n    # powerdns doesn't create the sqlite database for us\n    # so we gotta either do it manually one-off or do the below to ensure it's created\n    # if the file is missing before service start\n    ExecStartPre = lib.mkBefore [\n      (pkgs.writeScript \"pdns-sqlite-init.sh\" ''\n        #!${pkgs.bash}/bin/bash\n\n        pdns_folder=\"${directory}\"\n        echo \"INIT: checking if pdns sqlite exists\"\n        if [ ! -f \"${directory}/pdns.sqlite3\" ]; then\n          echo \"INIT: no sqlite db found, initializing from pdns pkgs schema...\"\n          ${pkgs.sqlite}/bin/sqlite3 \"${directory}/pdns.sqlite3\" < \"${pkgs.pdns}/share/doc/pdns/schema.sqlite3.sql\"\n          ${pkgs.busybox}/bin/chown pdns:pdns ${directory}/pdns.sqlite3\n        fi\n\n        # exit successfully\n        exit 0\n      '')\n    ];\n    ExecStartPost = (\n      pkgs.writeScript \"pdns-ddns-setup.sh\" ''\n        #!${pkgs.bash}/bin/bash\n\n        cmd=${pkgs.pdns}/bin/pdnsutil\n\n        $cmd create-zone home.arpa. || true\n        $cmd create-zone 168.192.in-addr.arpa. || true\n        $cmd create-zone 10.in-addr.arpa. || true\n\n        $cmd import-tsig-key kea hmac-sha512 $KEA_TSIG_KEY\n        $cmd set-meta home.arpa. TSIG-ALLOW-DNSUPDATE kea\n        $cmd set-meta 168.192.in-addr.arpa. TSIG-ALLOW-DNSUPDATE kea\n        $cmd set-meta 10.in-addr.arpa. TSIG-ALLOW-DNSUPDATE kea\n      ''\n    );\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/powerdns/secret.sops.yaml",
    "content": "powerdns:\n    env: ENC[AES256_GCM,data:dKz3m8vUPAwwK8QeXIZN9RrcCx2iBEAcVwx0befTalZQyAeynTxRrW3+aDKJA25HoQLtL/I5JqUW7+9xOuMEYQHH9+6OpXNS/j0FRCCGe0NbEIRXRGhXIJX+gutHuPU+hg0W/jrTgfQ=,iv:AyK/lDVeBX+YKpo+TZbFR4WCPBFVcfLLiQlUByeFZDA=,tag:CEl8jCsHE0f3cipCbgFy8Q==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3RlpmY1E1VkNCODFzTHMv\n            V1p5Vy8rckUrMWc3bklNOUFGUG95Vk14Q0dvCjJhbmE4WGN3OVhsTmI4bklsMHI3\n            Q0EwNjUySFhxV1lPblJMdmF0dE5Lb0kKLS0tIHkrMGh2VHhiVGM0ZWZhd2pFSmQy\n            VVU4RTJzN05LTmJPemFWTDR1a1BLSzAKaGbhfbvC/letEmNTLi26VDpf6dWpUqEq\n            o0jkxjc7KFKXkua0o0NWQPcHldKvqlLWldnih7lbBpKQs8W13h+PTQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqOThxZEd6S1E4MnlyUVZQ\n            R1J2dE5kQldEVWtObWdlOVFGcjdtcnIyRlZrCjFtOGpqNnVOV01CUWtlVDBCT2F2\n            TzUzUlJxYnRPUlM5YTlaaS9HV3pYbkUKLS0tIEJZUWhHTEpTYkhOYlFFL05JYXl0\n            ZFNndW0ra3JHRDl4bHIxUWRPSmpxNGMKEGTRssBQmk+6xT3oxDCYn0tOS6Y08G15\n            YwlZM08Wvd2McKiy8XdMRAKlYDg3tKdJaI7BGZp4BuuPuf8jilhi8A==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3cUlweVdaUTIvSEFEVUsx\n            QmVUMnBIdXU1MUJ0UnBJNzhVYjV1L08welRBCmlVKzdKdSsyc3VPZDI0SE94bE1p\n            N0lQZmp4aUtOQWZQS1lYNVhXTHJmNk0KLS0tIEwxdXRwdnN6TVdGMjVTTFVUeTlO\n            U1psRm5ERmNVOWxCK1QvZXBuRHRrMFUKeMmw/c5/dQ/+BgZuR8BVDW4n4CtiPaqh\n            xHoOPA8BNlTkBdkBB51cVnt+9O+tE7xQBmJXZZA1oeomiElu9zxGJw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsTHVZZ3FDRlhNQlN0dHhK\n            YkdZWkRRUExXRXg1UnBkR2JDeDNmVVg1b1M4CkxXR0xNUGsyYkZHbzBNa3BDWERq\n            Tkt4YXJTVDB4RDRPSFdQeUVLbHAycU0KLS0tIEZxci92YmEvSXlFcW5id1NMVmtz\n            ak5uZFA0bWFNQllvdHhxallnazZJYkkKY4XJx5t5JmVvvM+fx3ymBZG9Bot2oN1l\n            VB6NkoNMQf6YTRR58F1bZD/LnCpPdcWORxtdN19oH+0bZV9QEi1Q6A==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLei9TeVZCb1JFbHVlUzd2\n            bGw2aGEydC9PNi9xMThNRUFOcnQ0M2NLZ25VCndUcjNiSTNkNWx4aURWaXFJbC9T\n            SFdySUJGK1VYUGhoemRxWnJOSzNLa0EKLS0tIGNadGNSa2YwdHQwVXM0bGRBbFp5\n            ZW9XZzNDR3B0SVJ2OWxBL2s2M3Y4NkUKLunf5l2I0IIcke2fOTvHgPh2Kb2LoyiL\n            V1VoRkA9g+ao2NWNVSfbDT0LixpSHKH36v0RJh0xoTYjP/+AV70iRg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlOUVTT0JQdDBkYzRVMXVH\n            MlZ0NEtuNGpIbXg0bmZvMlpFcFFDTG9QbHpjCjBvSTN2WXlSUXZRWkRoUkJKY3FJ\n            dTdMS2hOTDRmTUl5emxkZXRzdDZibjAKLS0tIFNaVjhUUWM4b1o3K0VHdmRVWEQx\n            NSt1Nng0bTRMdFluY0NCaHNNdDdXVFUKJ/C8M59h8SISE2kIUnipqnuZUmfE06mp\n            43rDnKh36H8H0mzJl8HGz3G5lWMLJji6ebu/OXogbSLrt6DxVc+MZg==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2024-12-10T03:27:04Z\"\n    mac: ENC[AES256_GCM,data:Eh4UmkwzMcZjAllAHOE0HRTrUagbMq6vZ/vaS6BZizItRxeWO4DmkjrCPA3ffYThk89w4OW/CUSYATJC7tCHfM7+oSZCYbPJBs0adAidbH92twv1MoQUMmcptPRN0vXK+cEmejS6RtD+0xVisdxFiG+3wQeEylApkNkbPU9zvyc=,iv:SyVoD3m109joWWfNuSiZPTgPkdkcARHw014egfqO/Ag=,tag:m9GqmOmXGjRqsHsEdVV9Eg==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.8.1\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/rsyslogd/default.nix",
    "content": "{ config, ... }:\n{\n  services = {\n    journald.forwardToSyslog = true;\n    rsyslogd = {\n      enable = true;\n      defaultConfig = ''\n        # for some reason rsyslog shows the hostname as \"localhost\" which is not helpful\n        global(localHostname=\"${config.networking.hostName}\")\n\n        # TOOD: the options are needed because of how `vector` is setup in my kubernetes cluster\n        # I will remove them once I updated the vector part\n        module(load=\"imklog\" ParseKernelTimestamp=\"on\" KeepKernelTimestamp=\"on\")\n\n        kern.warning @@(o)192.168.15.5:6000;RSYSLOG_SyslogProtocol23Format\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/services/tdarr/default.nix",
    "content": "{ config, inputs, ... }:\nlet\n  app = \"tdarr\";\n  serverDir = \"/var/lib/tdarr-server\";\n  configsDir = \"/var/lib/tdarr-config\";\n  logsDir = \"/var/lib/tdarr-logs\";\n  cacheDir = \"/var/lib/tdarr-cache\";\nin\n{\n  # we mount media directory from the NFS server\n  boot.supportedFilesystems = [ \"nfs\" ];\n  services.rpcbind.enable = true;\n  systemd.mounts = [\n    {\n      type = \"nfs\";\n      what = \"192.168.200.30:/Kubernetes-Volumes/default-shared-media\";\n      where = \"/mnt/media-nfs\";\n      options = \"nfsvers=4.2,nolock,hard,rw,noatime,nconnect=8\";\n    }\n  ];\n  systemd.automounts = [\n    {\n      where = \"/mnt/media-nfs\";\n      requiredBy = [ \"podman-tdarr.service\" ];\n    }\n  ];\n\n  # create directories needed for the volumes\n  systemd.tmpfiles.rules = [\n    \"d ${serverDir} 0755 1000 1000\"\n    \"d ${configsDir} 0755 1000 1000\"\n    \"d ${logsDir} 0755 1000 1000\"\n    \"d ${cacheDir} 0755 1000 1000\"\n  ];\n\n  # the actual Tdarr service in container\n  virtualisation.oci-containers.containers.tdarr = {\n    image = \"ghcr.io/haveagitgat/tdarr:2.70.01\";\n    extraOptions = [\n      \"--ip=10.5.0.20\"\n      # we use our local dns server to connect to Radarr/Sonarr inside my internal k8s gateway\n      \"--dns=192.168.10.1\"\n      # tdarr can take out the system by eating all RAM sometimes\n      \"--memory=5g\"\n    ];\n    volumes = [\n      \"${serverDir}:/app/server:rw\"\n      \"${configsDir}:/app/configs:rw\"\n      \"${logsDir}:/app/logs:rw\"\n      \"${cacheDir}:/temp:rw\"\n      # my own tdarr-plugins repo fork that contains my personal flow\n      # it is fetched into a nix-store so I can mount it\n      \"${inputs.tdarr-plugins}:/tdarr-plugins:ro\"\n      \"/mnt/media-nfs:/media:rw\"\n    ];\n    environment = {\n      serverIP = \"0.0.0.0\";\n      serverPort = \"8266\";\n      webUIPort = \"8265\";\n      internalNode = \"true\";\n      inContainer = \"true\";\n      maxLogSizeMB = \"10\";\n      pluginsDir = \"/tdarr-plugins\";\n      TZ = \"Asia/Jakarta\";\n      PUID = \"1000\";\n      PGID = \"1000\";\n    };\n    devices = [ \"/dev/dri:/dev/dri\" ];\n  };\n\n  # restic backup\n  services.restic.backups = config.lib.mySystem.mkRestic {\n    inherit app;\n    paths = [\n      \"${serverDir}\"\n      \"${configsDir}\"\n    ];\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/_modules/wireguard.nix",
    "content": "{ config, ... }:\n{\n  sops.secrets.\"wireguard/privatekey\" = {\n    sopsFile = ./secret.sops.yaml;\n    owner = \"systemd-network\";\n    restartUnits = [ \"systemd-networkd.service\" ];\n  };\n  systemd.network = {\n    netdevs.\"50-wg0\" = {\n      netdevConfig = {\n        Name = \"wg0\";\n        Description = \"WireGuard\";\n        Kind = \"wireguard\";\n        MTUBytes = \"1420\";\n      };\n      wireguardConfig = {\n        PrivateKeyFile = \"${config.sops.secrets.\"wireguard/privatekey\".path}\";\n        RouteTable = \"main\";\n      };\n      wireguardPeers = [\n        {\n          # budimanjojo-oracle\n          PublicKey = \"e71Old3Ax2DEw8QB9yvhyOIIuNJHtp8nBYKJJaDVPkw=\";\n          AllowedIPs = [ \"10.10.10.0/24\" ];\n          Endpoint = \"140.245.111.170:51821\";\n          PersistentKeepalive = 15;\n        }\n      ];\n    };\n    networks.\"50-wg0\" = {\n      matchConfig.Name = \"wg0\";\n      address = [ \"10.10.10.11/32\" ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/default.nix",
    "content": "{ ... }:\n{\n  imports = [\n    ./disk-config.nix\n    ./hardware-configuration.nix\n    ./_modules # host specific modules\n    ../../profiles/server.nix\n  ];\n\n  config = {\n    myHardware.cpu = \"intel\";\n\n    mySystem = {\n      programs.nh = {\n        enable = true;\n        flake = \"/home/budiman/Github/nix-config\";\n      };\n      services = {\n        btrfs-autoscrub = {\n          enable = true;\n          fileSystems = [ \"/\" ];\n        };\n        restic-backup = {\n          enable = true;\n          location = \"/mnt/backup-nfs\";\n        };\n      };\n    };\n\n    boot.supportedFilesystems = [ \"nfs\" ];\n    services.rpcbind.enable = true;\n    systemd.mounts = [\n      {\n        type = \"nfs\";\n        what = \"192.168.200.30:/Backups/restic-budimanjojo-firewall\";\n        where = \"/mnt/backups-nfs\";\n        options = \"nfsvers=4.2,nolock,hard,rw,noatime,nconnect=8\";\n      }\n    ];\n    systemd.automounts = [\n      {\n        wantedBy = [ \"multi-user.target\" ];\n        automountConfig.TimeoutIdleSec = \"600\";\n        where = \"/mnt/backups-nfs\";\n      }\n    ];\n\n    services.openssh.listenAddresses = [\n      { addr = \"192.168.200.1\"; }\n      { addr = \"192.168.50.1\"; }\n    ];\n\n    # sshd failed to start on boot when interface is not ready yet\n    systemd.services.sshd = {\n      after = [ \"network-online.target\" ];\n      wants = [ \"network-online.target\" ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/disk-config.nix",
    "content": "{ ... }:\n{\n  config = {\n    # filesystems managed by Disko are defined here\n    disko.devices.disk.main = {\n      type = \"disk\";\n      device = \"/dev/disk/by-id/ata-BR_128GB_202209231491\";\n      content = {\n        type = \"gpt\";\n        partitions = {\n          ESP = {\n            priority = 1;\n            name = \"ESP\";\n            start = \"1M\";\n            end = \"512M\";\n            type = \"EF00\";\n            content = {\n              type = \"filesystem\";\n              format = \"vfat\";\n              mountpoint = \"/boot\";\n            };\n          };\n          root = {\n            size = \"100%\";\n            content = {\n              type = \"btrfs\";\n              extraArgs = [ \"-f\" ]; # override existing partiion\n              subvolumes = {\n                \"/rootfs\" = {\n                  mountpoint = \"/\";\n                };\n                \"/home\" = {\n                  mountpoint = \"/home\";\n                };\n                \"/nix\" = {\n                  mountpoint = \"/nix\";\n                  mountOptions = [\n                    \"compress=zstd\"\n                    \"noatime\"\n                  ];\n                };\n              };\n            };\n          };\n        };\n      };\n    };\n\n    # filesystems not managed by Disko are defined here\n    fileSystems = {\n      \"/home\" = {\n        # This is needed for `sops-nix` to work properly on reboot\n        # see: https://github.com/Mic92/sops-nix/issues/149\n        neededForBoot = true;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-firewall/hardware-configuration.nix",
    "content": "{ modulesPath, ... }:\n{\n  imports = [ (modulesPath + \"/installer/scan/not-detected.nix\") ];\n\n  boot = {\n    initrd = {\n      availableKernelModules = [\n        \"xhci_pci\"\n        \"ahci\"\n        \"nvme\"\n        \"usbhid\"\n        \"usb_storage\"\n        \"sd_mod\"\n      ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-main/default.nix",
    "content": "{ ... }:\nlet\n  pubkeys = import ../../pubkeys;\nin\n{\n  imports = [\n    ./hardware-configuration.nix\n\n    ../../profiles/workstation-hyprland.nix\n    ../../profiles/gaming.nix\n    ../../profiles/work.nix\n  ];\n\n  config = {\n    myHardware = {\n      cpu = \"amd\";\n      gpuDriver = \"nvidia\";\n      monitors = [\n        {\n          name = \"HDMI-A-1\";\n          xname = \"HDMI-0\";\n          width = 1920;\n          height = 1080;\n          wallpaper = ./wallpapers/tokyonight01-left.png;\n          workspaces = [\n            1\n            3\n            5\n            7\n            9\n          ];\n        }\n        {\n          name = \"DP-1\";\n          width = 1920;\n          height = 1080;\n          primary = true;\n          x = 1920;\n          wallpaper = ./wallpapers/tokyonight01-right.png;\n          workspaces = [\n            2\n            4\n            6\n            8\n            10\n          ];\n        }\n      ];\n    };\n\n    mySystem = {\n      displaymanager.sddm.wallpaper = ./wallpapers/tokyonight01-left.png;\n      programs.nh = {\n        enable = true;\n        flake = \"/home/budiman/Github/nix-config\";\n      };\n\n      services = {\n        btrfs-autoscrub = {\n          enable = true;\n          fileSystems = [\n            \"/\"\n            \"/home\"\n          ];\n        };\n        openssh = {\n          enable = true;\n          authorizedKeys = [\n            pubkeys.work-pc\n            pubkeys.op9-termux\n          ];\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-main/hardware-configuration.nix",
    "content": "{ modulesPath, lib, ... }:\n\n{\n  imports = [ (modulesPath + \"/installer/scan/not-detected.nix\") ];\n\n  config = {\n    boot = {\n      initrd.availableKernelModules = [\n        \"nvme\"\n        \"xhci_pci\"\n        \"ahci\"\n        \"usb_storage\"\n        \"usbhid\"\n        \"sd_mod\"\n      ];\n    };\n    fileSystems = {\n      \"/\" = {\n        device = \"/dev/disk/by-uuid/d1d4bccb-7efb-4af3-b0c8-6b3cf162604f\";\n        fsType = \"btrfs\";\n        options = [ \"subvol=root\" ];\n      };\n      \"/home\" = {\n        device = \"/dev/disk/by-uuid/e5dd078f-db8a-4652-8931-6c2e8c8c2ed6\";\n        fsType = \"btrfs\";\n        options = [ \"subvol=home\" ];\n        neededForBoot = true;\n      };\n      \"/nix\" = {\n        device = \"/dev/disk/by-uuid/d1d4bccb-7efb-4af3-b0c8-6b3cf162604f\";\n        fsType = \"btrfs\";\n        options = [ \"subvol=nix\" ];\n      };\n      \"/boot\" = {\n        device = \"/dev/disk/by-uuid/294B-A128\";\n        fsType = \"vfat\";\n      };\n    };\n    swapDevices = [\n      {\n        device = \"/var/lib/swapfile\";\n        size = 16 * 1024;\n      }\n    ];\n    networking.useDHCP = lib.mkDefault true;\n    hardware.bluetooth = {\n      enable = true;\n      powerOnBoot = true;\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/_modules/default.nix",
    "content": "{ ... }:\n{\n  imports = [\n    ./network.nix\n    ./nfs.nix\n    ./incus.nix\n  ];\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/_modules/incus.nix",
    "content": "{ config, pkgs, ... }:\nlet\n  runner = \"${config.virtualisation.incus.package}/bin/incus\";\n\n  oidcSetup = pkgs.writeShellScript \"oidcSetup\" ''\n    DOMAIN=$(${pkgs.coreutils}/bin/cat ${config.sops.secrets.secret-domain-0.path})\n\n    ${runner} config set oidc.client.id incus\n    ${runner} config set oidc.audience https://incus.\"$DOMAIN\"\n    ${runner} config set oidc.issuer https://auth.\"$DOMAIN\"\n  '';\nin\n{\n  sops.secrets.secret-domain-0 = {\n    sopsFile = ./secret.sops.yaml;\n  };\n\n  virtualisation.incus = {\n    enable = true;\n    ui.enable = true;\n    preseed = {\n      config = {\n        \"core.https_address\" = \"[::]:8443\";\n      };\n      networks = [\n        {\n          name = \"incusbr0\";\n          type = \"bridge\";\n          config = {\n            \"ipv4.address\" = \"10.0.100.1/24\";\n            \"ipv4.nat\" = true;\n            \"ipv6.address\" = \"auto\";\n          };\n        }\n      ];\n      storage_pools = [\n        {\n          config.source = \"/vm-data\";\n          driver = \"btrfs\";\n          name = \"default\";\n        }\n      ];\n      profiles = [\n        {\n          name = \"default\";\n          devices = {\n            eth0 = {\n              name = \"eth0\";\n              type = \"nic\";\n              network = \"incusbr0\";\n            };\n            root = {\n              path = \"/\";\n              pool = \"default\";\n              size = \"20GB\";\n              type = \"disk\";\n            };\n          };\n        }\n        {\n          name = \"external\";\n          devices = {\n            eth1 = {\n              name = \"eth1\";\n              type = \"nic\";\n              nictype = \"bridged\";\n              parent = \"br0\";\n            };\n            root = {\n              path = \"/\";\n              pool = \"default\";\n              size = \"20GB\";\n              type = \"disk\";\n            };\n          };\n        }\n      ];\n    };\n  };\n\n  users.users.${config.mySystem.adminUser}.extraGroups = [ \"incus-admin\" ];\n\n  systemd.services.incus-preseed.postStart = \"${oidcSetup}\";\n\n  networking = {\n    nftables.enable = true;\n    firewall = {\n      allowedTCPPorts = [\n        8443\n        53\n        67\n      ];\n      allowedUDPPorts = [\n        53\n        67\n      ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/_modules/network.nix",
    "content": "{ ... }:\n{\n  networking.useDHCP = false;\n  # creating a `br0` network bridge that is used for Incus to get IP address from router\n  # so our other machines can connect to containers or VM in `external` profile\n  systemd.network = {\n    enable = true;\n    netdevs = {\n      \"20-br0\" = {\n        netdevConfig = {\n          Name = \"br0\";\n          Description = \"virtual network interface with fixed MAC address\";\n          Kind = \"bridge\";\n          MACAddress = \"d0:50:99:25:88:91\";\n        };\n      };\n    };\n\n    networks = {\n      \"20-br0\" = {\n        matchConfig.Name = \"br0\";\n        networkConfig = {\n          Description = \"configure br0 to get IP from DHCP server\";\n          DHCP = true;\n        };\n        linkConfig.RequiredForOnline = \"carrier\";\n      };\n      \"20-br0-en\" = {\n        matchConfig.Name = \"en*\";\n        networkConfig = {\n          Description = \"add en* interfaces to become member of br0 bridge\";\n          Bridge = \"br0\";\n        };\n        linkConfig.RequiredForOnline = \"enslaved\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/_modules/nfs.nix",
    "content": "{ ... }:\n{\n  fileSystems = {\n    \"/export/Kubernetes-Volumes\" = {\n      device = \"/nas-data/Kubernetes-Volumes\";\n      options = [\n        \"bind\"\n        \"nfsvers=4.2\"\n        \"nofail\"\n      ];\n    };\n    \"/export/Media\" = {\n      device = \"/nas-data/Media\";\n      options = [\n        \"bind\"\n        \"nfsvers=4.2\"\n        \"nofail\"\n      ];\n    };\n    \"/export/Backups\" = {\n      device = \"/nas-data/Backups\";\n      options = [\n        \"bind\"\n        \"nfsvers=4.2\"\n        \"nofail\"\n      ];\n    };\n  };\n\n  services.nfs.server = {\n    enable = true;\n    exports = ''\n      /export/Backups *(rw,no_subtree_check,insecure,no_root_squash,crossmnt,async)\n      /export/Media *(fsid=aa341bb8-719d-4700-96f2-40f33abfefad,rw,no_subtree_check,insecure,no_root_squash,crossmnt,async)\n      /export/Kubernetes-Volumes *(fsid=0241f5d9-633f-457a-9f48-dbbbf9d5adcb,rw,no_subtree_check,insecure,no_root_squash,crossmnt,async)\n      /export *(ro,fsid=0,root_squash,no_subtree_check,hide)\n    '';\n  };\n\n  networking.firewall.allowedTCPPorts = [ 2049 ];\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/_modules/secret.sops.yaml",
    "content": "secret-domain-0: ENC[AES256_GCM,data:r4F6tC+u2XkxlFsldxXg/3A=,iv:lV0NHP3/zgQKqtGItFfi7FRbjFdvGb+Mh9jpu42vKSM=,tag:nJCnP6BnLwgpHlzkQF28hQ==,type:str]\nsops:\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJNGxUNjc1L1JNUFd1TGZw\n            clFSOUpSbXNIa3plNHdHaWVaaklLVEtNQ2dnCloremxUV1crVFVTNEZleVJTNnZo\n            TFdYWlY2OVdYMGdLNnRWdEFHczd5SjQKLS0tICs4QnloUTRXNVNRYnZsbUxlK3dW\n            a1RBVGxUV3czUWhPeUczeWh0MitpSTgKtOukeVZHlJfqVNgQEmtzvAidfvaxQ0Vu\n            eAZBxVlGlqRGUmSzhHukdBUY8RTjfsbP0NXaL/FzGfWp8EjMfu9xUA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpMWI3bUtDQkwrdXVmYUxU\n            SmU1WHdlK1FLNDdBcklVOTZ6aVpnbnEyZURFCnVVY2dqbzlua2d0dkU2NFY2M2pY\n            UW94bVNOWi9vWFprSmxRQkxTRldpcXMKLS0tIHFOMjhKc214NlMvMGxZSW9ZSHUz\n            TnppY1ZWTFdhNlpXNTVHeDlGZkwxRWsKRZdQHvJV+ZxWJblV67sE5hQ2mNbX5Nix\n            QUBNhgsKmHgU9yQxOekjOyV+8PEU9ziHtNOP87TrclF3IBawUIYYYQ==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrZTcxWnRYNThKZkZQTGo4\n            aDhNVlV6VTN1MHpTSkdwRVk1SVJSb1RKTDBvClhuNUxiTTZwQXhRTWdPU1VsY3hu\n            YzFhaHNVK3JxYVpLdVpOalVwQmxhTjgKLS0tIHU0NHY0ZnVkbXB5UnBXSGNFcVYz\n            Ti9RanBDc2pmSGlYbTQzQUc5Q0pwdzAKfq7K2LYsr260aJcTwHSSEyuAi+aj5jaK\n            L3jfnKuQ7P0sq0sMu8il9ThOoLaNWgD35n+Vf+5ene7WL55hJEjd1A==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4WlZyV1Axdi96eCtpOUkw\n            cjcySStsazV4MFdEcWtZWXpQS25HNjdTWHdNCitYR1J2ajdKb0hEbjZFQ05jaG9D\n            cWw1VzFVQ29HRnFVdUR0QitBVW90QkEKLS0tIC9MUlRvSW1uajVLQ1lWNStDblg1\n            MVpHalZ5TTRQamFGUFlvR3pPWGRlRFUKTkmSQOiOkK4BVHKVumkVglNdpIG6vkc9\n            9kJwLfTbD+XTYsJTtzwKRbCSDEDaTNyIyWBrJBcDxiwEDK7dKsptTg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxQVZ4RC9MZkcyRURsUllS\n            TWRhS3JxTkpJN01YRUg3azQvTFRPMXFOUWxvCkp6eVQ3dnVvVzFUZEhwQkNscnpX\n            OGQ1VFZWMFYvN0xYVmhGY1B2RzlsQXMKLS0tIGdvd3N5VlRTdDBpM0hBS2hENUEw\n            T3JVZGc2MkwyWTcrOUgyWE14WmpqMEEKXB4VPhWSe/z3yUH6X7cBYUQBXUjjLBxP\n            o7M8uCCpKSM4VyEzeHiga4cskWI/PjLgRjoo569z6lw5rXhCXv2vtg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1LzU0RFBIU2ozQlhWTjVw\n            MFlqd2dYWnNEeWRZNlJMZnBOVUo2OXZiQmswCkFpRTkwVGtnRkJaSllkNkdWK21j\n            VDBpMkU5Vm5yVkhBQUhpUnRlN0N1bEkKLS0tIDlyT3JXaG9lc0hlS2FYS3gyRUZz\n            d2RscGFWZ1h3cWJSQjJxTFJvSkNlekUKQ7kY3N0uiPiLbrSKMbzxvP0h6ptMrfJl\n            TOhgqrijxet/giycycam5OilBbtEkUGxFJ95w/BlG2+QRgnQPl1g6g==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2025-09-25T07:14:25Z\"\n    mac: ENC[AES256_GCM,data:6GfeWvYdem9o72hH+2TXnjo8gLMGLgupX3/fDk88oK+8VhZlyn/NA0WGMw1zzic7TdLojbHBFr1jV2qhCqUlexIimR2+hyMuxwe8xLKk+R/n6NNQIOeND72gbymezUrDhMZLtDPEZXAdMd95bUZjYcrU1zcXAg9YntllNp+JTdY=,iv:kEPQ4VMElpx0tJ1oAmErCFAX8t0HYXsHrvRY8fJPVCM=,tag:qvDmpY5hGuEQjy6Y3vSPiQ==,type:str]\n    unencrypted_suffix: _unencrypted\n    version: 3.10.2\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/default.nix",
    "content": "{ ... }:\n{\n  imports = [\n    ./disk-config.nix\n    ./hardware-configuration.nix\n    ./_modules # host specific modules\n    ../../profiles/server.nix\n  ];\n\n  config = {\n    myHardware.cpu = \"intel\";\n\n    mySystem = {\n      programs.nh = {\n        enable = true;\n        flake = \"/home/budiman/Github/nix-config\";\n      };\n      services.btrfs-autoscrub = {\n        enable = true;\n        fileSystems = [\n          \"/\"\n          \"/nas-data\"\n        ];\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/disk-config.nix",
    "content": "{ ... }:\n{\n  config = {\n    # filesystems managed by Disko are defined here\n    disko.devices.disk = {\n      main = {\n        type = \"disk\";\n        device = \"/dev/disk/by-id/ata-KINGSTON_SA400S37120G_50026T0910A0397\";\n        content = {\n          type = \"gpt\";\n          partitions = {\n            ESP = {\n              priority = 1;\n              name = \"ESP\";\n              start = \"1M\";\n              end = \"512M\";\n              type = \"EF00\";\n              content = {\n                type = \"filesystem\";\n                format = \"vfat\";\n                mountpoint = \"/boot\";\n              };\n            };\n            root = {\n              size = \"100%\";\n              content = {\n                type = \"btrfs\";\n                extraArgs = [ \"-f\" ]; # override existing partiion\n                subvolumes = {\n                  \"/rootfs\" = {\n                    mountpoint = \"/\";\n                  };\n                  \"/home\" = {\n                    mountpoint = \"/home\";\n                  };\n                  \"/nix\" = {\n                    mountpoint = \"/nix\";\n                    mountOptions = [\n                      \"compress=zstd\"\n                      \"noatime\"\n                    ];\n                  };\n                  \"/swap\" = {\n                    mountpoint = \"/swap\";\n                    swap.swapfile.size = \"16G\";\n                  };\n                };\n              };\n            };\n          };\n        };\n      };\n\n      # full btrfs drive for incus\n      vm-data = {\n        type = \"disk\";\n        device = \"/dev/disk/by-id/ata-Kingmax_SSD_240GB_1393070A113C00097082\";\n        content = {\n          type = \"btrfs\";\n          extraArgs = [ \"-f\" ]; # override existing partition\n          mountpoint = \"/vm-data\";\n        };\n      };\n    };\n\n    # NAS data not managed by Disko\n    fileSystems = {\n      \"/nas-data\" = {\n        device = \"/dev/disk/by-label/BTRFS-RAID10\";\n        fsType = \"btrfs\";\n      };\n      \"/home\" = {\n        # This is needed for `sops-nix` to work properly on reboot\n        # see: https://github.com/Mic92/sops-nix/issues/149\n        neededForBoot = true;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-nas/hardware-configuration.nix",
    "content": "{ modulesPath, ... }:\n{\n  imports = [ (modulesPath + \"/installer/scan/not-detected.nix\") ];\n\n  boot = {\n    initrd = {\n      availableKernelModules = [\n        \"xhci_pci\"\n        \"ehci_pci\"\n        \"ahci\"\n        \"mpt3sas\"\n        \"usbhid\"\n        \"usb_storage\"\n        \"sd_mod\"\n      ];\n      kernelModules = [ \"dm-snapshot\" ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/default.nix",
    "content": "{ ... }:\n{\n  imports = [\n    ./services/blocky\n    ./services/qbittorrent\n\n    ./firewall.nix\n    ./network.nix\n    ./wireguard.nix\n  ];\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/firewall.nix",
    "content": "{ ... }:\n{\n  networking = {\n    firewall.enable = false;\n    nat.enable = false;\n    nftables = {\n      enable = true;\n      flushRuleset = true;\n      tables = {\n        main_filter = {\n          family = \"inet\";\n          content = ''\n            chain INPUT {\n              type filter hook input priority filter; policy drop;\n              ct state vmap {\n                established : accept, related : accept,\n                invalid : drop\n              }\n              iifname lo accept comment \"allow loopback traffic\"\n\n              # FROM OUTSIDE\n              iifname enp0s6 udp dport 51821 counter accept comment \"allow access to WireGuard\"\n              iifname enp0s6 tcp dport 22 counter accept comment \"allow access to SSH\"\n              iifname enp0s6 tcp dport 60413 counter accept comment \"allow access to qBittorrent torrentingPort\"\n\n              # FROM WIREGUARD\n              iifname wg0 tcp dport 22 counter accept comment \"allow access to SSH\"\n              iifname wg0 ip saddr 10.10.10.0/24 counter accept comment \"allow access from own CIDR\"\n              iifname wg0 tcp dport 8080 counter accept comment \"allow access to qBittorrent webuiPort\"\n            }\n          '';\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/network.nix",
    "content": "{ ... }:\n{\n  networking = {\n    nameservers = [\n      \"1.1.1.1\"\n      \"8.8.8.8\"\n    ];\n    useDHCP = false;\n  };\n  systemd.network = {\n    enable = true;\n    networks = {\n      \"30-enp0s6\" = {\n        matchConfig.Name = \"enp0s6\";\n        networkConfig.DHCP = \"yes\";\n        linkConfig.RequiredForOnline = \"routable\";\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/secret.sops.yaml",
    "content": "wireguard:\n    privatekey: ENC[AES256_GCM,data:WgGBE9Gx7s66/ouwZnLFoUDDcmJO3Bvq6tkifCTrtTdYCAVKr63lkc/ijMg=,iv:SkrjD0XsxTxIKqpElLVZfiyZw0Ivi+IQAM586HJneLw=,tag:/8RWls0rxggMiLFvDsDVGQ==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkc2tMaC9iSmpkaU94OVVR\n            MFk1Mk1RQjBQZGhLL1hONGR5Q05hRGhZNVFnCnNiMURZWXZmL20wcktlbFV6ZXZB\n            TFhnZk5US2RjL2wwOWVDMG9jeVk1OGcKLS0tIHJBUExRQmhBaVFoOXRkblh3Znc1\n            bUhqY21SclZHMGNXMmdSM3o0eTJIOEEKN/qOSLEmNOQK5uw1O1HVBtpyfvq9V7yG\n            ltNe6dLSClbYU3xFXHpWSEHzzQ+r4rYQBcvM1r2Axd3lPSF1JtOwow==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3QlJ5anpOTTJzMkY3NU1v\n            NW5yN3VXZEtEc1NDVmluelZTOWJEMUxLeUEwCm9GMmJybGlzZ2R1S0tVbHpWaXNj\n            eUpTOU5JdmpFN3JjRmhlRWRyenJhK3cKLS0tIDJIa25XSnhXRjBvTWFTekxvRldH\n            MjJGemlPV3J5UXRCcWFKWUR0cjBPeHMKd++ZcoSWXDlJ32v04VGB1bXtMMhwsPR9\n            1eEU5IWuNSFlPnuoohpBGq0E8NQ5iv2FT2pB6J4ecT6OmLEshPYwzg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsbm14WEdpTVl6VVQxNHJQ\n            UEhUYXI4L2dUOGRnT2Z1amdTd1grUHVOSVJNCmNRZnhBV005S2ZLL05FZnBHMEt6\n            dE9GbXR5QmtJL1hWY20yYm9oNkFWY2cKLS0tIDZlUkNCWDNFM2ZSKzQyTytWeW4r\n            ZTFWUWlYdGF6eTAyM2VVRXVqOVVkVG8K/d/gbOq9g/YzVZQtaLObxf3KScgUgWc8\n            avSzLzrCeaZSsKnlp0+Ty2nPan6xLYKHZunEuDhEkJufhRaUpBRyAw==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtNFFHVmlvbUo3VTEyYWNp\n            RkVxRE5oSXJRMUh5MklVenhEaE5SZm8wVFQ4CjU3WHhjL3JwTTVqUFp3dFRPNlpF\n            ZzIxdEVZTXpQbktJSTVadDRPMXZqSmcKLS0tIEliQ0NQRVI2bXNFNHJjUlVpVXBI\n            NG1UckhSSkxuanl5eEswZ0lDVWZ5Y1EKHGpTpMoRap1//A/bLP30HRBg6ThFOCbc\n            wEcH38Vw3R8fjgLlPZp8rGDoiPIOJH6uDevQkkXeU8MetWNC8M97nA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvL3QyZ3h1VHVwQ0djUkl1\n            UjFsL2Rybk5PaXkyOElEbVRzcnFBd1QxeXlZClM1MjZ6RUgrSUZLZ2lOejdLQWJJ\n            Smx6KzBkcG1ITzREblkxNUJRSmlBMGsKLS0tIFp2cnV6QmdmYTFKbnBJS2pFcE1y\n            SzAwWVM1OElXU2RwWmp2UXRpUkhKSm8KnLOp1YUeLaxXt4lTDs2nCk815KDetxdq\n            mhca1uhcXnrOpFOyKSLj5sAx/1v0JZv8Uz22j5kHa8IPo7+KJ0Yr4Q==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3VkEraEdtZUxwUjdVRmQv\n            MUN4SGo4RzhkOFY1OHRMQndNRW9FY21hREVJCnU3eVVTRjFlVUlHZjl4RERZS01l\n            YzdmL2hmaDM0TzcvMEhRMmgxeE5kYjAKLS0tIFNQd1kySlE0aDZNQ2xJU2RuWGRI\n            MWVrbTlOUDZrVDJUc0lOczRaM3FyN0EKOvVa7ubBozsa2TCMhdQLrRSe4iZmm6rd\n            6qYBWtlfiTaqHKgVi6MVrob9vPACrkegQgHIH/OAcIDubfY8kLGDZg==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2025-02-18T04:57:27Z\"\n    mac: ENC[AES256_GCM,data:pWaGivxmqz289Q3afJKD7eiCmIhkZFyeVPBrS/noKA+azD6xHkLZG1thoQw7V5e1q1OqeMJzWMChXGTB4VC9kwKGC+/CwsJ+Xw/+ZaUj8ruUugEHKeAx41AkXxg+KOwejgHI5DCUksrHGUwjCEQKb5jWF2nLpld2QenaKyO90iw=,iv:jwweOgqiycYcoQLvl4IOyijEGNf/RarvYMTmG/PhYk4=,tag:aeERpYuTJT2TMpCZmAsjLA==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.9.4\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/services/blocky/config.yaml",
    "content": "---\nupstreams:\n  groups:\n    default:\n      - 1.1.1.1\n      - 8.8.8.8\n# we let the adguardhome running in my home network to handle my own domains\nconditional:\n  mapping:\n    ${SECRET_DOMAIN_0}: 192.168.10.1\n    ${SECRET_DOMAIN_1}: 192.168.10.1\nblocking:\n  denylists:\n    ads:\n      - https://raw.githubusercontent.com/jerryn70/GoodbyeAds/master/Hosts/GoodbyeAds.txt\n      - https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt\n  allowlists:\n    ads:\n      - |\n        s2.youtube.com\n        graph.facebook.com\n        i.instagram.com\n        fonts.gstatic.com\n        click.redditmail.com\n        aypbpr.tokopedia.com\n        plenty.vidio.com\n        i.sgsnssdk.com\n  clientGroupsBlock:\n    default:\n      - ads\ncaching:\n  cacheTimeNegative: -1\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/services/blocky/default.nix",
    "content": "{ pkgs, config, ... }:\nlet\n  blockyUser = \"blocky\";\n  configFile = ./config.yaml;\nin\n{\n  # this is enabled by default when `systemd.network.enable` is `true`\n  # it listens on port 53 for stub DNS by default so we should just disable it\n  services.resolved.enable = false;\n\n  sops.secrets.\"blocky/env\" = {\n    sopsFile = ./secret.sops.yaml;\n    owner = blockyUser;\n    restartUnits = [ \"blocky.service\" ];\n  };\n\n  # add user, needed to access the secret\n  users = {\n    users.${blockyUser} = {\n      isSystemUser = true;\n      group = blockyUser;\n    };\n    groups.${blockyUser} = { };\n  };\n\n  systemd.services.blocky = {\n    description = \"A DNS proxy and ad-blocker for the local network\";\n    wantedBy = [ \"multi-user.target\" ];\n    # we do `envsubst` to substitute string like ${VAR} using the environment secrets\n    preStart = ''\n      ${pkgs.envsubst}/bin/envsubst -no-unset -i \"${configFile}\" -o \"$RUNTIME_DIRECTORY/config.yaml\"\n    '';\n    serviceConfig = {\n      EnvironmentFile = \"${config.sops.secrets.\"blocky/env\".path}\";\n      User = blockyUser;\n      RuntimeDirectory = \"blocky\";\n      ExecStart = \"${pkgs.blocky}/bin/blocky --config /run/blocky/config.yaml\";\n      Restart = \"on-failure\";\n      AmbientCapabilities = [ \"CAP_NET_BIND_SERVICE\" ];\n      CapabilityBoundingSet = [ \"CAP_NET_BIND_SERVICE\" ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/services/blocky/secret.sops.yaml",
    "content": "blocky:\n    env: ENC[AES256_GCM,data:wp3pLGX07GJnzWlhahn4ZfQsF7TVK7fmMJqdiPekygDHdHMxXT0NXKGUY8zSVRJcwSpE3TrmzHldTPuXSU/PHT6g,iv:WRVJACMx+57APiCRyBzt4CCnxXeSeIIpl5UrXT9r3dY=,tag:YIAAOcZi/45HU3m8+DtzxA==,type:str]\nsops:\n    kms: []\n    gcp_kms: []\n    azure_kv: []\n    hc_vault: []\n    age:\n        - recipient: age1zeqkpfz7e3s207ynea0z0auc0mrct0pc7w4sh6j3d0c4qac3dahqj9ufdg\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqRC93QTI3eWhwQ2ZsdUky\n            UHFiY21yb3RWMFBCNUdkbFEwVTUyQ3BpUm13CkhIZkd4cHozdTR5YVJBdWZDcHg0\n            QW5OTkxqd2c2SEIyUkd2V2JpN2JVZDQKLS0tIHNRMFZpbDJaSDlsbWU3Z0dUcXBh\n            ek5jTDRUaXUrU1lvNEdQdUdmQmFzRzQKjASIGxTeZr67bE5EvhwUG0qVTlyGW/Jd\n            p3eXV+XEp5CgBnKgWZ4s8IBrNr0gASZj6d0b4Cj0MUNhgks55K668Q==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1tdwlq9y4jgejkhasqwynw5uaen9xwatcvr52l70trsdxkeyvlesqjnh7l8\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvK2FnQVFYTnVzRjltazRC\n            SG9qRTlyNlQvRTI2YklOZ0tPbDN2dFBHTW1NClZVQlVPK2lmUWdLZDZCOURYbTNj\n            T3Vlbm9TazVoVkZKNnhISEVNQ01Ba00KLS0tIEVKYnlES0FRNzU3R1JkbjNoQ0dl\n            YnJGbk5Zc2NaSlBacnVUTlJmdklVT1UKFCZtfeIxHFZqfFw8CbcW4G6AECC0W1LD\n            2fixufcXwoIOqPXhxszYB0BSlI3odtLbXb4LDwSpzdJuYSq77f79ig==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age16p3zls5n0jks6amszwcuaqgl5dyuyf8k8wgeyrw562s5s88xtq3qq046fh\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIUTN0NzFaQ1VHRUZWa0hQ\n            KzJHQWtWcHdJdjBObmZYbzJxYXpIcXdiUmhJCmVFT2UzbXhkU1BsUmZkY3cxak92\n            L0hCSDE5SWc5U2FGM3lJV3hBNGttRFEKLS0tIGMzQmdaSUdIRERNOGtCcmdXZldL\n            WVpMU2xwL1I4K2J5djloNUMrbTNmTEUK5g9TLwVGqcchUHYcjf/lSS0xEruq44aq\n            HKJ3EPYCCLexOFm2FzeZ4IC8eqkgKINaPZe2k6ZVcFqSckKAhuQ5jg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1v52mx8gs4ephprep0wcc4j3fvvprppvs9vktf2p24yv52sqsf33sd5crk9\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIYUhsZHRtYWF5ME0yWisw\n            Y3NvRHlNTlFPTzlPR0l0SnlhT2wxVkZLeUZRCmJ6L1lVQXVyQ3FvQ3VyYk1ZSzVz\n            Yk13S1gxcVg2TmxDYU10WUI3cDhPVkEKLS0tIEkveVVJTVdqcnI0MmRQTWhYYnFU\n            WGc3ckVBbDhWeFpiSXBYT3V6NXN3QzgK5EDt6GoAsKfCIKd4ZxFFTGNYBR+xHXDG\n            k2qgsKs8W8z+AI1qNou216fU1z/4tsK+kix1YQglYf3k164npCvVfA==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1k8ufac2s0gs6nh0xsfavafz062vd36petmyv6nwmg00z4a7s4gnsjtd837\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsRUlwS3NTK1R3SkZOd0hC\n            UVFwNkZWYW5IKzZ1L3dpRTFRQng4VFBiR0FFCnRETFNVTnRKZTQ2d2ZXT2RBUTVX\n            YTNwMXkrWXh4YWsvTEZPNS9JeDlXRVUKLS0tIGRlZ1V4WU5kQ25WVzNvMjF1VEMw\n            Q1VBMVl2UVcra29ieXUwV3E3RkwwN2MKFsytvsuLSFub/5xWjVto3GN+XaUNHvGg\n            dVwyqo0mIwsC6Kvay7UaPQm/y8n2NmQRWpdDOLOe86s7IDzvXAvpHg==\n            -----END AGE ENCRYPTED FILE-----\n        - recipient: age1dcsm5awz8ekzchk7gsvndkc4kq65n2wzgavxtqe53vxdsfk9k9pqh3whru\n          enc: |\n            -----BEGIN AGE ENCRYPTED FILE-----\n            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6OFk5c3ZSMjc1SzVYcStv\n            RDVlRGl0NDY1U0lza0ZNRGxpMmF5WitSNUFnCit0RFZ3Um9lZWJGS1dOOVhhZUZz\n            bjVUL3RuZjZKSUVOczFGblgva0RFencKLS0tIDFEbWxtWjJ3WFRkdklreC9YRlVH\n            YUt0WVVEc2cvSFpQMWtMY1BVdkE3VXMKszlTBVsJlkmelUZLx6llgCNzcdncIqCK\n            G+4heKmxGtYkigj+CRuW5Pdpl2Nu0dEsyuUpEFzwESo64elFfHQ69Q==\n            -----END AGE ENCRYPTED FILE-----\n    lastmodified: \"2025-02-23T06:36:37Z\"\n    mac: ENC[AES256_GCM,data:ROQmlC2yiL1sADLXB/ejOMcEm08zD6TqUpry9twClwWvUKOljJWGoRB+oba6pGw4HKNdd/v1wCD/qwPM5Hr2jWSt/vEMC/iZg9XSQtjmLbuX4PvCv+td33z8HByY/MGm4U3Okp/7sulqCxm/Z6GWDagQkC6SEMEZO9SxOPI+j3E=,iv:UKx1fu5tbuSGA+6pecbPUMeJpHLP30zDugqv92V3Exw=,tag:23AYOjpw1slPxKd0xZrTag==,type:str]\n    pgp: []\n    unencrypted_suffix: _unencrypted\n    version: 3.9.4\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/services/qbittorrent/default.nix",
    "content": "{ pkgs, ... }:\nlet\n  downloadsDir = \"/var/qBittorrent-downloads\";\n  qbitUser = \"qbittorrent\";\nin\n{\n  systemd.tmpfiles.settings.qbittorrent = {\n    \"${downloadsDir}\".d = {\n      mode = \"755\";\n      user = qbitUser;\n      group = qbitUser;\n    };\n  };\n\n  services.qbittorrent = {\n    enable = true;\n    package = pkgs.unstable.qbittorrent-nox;\n    user = qbitUser;\n    group = qbitUser;\n    webuiPort = 8080;\n    torrentingPort = 60413;\n    serverConfig = {\n      Preferences = {\n        WebUI = {\n          AuthSubnetWhitelistEnabled = true;\n          AuthSubnetWhitelist = \"10.10.10.12/32, 10.10.10.13/32, 192.168.50.49/32\";\n          ClickjackingProtection = false;\n          HostHeaderValidation = false;\n          SecureCookie = false;\n          CSRFProtection = false;\n        };\n        Connection.UPnP = false;\n      };\n      BitTorrent = {\n        Session = {\n          BTProtocol = \"TCP\";\n          Preallocation = true;\n          DefaultSavePath = downloadsDir;\n          DisableAutoTMMByDefault = false;\n          DisableAutoTMMTriggers = {\n            CategorySavePathChanged = false;\n            DefaultSavePathChanged = false;\n          };\n          MaxConnections = -1;\n          MaxConnectionsPerTorrent = -1;\n          MaxUploads = -1;\n          MaxUploadsPerTorrent = -1;\n          QueueingSystemEnabled = false;\n        };\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/_modules/wireguard.nix",
    "content": "{ config, ... }:\nlet\n  homeNetworks = [\n    \"192.168.10.0/24\" # LAN0 network\n    \"192.168.50.0/24\" # HOME network\n    \"192.168.69.0/24\" # IOT network\n    \"192.168.200.0/24\" # SERVER network\n    \"192.168.250.0/24\" # GUEST network\n    \"10.5.0.0/24\" # CONTAINER network\n    \"192.168.15.0/24\" # k8s LB network\n  ];\nin\n{\n  sops.secrets.\"wireguard/privatekey\" = {\n    sopsFile = ./secret.sops.yaml;\n    owner = \"systemd-network\";\n    restartUnits = [ \"systemd-networkd.service\" ];\n  };\n\n  systemd.network = {\n    netdevs.\"50-wg0\" = {\n      netdevConfig = {\n        Name = \"wg0\";\n        Description = \"WireGuard\";\n        Kind = \"wireguard\";\n        MTUBytes = \"1420\";\n      };\n      wireguardConfig = {\n        PrivateKeyFile = \"${config.sops.secrets.\"wireguard/privatekey\".path}\";\n        ListenPort = 51821;\n        RouteTable = \"main\";\n      };\n      wireguardPeers = [\n        {\n          # budimanjojo-firewall\n          PublicKey = \"ZfOwDdOBpC2bpTp1pQl9Jlr0tBhm6njonXoJGU0xyBM=\";\n          AllowedIPs = [ \"10.10.10.11/32\" ] ++ homeNetworks;\n        }\n        {\n          # pocof6-phone\n          PublicKey = \"WCESN/SAmvJekIxJSlmw+2FQg+uhvlseGhGvN/VZ0AU=\";\n          AllowedIPs = [ \"10.10.10.12/32\" ];\n        }\n        {\n          # work-pc\n          PublicKey = \"E+sLt3iBptnj6+7X/9S2ROglx8G3urufr18wCBt8bGQ=\";\n          AllowedIPs = [ \"10.10.10.13/32\" ];\n        }\n        {\n          # oneplus6t-phone\n          PublicKey = \"4g38+3wtUHHlrOCl/HsIM6i8p1QeoWgHtPh7hjcQx10=\";\n          AllowedIPs = [ \"10.10.10.14/32\" ];\n        }\n        {\n          # android-livingroom-tv\n          PublicKey = \"LYF3KLxBtfD1tIy2qS4Rl4COGlBLxFye7OaIUkZtfHM=\";\n          AllowedIPs = [ \"10.10.10.15/32\" ];\n        }\n        {\n          # qbit-gluetun\n          PublicKey = \"U1/0yWmjrRQapq+TWNv0mi1+gJkKkyBmN/ZtJWGbb0k=\";\n          AllowedIPs = [ \"10.10.10.50/32\" ];\n        }\n      ];\n    };\n    networks = {\n      \"50-wg0\" = {\n        matchConfig.Name = \"wg0\";\n        address = [ \"10.10.10.10/24\" ];\n        networkConfig = {\n          # IPMasquerade = \"ipv4\"; # we don't want to masquerade everything\n          IPv4Forwarding = true;\n        };\n      };\n      # we need to enable IP forwarding for outbound interface too\n      \"30-enp0s6\".networkConfig.IPv4Forwarding = true;\n    };\n  };\n\n  # this ensures the source address of peers are correctly forwarded to my\n  # firewall server so I can set firewall rules for each peer while peers\n  # still have access to the internet acting as this server\n  networking.nftables = {\n    enable = true;\n    tables.wg_nat = {\n      family = \"ip\";\n      content = ''\n        set home_networks {\n          type ipv4_addr\n          flags interval\n          elements = {\n            ${builtins.concatStringsSep \", \" homeNetworks}\n          }\n        }\n\n        chain PREROUTING {\n          type nat hook prerouting priority dstnat; policy accept;\n          tcp dport 50413 counter dnat to 10.10.10.50 comment \"port forward 50413 to qbit-gluetun\"\n        }\n\n        chain POSTROUTING {\n          type nat hook postrouting priority srcnat; policy accept;\n          ip saddr 10.10.10.0/24 ip daddr != @home_networks masquerade\n        }\n      '';\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/default.nix",
    "content": "{ ... }:\n{\n  imports = [\n    ./hardware-configuration.nix\n    ./_modules # host specific modules\n    ../../profiles/server.nix\n  ];\n\n  config = {\n    mySystem = {\n      programs.nh = {\n        enable = true;\n        flake = \"/home/budiman/Github/nix-config\";\n      };\n    };\n\n    # sshd failed to start on boot when interface is not ready yet\n    systemd.services.sshd = {\n      after = [ \"network-online.target\" ];\n      wants = [ \"network-online.target\" ];\n    };\n  };\n}\n"
  },
  {
    "path": "system/hosts/budimanjojo-oracle/hardware-configuration.nix",
    "content": "{ modulesPath, lib, ... }:\n{\n  imports = [ (modulesPath + \"/profiles/qemu-guest.nix\") ];\n\n  boot = {\n    initrd = {\n      availableKernelModules = [\n        \"xhci_pci\"\n        \"virtio_scsi\"\n      ];\n    };\n  };\n\n  fileSystems = {\n    \"/boot\" = {\n      device = \"/dev/disk/by-uuid/C241-FDB6\";\n      fsType = \"vfat\";\n      options = [\n        \"fmask=0022\"\n        \"dmask=0022\"\n      ];\n    };\n    \"/\" = {\n      device = \"/dev/disk/by-uuid/c85a6137-9a10-4e79-a5a5-86368db22114\";\n      fsType = \"ext4\";\n    };\n  };\n\n  nixpkgs.hostPlatform = lib.mkDefault \"aarch64-linux\";\n}\n"
  },
  {
    "path": "system/hosts/default.nix",
    "content": "{ hostname, pkgs, ... }:\n{\n  # host specific config\n  imports = [ ./${hostname} ];\n\n  # global config applied to all hosts\n  config = {\n    environment.systemPackages = with pkgs; [\n      dig\n      htop\n      tree\n      usbutils\n    ];\n  };\n}\n"
  },
  {
    "path": "system/hosts/nixos-livecd/default.nix",
    "content": "{\n  modulesPath,\n  pkgs,\n  myPkgs,\n  hostname,\n  ...\n}:\nlet\n  pubkeys = import ../../pubkeys;\nin\n{\n  imports = [ (modulesPath + \"/installer/cd-dvd/installation-cd-minimal.nix\") ];\n\n  config = {\n    # faster build time\n    isoImage.squashfsCompression = \"gzip -Xcompression-level 1\";\n\n    # enable SSH in the boot process\n    systemd.services.sshd.wantedBy = pkgs.lib.mkForce [ \"multi-user.target\" ];\n    users.users.nixos.openssh.authorizedKeys.keys = [ pubkeys.main-pc ];\n\n    environment.systemPackages = [\n      myPkgs.neovim\n      pkgs.gitMinimal\n    ];\n\n    networking.hostName = hostname;\n\n    time.timeZone = \"Asia/Jakarta\";\n\n    security = {\n      sudo.wheelNeedsPassword = false;\n    };\n  };\n}\n"
  },
  {
    "path": "system/profiles/gaming.nix",
    "content": "{ ... }:\n{\n  config = {\n    programs.gamemode.enable = true;\n  };\n}\n"
  },
  {
    "path": "system/profiles/server.nix",
    "content": "{ ... }:\nlet\n  pubkeys = import ../pubkeys;\nin\n{\n  config = {\n    mySystem = {\n      system.autoupgrade.enable = true;\n      services.openssh = {\n        enable = true;\n        authorizedKeys = [\n          pubkeys.work-pc\n          pubkeys.main-pc\n          pubkeys.op9-termux\n        ];\n      };\n      monitoring = {\n        node-exporter.enable = true;\n        smartctl-exporter.enable = true;\n      };\n    };\n  };\n}\n"
  },
  {
    "path": "system/profiles/work.nix",
    "content": "{ ... }:\n{\n  config = {\n    mySystem.containers.beeaccounting.enable = true;\n  };\n}\n"
  },
  {
    "path": "system/profiles/workstation-common.nix",
    "content": "{ ... }:\n{\n  config = {\n    mySystem = {\n      programs = {\n        adb.enable = true;\n        hugo.enable = true;\n        qmk.enable = true;\n      };\n      monitoring.smartctl-exporter.enable = true;\n    };\n    boot.plymouth.enable = true;\n  };\n}\n"
  },
  {
    "path": "system/profiles/workstation-hyprland.nix",
    "content": "{ ... }:\n{\n  imports = [ ./workstation-common.nix ];\n\n  config.mySystem.windowmanager.hyprland.enable = true;\n}\n"
  },
  {
    "path": "system/profiles/workstation-i3.nix",
    "content": "{ ... }:\n{\n  imports = [ ./workstation-common.nix ];\n\n  config.mySystem.windowmanager.i3.enable = true;\n}\n"
  },
  {
    "path": "system/profiles/workstation-sway.nix",
    "content": "{ ... }:\n{\n  imports = [ ./workstation-common.nix ];\n\n  config.mySystem.windowmanager.sway.enable = true;\n}\n"
  },
  {
    "path": "system/pubkeys/default.nix",
    "content": "# this contains all public keys of my machines\n{\n  work-pc = \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDIgzLDHHRtLD9DLfNTX8Te9zBcDmEF33UPJ5HngP6aJAhdQln7ebSOzQoehxN0th644G9csnujxjNAStIEUjzeXO1NQQfEYGqDVxDL0jE4hXr1WVg+6GxQnV1nWP5Sd2i24+ElygCdw3KuteeNlfGZ7BKs91zySb03DICXPNcgXj6ZR9INalabFhbjeVG5MRH38KRR9cxZKbgW+eQZZwVtDRPzL7rAfaeeJPg7ZQ3Iu0SC3q5SskGQD5XfqCwPDx9n0GWva36jwNneifv5WFDh0U+xKaoVT4HJTWyV/vf3+fTji1yEsGMBbPexuD5aHvmun9SdgIlGw65GJB7Ibz47Bq/jVfnTV6o5BVDhEfwayHZgahODl5Uyc3VqkKoJh9IWivoBr/bLHXepiJGUReEw61nBc3JL9QC4J5ngterLXP/iapl185+JSvUzDjbtFDHLiXCqa8X17Cm9LSIKik/W7gM2tU63QcQd/p8H55he/Kgm94vWJlq98rjLCtBYTNQDSNAU6AFQqk2c3x23L09wSRIQJ1aUEq6aPx3yfbiRHyTslTyP4tg0I1U1o+jjh9tZV/+JpRcwg9xi9YLoGMAv9aUVGHodjehZw1wHnuql3ALiy/Nnm2LANDh4vhJ2fKsrBqhk8dyDdqFHFsLyTSXUAE4NKGG1AUV3fgMjvRRZZQ==\";\n  main-pc = \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJVJ+gHYFCoyd1WG8uJ2d0ErwmyASjhqv8ZXblNBHMjTSNXtqxTZ/MIIAPkxi3XSSFIcAGO7nr5myKyQVfiFlPAQMDR64iTfL6N/peMi25xE5+MhQKTIcJBSlRYx3RjJmx9JwmDuT8aXlPqL16QWk8WVe5XErNAXrsj+PrTISlS9O8wbqKExcC2jTzieg4L3II4cBv+M7QySt8ApzLJ5geYniO4mc5Jqor1hBZakQDw8vJdohOgnjVK+MIvVFm97iVmhNXUCgqFn3EGXVGLAB8J8h2aix8WvkP85WmvJ4nZ9k22QvijBr3WHQdwU/VoZ7xWpXVcGsXwnlD9tpGUotx\";\n  op9-termux = \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDwktTcrOt7pytCS3C8qWg+eRsOjVgEHLJ5iOufmsa6dlmvqYroalwCgvN0YcjoqPdg53S5F12C3voV3nvRhRPB0AEQQYlWEynSFdc/omCktA7NqHcj/Wb/r+52Bq5xVqjXJDLT3f4YCE3pectoaiKgKIS+OoNMOErFmnxO/uSetNrvJLIr6DrxLRW/lZ+xPDiN1eIHM1ETfk9CxIYjD3UztBPAa6qfahchqd64DS8UevLNlvc/SxPniqaZfaxlmk1qMUfvTSPJAcMBSVK2EasHd7tUZhbHvoC+F9723fwtxedTFofgw3iZHWjz8pL7ngFNA4NpyUdDSId3mydPYk0E/rY8WVbknvGlRBjI1OnXWHTOZty26vcoNYKfjP/+1Q3TINVIkvjhNCmS6UuUGfBtqB8uJkUHi4wVYkXLHsK5vUxM664bkeCezHbkR7o17Sa0YbINL510UOi+PLUPEHk98yKh+Xlw77kc0EEQdvmRDTVI+YBmsJ83brtouc2cXm0=\";\n}\n"
  }
]