Full Code of barrucadu/nixfiles for AI

master 866707b7db91 cached
95 files
1.1 MB
261.1k tokens
22 symbols
1 requests
Download .txt
Showing preview only (1,170K chars total). Download the full file or copy to clipboard to get everything.
Repository: barrucadu/nixfiles
Branch: master
Commit: 866707b7db91
Files: 95
Total size: 1.1 MB

Directory structure:
gitextract_v3gowx2a/

├── .forgejo/
│   ├── scripts/
│   │   └── deploy-documentation.sh
│   └── workflows/
│       └── deploy-documentation.yml
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yaml
│       └── github-pages.yml
├── .gitignore
├── .sops.yaml
├── README.markdown
├── docs/
│   ├── .gitignore
│   ├── book.toml
│   └── src/
│       ├── SUMMARY.md
│       └── runbooks/
│           ├── alerts/
│           │   ├── diskspacelow.md
│           │   └── zpoolstatusdegraded.md
│           ├── move-a-configuration-to-a-new-machine.md
│           ├── set-up-a-new-host.md
│           ├── upgrade-to-a-new-version-of-elasticsearch.md
│           └── upgrade-to-a-new-version-of-postgres.md
├── flake.nix
├── hosts/
│   ├── carcosa/
│   │   ├── caddy/
│   │   │   ├── lainon-life/
│   │   │   │   ├── 404.html
│   │   │   │   └── duvet.ogg
│   │   │   └── www-lookwhattheshoggothdraggedin-com.caddyfile
│   │   ├── configuration.nix
│   │   ├── hardware.nix
│   │   └── secrets.yaml
│   ├── nyarlathotep/
│   │   ├── configuration.nix
│   │   ├── dashboards/
│   │   │   ├── finance.json
│   │   │   └── smart-home.json
│   │   ├── hardware.nix
│   │   ├── jobs/
│   │   │   ├── flac-and-tag-album.sh
│   │   │   ├── hledger-export-to-victoriametrics.py
│   │   │   ├── hledger-fetch-fx-rates.py
│   │   │   ├── restic-prepare--fetch-youtube.py
│   │   │   ├── restic-prepare--hardlink-torrent-files.py
│   │   │   ├── rss-to-mastodon.py
│   │   │   └── tag-podcasts.sh
│   │   └── secrets.yaml
│   └── yuggoth/
│       ├── configuration.nix
│       ├── hardware.nix
│       └── secrets.yaml
├── scripts/
│   ├── backups.sh
│   ├── documentation.sh
│   ├── fmt.sh
│   ├── lint.sh
│   └── secrets.sh
├── shared/
│   ├── acme/
│   │   ├── default.nix
│   │   └── options.nix
│   ├── bookdb/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   ├── options.nix
│   │   ├── remote-sync-receive.nix
│   │   ├── remote-sync-send.nix
│   │   └── uuids.yaml
│   ├── bookmarks/
│   │   ├── default.nix
│   │   ├── options.nix
│   │   ├── remote-sync-receive.nix
│   │   └── remote-sync-send.nix
│   ├── dashboards/
│   │   └── node-stats-detailed.json
│   ├── default.nix
│   ├── erase-your-darlings/
│   │   ├── default.nix
│   │   └── options.nix
│   ├── finder/
│   │   ├── default.nix
│   │   └── options.nix
│   ├── forgejo/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── foundryvtt/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── host-templates/
│   │   ├── default.nix
│   │   └── website-mirror/
│   │       ├── default.nix
│   │       ├── options.nix
│   │       └── resources/
│   │           ├── memo-barrucadu-co-uk.caddyfile
│   │           └── www-barrucadu-co-uk.caddyfile
│   ├── minecraft/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── oci-containers/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── options.nix
│   ├── pleroma/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── resolved/
│   │   ├── dashboard.json
│   │   ├── default.nix
│   │   └── options.nix
│   ├── restic-backups/
│   │   ├── default.nix
│   │   └── options.nix
│   └── torrents/
│       ├── default.nix
│       ├── erase-your-darlings.nix
│       ├── options.nix
│       └── transmission_3/
│           ├── default.nix
│           ├── transmission-3.00-miniupnpc-2.2.8.patch
│           └── transmission-3.00-openssl-3.patch
└── tools/
    └── provision-machine.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .forgejo/scripts/deploy-documentation.sh
================================================
#!/usr/bin/env bash

echo "$SSH_PRIVATE_KEY" > .ssh_private_key
cleanup() {
    rm .ssh_private_key
}
trap cleanup EXIT
chmod 600 .ssh_private_key

set -x

rsync -Pavz --delete -e "ssh -i .ssh_private_key -o StrictHostKeyChecking=no" \
    _site/ "concourse-deploy-robot@${TARGET}:/persist/srv/http/barrucadu.dev/docs/nixfiles/"


================================================
FILE: .forgejo/workflows/deploy-documentation.yml
================================================
on:
  push:
    branches: ["master"]

jobs:
  deploy_documentation:
    runs-on: nix
    steps:
      - name: Install tools
        run: |
          nix-env -iA nixpkgs.nodejs_24 nixpkgs.rsync
      - uses: https://code.forgejo.org/actions/checkout@v4
      - name: Build
        run: |
          nix --extra-experimental-features "nix-command flakes" run .\#documentation
      - name: Deploy carcosa
        run: bash -e .forgejo/scripts/deploy-documentation.sh
        env:
          SSH_PRIVATE_KEY: ${{ secrets.CARCOSA_SSH_PRIVATE_KEY }}
          TARGET: carcosa.barrucadu.co.uk
      - name: Deploy yuggoth
        run: bash -e .forgejo/scripts/deploy-documentation.sh
        env:
          SSH_PRIVATE_KEY: ${{ secrets.YUGGOTH_SSH_PRIVATE_KEY }}
          TARGET: yuggoth.barrucadu.co.uk


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: daily


================================================
FILE: .github/workflows/ci.yaml
================================================
name: Run tests

on: pull_request

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: cachix/install-nix-action@v31
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      - name: Lint
        run: |
          set -ex
          nix flake check
          nix run .#fmt
          nix run .#lint
          git diff --exit-code

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: cachix/install-nix-action@v31
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      - name: Check documentation site builds
        run: nix run .#documentation


================================================
FILE: .github/workflows/github-pages.yml
================================================
on:
  push:
    branches: ["master"]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Setup Pages
        uses: actions/configure-pages@v6
      - name: Create placeholder site
        run: |
          mkdir _site
          cat <<EOF > _site/index.html
            <!DOCTYPE html>
            <html>
              <head>
                <meta charset="utf-8">
                <title>Redirecting to https://nixfiles.docs.barrucadu.dev/</title>
                <meta http-equiv="refresh" content="0; url=https://nixfiles.docs.barrucadu.dev/">
                <link rel="canonical" href="https://nixfiles.docs.barrucadu.dev/">
              </head>
              <body>
                <p>This website has moved to <a href="https://nixfiles.docs.barrucadu.dev/">"https://nixfiles.docs.barrucadu.dev/</a></p>
              </body>
            </html>
          EOF
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v5

  # Deployment job
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v5


================================================
FILE: .gitignore
================================================
/_site


================================================
FILE: .sops.yaml
================================================
keys:
  - &barrucadu 'age1sdnp5uxhdtujc78penv2gntnenzcfju7est4hslz6eqgfk26u9nskkk634'

creation_rules:
  - path_regex: hosts/carcosa/secrets(/[^/]+)?\.yaml$
    key_groups:
      - age:
          - *barrucadu
          - 'age1ty4vs59695vuavnvgdftguyq2aau29nv75y4tqrr6ag8z26vfc5sc5rc4n'

  - path_regex: hosts/nyarlathotep/secrets(/[^/]+)?\.yaml$
    key_groups:
      - age:
          - *barrucadu
          - 'age1700sgwfejx38fh66k6sajxe507w9x6ptcxfh4dmyffflml75w4fqmteyfy'

  - path_regex: hosts/yuggoth/secrets(/[^/]+)?\.yaml$
    key_groups:
      - age:
          - *barrucadu
          - 'age1xj0vderjss6wvyuu5uw5gag6lhxzfh6qwfrewgpff5ttpfa03azsxc8600'


================================================
FILE: README.markdown
================================================
nixfiles
========

My [NixOS][] configuration and assorted other crap, powered by [flakes][].
Clone to `/etc/nixos`.

CI checks ensure that code is formatted and passes linting.  Run those locally
with:

```bash
nix flake check
nix run .#fmt
nix run .#lint
```

See [the documentation](https://nixfiles.docs.barrucadu.dev).

[NixOS]: https://nixos.org
[flakes]: https://wiki.nixos.org/wiki/Flakes


Overview
--------

This is an opinionated config making assumptions which work for me but might not
for you:

- These are primarily single-user hosts, with me being that user.  While
  security and availability are important, convenience takes priority.
- Observability is good but there's no central graphing or alerting stack, every
  host has to run their own.
- Databases should not be shared, each service has its own containerised
  instance.  This means a single host may run several instances of the same
  database software, but that's an acceptable overhead.
- Persistent docker volumes should be backed by bind-mounts to the filesystem.
- For ZFS systems, [wiping `/` on boot][] is good actually.

Everything in `shared/default.nix` is **enabled on every host by default**.
Notable decisions are:

- Every user gets a `~/tmp` directory with files cleaned out after 7 days.
- Automatic upgrades (including reboots if needed), automatic deletions of
  generations older than 30 days, and automatic garbage collection are all
  enabled.
- Locale, timezone, and keyboard layout all set to UK / GB values (yes, even on
  servers).
- Firewall and fail2ban are enabled, but pings are explicitly allowed.
- SSH accepts pubkey auth only: no passwords.
- Syncthing is enabled.

For monitoring and alerting specifically:

- Prometheus, Grafana, and Alertmanager are all enabled by default (Alertmanager
  needs AWS credentials provided to actually send alerts).
- The Node Exporter is enabled, along with a dashboard.
- cAdvisor is enabled, along with a dashboard.

If using ZFS there are a few more things configured:

- All pools are scrubbed monthly.
- The auto-trim and auto-snapshot jobs are enabled (for pools which have those
  configured).
- There's a Prometheus alert for pools in a state other than "online".

Everything else in `shared/` is available to every host, but disabled by
default.

[wiping `/` on boot]: https://grahamc.com/blog/erase-your-darlings


Tools
-----

### Backups

Backups are managed by `shared/restic-backups` and uploaded to [Backblaze B2][]
with [restic][].

List all the snapshots with:

```bash
nix run .#backups                                # all snapshots
nix run .#backups -- snapshots --host <hostname> # for a specific host
nix run .#backups -- snapshots --tag <tag>       # for a specific tag
```

Restore a snapshot to `<restore-dir>` with:

```bash
nix run .#backups restore <snapshot> [<restore-dir>]
```

If unspecified, the snapshot is restored to `/tmp/restic-restore-<snapshot>`.

[Backblaze B2]: https://www.backblaze.com/
[restic]: https://restic.net/

### Secrets

Secrets are managed with [sops-nix][].  Create / edit secrets with:

```bash
nix run .#secrets                   # secrets.yaml for current host
nix run .#secrets <hostname>        # secrets.yaml for <hostname>
nix run .#secrets <hostname> <name> # <name>.yaml for <hostname>
```

[sops-nix]: https://github.com/Mic92/sops-nix


================================================
FILE: docs/.gitignore
================================================
book

# generated by the build script
src/README.md
src/hosts.md
src/host-templates.md
src/modules.md
src/options.md
src/packages.md


================================================
FILE: docs/book.toml
================================================
[book]
title = "nixfiles"
authors = ["Michael Walker (barrucadu)"]
description = "My NixOS configuration and assorted other crap, powered by flakes."
language = "en"

[build]
create-missing = false

[output.html]
git-repository-url = "https://github.com/barrucadu/nixfiles"
cname = "nixfiles.docs.barrucadu.dev"


================================================
FILE: docs/src/SUMMARY.md
================================================
# Summary

[README](./README.md)

# Reference

- [Hosts](./hosts.md)
- [Host Templates](./host-templates.md)
- [Modules](./modules.md)
- [Options](./options.md)

# Alert Runbooks

- [DiskSpaceLow](./runbooks/alerts/diskspacelow.md)
- [ZPoolStatusDegraded](./runbooks/alerts/zpoolstatusdegraded.md)

# "How to" Runbooks

- [Set up a new host](./runbooks/set-up-a-new-host.md)
- [Move a configuration to a new machine](./runbooks/move-a-configuration-to-a-new-machine.md)
- [Upgrade to a new version of elasticsearch](./runbooks/upgrade-to-a-new-version-of-elasticsearch.md)
- [Upgrade to a new version of postgres](./runbooks/upgrade-to-a-new-version-of-postgres.md)


================================================
FILE: docs/src/runbooks/alerts/diskspacelow.md
================================================
DiskSpaceLow
============

This alert fires when a partition has under 10% free space remaining.

The alert will say which partitions are affected, `df -h` also has the
information:

```
$ df -h
Filesystem                Size  Used Avail Use% Mounted on
devtmpfs                  1.6G     0  1.6G   0% /dev
tmpfs                      16G  112K   16G   1% /dev/shm
tmpfs                     7.8G  9.8M  7.8G   1% /run
tmpfs                      16G  1.1M   16G   1% /run/wrappers
local/volatile/root       1.7T  1.8G  1.7T   1% /
local/persistent/nix      1.7T  5.1G  1.7T   1% /nix
local/persistent/persist  1.7T  2.0G  1.7T   1% /persist
local/persistent/var-log  1.7T  540M  1.7T   1% /var/log
efivarfs                  128K   40K   84K  33% /sys/firmware/efi/efivars
local/persistent/home     1.7T   32G  1.7T   2% /home
/dev/nvme0n1p2            487M   56M  431M  12% /boot
data/nas                   33T   22T   11T  68% /mnt/nas
tmpfs                     3.2G   12K  3.2G   1% /run/user/1000
```

Note all ZFS datasets in the same pool (`local/*` and `data/*` in the example
above) share the underlying storage.

Debugging steps:

- See the `node_filesystem_avail_bytes` metric for how quickly disk space is
  being consumed
- Use `ncdu -x` to work out where the space is going
- Buy more storage if need be


================================================
FILE: docs/src/runbooks/alerts/zpoolstatusdegraded.md
================================================
ZPoolStatusDegraded
===================

This alert fires when an HDD fails.

The `zpool status -x` command will say which drive has failed; what,
specifically, the problem is; and link to a runbook:

```
$ zpool status -x
  pool: data
 state: DEGRADED
status: One or more devices could not be used because the label is missing or
        invalid.  Sufficient replicas exist for the pool to continue
        functioning in a degraded state.
action: Replace the device using 'zpool replace'.
   see: https://openzfs.github.io/openzfs-docs/msg/ZFS-8000-4J
  scan: scrub in progress since Thu Feb  1 00:00:01 2024
        19.3T / 20.6T scanned at 308M/s, 17.8T / 20.6T issued at 284M/s
        0B repaired, 86.49% done, 02:51:42 to go
config:

        NAME                                         STATE     READ WRITE CKSUM
        data                                         DEGRADED     0     0     0
          mirror-0                                   DEGRADED     0     0     0
            11478606759844821041                     UNAVAIL      0     0     0  was /dev/disk/by-id/ata-ST10000VN0004-1ZD101_ZA206882-part2
            ata-ST10000VN0004-1ZD101_ZA27G6C6-part2  ONLINE       0     0     0
          mirror-1                                   ONLINE       0     0     0
            ata-ST10000VN0004-1ZD101_ZA22461Y        ONLINE       0     0     0
            ata-ST10000VN0004-1ZD101_ZA27BW6R        ONLINE       0     0     0
          mirror-2                                   ONLINE       0     0     0
            ata-ST10000VN0008-2PJ103_ZLW0398A        ONLINE       0     0     0
            ata-ST10000VN0008-2PJ103_ZLW032KE        ONLINE       0     0     0

errors: No known data errors
```

Follow the provided runbook.  In most cases the solution will be to:

1. Buy a new HDD (of at least the same size as the failed one)
1. Physically replace the failed HDD with the new one
1. Run `zpool replace <pool> <old-device> <new-device>`
1. Wait for the new device to resilver


================================================
FILE: docs/src/runbooks/move-a-configuration-to-a-new-machine.md
================================================
Move a configuration to a new machine
=====================================

Follow the [set up a new host](./set-up-a-new-host.md) instructions up to
**step 5** (cloning the nixfiles repo to `/etc/nixos`).

Then:

1. Merge the generated machine configuration into the nixfiles configuration
1. Copy the sops master key to `.config/sops/age/keys.txt`
1. **If using secrets:** Re-encrypt the secrets
1. **If there is a backup:** Restore the latest backup
1. Remove the sops master key
1. **If wiping / on boot:** Copy any files which need to be preserved to the appropriate place in `/persist`
1. **Optional:** Update DNS records
1. **Optional:** Generate SSH key
1. Build the new system configuration with `sudo nixos-rebuild switch --flake '.#<hostname>'`
1. Reboot
1. Commit, push, & merge
1. **Optional:** Configure Syncthing


If using secrets: Re-encrypt the secrets
----------------------------------------

After first boot, generate an age public key from the host SSH key:

```bash
nix-shell -p ssh-to-age --run 'ssh-keyscan localhost | ssh-to-age'
```

Replace the old key in `.sops.yaml` with the new key:

```yaml
creation_rules:
  ...
  - path_regex: hosts/<hostname>/secrets(/[^/]+)?\.yaml$
    key_groups:
      - age:
          - *barrucadu
          - '<old-key>' # delete
          - '<new-key>' # insert
```

Update the host's encryption key:

```bash
nix shell "nixpkgs#sops" -c sops updatekeys hosts/<hostname>/secrets.yaml
```


If there is a backup: Restore the latest backup
-----------------------------------------------

Download the latest backup to `/tmp/backup-restore`:

```bash
nix run .#backups restore <hostname>
```

Then move files to restore to the appropriate locations.


Optional: Update DNS records
----------------------------

If there are any DNS records referring to the old machine which are now
incorrect (e.g. due to an IP address change), make the needed changes to [the
ops repo][] and apply the change via [Concourse][].

[the ops repo]: https://github.com/barrucadu/ops
[Concourse]: https://cd.barrucadu.dev/


Optional: Generate SSH key
--------------------------

Generate an ed25519 SSH key:

```bash
ssh-keygen -t ed25519
```

**If the host should be able to interact with GitHub:** add the public key to
the GitHub user configuration *as an SSH key*.

**If the host should be able to push commits to GitHub:** add
the public key to the GitHub user configuration *as a signing key*, and also add
it to [the allowed_signers
file](https://github.com/barrucadu/dotfiles/blob/master/dot_config/git/allowed_signers.tmpl).

**If the host should be able to connect to other machines:** add the public key
to `shared/default.nix`.

Remove the old SSH key for this host from anywhere it's used.


Optional: Configure Syncthing
-----------------------------

Use the Syncthing Web UI (`localhost:8384`) to get the machine's ID.  Replace
the old machine's ID and folder sharing permissions with the new machine, for
any other machines which synchronised files with it.


================================================
FILE: docs/src/runbooks/set-up-a-new-host.md
================================================
Set up a new host
=================

> [!NOTE]
> See also [the NixOS installation instructions](https://nixos.org/manual/nixos/stable/index.html#ch-installation).

Install NixOS
-------------

Boot into the ISO and install NixOS with `tools/provision-machine.sh`:

```bash
sudo -i
nix-env -f '<nixpkgs>' -iA git
curl https://raw.githubusercontent.com/barrucadu/nixfiles/master/tools/provision-machine.sh > provision-machine.sh
bash provision-machine.sh gpt /dev/sda
```

Then:

1. Rename `/mnt/persist/etc/nixos/hosts/new` after the new hostname
2. Add the host to `/mnt/persist/etc/nixos/flake.nix`
3. Add the new files to git
4. Run `nixos-install --flake /mnt/persist/etc/nixos#hostname`
5. Reboot


First boot
----------

Generate an age public key from the host SSH key:

```bash
nix-shell -p ssh-to-age --run 'ssh-keyscan localhost | ssh-to-age'
```

Add a new section with this key to `/persist/etc/nixos/.sops.yaml`:

```yaml
creation_rules:
  ...
  - path_regex: hosts/<hostname>/secrets(/[^/]+)?\.yaml$
    key_groups:
      - age:
          - *barrucadu
          - '<key>'
```

Add a `users/barrucadu` secret with the hashed user password:

```bash
nix run .#secrets
```

Copy the host SSH keys to `/etc/persist`:

```bash
sudo mkdir /persist/etc/ssh
sudo cp /etc/ssh/ssh_host_rsa_key /persist/etc/ssh/ssh_host_rsa_key
sudo cp /etc/ssh/ssh_host_ed25519_key /persist/etc/ssh/ssh_host_ed25519_key
```

Enable `nixfiles.eraseYourDarlings`:

```nix
nixfiles.eraseYourDarlings.enable = true;
nixfiles.eraseYourDarlings.barrucaduPasswordFile = config.sops.secrets."users/barrucadu".path;
sops.secrets."users/barrucadu".neededForUsers = true;
```

Make the `/persist` volume available in early boot:

```nix
fileSystems."/persist" =
  {
    device = "local/persistent/persist";
    fsType = "zfs";
    neededForBoot = true;
  };
```

Then:

1. Rebuild the system: `sudo nixos-rebuild boot --flake /persist/etc/nixos`
2. Reboot


Optional: Add DNS records
-------------------------

Add `A` / `AAAA` records to [the ops repo][] and apply the change via
[Concourse][].

[the ops repo]: https://github.com/barrucadu/ops
[Concourse]: https://cd.barrucadu.dev/


Optional: Configure alerting
----------------------------

All hosts have [Alertmanager][] installed and enabled.  To actually publish
alerts, create a secret for the environment file with credentials for the
`host-notifications` SNS topic:

```text
AWS_ACCESS_KEY="..."
AWS_SECRET_ACCESS_KEY="..."
```

Then configure the environment file:

```nix
services.prometheus.alertmanager.environmentFile = config.sops.secrets."services/alertmanager/env".path;
sops.secrets."services/alertmanager/env" = { };
```

[Alertmanager]: https://prometheus.io/docs/alerting/latest/alertmanager/


Optional: Configure backups
---------------------------

All hosts which run any sort of service with data I care about should take
automatic backups.

Firstly, add the backup credentials to the secrets:

```bash
nix run .#secrets
```

Then enable backups in the host configuration:

```nix
nixfiles.restic-backups.enable = true;
nixfiles.restic-backups.environmentFile = config.sops.secrets."nixfiles/restic-backups/env".path;
sops.secrets."nixfiles/restic-backups/env" = { };
```

Most services define their own backup scripts.  For any other needs, write a
custom backup job:

```nix
nixfiles.restic-backups.backups.<name> = { ... };
```


Optional: Generate SSH key
--------------------------

Generate an ed25519 SSH key:

```bash
ssh-keygen -t ed25519
```

**If the host should be able to interact with GitHub:** add the public key to
the GitHub user configuration *as an SSH key*.

**If the host should be able to push commits to GitHub:** add
the public key to the GitHub user configuration *as a signing key*, and also add
it to [the allowed_signers
file](https://github.com/barrucadu/dotfiles/blob/master/dot_config/git/allowed_signers.tmpl).

**If the host should be able to connect to other machines:** add the public key
to `shared/default.nix`.


Optional: Configure Syncthing
-----------------------------

Use the Syncthing Web UI (`localhost:8384`) to get the machine's ID.  Add this
ID to any other machines which it should synchronise files with, through their
web UIs.

Then configure any shared folders.



================================================
FILE: docs/src/runbooks/upgrade-to-a-new-version-of-elasticsearch.md
================================================
Upgrade to a new version of elasticsearch
====================================


Change the default elasticsearch version for a module
-----------------------------------------------------

1. Individually upgrade all hosts to the new version, following the processes below.
2. Change the default value of the `elasticsearchTag` option for the module.
3. Remove the per-host `elasticsearchTag` options.


Upgrade to a new minor version
------------------------------

This is generally safe.  Just change the `elasticsearchTag` and rebuild the NixOS
configuration.


Upgrade to a new major version
------------------------------

In brief: take a backup, upgrade to the latest minor release of the current
major version, fix any application warnings, and then upgrade to the initial
release of the new major version.

Shell variables:

- `$VOLUME_DIR` - the directory on the host that the container's `/usr/share/elasticsearch/data` is bind-mounted to

1. Upgrade to the latest minor release of the current major version.

    1. Change the `elasticsearchTag` option in the host's NixOS configuration to the latest minor release of the current major version.

    2. Rebuild the NixOS configuration and check that the database and all of its dependent services come back up:

        ```bash
        sudo nixos-rebuild switch
        ```

2. Exercise the application, performing both reads and writes: check the elasticsearch log for deprecation warnings, and fix any issues in the application.

3. Stop the database.

4. Take a backup:

    ```bash
    sudo cp -a "$VOLUME_DIR" "${VOLUME_DIR}.bak"
    ```

5. Change the `elasticsearchTag` option in the host's NixOS configuration to the initial release of the new major version.

6. Rebuild the NixOS configuration and check that the database and all of its dependent services come back up:

    ```bash
    sudo nixos-rebuild switch
    ```

### Rollback

The old database files are still present at `${VOLUME_DIR}.bak`, so:

1. Stop all the relevant services, including the database container.

2. Restore the backup:

    ```bash
    sudo mv "$VOLUME_DIR" "${VOLUME_DIR}.aborted"
    sudo mv "${VOLUME_DIR}.bak" "$VOLUME_DIR"
    ```

3. If the `elasticsearchTag` has been updated in the NixOS configuration:

    1. Revert it to its previous version.
    2. Rebuild the NixOS configuration.

4. Restart all the relevant services.


================================================
FILE: docs/src/runbooks/upgrade-to-a-new-version-of-postgres.md
================================================
Upgrade to a new version of postgres
====================================


Change the default postgres version for a module
------------------------------------------------

1. Individually upgrade all hosts to the new version, following the processes below.
2. Change the default value of the `postgresTag` option for the module.
3. Remove the per-host `postgresTag` options.


Upgrade to a new minor version
------------------------------

This is generally safe.  Just change the `postgresTag` and rebuild the NixOS
configuration.


Upgrade to a new major version
------------------------------

In brief: take a backup, shut down the database, bring up the new one, and
restore the backup.  This does have some downtime, but is relatively risk free.

Shell variables:

- `$CONTAINER` - the database container name
- `$POSTGRES_DB` - the database name
- `$POSTGRES_USER` - the database user
- `$POSTGRES_PASSWORD` - the database password
- `$VOLUME_DIR` - the directory on the host that the container's `/var/lib/postgresql/data` is bind-mounted to
- `$TAG` - the new container tag to use

Replace `podman` with `docker` in the following commands if you're using that.

1. Stop all services which write to the database.

2. Dump the database:

    ```bash
    sudo podman exec -i "$CONTAINER" pg_dump -U "$POSTGRES_USER" --no-owner -Fc "$POSTGRES_DB" > "${CONTAINER}.dump"
    ```

3. Stop the database container:

    ```bash
    sudo systemctl stop "podman-$CONTAINER"
    ```

4. Back up the database volume:

    ```bash
    sudo mv "$VOLUME_DIR" "${VOLUME_DIR}.bak"
    ```

5. Create the new volume:

    ```bash
    sudo mkdir "$VOLUME_DIR"
    ```

6. Bring up a new database container with the dump bind-mounted into it:

    ```bash
    sudo podman run --rm --name="$CONTAINER" -v "$(pwd):/backup" -v "${VOLUME_DIR}:/var/lib/postgresql/data" -e "POSTGRES_DB=${POSTGRES_DB}" -e "POSTGRES_USER=${POSTGRES_USER}" -e "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}" --shm-size=1g "postgres:${TAG}"
    ```

7. In another shell, restore the dump:

    ```bash
    sudo podman exec "$CONTAINER" pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc -j4 --clean "/backup/${CONTAINER}.dump"
    ```

8. Ctrl-c the database container after the dump has restored successfully.

9. Change the `postgresTag` option in the host's NixOS configuration.

10. Rebuild the NixOS configuration and check that the database and all of its dependent services come back up:

    ```bash
    sudo nixos-rebuild switch
    ```

### Rollback

The old database files are still present at `${VOLUME_DIR}.bak`, so:

1. Stop all the relevant services, including the database container.

2. Restore the backup:

    ```bash
    sudo mv "$VOLUME_DIR" "${VOLUME_DIR}.aborted"
    sudo mv "${VOLUME_DIR}.bak" "$VOLUME_DIR"
    ```

3. If the `postgresTag` has been updated in the NixOS configuration:

    1. Revert it to its previous version.
    2. Rebuild the NixOS configuration.

4. Restart all the relevant services.


================================================
FILE: flake.nix
================================================
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-small";
    sops-nix = {
      url = "github:Mic92/sops-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    # my packages
    bookdb = {
      url = "github:barrucadu/bookdb";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.gitignore.follows = "gitignore";
      inputs.rust-overlay.follows = "rust-overlay";
    };
    bookmarks = {
      url = "github:barrucadu/bookmarks";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.gitignore.follows = "gitignore";
      inputs.rust-overlay.follows = "rust-overlay";
    };
    prometheus-awair-exporter = {
      url = "github:barrucadu/prometheus-awair-exporter";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.gitignore.follows = "gitignore";
    };
    resolved = {
      url = "github:barrucadu/resolved";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.gitignore.follows = "gitignore";
      inputs.rust-overlay.follows = "rust-overlay";
    };
    # dependencies
    gitignore = {
      url = "github:hercules-ci/gitignore.nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, sops-nix, ... }@flakeInputs:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
      };
    in
    {
      formatter.${system} = pkgs.nixpkgs-fmt;

      nixosConfigurations =
        let
          mkNixosConfiguration = name: extraModules: nixpkgs.lib.nixosSystem {
            inherit system;
            specialArgs = { inherit flakeInputs; };
            modules = [
              {
                networking.hostName = name;
                nixpkgs.overlays = [ (_: _: { nixfiles = self.packages.${system}; }) ];
                sops.defaultSopsFile = ./hosts + "/${name}" + /secrets.yaml;
              }
              ./shared
              (./hosts + "/${name}" + /configuration.nix)
              (./hosts + "/${name}" + /hardware.nix)
              sops-nix.nixosModules.sops
            ] ++ extraModules;
          };
        in
        {
          carcosa = mkNixosConfiguration "carcosa" [ "${nixpkgs}/nixos/modules/profiles/qemu-guest.nix" ];
          nyarlathotep = mkNixosConfiguration "nyarlathotep" [ "${nixpkgs}/nixos/modules/installer/scan/not-detected.nix" ];
          yuggoth = mkNixosConfiguration "yuggoth" [ "${nixpkgs}/nixos/modules/profiles/qemu-guest.nix" ];
        };

      packages.${system} =
        {
          bookdb = flakeInputs.bookdb.packages.${system}.default;
          bookmarks = flakeInputs.bookmarks.packages.${system}.default;
          prometheus-awair-exporter = flakeInputs.prometheus-awair-exporter.packages.${system}.default;
          resolved = flakeInputs.resolved.packages.${system}.default;
        };

      apps.${system} =
        let
          mkApp = name: script: {
            type = "app";
            program = toString (pkgs.writeShellScript "${name}.sh" script);
          };
        in
        {
          backups = mkApp "backups" ''
            PATH=${with pkgs; lib.makeBinPath [ restic sops nettools ]}

            ${pkgs.lib.fileContents ./scripts/backups.sh}
          '';

          fmt = mkApp "fmt" ''
            PATH=${with pkgs; lib.makeBinPath [ nix git python3Packages.black ]}

            ${pkgs.lib.fileContents ./scripts/fmt.sh}
          '';

          lint = mkApp "lint" ''
            # TODO: add nix-linter back when the package is no longer broken
            PATH=${with pkgs; lib.makeBinPath [ findutils shellcheck git gnugrep python3Packages.flake8 ]}

            ${pkgs.lib.fileContents ./scripts/lint.sh}
          '';

          secrets = mkApp "secrets" ''
            PATH=${with pkgs; lib.makeBinPath [ sops nettools vim ]}
            export EDITOR=vim

            ${pkgs.lib.fileContents ./scripts/secrets.sh}
          '';

          documentation =
            let
              eval = pkgs.lib.evalModules {
                modules = [
                  { config._module.args = { inherit pkgs; }; }
                  ./shared/options.nix
                  # modules
                  ./shared/acme/options.nix
                  ./shared/bookdb/options.nix
                  ./shared/bookmarks/options.nix
                  ./shared/erase-your-darlings/options.nix
                  ./shared/finder/options.nix
                  ./shared/forgejo/options.nix
                  ./shared/foundryvtt/options.nix
                  ./shared/minecraft/options.nix
                  ./shared/oci-containers/options.nix
                  ./shared/pleroma/options.nix
                  ./shared/resolved/options.nix
                  ./shared/restic-backups/options.nix
                  ./shared/torrents/options.nix
                  # host templates
                  ./shared/host-templates/website-mirror/options.nix
                ];
              };
              optionsDoc = pkgs.nixosOptionsDoc {
                options = eval.options;
              };
            in
            mkApp "documentation" ''
              PATH=${with pkgs; lib.makeBinPath [ coreutils gnused mdbook python3 ]}
              export NIXOS_OPTIONS_JSON="${optionsDoc.optionsJSON}/share/doc/nixos/options.json"

              ${pkgs.lib.fileContents ./scripts/documentation.sh}
            '';
        };
    };
}


================================================
FILE: hosts/carcosa/caddy/lainon-life/404.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>LAINON.LIFE IS DEAD</title>
    <meta charset="UTF-8">
    <meta name="theme-color" content="#1e1a1c">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <style type="text/css">
      body {
          background: url("/background.gif") center center / cover no-repeat fixed;
          color: white;
          font-family: monospace;
          font-size: 32px;
          padding: 1em;
          text-align: center;
      }

      h1 {
          color: #46C0AB;
          text-shadow: 0 0 5px #46C0AB;
          font-weight: normal;
          margin: 0;
          padding: 0;
      }

      h2 {
          color: #F0546A;
          text-shadow: 0 0 5px #F0546A;
          font-weight: normal;
          margin: 0;
          padding: 0;
      }
    </style>
  </head>
  <body>
    <h1>LAINON.LIFE IS DEAD</h1>
    <h2>Never trust a hard drive</h2>
    <audio autoplay loop>
      <source src="/duvet.ogg" type="audio/ogg" />
      <source src="/duvet.mp3" type="audio/mpeg" />
    </audio>
  </body>
</html>


================================================
FILE: hosts/carcosa/caddy/www-lookwhattheshoggothdraggedin-com.caddyfile
================================================
# Removed
error /files/privacy/analytics.png 410
error /glossary.html 410
error /privacy.html 410

# Redirected
redir /arden-vul-6-months-in-censored/session-screenshot.png /post/arden-vul-6-months-in-censored/session-screenshot.png permanent
redir /arden-vul-6-months-in/house-rules.pdf /post/arden-vul-6-months-in/house-rules.pdf permanent
redir /arden-vul-6-months-in/miro-full.jpg /post/arden-vul-6-months-in/miro-full.jpg permanent
redir /arden-vul-6-months-in/miro-level3.jpg /post/arden-vul-6-months-in/miro-level3.jpg permanent
redir /arden-vul-6-months-in/session-screenshot.png /post/arden-vul-6-months-in/session-screenshot.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/all.png /post/dice-rolls-in-call-of-cthulhu/all.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/all.thumb.png /post/dice-rolls-in-call-of-cthulhu/all.thumb.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/basic.png /post/dice-rolls-in-call-of-cthulhu/basic.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/bonus.png /post/dice-rolls-in-call-of-cthulhu/bonus.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/difficulties.png /post/dice-rolls-in-call-of-cthulhu/difficulties.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/opposed-bonus.png /post/dice-rolls-in-call-of-cthulhu/opposed-bonus.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/opposed-penalty.png /post/dice-rolls-in-call-of-cthulhu/opposed-penalty.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/opposed.png /post/dice-rolls-in-call-of-cthulhu/opposed.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/penalty.png /post/dice-rolls-in-call-of-cthulhu/penalty.png permanent
redir /files/dice-rolls-in-call-of-cthulhu/pushed.png /post/dice-rolls-in-call-of-cthulhu/pushed.png permanent
redir /files/dice-rolls-in-traveller/basic.png /post/dice-rolls-in-traveller/basic.png permanent
redir /files/dice-rolls-in-traveller/boon-bane.png /post/dice-rolls-in-traveller/boon-bane.png permanent
redir /files/dice-rolls-in-traveller/difficulties.png /post/dice-rolls-in-traveller/difficulties.png permanent
redir /files/dice-rolls-in-traveller/normalised-difficulties-trimmed.png /post/dice-rolls-in-traveller/normalised-difficulties-trimmed.png permanent
redir /files/dice-rolls-in-traveller/normalised-difficulties.png /post/dice-rolls-in-traveller/normalised-difficulties.png permanent
redir /files/dice-rolls-in-traveller/opposed-heat-chain-dm.png /post/dice-rolls-in-traveller/opposed-heat-chain-dm.png permanent
redir /files/dice-rolls-in-traveller/opposed-heat.png /post/dice-rolls-in-traveller/opposed-heat.png permanent
redir /files/dice-rolls-in-traveller/task-chain-dms.png /post/dice-rolls-in-traveller/task-chain-dms.png permanent
redir /files/dice-rolls-in-traveller/task-chain-heat-unbound.png /post/dice-rolls-in-traveller/task-chain-heat-unbound.png permanent
redir /files/dice-rolls-in-traveller/task-chain-heat.png /post/dice-rolls-in-traveller/task-chain-heat.png permanent
redir /files/dice-rolls-in-traveller/try-again.png /post/dice-rolls-in-traveller/try-again.png permanent
redir /files/first-impressions-troika/beef-steaks.jpg /post/first-impressions-troika/beef-steaks.jpg permanent
redir /files/first-impressions-troika/book-interior.jpg /post/first-impressions-troika/book-interior.jpg permanent
redir /files/first-impressions-troika/damage-table.jpg /post/first-impressions-troika/damage-table.jpg permanent
redir /files/how-to-learn-a-new-system/pulp-cthulhu.pdf /post/how-to-learn-a-new-system/pulp-cthulhu.pdf permanent
redir /files/how-to-learn-a-new-system/traveller.pdf /post/how-to-learn-a-new-system/traveller.pdf permanent
redir /files/knock-issue-1/art.jpg /post/knock-issue-1/art.jpg permanent
redir /files/knock-issue-1/leaving-kansas.jpg /post/knock-issue-1/leaving-kansas.jpg permanent
redir /files/knock-issue-1/spine.jpg /post/knock-issue-1/spine.jpg permanent
redir /files/troika-the-manse-of-mazirian/manse.png /post/troika-the-manse-of-mazirian/manse.png permanent
redir /ironsworn/flowchart.png /post/ironsworn/flowchart.png permanent
redir /ironsworn/move.png /post/ironsworn/move.png permanent
redir /ironsworn/progress.png /post/ironsworn/progress.png permanent
redir /ironsworn/sabine-4.1.png /post/ironsworn/sabine-4.1.png permanent
redir /ironsworn/sabine-4.2.png /post/ironsworn/sabine-4.2.png permanent
redir /ironsworn/sabine-4.3.png /post/ironsworn/sabine-4.3.png permanent
redir /ironsworn/sabine-4.4.png /post/ironsworn/sabine-4.4.png permanent
redir /ironsworn/sabine-4.5.png /post/ironsworn/sabine-4.5.png permanent
redir /ironsworn/sabine.png /post/ironsworn/sabine.png permanent
redir /izirions-enchiridion/book.jpg /post/izirions-enchiridion/book.jpg permanent
redir /izirions-enchiridion/boots-of-cartography.jpg /post/izirions-enchiridion/boots-of-cartography.jpg permanent
redir /izirions-enchiridion/danger-level.jpg /post/izirions-enchiridion/danger-level.jpg permanent
redir /izirions-enchiridion/dice-rolls.jpg /post/izirions-enchiridion/dice-rolls.jpg permanent
redir /traveller-cheatsheets/basics-no-houserules.pdf /post/traveller-cheatsheets/basics-no-houserules.pdf permanent
redir /traveller-cheatsheets/basics.pdf /post/traveller-cheatsheets/basics.pdf permanent
redir /traveller-cheatsheets/piracy.pdf /post/traveller-cheatsheets/piracy.pdf permanent
redir /traveller-cheatsheets/spacecraft.pdf /post/traveller-cheatsheets/spacecraft.pdf permanent
redir /traveller-cheatsheets/uwp.pdf /post/traveller-cheatsheets/uwp.pdf permanent


================================================
FILE: hosts/carcosa/configuration.nix
================================================
# This is a VPS (hosted by Hetzner Cloud).
#
# It serves [barrucadu.co.uk][] and other services on it.  Websites are served
# with Caddy, with certs from Let's Encrypt.
#
# It's set up in "erase your darlings" style, so most of the filesystem is wiped
# on boot and restored from the configuration, to ensure there's no accidentally
# unmanaged configuration or state hanging around.  However, it doesn't reboot
# automatically, because I also use this server for a persistent IRC connection.
#
# **Alerting:** enabled (standard only)
#
# **Backups:** enabled (standard + extras)
#
# **Public hostname:** `carcosa.barrucadu.co.uk`
#
# **Role:** server
#
# [barrucadu.co.uk]: https://www.barrucadu.co.uk/
{ config, lib, pkgs, ... }:

with lib;
let
  httpdir = "${toString config.nixfiles.eraseYourDarlings.persistDir}/srv/http";
in
{
  ###############################################################################
  ## General
  ###############################################################################

  networking.hostId = "f62895cc";
  boot.supportedFilesystems = { zfs = true; };

  # Bootloader
  boot.loader.grub.enable = true;
  boot.loader.grub.device = "/dev/sda";

  # Networking
  networking.firewall.allowedTCPPorts = [ 80 443 ];

  networking.interfaces.enp1s0 = {
    ipv6.addresses = [{ address = "2a01:4f8:c0c:bfc1::"; prefixLength = 64; }];
  };
  networking.defaultGateway6 = { address = "fe80::1"; interface = "enp1s0"; };

  nixfiles.firewall.ipBlocklistFile = config.sops.secrets."nixfiles/firewall/ip_blocklist".path;
  sops.secrets."nixfiles/firewall/ip_blocklist" = { };

  # No automatic reboots (for irssi)
  system.autoUpgrade.allowReboot = mkForce false;

  # Wipe / on boot
  nixfiles.eraseYourDarlings.enable = true;
  nixfiles.eraseYourDarlings.machineId = "64b1b10f3bef4616a7faf5edf1ef3ca5";
  nixfiles.eraseYourDarlings.barrucaduPasswordFile = config.sops.secrets."users/barrucadu".path;
  sops.secrets."users/barrucadu".neededForUsers = true;

  # Monitoring
  services.prometheus.alertmanager.environmentFile = config.sops.secrets."services/alertmanager/env".path;
  sops.secrets."services/alertmanager/env" = { };


  ###############################################################################
  ## Backups
  ###############################################################################

  nixfiles.restic-backups.enable = true;
  nixfiles.restic-backups.environmentFile = config.sops.secrets."nixfiles/restic-backups/env".path;
  nixfiles.restic-backups.checkRepositoryAt = "Wed, 12:00";
  nixfiles.restic-backups.backups.github = {
    # TODO: this will break when I have >100 github repos
    # TODO: use a backup-specific SSH key?
    prepareCommand = ''
      ${pkgs.coreutils}/bin/mkdir repositories
      cd repositories

      ${pkgs.curl}/bin/curl -u "barrucadu:''${GITHUB_TOKEN}" 'https://api.github.com/user/repos?type=owner&per_page=100' 2>/dev/null | \
        ${pkgs.jq}/bin/jq -r '.[].ssh_url' | \
        while read url; do
          env GIT_SSH_COMMAND="${pkgs.openssh}/bin/ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /home/barrucadu/.ssh/id_ed25519" \
            ${pkgs.git}/bin/git clone --bare "$url"
        done
    '';
    paths = [
      "repositories"
    ];
  };
  nixfiles.restic-backups.backups.syncthing = {
    paths = [
      "/home/barrucadu/s"
    ];
  };
  sops.secrets."nixfiles/restic-backups/env" = { };


  ###############################################################################
  ## Website Mirror
  ###############################################################################

  nixfiles.hostTemplates.websiteMirror = {
    enable = true;
    acmeEnvironmentFile = config.sops.secrets."services/acme/env".path;
  };
  sops.secrets."services/acme/env" = { };


  ###############################################################################
  ## Services
  ###############################################################################

  # WWW - there are more websites, see website-mirror
  services.caddy.enable = true;
  services.caddy.extraConfig = ''
    (common_config) {
      encode gzip

      header Permissions-Policy "interest-cohort=()"
      header Referrer-Policy "strict-origin-when-cross-origin"
      header Strict-Transport-Security "max-age=31536000; includeSubDomains"
      header X-Content-Type-Options "nosniff"
      header X-Frame-Options "SAMEORIGIN"

      header -Server
    }
  '';

  services.caddy.virtualHosts."foundry.barrucadu.co.uk".extraConfig = ''
    import common_config
    reverse_proxy http://localhost:${toString config.nixfiles.foundryvtt.port}
  '';

  services.caddy.virtualHosts."misc.barrucadu.co.uk".extraConfig = ''
    import common_config
    basicauth /_site/* {
      import ${config.sops.secrets."services/caddy/fragments/misc_site".path}
    }

    @subdirectory path_regexp ^/(7day|14day|28day|forever)/[a-z0-9]

    root * ${httpdir}/barrucadu.co.uk/misc
    file_server @subdirectory browse
    file_server
  '';
  sops.secrets."services/caddy/fragments/misc_site".owner = config.users.users.caddy.name;

  services.caddy.virtualHosts."carcosa.barrucadu.co.uk".extraConfig = ''
    import common_config
    redir https://www.barrucadu.co.uk
  '';

  services.caddy.virtualHosts."grafana.carcosa.barrucadu.co.uk".extraConfig = ''
    import common_config
    reverse_proxy http://localhost:${toString config.services.grafana.settings.server.http_port}
  '';

  services.caddy.virtualHosts."prometheus.carcosa.barrucadu.co.uk".extraConfig = ''
    import common_config
    reverse_proxy http://localhost:${toString config.services.prometheus.port}
  '';

  services.caddy.virtualHosts."git.barrucadu.dev".extraConfig = ''
    import common_config
    reverse_proxy http://127.0.0.1:${toString config.nixfiles.forgejo.port}
  '';

  services.caddy.virtualHosts."registry.barrucadu.dev".extraConfig = ''
    import common_config
    basicauth /v2/* {
      import ${config.sops.secrets."services/caddy/fragments/registry".path}
    }
    header /v2/* Docker-Distribution-Api-Version "registry/2.0"
    reverse_proxy /v2/* http://127.0.0.1:${toString config.services.dockerRegistry.port}
  '';
  sops.secrets."services/caddy/fragments/registry".owner = config.users.users.caddy.name;

  services.caddy.virtualHosts."lainon.life".extraConfig = ''
    import common_config

    root * ${./caddy/lainon-life}
    file_server

    handle_errors {
      @404 {
        expression {http.error.status_code} == 404
      }
      rewrite @404 /404.html
      file_server
    }
  '';

  services.caddy.virtualHosts."social.lainon.life".extraConfig = ''
    import common_config
    reverse_proxy http://127.0.0.1:${toString config.nixfiles.pleroma.port}
  '';

  services.caddy.virtualHosts."www.lainon.life".extraConfig = ''
    import common_config
    redir https://lainon.life{uri}
  '';

  services.caddy.virtualHosts."lookwhattheshoggothdraggedin.com".extraConfig = ''
    import common_config
    redir https://www.lookwhattheshoggothdraggedin.com{uri}
  '';

  services.caddy.virtualHosts."www.lookwhattheshoggothdraggedin.com".extraConfig = ''
    import common_config

    header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' 'unsafe-inline' data:"

    header /files/*         Cache-Control "public, immutable, max-age=604800"
    header /fonts/*         Cache-Control "public, immutable, max-age=31536000"
    header /logo.png        Cache-Control "public, immutable, max-age=604800"
    header /*.css           Cache-Control "public, immutable, max-age=31536000"
    header /twitter-cards/* Cache-Control "public, immutable, max-age=604800"

    root * ${httpdir}/lookwhattheshoggothdraggedin.com/www
    file_server

    handle_errors {
      @404 {
        expression {http.error.status_code} == 404
      }
      @410 {
        expression {http.error.status_code} == 410
      }
      rewrite @404 /404.html
      rewrite @410 /404.html
      file_server
    }

    ${fileContents ./caddy/www-lookwhattheshoggothdraggedin-com.caddyfile}
  '';

  services.caddy.virtualHosts."uzbl.org".extraConfig = ''
    import common_config
    redir https://www.uzbl.org{uri}
  '';

  services.caddy.virtualHosts."www.uzbl.org".extraConfig = ''
    import common_config

    rewrite /archives.php    /index.php
    rewrite /faq.php         /index.php
    rewrite /readme.php      /index.php
    rewrite /keybindings.php /index.php
    rewrite /get.php         /index.php
    rewrite /community.php   /index.php
    rewrite /contribute.php  /index.php
    rewrite /commits.php     /index.php
    rewrite /news.php        /index.php
    rewrite /doesitwork/     /index.php
    rewrite /fosdem2010/     /index.php

    redir /doesitwork /doesitwork/
    redir /fosdem2020 /fosdem2020/

    root * ${httpdir}/uzbl.org/www

    php_fastcgi unix//run/phpfpm/caddy.sock
    php_fastcgi /atom.xml unix//run/phpfpm/caddy.sock {
      split .xml
    }

    file_server
  '';

  services.phpfpm.pools.caddy = {
    user = "caddy";
    group = "caddy";
    settings = {
      "listen" = "/run/phpfpm/caddy.sock";
      "listen.owner" = "caddy";
      "listen.group" = "caddy";
      "pm" = "dynamic";
      "pm.max_children" = "5";
      "pm.start_servers" = "2";
      "pm.min_spare_servers" = "1";
      "pm.max_spare_servers" = "3";
      "security.limit_extensions" = ".php .xml";
    };
  };

  systemd.tmpfiles.rules = [
    "d ${httpdir}/barrucadu.co.uk/misc/_site 0755 barrucadu users  1d"
    "d ${httpdir}/barrucadu.co.uk/misc/7day  0755 barrucadu users  7d"
    "d ${httpdir}/barrucadu.co.uk/misc/14day 0755 barrucadu users 14d"
    "d ${httpdir}/barrucadu.co.uk/misc/28day 0755 barrucadu users 28d"
  ];

  # Docker registry
  services.dockerRegistry.enable = true;

  # Forgejo
  nixfiles.forgejo.enable = true;
  nixfiles.forgejo.domain = "git.barrucadu.dev";
  nixfiles.forgejo.adminUserPasswordPath = config.sops.secrets."nixfiles/forgejo/admin_password".path;
  nixfiles.forgejo.runnerTokenPath = config.sops.secrets."nixfiles/forgejo/runner_token".path;
  sops.secrets."nixfiles/forgejo/admin_password" = { owner = "forgejo"; };
  sops.secrets."nixfiles/forgejo/runner_token" = { };

  # minecraft
  nixfiles.minecraft.enable = true;
  nixfiles.minecraft.servers.tea = {
    autoStart = false;
    port = 25565;
    jar = "fabric-server-launch.jar";
  };

  # Foundry VTT
  nixfiles.foundryvtt.enable = true;

  # social.lainon.life
  nixfiles.pleroma.enable = true;
  nixfiles.pleroma.domain = "social.lainon.life";
  nixfiles.pleroma.faviconPath = ./pleroma-favicon.png;
  nixfiles.pleroma.secretsFile = config.sops.secrets."nixfiles/pleroma/exc".path;
  nixfiles.pleroma.allowRegistration = true;
  sops.secrets."nixfiles/pleroma/exc".owner = config.users.users.pleroma.name;


  ###############################################################################
  ## Remote Builds
  ###############################################################################

  users.users.nix-remote-builder = {
    uid = 983;
    home = "/var/lib/nix-remote-builder";
    createHome = true;
    isSystemUser = true;
    shell = pkgs.bashInteractive;
    group = "nogroup";
    openssh.authorizedKeys.keys =
      [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHFzMpx7QNSAb5tCbkzMRIG62PvBZysflwwCKchFDHtY nix@yuggoth" ];
  };
  nix.settings.trusted-users = [ config.users.users.nix-remote-builder.name ];


  ###############################################################################
  ## Miscellaneous
  ###############################################################################

  # Metrics
  services.grafana.settings = {
    server.root_url = "https://grafana.carcosa.barrucadu.co.uk";
    security.admin_password = "$__file{${config.sops.secrets."services/grafana/admin_password".path}}";
    security.secret_key = "$__file{${config.sops.secrets."services/grafana/secret_key".path}}";
  };
  sops.secrets."services/grafana/admin_password".owner = config.users.users.grafana.name;
  sops.secrets."services/grafana/secret_key".owner = config.users.users.grafana.name;

  services.prometheus.webExternalUrl = "https://prometheus.carcosa.barrucadu.co.uk";

  # Extra packages
  users.users.barrucadu.packages = with pkgs; [
    irssi
    perl
  ];
}


================================================
FILE: hosts/carcosa/hardware.nix
================================================
{ ... }:

{
  boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sd_mod" "sr_mod" ];
  boot.initrd.kernelModules = [ ];
  boot.kernelModules = [ ];
  boot.extraModulePackages = [ ];

  fileSystems."/" =
    {
      device = "local/volatile/root";
      fsType = "zfs";
    };

  fileSystems."/home" =
    {
      device = "local/persistent/home";
      fsType = "zfs";
    };

  fileSystems."/nix" =
    {
      device = "local/persistent/nix";
      fsType = "zfs";
    };

  fileSystems."/persist" =
    {
      device = "local/persistent/persist";
      fsType = "zfs";
      neededForBoot = true;
    };

  fileSystems."/var/log" =
    {
      device = "local/persistent/var-log";
      fsType = "zfs";
    };

  fileSystems."/var/lib/containers" =
    {
      device = "/dev/disk/by-uuid/bbc94c9d-9e32-435b-9fe7-1290acb96a40";
      fsType = "ext4";
    };

  fileSystems."/boot" =
    {
      device = "/dev/disk/by-uuid/C83B-AA71";
      fsType = "vfat";
    };

  swapDevices = [ ];

}


================================================
FILE: hosts/carcosa/secrets.yaml
================================================
users:
    barrucadu: ENC[AES256_GCM,data:5mAoxmMEn4zOhwDHS4cal7b0fPZb7SshS3QmfrBgLKQMJfExiRdnWvwnqTvLY/ObRTYqeWrcik+GVyeFF4gkMm8FxcR7U5ExIXBbtg5uDQgcvQYNlWEfJWEjVYrfKCoIxCAhUDf+2oNr,iv:YEpqdGz/DNdjxNmIrVgahEoH7BNYnqBEHV6L7UrV2aI=,tag:4teSWBW+SJ06KCoTMwjN6g==,type:str]
nixfiles:
    forgejo:
        admin_password: ENC[AES256_GCM,data:4hgVpxFod0m+sizMAxWApHMCJLCra867Y9MHtBXW3+Z4,iv:dUcyD2xP4L38ldiHYMTCEuE7AUXJcVEgxszdhFC1prU=,tag:3ZNooGvOU2P4r0LbgYybTg==,type:str]
        runner_token: ENC[AES256_GCM,data:PL4c2RFaVGITrfBNO/rtf5rtCdFRyd7P2xf9eR/fRq4bzyDXpS0jE7r4jDQxZ6Q=,iv:qf6muCqZLWfyJCLcyevBFMSueyPDhYnVWYO5y5tOPZE=,tag:Fuo5PXS4SRqAm3oVoto74Q==,type:str]
    firewall:
        ip_blocklist: ENC[AES256_GCM,data:WgDvhHX/CTbbBRVJVr5v4nLQP5sxI8A5KomrSoXble7eUlAZeXqnhw4UT/EugIKaudZzVWovHe30G1U9W86X1/dgAj1S8hD0mA0TH6Qul3RfmQdqnZIdJXfDBLH1mF4OXaox6A==,iv:PVKlgOErmZGAC2tf5mW7d3S7vvMRclrpr+aDd/xJ1WI=,tag:EeXzbX9BELSCeBcxifbHPQ==,type:str]
    pleroma:
        exc: ENC[AES256_GCM,data:ZyA/hBlQs8S56oy7J1f9u36nYBFwWH4Ehq5VbjYDAxcThgP5x09qqt0G8URuLSbBZYuWOOrCjSF1gkt9oLKBPYw2bKxf0yg69GL8xkBtZuJU9NNLXcVswG8HZFWG6Rwa6F65GIHQWdZLwkA8ef5RcYD77FFV3YG4qCIxyH5efyVd0xTO7ukuapGC23mRPd1auUNHlTt0/mac8a+wjBmG175j2MFnzDj7e4Z2ynqgSVrkM/DKm4/xiED5J1vNpPQAkNWouzELo70x7XsLk8/WIVXtsLI24OEo+BE+9/pf9T+RObftUDMnSejhLFadKTqa7QHh6RJgV1lkJL+ZvDm5XY962BzJrMw/3N+h30qD+QCmkuyLqIbTAP7nFeJ7iumU1Bgv6ojLPtrxLX4TE/lP9Mv9JG1/w+4oMAeSv5t/uFw4pI4P07c5cGRwE9VHIT57roJ6beKRD/3p7FJxeEFOu5/O9EoxHTCjXWXMrdEM6bYfMMQZYBM7p8Ve0bE=,iv:4njRxb8LiKdW5YgQEP2Esvh8/yKHpiCxTiL/n6b+c5M=,tag:lPqLddPhY0r0rnuxtwvwIg==,type:str]
    restic-backups:
        env: ENC[AES256_GCM,data:Uls7HrNxFyoMZf3u4zNSk/F3XIYE1mQotcCvnNggvbOqA9RVK9FQpIfqJms8WtKZl6ol15rHW+au73QldJ9TE2Km7EJmCSa87AmRQ1Azt0lrJtn+v9l9rFqnu82WSD2awv8geB82OPQ0/w2x9W4qHPXw3teYx9p9Kiz/C1NVw0Z9TlsChKHsVpR6FVdyB8FY8zZG0Z1iuzDHMr6q9QsI8pMn5DkCruYhcg8R6GR73naWQb0nHxx3oFfNR3KtPHQn8diJZsv0+Hh2IWbHyPRlJOs+WYJtVc7LbqjB1jPrTozqteijclVSHAoB1RTQ7gVe9TiYdQIhOJswo89xH4l7U/D9wYH7tGRlrrKz8F8iUQR02G7gADftmwI9kekhCjGDgSudYrRJ//UCJKTn6bSsEpW0Eg9knozFCUOhc5RREmX7JyKA,iv:BqaFmxO+HRZzQchypReabxer0XwHW9KSsA1lfhWmdIM=,tag:3rJjQm+kLRMscygvkLHTcw==,type:str]
services:
    acme:
        env: ENC[AES256_GCM,data:+kgHlD5tlcZbNiqeLnF0Ow82UqmgiGmSAoBZ3o6zqdKTfDIVSTS/0ChYopbngi1/HW5Imx6uikfXnLUx7YMgXkcxOdEWzopVMGBf3NWIlBqmBhvxVhpoYI8E+rIN+f4Sf5OKNq1NUluZ5XKzg7qWRW66dWEaKzjgD81v8RUp94C0dKY=,iv:nZAQBmXO7VRe/RW5Vn8ha0B3kx/6aEBUTSUacuyFS1o=,tag:csCiEGlGc0jPy/AHPm8z9g==,type:str]
    alertmanager:
        env: ENC[AES256_GCM,data:0XJmdUlSzYnfKrovd/MpjXpSmazltAc8mgalp8U1nv30ek7OIZJS2ehih26UiwMf9pa/yx4lfoWQlU4Zx4eF7kep/rcr74sznFGRAYxvfVs1DTaWOuvSUqs1ZQOk8xTv5MAxBgP/,iv:F5aHKGSAJO762dXdwhWSCMhanLE/Z/Kbt2rqy+BFSLk=,tag:+/cZRlSzpGjgNQmr8f7dlQ==,type:str]
    caddy:
        fragments:
            misc_site: ENC[AES256_GCM,data:y8VPYPzlrdFdODmbtZ8o7aMKm+hZLpc0kkuXatth29RfsN1XO1sfgIj4bPmDpUUf5yQQLsKK4F/cULnhMU0TOhOMKHzTduh59b6pxmJgbufETX1Hghnq+zLx,iv:QgbGVm3Vj1PtU6/W31xDUoxtPSgS8ot8IBqOxfeaJQk=,tag:kLfPVxrLdxtXwfIHC2y8Rg==,type:str]
            registry: ENC[AES256_GCM,data:TMXUTbMzpP8QHW4aAyW2auGQYFhV3cHjfne0AnQ+CFI41A/3SuelQumQR17HOr9Z/Hs5LQQ9QssqQlhwOEMCIuTu2x81AUuEdineJc1T/xq1nhCrWA8olVI=,iv:dov05Z5jkB44lEQI0DPieDjT9thfHJE1vTiIYZbA79M=,tag:fIBo46vac8Ciu/dO1Bl18A==,type:str]
    grafana:
        admin_password: ENC[AES256_GCM,data:nGzl+HjDKz/ushife6YLCXtzTpIlHrbqPFWxsV3QxhG5FwXj5+vPNo8Pyg==,iv:j+ZS5PXFqV7t/mGK5lRQv0pG7+sh2BHy3N7MQ0YgWTY=,tag:F9r2iiMxG4xnFIz0vnP88w==,type:str]
        secret_key: ENC[AES256_GCM,data:KeWJ9AVXCYjFgaFWbV7rqGRKOLT+lqzLP/p3Dajnemk=,iv:rfcm8PordRN0bCQmIQ7flZ/ShaCG1X30wUtHDjLD79Q=,tag:C+Ze2RmQ9yfR5/6K6oMqlA==,type:str]
sops:
    age:
        - recipient: age1sdnp5uxhdtujc78penv2gntnenzcfju7est4hslz6eqgfk26u9nskkk634
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3M1Fvdlo4RjdKcCtQZ20v
            ODA3aklyaHNQdWtiQVRZejFJL3A1QjFvY0cwCnhuVlUvRzdZQzhaQlFsVDg1MWhN
            akhMT0RQV20wbkhCOTk1eVR3TThhYUkKLS0tIE5ncVZ4ZXh3Y3hWWHdwTHpPQVd5
            cTJ6cWF3WUxKdG95R1I2QmNxUlMxblEKZWY5u1NCQfLW4NipX9f5txgZnopWIykD
            kFfbaTn0cv9lKSJJKcY3t5WPASc1/Hd52ANzpa4qUBMoirLAmrfkOw==
            -----END AGE ENCRYPTED FILE-----
        - recipient: age1ty4vs59695vuavnvgdftguyq2aau29nv75y4tqrr6ag8z26vfc5sc5rc4n
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6OS9iYVJkdXlvR29UU20y
            ZFpqK0Rmd1g5bE8zOEtsTHlNOFRjS3p1TFVJClBBREw0V0xNd2ZwVENheUVXR1FR
            a1ovNlZFQys2a1pNOWRaR0xrZzFpazQKLS0tIHF1cUlkQ0JOeHM1ekZ5NWV3V2NE
            dTQwclBRU3JmQ2hBOFVlaVIxZHN4cVEKvVR1bSH4yo2Td8YGNI2hmflT0M3hcYu8
            qyYoWd5MFP9VBYKxrF8W2naQSY6Ax4IiVuNbKDDLFooTC93V+oxNDA==
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2026-05-06T20:08:42Z"
    mac: ENC[AES256_GCM,data:XGh1OnFfCcIJI8omyFwj/oQEPxFKlDuQBwQCBJBOK5LBxt6hVmYrg6va4qvMwORpkwSc9aPnPLWP3j+xGK7jM52UL025ZJavQWQIYL+9PH+Bi1MHwW+XjXna0gkStG9ZGe0zng7sFl9A4BvAg0pDwo45t6DxlS0+S4YE6lW+zLA=,iv:Jpd/a+jXG5mljq6CU7auWeJ78Q9NMc7L3TNpYQYPSkk=,tag:wWoLzAFKavZw1d4BkVk3Hg==,type:str]
    unencrypted_suffix: _unencrypted
    version: 3.12.1


================================================
FILE: hosts/nyarlathotep/configuration.nix
================================================
# This is my home server.
#
# It runs writable instances of the bookdb and bookmarks services, which have
# any updates copied across to carcosa hourly; it acts as a NAS; and it runs a
# few utility services.
#
# Like carcosa, this host is set up in "erase your darlings" style but, unlike
# carcosa, it automatically reboots to install updates: so that takes effect
# significantly more frequently.
#
# **Alerting:** enabled (standard only)
#
# **Backups:** enabled (standard + extras)
#
# **Public hostname:** n/a
#
# **Role:** server
{ config, pkgs, lib, ... }:

# Bring names from 'lib' into scope.
with lib;
let
  sharesPublic = [ "anime" "misc" "music" "movies" "tv" "torrents" ];
  sharesPrivate = [ "private" ];

  prometheusAwairExporterPort = 9517;

  httpdir = "${toString config.nixfiles.eraseYourDarlings.persistDir}/srv/http";
in
{
  ###############################################################################
  ## General
  ###############################################################################

  networking.hostId = "4a592971"; # ZFS needs one of these
  boot.supportedFilesystems = { zfs = true; };

  # Bootloader
  boot.loader.systemd-boot.enable = true;

  # Enable memtest
  boot.loader.systemd-boot.memtest86.enable = true;

  # Firewall
  networking.firewall.allowedTCPPorts = [
    80
    8888
    32400 # Plex
  ];

  # Wipe / on boot
  nixfiles.eraseYourDarlings.enable = true;
  nixfiles.eraseYourDarlings.machineId = "0f7ae3bda2a9428ab77a0adddc4c8cff";
  nixfiles.eraseYourDarlings.barrucaduPasswordFile = config.sops.secrets."users/barrucadu".path;
  sops.secrets."users/barrucadu".neededForUsers = true;

  # Set up a bridge network so that VMs can connect to the LAN
  #
  # `enp8s0` is the physical ethernet interface, but I am slaving that to the
  # `br0` bridge - so it's the bridge's MAC address that gets presented to the
  # physical network.
  #
  # To avoid having to reconfigure static IP assignments in my router if I
  # switch between bridged and non-bridged networking, set up the MAC addresses
  # such that:
  #
  # - `br0` has the MAC address of the physical ethernet card
  # - `enp8s0` has a new random MAC address (https://serverfault.com/a/631119)
  #
  # So if I delete this block, the MAC address the router sees is unchanged, and
  # so the static IP assignment is unaffected.
  networking.useDHCP = false;
  networking.interfaces.br0 = {
    useDHCP = true;
    macAddress = "a0:36:bc:bb:65:8d";
  };
  networking.interfaces.enp8s0 = {
    macAddress = "92:0b:e6:21:86:99";
    useDHCP = true;
  };
  networking.bridges.br0.interfaces = [ "enp8s0" ];

  virtualisation.libvirtd.enable = true;
  virtualisation.libvirtd.allowedBridges = [ "br0" ];

  ###############################################################################
  ## Backups
  ###############################################################################

  nixfiles.restic-backups.enable = true;
  nixfiles.restic-backups.environmentFile = config.sops.secrets."nixfiles/restic-backups/env".path;
  nixfiles.restic-backups.backups.torrents = {
    prepareCommand = ''
      ${pkgs.python3}/bin/python3 ${./jobs/restic-prepare--hardlink-torrent-files.py} > hardlink-torrent-files.sh
    '';
    paths = [
      "hardlink-torrent-files.sh"
      "/mnt/nas/torrents/watch"
    ];
  };
  nixfiles.restic-backups.backups.youtube = {
    prepareCommand = ''
      ${pkgs.python3}/bin/python3 ${./jobs/restic-prepare--fetch-youtube.py} > fetch-youtube.sh
    '';
    paths = [
      "fetch-youtube.sh"
    ];
  };
  sops.secrets."nixfiles/restic-backups/env" = { };


  ###############################################################################
  ## DNS
  ###############################################################################

  nixfiles.resolved.enable = true;
  nixfiles.resolved.address = "10.0.0.3:53";
  nixfiles.resolved.cacheSize = 1000000;
  nixfiles.resolved.hostsDirs = [ "/etc/dns/hosts" ];
  nixfiles.resolved.zonesDirs = [ "/etc/dns/zones" ];

  environment.etc."dns/hosts/stevenblack".source =
    let commit = "14b698abcd97446bae349292aacc9ecb4feb2db5";
    in builtins.fetchurl {
      url = "https://raw.githubusercontent.com/StevenBlack/hosts/${commit}/hosts";
      sha256 = "1hwyn1w1c7brzigp7fqpsgh107pzvsrahilq6n90jw7yzvi704gl";
    };

  environment.etc."dns/zones/10.in-addr.arpa".text = ''
    $ORIGIN 10.in-addr.arpa.

    @ IN SOA . . 3 3600 3600 3600 3600

    1.0.0    IN PTR router.lan.
    3.0.0    IN PTR nyarlathotep.lan.

    117.20.0 IN PTR living-room.awair.lan.
    130.20.0 IN PTR guest-bedroom.awair.lan.
    187.20.0 IN PTR bedroom.awair.lan.
    194.20.0 IN PTR office.awair.lan.
  '';

  environment.etc."dns/zones/lan".text = ''
    $ORIGIN lan.

    @ 300 IN SOA @ @ 6 300 300 300 300

    router              300 IN A     10.0.0.1

    nyarlathotep        300 IN A     10.0.0.3
    *.nyarlathotep      300 IN CNAME nyarlathotep

    help                300 IN CNAME nyarlathotep
    *.help              300 IN CNAME help

    nas                 300 IN CNAME nyarlathotep

    bedroom.awair       300 IN A     10.0.20.187
    guest-bedroom.awair 300 IN A     10.0.20.130
    living-room.awair   300 IN A     10.0.20.117
    office.awair        300 IN A     10.0.20.194
  '';


  ###############################################################################
  ## Network storage
  ###############################################################################


  # Samba
  services.samba.enable = true;
  services.samba.openFirewall = true;
  services.samba.settings =
    let
      mkPublic = n: nameValuePair n { path = "/mnt/nas/${n}"; writable = "yes"; };
      mkPrivate = n: nameValuePair n { path = "/mnt/nas/${n}"; writable = "yes"; "valid users" = ["barrucadu"]; };
    in listToAttrs (map mkPublic sharesPublic ++ map mkPrivate sharesPrivate);

  # Guest user for Samba
  users.users.notbarrucadu = {
    uid = 1001;
    description = "Guest user";
    isNormalUser = true;
    group = "users";
    hashedPasswordFile = config.sops.secrets."users/notbarrucadu".path;
    shell = "/run/current-system/sw/bin/nologin";
  };
  sops.secrets."users/notbarrucadu".neededForUsers = true;


  ###############################################################################
  ## Reverse proxy
  ###############################################################################

  services.caddy.enable = true;
  services.caddy.extraConfig = ''
    (vlan_matchers) {
      @vlan1 remote_ip 10.0.0.0/24
      @not_vlan1 not remote_ip 10.0.0.0/24

      @vlan10 remote_ip 10.0.10.0/24
      @not_vlan10 not remote_ip 10.0.10.0/24

      @vlan20 remote_ip 10.0.20.0/24
      @not_vlan20 not remote_ip 10.0.20.0/24
    }

    (restrict_vlan) {
      import vlan_matchers
      redir @vlan20 http://help.lan 307
    }
  '';

  services.caddy.virtualHosts."nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    file_server {
      root ${httpdir}/nyarlathotep.lan
    }
  '';

  services.caddy.virtualHosts."alertmanager.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    reverse_proxy http://localhost:${toString config.services.prometheus.alertmanager.port}
  '';

  services.caddy.virtualHosts."bookdb.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    reverse_proxy http://localhost:${toString config.nixfiles.bookdb.port}
  '';

  services.caddy.virtualHosts."bookmarks.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    reverse_proxy http://localhost:${toString config.nixfiles.bookmarks.port}
  '';

  services.caddy.virtualHosts."flood.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    reverse_proxy http://localhost:${toString config.nixfiles.torrents.rpcPort}
  '';

  services.caddy.virtualHosts."finder.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    reverse_proxy http://localhost:${toString config.nixfiles.finder.port}
  '';

  services.caddy.virtualHosts."grafana.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    reverse_proxy http://localhost:${toString config.services.grafana.settings.server.http_port}
  '';

  services.caddy.virtualHosts."rpg-tools.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    file_server {
      root ${httpdir}/rpg-tools.nyarlathotep.lan
    }
  '';

  # don't restrict vlan as the port is open unrestricted anyway
  services.caddy.virtualHosts."plex.nyarlathotep.lan:80".extraConfig = ''
    encode gzip
    reverse_proxy http://localhost:32400
  '';

  services.caddy.virtualHosts."prometheus.nyarlathotep.lan:80".extraConfig = ''
    import restrict_vlan
    encode gzip
    reverse_proxy http://localhost:${toString config.services.prometheus.port}
  '';

  services.caddy.virtualHosts."help.lan:80".extraConfig = ''
    import vlan_matchers
    redir @vlan1 http://vlan1.help.lan 302
    redir @vlan10 http://vlan10.help.lan 302
    redir @vlan20 http://vlan20.help.lan 302
  '';

  services.caddy.virtualHosts."vlan1.help.lan:80".extraConfig = ''
    import vlan_matchers
    encode gzip
    redir @not_vlan1 http://help.lan 302
    file_server {
      root ${httpdir}/vlan1.help.lan
    }
  '';

  services.caddy.virtualHosts."vlan10.help.lan:80".extraConfig = ''
    import vlan_matchers
    encode gzip
    redir @not_vlan10 http://help.lan 302
    file_server {
      root ${httpdir}/vlan10.help.lan
    }
  '';

  services.caddy.virtualHosts."vlan20.help.lan:80".extraConfig = ''
    import vlan_matchers
    encode gzip
    redir @not_vlan20 http://help.lan 302
    file_server {
      root ${httpdir}/vlan20.help.lan
    }
  '';

  services.caddy.virtualHosts."*:80".extraConfig = ''
    respond * 421
  '';


  ###############################################################################
  ## bookdb - https://github.com/barrucadu/bookdb
  ###############################################################################

  nixfiles.bookdb.enable = true;


  ###############################################################################
  ## bookmarks - https://github.com/barrucadu/bookmarks
  ###############################################################################

  nixfiles.bookmarks.enable = true;


  ###############################################################################
  ## finder
  ###############################################################################

  nixfiles.finder.enable = true;
  nixfiles.finder.image = "localhost:${toString config.services.dockerRegistry.port}/finder:latest";
  nixfiles.finder.mangaDir = "/mnt/nas/private";


  ###############################################################################
  ## torrents
  ###############################################################################

  nixfiles.torrents.enable = true;
  nixfiles.torrents.downloadDir = "/mnt/nas/torrents/files";
  nixfiles.torrents.watchDir = "/mnt/nas/torrents/watch";
  nixfiles.torrents.user = "barrucadu";
  nixfiles.torrents.group = "users";


  ###############################################################################
  ## Network Media
  ###############################################################################

  services.plex.enable = true;
  services.plex.dataDir = "/persist/var/lib/plex";


  ###############################################################################
  # Monitoring & Dashboards
  ###############################################################################

  services.prometheus.alertmanager.environmentFile = config.sops.secrets."services/alertmanager/env".path;
  sops.secrets."services/alertmanager/env" = { };

  services.grafana = {
    settings.server.root_url = "http://grafana.nyarlathotep.lan";
    provision = {
      datasources.settings.datasources = [
        {
          name = "victoriametrics";
          url = "http://${config.services.victoriametrics.listenAddress}";
          type = "prometheus";
        }
      ];
      dashboards.settings.providers =
        let
          dashboard = folder: name: path: { inherit name folder; options.path = path; };
        in
        [
          (dashboard "My Dashboards" "finance.json" ./dashboards/finance.json)
          (dashboard "My Dashboards" "smart-home.json" ./dashboards/smart-home.json)
        ];
    };
  };

  services.prometheus.webExternalUrl = "http://prometheus.nyarlathotep.lan";
  services.prometheus.scrapeConfigs = [
    {
      job_name = "awair";
      static_configs = [{ targets = [ "localhost:${toString prometheusAwairExporterPort}" ]; }];
    }
  ];

  systemd.services.prometheus-awair-exporter =
    {
      description = "barrucadu/prometheus-awair-exporter metrics exporter";
      wantedBy = [ "multi-user.target" ];
      after = [ "network-online.target" ];
      wants = [ "network-online.target" ];
      serviceConfig = {
        ExecStart = concatStringsSep " " [
          "${pkgs.nixfiles.prometheus-awair-exporter}/bin/prometheus-awair-exporter"
          "--address 127.0.0.1:${toString prometheusAwairExporterPort}"
          "--sensor bedroom:10.0.20.187"
          "--sensor guest-bedroom:10.0.20.130"
          "--sensor living-room:10.0.20.117"
          "--sensor office:10.0.20.194"
        ];
        DynamicUser = "true";
        Restart = "on-failure";
      };
    };


  ###############################################################################
  ## Docker registry (currently just used on this machine)
  ###############################################################################

  services.dockerRegistry.enable = true;
  virtualisation.containers.registries.insecure = [ "localhost:${toString config.services.dockerRegistry.port}" ];


  ###############################################################################
  # Automatic music tagging
  ###############################################################################

  systemd.services.tag-podcasts = {
    enable = true;
    description = "Automatically tag new podcast files";
    wantedBy = [ "multi-user.target" ];
    path = with pkgs; [ ffmpeg inotify-tools id3v2 ];
    unitConfig.RequiresMountsFor = "/mnt/nas";
    serviceConfig = {
      WorkingDirectory = "/mnt/nas/music/Podcasts/";
      ExecStart = pkgs.writeShellScript "tag-podcasts.sh" (fileContents ./jobs/tag-podcasts.sh);
      User = "barrucadu";
      Group = "users";
      Restart = "always";
    };
  };

  systemd.paths.flac-and-tag-album = {
    enable = true;
    description = "Automatically flac and tag new albums";
    wantedBy = [ "multi-user.target" ];
    unitConfig.RequiresMountsFor = "/mnt/nas";
    pathConfig.PathExistsGlob = "/mnt/nas/music/to_convert/in/*";
  };
  systemd.services.flac-and-tag-album = {
    path = with pkgs; [ flac ];
    serviceConfig = {
      WorkingDirectory = "/mnt/nas/music/to_convert/in/";
      ExecStart = pkgs.writeShellScript "flac-and-tag-album.sh" (fileContents ./jobs/flac-and-tag-album.sh);
      User = "barrucadu";
      Group = "users";
    };
  };


  ###############################################################################
  # Finance dashboard & FX rate fetching
  ###############################################################################

  systemd.services.hledger-fetch-fx-rates = {
    description = "Download GBP exchange rates for commodities";
    startAt = "*-*-* 21:00:00";
    path = with pkgs; [ hledger ];
    serviceConfig = {
      ExecStart =
        let python = pkgs.python3.withPackages (ps: [ ps.requests ]);
        in "${python}/bin/python3 ${pkgs.writeText "hledger-fetch-fx-rates.py" (fileContents ./jobs/hledger-fetch-fx-rates.py)}";
      User = "barrucadu";
      Group = "users";
    };
    environment = {
      PRICE_FILE = "/home/barrucadu/s/ledger/prices";
    };
  };

  systemd.services.hledger-export-to-victoriametrics = {
    description = "Export personal finance data to VictoriaMetrics";
    startAt = "daily";
    path = with pkgs; [ hledger ];
    serviceConfig = {
      ExecStart =
        let python = pkgs.python3.withPackages (ps: [ ps.requests ]);
        in "${python}/bin/python3 ${pkgs.writeText "hledger-export-to-victoriametrics.py" (fileContents ./jobs/hledger-export-to-victoriametrics.py)}";
      User = "barrucadu";
      Group = "users";
    };
    environment = {
      LEDGER_FILE = "/home/barrucadu/s/ledger/combined.journal";
      VICTORIAMETRICS_URI = "http://${config.services.victoriametrics.listenAddress}";
    };
  };
  # also reload data after boot
  systemd.timers.hledger-export-to-victoriametrics.timerConfig.OnBootSec = "5m";

  services.victoriametrics = {
    enable = true;
    listenAddress = "127.0.0.1:8428";
    retentionPeriod = "10y";
  };


  ###############################################################################
  # Remote Sync
  ###############################################################################

  nixfiles.bookdb.remoteSync.send.enable = true;
  nixfiles.bookdb.remoteSync.send.sshKeyFile = config.sops.secrets."users/bookdb_remote_sync/ssh_private_key".path;
  nixfiles.bookdb.remoteSync.send.targets = [
    "carcosa.barrucadu.co.uk"
    "yuggoth.barrucadu.co.uk"
  ];

  sops.secrets."users/bookdb_remote_sync/ssh_private_key" = {
    owner = config.users.users.bookdb-remote-sync-send.name;
    key = "users/remote_sync/ssh_private_key";
  };

  nixfiles.bookmarks.remoteSync.send.enable = true;
  nixfiles.bookmarks.remoteSync.send.sshKeyFile = config.sops.secrets."users/bookmarks_remote_sync/ssh_private_key".path;
  nixfiles.bookmarks.remoteSync.send.targets = [
    "carcosa.barrucadu.co.uk"
    "yuggoth.barrucadu.co.uk"
  ];

  sops.secrets."users/bookmarks_remote_sync/ssh_private_key" = {
    owner = config.users.users.bookmarks-remote-sync-send.name;
    key = "users/remote_sync/ssh_private_key";
  };

  ###############################################################################
  # RSS-to-Mastodon
  ###############################################################################

  users.users.rss-to-mastodon = {
    uid = 991;
    home = "/persist/var/lib/rss-to-mastodon";
    createHome = true;
    isSystemUser = true;
    group = "nogroup";
  };

  systemd.services.rss-to-mastodon-kjp-hacksrus = {
    description = "Publish King James Programming to hacksrus.xyz";
    startAt = "hourly";
    serviceConfig = {
      ExecStart =
        let python = pkgs.python3.withPackages (ps: [ ps.beautifulsoup4 ps.docopt ps.feedparser ps.requests ]);
        in concatStringsSep " " [
          "${python}/bin/python3"
          (pkgs.writeText "rss-to-mastodon.py" (fileContents ./jobs/rss-to-mastodon.py))
          "--use-summary"
          "-d https://hacksrus.xyz/"
          "-f https://kingjamesprogramming.tumblr.com/rss"
          "-l /persist/var/lib/rss-to-mastodon/kjp-hacksrus.txt"
        ];
      User = "rss-to-mastodon";
      EnvironmentFile = config.sops.secrets."users/rss_to_mastodon/kjp_hacksrus_env".path;
    };
  };

  sops.secrets."users/rss_to_mastodon/kjp_hacksrus_env" = { };
}


================================================
FILE: hosts/nyarlathotep/dashboards/finance.json
================================================
{
  "annotations": {
    "list": [
      {
        "$$hashKey": "object:321",
        "builtIn": 1,
        "datasource": {
          "type": "datasource",
          "uid": "grafana"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0, 211, 255, 1)",
        "name": "Annotations & Alerts",
        "target": {
          "limit": 100,
          "matchAny": false,
          "tags": [],
          "type": "dashboard"
        },
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 0,
  "id": 4,
  "links": [],
  "panels": [
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "decimals": 1,
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "green",
                "value": 0
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 4,
        "x": 0,
        "y": 0
      },
      "id": 101,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Net Worth",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "decimals": 1,
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "green",
                "value": 0
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 4,
        "x": 4,
        "y": 0
      },
      "id": 61,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n)\r\n+ on(target_currency)\r\n(\r\n    sum(hledger_balance{account=\"liabilities\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"liabilities:mortgage\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n)",
          "hide": false,
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Net Worth (ex. property)",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "decimals": 1,
          "mappings": [],
          "max": 1,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 0.33
              },
              {
                "color": "green",
                "value": 0.5
              }
            ]
          },
          "unit": "percentunit"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 4,
        "x": 8,
        "y": 0
      },
      "id": 62,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "center",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "mean"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "value",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "# saved income\r\n(\r\n    sum(hledger_monthly_decrease{account=\"income\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_monthly_increase{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    # ignore pension contributions (assumes pensions only go up - include 'decrease' as well to handle January roll-over)\r\n    - on(target_currency)\r\n    (\r\n        sum(hledger_monthly_increase{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency) -\r\n        sum(hledger_monthly_decrease{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    )\r\n)\r\n\r\n/ on(target_currency)\r\n\r\n# net income\r\n(\r\n    sum(hledger_monthly_decrease{account=\"income\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_monthly_increase{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    # as above\r\n    - on(target_currency)\r\n    (\r\n        sum(hledger_monthly_increase{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency) -\r\n        sum(hledger_monthly_decrease{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    )\r\n)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Savings Rate",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "decimals": 1,
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 60
              },
              {
                "color": "green",
                "value": 90
              }
            ]
          },
          "unit": "d"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 4,
        "x": 12,
        "y": 0
      },
      "id": 63,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "# total available cash and emergency fund\r\n(\r\n    sum(hledger_balance{account=\"assets:cash\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    + on (target_currency)\r\n    sum(hledger_balance{account=\"assets:investments:nsi:premium_bonds:emergency\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n)\r\n\r\n/ on (target_currency)\r\n\r\n# average daily expense\r\n(\r\n    (\r\n        sum(hledger_balance{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"expenses\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        + on(target_currency)\r\n        sum(hledger_balance{account=\"expenses:gross\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    ) / $agg_window\r\n)",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Short Runway",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "decimals": 1,
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 60
              },
              {
                "color": "green",
                "value": 90
              }
            ]
          },
          "unit": "d"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 4,
        "x": 16,
        "y": 0
      },
      "id": 85,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n)\r\n/ on (target_currency)\r\n\r\n# average daily expense\r\n(\r\n    (\r\n        sum(hledger_balance{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"expenses\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        + on(target_currency)\r\n        sum(hledger_balance{account=\"expenses:gross\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    ) / $agg_window\r\n)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Long Runway",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "#EAB839",
                "value": 0.5
              },
              {
                "color": "green",
                "value": 0.75
              }
            ]
          },
          "unit": "percentunit"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 4,
        "x": 20,
        "y": 0
      },
      "id": 72,
      "options": {
        "colorMode": "background",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "auto",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "# all assets (sans pension)\r\n(\r\n    sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n)\r\n\r\n/ on(target_currency)\r\n\r\n# FIRE number\r\n(\r\n    (\r\n        sum(hledger_balance{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"expenses\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        + on(target_currency)\r\n        sum(hledger_balance{account=\"expenses:gross\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    ) / $agg_window * 365 * $fire_annual_factor\r\n)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "FIRE Progress",
      "transparent": true,
      "type": "stat"
    },
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 3
      },
      "id": 109,
      "panels": [],
      "title": "Overview",
      "type": "row"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#299c46",
                "value": 0
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 10
              },
              {
                "color": "#d44a3a",
                "value": 25
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 0,
        "y": 4
      },
      "id": 21,
      "maxDataPoints": 100,
      "options": {
        "colorMode": "none",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_transactions_total) without(status)",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Total Transactions",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 15,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "stepAfter",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "showValues": false,
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "normal"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "fieldMinMax": false,
          "mappings": [],
          "max": 500000,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Cash"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "green",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Funds"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "yellow",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Premium Bonds"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "blue",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Receivable"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "orange",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Target: AAW"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "rgb(117, 12, 24)",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Target: FIRE"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "light-red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Target: PAW"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "dark-red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byRegexp",
              "options": "/Target.*/"
            },
            "properties": [
              {
                "id": "custom.fillOpacity",
                "value": 0
              },
              {
                "id": "custom.stacking",
                "value": {
                  "group": false,
                  "mode": "none"
                }
              },
              {
                "id": "custom.lineStyle",
                "value": {
                  "dash": [
                    10,
                    10
                  ],
                  "fill": "dash"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Property"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "purple",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "__systemRef": "hideSeriesFrom",
            "matcher": {
              "id": "byNames",
              "options": {
                "mode": "exclude",
                "names": [
                  "Receivable",
                  "Premium Bonds",
                  "Funds",
                  "Cash",
                  "Target: AAW",
                  "Target: PAW",
                  "Target: FIRE"
                ],
                "prefix": "All except:",
                "readOnly": true
              }
            },
            "properties": [
              {
                "id": "custom.hideFrom",
                "value": {
                  "legend": false,
                  "tooltip": true,
                  "viz": true
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 13,
        "x": 4,
        "y": 4
      },
      "id": 68,
      "options": {
        "legend": {
          "calcs": [
            "mean",
            "lastNotNull"
          ],
          "displayMode": "table",
          "placement": "right",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Property",
          "range": true,
          "refId": "AssetsProperty"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:receivable\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "groupBy": [
            {
              "params": [
                "$__interval"
              ],
              "type": "time"
            },
            {
              "params": [
                "null"
              ],
              "type": "fill"
            }
          ],
          "hide": false,
          "interval": "",
          "legendFormat": "Receivable",
          "orderByTime": "ASC",
          "policy": "default",
          "refId": "AssetsReceivable",
          "resultFormat": "time_series",
          "select": [
            [
              {
                "params": [
                  "value"
                ],
                "type": "field"
              },
              {
                "params": [],
                "type": "mean"
              }
            ]
          ],
          "tags": []
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:investments:nsi:premium_bonds\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Premium Bonds",
          "refId": "AssetsPremiumBonds"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:investments\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n- on(target_currency)\r\nsum(hledger_balance{account=\"assets:investments:nsi:premium_bonds\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Funds",
          "refId": "AssetsFunds"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:cash\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Cash",
          "refId": "AssetsCash"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    # average daily income\r\n    (\r\n        sum(hledger_balance{account=\"income\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"income\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"income:gift\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        + on(target_currency)\r\n        sum(hledger_balance{account=\"income:gift\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    ) / $agg_window * -1\r\n\r\n    # age/10 years worth\r\n    * 365 * ignoring(unit) quantified_self_age{unit=\"years\"} / 10\r\n) / 2\r\n\r\n# ignore gifted income\r\n- on(target_currency)\r\nsum(hledger_balance{account=\"income:gift\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n\r\n# add (subtract) liabilities, other than student loan & mortgage\r\n- on(target_currency)\r\nsum(hledger_balance{account=\"liabilities\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities:loan:slc\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities:mortgage\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Target: AAW",
          "range": true,
          "refId": "TargetAAW"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    # average daily income\r\n    (\r\n        sum(hledger_balance{account=\"income\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"income\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        - on(target_currency)\r\n        sum(hledger_balance{account=\"income:gift\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n        + on(target_currency)\r\n        sum(hledger_balance{account=\"income:gift\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    ) / $agg_window * -1\r\n\r\n    # age/10 years worth\r\n    * 365 * ignoring(unit) quantified_self_age{unit=\"years\"} / 10\r\n) * 2\r\n\r\n# ignore gifted income\r\n- on(target_currency)\r\nsum(hledger_balance{account=\"income:gift\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n\r\n# add (subtract) liabilities, other than student loan & mortgage\r\n- on(target_currency)\r\nsum(hledger_balance{account=\"liabilities\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities:loan:slc\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities:mortgage\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Target: PAW",
          "range": true,
          "refId": "TargetPAW"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "# average daily expense\r\n(\r\n    sum(hledger_balance{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"expenses\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    + on(target_currency)\r\n    sum(hledger_balance{account=\"expenses:gross\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n) / $agg_window\r\n\r\n# $fire_annual_factor years worth\r\n* 365 * $fire_annual_factor",
          "hide": false,
          "interval": "",
          "legendFormat": "Target: FIRE",
          "refId": "TargetFIRE"
        }
      ],
      "title": "Assets (Stacked)",
      "transparent": true,
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            }
          },
          "decimals": 2,
          "mappings": [],
          "unit": "currencyGBP"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Cash"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "green",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Funds"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "yellow",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Premium Bonds"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "blue",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Receivable"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "orange",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Property"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "purple",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "__systemRef": "hideSeriesFrom",
            "matcher": {
              "id": "byNames",
              "options": {
                "mode": "exclude",
                "names": [
                  "Cash",
                  "Funds",
                  "Premium Bonds",
                  "Receivable"
                ],
                "prefix": "All except:",
                "readOnly": true
              }
            },
            "properties": [
              {
                "id": "custom.hideFrom",
                "value": {
                  "legend": false,
                  "tooltip": true,
                  "viz": true
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 3,
        "x": 17,
        "y": 4
      },
      "id": 69,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true,
          "values": [
            "value"
          ]
        },
        "pieType": "donut",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "sort": "desc",
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:cash\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "Cash",
          "refId": "Cash"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:investments\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n- on(target_currency)\r\nsum(hledger_balance{account=\"assets:investments:nsi:premium_bonds\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Funds",
          "refId": "Funds"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:investments:nsi:premium_bonds\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Premium Bonds",
          "refId": "PremiumBonds"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:receivable\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Receivable",
          "refId": "Receivable"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Property",
          "range": true,
          "refId": "Property"
        }
      ],
      "title": "Current Allocation",
      "transparent": true,
      "type": "piechart"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 4,
        "x": 20,
        "y": 4
      },
      "id": 87,
      "options": {
        "colorMode": "none",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n) * 0.04",
          "interval": "",
          "legendFormat": "4.0%",
          "range": true,
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n) * 0.035",
          "hide": false,
          "interval": "",
          "legendFormat": "3.5%",
          "range": true,
          "refId": "B"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n) * 0.03",
          "hide": false,
          "interval": "",
          "legendFormat": "3.0%",
          "range": true,
          "refId": "C"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "(\r\n    sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"assets:property\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n) * 0.025",
          "hide": false,
          "interval": "",
          "legendFormat": "2.5%",
          "range": true,
          "refId": "D"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "expr": "(\r\n    sum(hledger_balance{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"expenses\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_balance{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n    + on(target_currency)\r\n    sum(hledger_balance{account=\"expenses:gross\"} offset ${agg_window}d * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by (target_currency)\r\n) / $agg_window * 365",
          "hide": false,
          "legendFormat": "Target",
          "range": true,
          "refId": "E"
        }
      ],
      "title": "Safe Withdrawal Rate",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#299c46",
                "value": 0
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 10
              },
              {
                "color": "#d44a3a",
                "value": 25
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 2,
        "x": 0,
        "y": 6
      },
      "id": 17,
      "maxDataPoints": 100,
      "options": {
        "colorMode": "none",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "hledger_transactions_total{status=\"cleared\"}",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Cleared",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#299c46",
                "value": 0
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 10
              },
              {
                "color": "#d44a3a",
                "value": 25
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 2,
        "x": 2,
        "y": 6
      },
      "id": 18,
      "maxDataPoints": 100,
      "options": {
        "colorMode": "value",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "hledger_transactions_total{status=\"pending\"}",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Uncleared",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#299c46",
                "value": 0
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 10
              },
              {
                "color": "#d44a3a",
                "value": 25
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 2,
        "x": 0,
        "y": 9
      },
      "id": 19,
      "maxDataPoints": 100,
      "options": {
        "colorMode": "none",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "hledger_transactions_total{status=\"bookkeeping\"}",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Bookkeeping",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [
            {
              "options": {
                "match": "null",
                "result": {
                  "text": "N/A"
                }
              },
              "type": "special"
            }
          ],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "#299c46",
                "value": 0
              },
              {
                "color": "rgba(237, 129, 40, 0.89)",
                "value": 10
              },
              {
                "color": "#d44a3a",
                "value": 25
              }
            ]
          },
          "unit": "percentunit"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 3,
        "w": 2,
        "x": 2,
        "y": 9
      },
      "id": 20,
      "maxDataPoints": 100,
      "options": {
        "colorMode": "none",
        "graphMode": "none",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "text": {},
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "hledger_transactions_total{status=\"bookkeeping\"}\r\n/ ignoring(status)\r\nsum(hledger_transactions_total) without(status)",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Bookkeeping",
      "transparent": true,
      "type": "stat"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 15,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "stepAfter",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "showValues": false,
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "links": [],
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Delta"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "blue",
                  "mode": "fixed"
                }
              },
              {
                "id": "custom.fillOpacity",
                "value": 0
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Total Assets"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "green",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Total Liabilities"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 12,
        "x": 0,
        "y": 12
      },
      "id": 23,
      "options": {
        "legend": {
          "calcs": [
            "mean",
            "lastNotNull"
          ],
          "displayMode": "table",
          "placement": "right",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Total Assets",
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"liabilities\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Total Liabilities",
          "refId": "B"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Delta",
          "refId": "C"
        }
      ],
      "title": "Net Worth",
      "transparent": true,
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 15,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "stepAfter",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "showValues": false,
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "links": [],
          "mappings": [],
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Expenses"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Savings Rate"
            },
            "properties": [
              {
                "id": "unit",
                "value": "percentunit"
              },
              {
                "id": "custom.drawStyle",
                "value": "line"
              },
              {
                "id": "custom.fillOpacity",
                "value": 0
              },
              {
                "id": "custom.lineInterpolation",
                "value": "stepAfter"
              },
              {
                "id": "custom.axisSoftMin",
                "value": -1
              },
              {
                "id": "custom.axisSoftMax",
                "value": 1
              },
              {
                "id": "custom.lineWidth",
                "value": 2
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 12,
        "x": 12,
        "y": 12
      },
      "id": 58,
      "options": {
        "legend": {
          "calcs": [
            "mean",
            "lastNotNull"
          ],
          "displayMode": "table",
          "placement": "right",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(hledger_monthly_decrease{account=\"income\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n- on(target_currency)\r\nsum(hledger_monthly_increase{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n\r\n# ignore pension contributions (assumes pensions only go up - include 'decrease' as well to handle January roll-over)\r\n- on(target_currency)\r\n(\r\n    sum(hledger_monthly_increase{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency) -\r\n    sum(hledger_monthly_decrease{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n)",
          "hide": false,
          "interval": "",
          "legendFormat": "Income (net)",
          "range": true,
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(hledger_monthly_increase{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n- on(target_currency)\r\nsum(hledger_monthly_increase{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Expenses",
          "range": true,
          "refId": "B"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "# saved income\r\n(\r\n    sum(hledger_monthly_decrease{account=\"income\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_monthly_increase{account=\"expenses\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    # ignore pension contributions (assumes pensions only go up - include 'decrease' as well to handle January roll-over)\r\n    - on(target_currency)\r\n    (\r\n        sum(hledger_monthly_increase{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency) -\r\n        sum(hledger_monthly_decrease{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    )\r\n)\r\n\r\n/ on(target_currency)\r\n\r\n# net income\r\n(\r\n    sum(hledger_monthly_decrease{account=\"income\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    - on(target_currency)\r\n    sum(hledger_monthly_increase{account=\"expenses:gross\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    # as above\r\n    - on(target_currency)\r\n    (\r\n        sum(hledger_monthly_increase{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency) -\r\n        sum(hledger_monthly_decrease{account=\"assets:pension\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n    )\r\n)",
          "hide": false,
          "interval": "",
          "legendFormat": "Savings Rate",
          "range": true,
          "refId": "D"
        }
      ],
      "title": "Cash Flow",
      "transparent": true,
      "type": "timeseries"
    },
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 20
      },
      "id": 106,
      "panels": [],
      "title": "Liabilities",
      "type": "row"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 15,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "stepAfter",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "showValues": false,
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "links": [],
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Delta"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "blue",
                  "mode": "fixed"
                }
              },
              {
                "id": "custom.fillOpacity",
                "value": 0
              },
              {
                "id": "custom.gradientMode",
                "value": "opacity"
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Liability"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Put Aside"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "green",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 0,
        "y": 21
      },
      "id": 24,
      "options": {
        "legend": {
          "calcs": [
            "mean",
            "lastNotNull"
          ],
          "displayMode": "table",
          "placement": "right",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:cash:nationwide:flexdirect:pending:amex\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "Put Aside",
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"liabilities:creditcard:amex\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Liability",
          "refId": "B"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:cash:nationwide:flexdirect:pending:amex\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities:creditcard:amex\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Delta",
          "refId": "C"
        }
      ],
      "title": "Credit Card",
      "transparent": true,
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 15,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "stepAfter",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "showValues": false,
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "links": [],
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Delta"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "blue",
                  "mode": "fixed"
                }
              },
              {
                "id": "custom.fillOpacity",
                "value": 0
              },
              {
                "id": "custom.gradientMode",
                "value": "opacity"
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Liability"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Put Aside"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "green",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 8,
        "y": 21
      },
      "id": 107,
      "options": {
        "legend": {
          "calcs": [
            "mean",
            "lastNotNull"
          ],
          "displayMode": "table",
          "placement": "right",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:dad\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "Put Aside",
          "range": true,
          "refId": "A"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"liabilities:owed:dad\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Liability",
          "range": true,
          "refId": "B"
        },
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:dad\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)\r\n+ on(target_currency)\r\nsum(hledger_balance{account=\"liabilities:owed:dad\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Delta",
          "range": true,
          "refId": "C"
        }
      ],
      "title": "Dad",
      "transparent": true,
      "type": "timeseries"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 15,
            "gradientMode": "opacity",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "stepAfter",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "never",
            "showValues": false,
            "spanNulls": true,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "links": [],
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": 0
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": [
          {
            "matcher": {
              "id": "byName",
              "options": "Delta"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "blue",
                  "mode": "fixed"
                }
              },
              {
                "id": "custom.fillOpacity",
                "value": 0
              },
              {
                "id": "custom.gradientMode",
                "value": "opacity"
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Liability"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "red",
                  "mode": "fixed"
                }
              }
            ]
          },
          {
            "matcher": {
              "id": "byName",
              "options": "Put Aside"
            },
            "properties": [
              {
                "id": "color",
                "value": {
                  "fixedColor": "green",
                  "mode": "fixed"
                }
              }
            ]
          }
        ]
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 16,
        "y": 21
      },
      "id": 108,
      "options": {
        "legend": {
          "calcs": [
            "mean",
            "lastNotNull"
          ],
          "displayMode": "table",
          "placement": "right",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "multi",
          "sort": "none"
        }
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"liabilities:mortgage:.*\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "hide": false,
          "interval": "",
          "legendFormat": "Liability",
          "range": true,
          "refId": "B"
        }
      ],
      "title": "Mortgage",
      "transparent": true,
      "type": "timeseries"
    },
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 29
      },
      "id": 89,
      "panels": [],
      "title": "Budget",
      "type": "row"
    },
    {
      "fieldConfig": {
        "defaults": {},
        "overrides": []
      },
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 30
      },
      "id": 90,
      "options": {
        "code": {
          "language": "plaintext",
          "showLineNumbers": false,
          "showMiniMap": false
        },
        "content": "",
        "mode": "markdown"
      },
      "pluginVersion": "12.3.1",
      "title": "MONTHLY - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -",
      "transparent": true,
      "type": "text"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£170",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 750,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 375
              },
              {
                "color": "green",
                "value": 563
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 0,
        "y": 31
      },
      "id": 34,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:house:counciltax\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Council Tax",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£25",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 50
              },
              {
                "color": "green",
                "value": 75
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 4,
        "y": 31
      },
      "id": 76,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "exemplar": true,
          "expr": "sum(hledger_balance{account=\"assets:investments:fidelity:management\"} * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "refId": "A"
        }
      ],
      "title": "Fidelity Fees",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£100",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 500,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 250
              },
              {
                "color": "green",
                "value": 375
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 8,
        "y": 31
      },
      "id": 32,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:household\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Household",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£75",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 500,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 250
              },
              {
                "color": "green",
                "value": 375
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 12,
        "y": 31
      },
      "id": 100,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:huel\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Huel",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£20",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 50
              },
              {
                "color": "green",
                "value": 75
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 16,
        "y": 31
      },
      "id": 37,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:patreon\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Patreon",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£20",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 50
              },
              {
                "color": "green",
                "value": 75
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 20,
        "y": 31
      },
      "id": 33,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:phone\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Phone",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£500",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 2500,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 1250
              },
              {
                "color": "green",
                "value": 1875
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 0,
        "y": 33
      },
      "id": 35,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:travel\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Travel",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£20",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 50
              },
              {
                "color": "green",
                "value": 75
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 4,
        "y": 33
      },
      "id": 110,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:twitch\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Twitch",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£200",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 1000,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 500
              },
              {
                "color": "green",
                "value": 750
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 8,
        "y": 33
      },
      "id": 36,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:house:utilities\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Utilities",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£75",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 500,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 250
              },
              {
                "color": "green",
                "value": 375
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 12,
        "y": 33
      },
      "id": 40,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:web\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Web",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "fieldConfig": {
        "defaults": {},
        "overrides": []
      },
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 35
      },
      "id": 88,
      "options": {
        "code": {
          "language": "plaintext",
          "showLineNumbers": false,
          "showMiniMap": false
        },
        "content": "",
        "mode": "markdown"
      },
      "pluginVersion": "12.3.1",
      "title": "ANNUALLY - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -",
      "transparent": true,
      "type": "text"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£100 (due November)",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 100,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 50
              },
              {
                "color": "green",
                "value": 75
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 0,
        "y": 36
      },
      "id": 92,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:british_museum\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "British Museum",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~£500 (due April)",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 500,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 250
              },
              {
                "color": "green",
                "value": 375
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 4,
        "y": 36
      },
      "id": 102,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:house:insurance\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Home Insurance",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~50 USD (due April)",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 50,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 25
              },
              {
                "color": "green",
                "value": 37.5
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 8,
        "y": 36
      },
      "id": 93,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:obsidian\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Obsidian",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "~50 EUR (due July)",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 50,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 25
              },
              {
                "color": "green",
                "value": 37.5
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 12,
        "y": 36
      },
      "id": 38,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:protonmail\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Protonmail",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "fieldConfig": {
        "defaults": {},
        "overrides": []
      },
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 38
      },
      "id": 91,
      "options": {
        "code": {
          "language": "plaintext",
          "showLineNumbers": false,
          "showMiniMap": false
        },
        "content": "",
        "mode": "markdown"
      },
      "pluginVersion": "12.3.1",
      "title": "SINKING - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -",
      "transparent": true,
      "type": "text"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 300,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 150
              },
              {
                "color": "green",
                "value": 225
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 0,
        "y": 39
      },
      "id": 98,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:clothes\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Clothes",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 5000,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 2500
              },
              {
                "color": "green",
                "value": 3750
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 4,
        "y": 39
      },
      "id": 97,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:investments:nsi:premium_bonds:emergency\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Emergency",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 250,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 125
              },
              {
                "color": "green",
                "value": 187.5
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 8,
        "y": 39
      },
      "id": 78,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:gift\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Gift",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "description": "Excluding money allocated towards specific goals.",
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 250,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 125
              },
              {
                "color": "green",
                "value": 187.5
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 12,
        "y": 39
      },
      "id": 105,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
            "lastNotNull"
          ],
          "fields": "",
          "values": false
        },
        "showUnfilled": true,
        "sizing": "auto",
        "valueMode": "color"
      },
      "pluginVersion": "12.3.1",
      "targets": [
        {
          "datasource": {
            "type": "prometheus",
            "uid": "${datasource}"
          },
          "editorMode": "code",
          "exemplar": true,
          "expr": "sum(sum(hledger_balance{account=~\"assets:.*:saved:goals\"}) by (currency) * on(currency) hledger_fx_rate{target_currency=\"$currency\"}) by(target_currency)",
          "interval": "",
          "legendFormat": "",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "Goals",
      "transparent": true,
      "type": "bargauge"
    },
    {
      "datasource": {
        "type": "prometheus",
        "uid": "${datasource}"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "thresholds"
          },
          "mappings": [],
          "max": 2000,
          "min": 0,
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "red",
                "value": 0
              },
              {
                "color": "yellow",
                "value": 1000
              },
              {
                "color": "green",
                "value": 1500
              }
            ]
          },
          "unit": "currencyGBP"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 2,
        "w": 4,
        "x": 16,
        "y": 39
      },
      "id": 31,
      "options": {
        "displayMode": "lcd",
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": false
        },
        "maxVizHeight": 300,
        "minVizHeight": 16,
        "minVizWidth": 8,
        "namePlacement": "auto",
        "orientation": "horizontal",
        "reduceOptions": {
          "calcs": [
       
Download .txt
gitextract_v3gowx2a/

├── .forgejo/
│   ├── scripts/
│   │   └── deploy-documentation.sh
│   └── workflows/
│       └── deploy-documentation.yml
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yaml
│       └── github-pages.yml
├── .gitignore
├── .sops.yaml
├── README.markdown
├── docs/
│   ├── .gitignore
│   ├── book.toml
│   └── src/
│       ├── SUMMARY.md
│       └── runbooks/
│           ├── alerts/
│           │   ├── diskspacelow.md
│           │   └── zpoolstatusdegraded.md
│           ├── move-a-configuration-to-a-new-machine.md
│           ├── set-up-a-new-host.md
│           ├── upgrade-to-a-new-version-of-elasticsearch.md
│           └── upgrade-to-a-new-version-of-postgres.md
├── flake.nix
├── hosts/
│   ├── carcosa/
│   │   ├── caddy/
│   │   │   ├── lainon-life/
│   │   │   │   ├── 404.html
│   │   │   │   └── duvet.ogg
│   │   │   └── www-lookwhattheshoggothdraggedin-com.caddyfile
│   │   ├── configuration.nix
│   │   ├── hardware.nix
│   │   └── secrets.yaml
│   ├── nyarlathotep/
│   │   ├── configuration.nix
│   │   ├── dashboards/
│   │   │   ├── finance.json
│   │   │   └── smart-home.json
│   │   ├── hardware.nix
│   │   ├── jobs/
│   │   │   ├── flac-and-tag-album.sh
│   │   │   ├── hledger-export-to-victoriametrics.py
│   │   │   ├── hledger-fetch-fx-rates.py
│   │   │   ├── restic-prepare--fetch-youtube.py
│   │   │   ├── restic-prepare--hardlink-torrent-files.py
│   │   │   ├── rss-to-mastodon.py
│   │   │   └── tag-podcasts.sh
│   │   └── secrets.yaml
│   └── yuggoth/
│       ├── configuration.nix
│       ├── hardware.nix
│       └── secrets.yaml
├── scripts/
│   ├── backups.sh
│   ├── documentation.sh
│   ├── fmt.sh
│   ├── lint.sh
│   └── secrets.sh
├── shared/
│   ├── acme/
│   │   ├── default.nix
│   │   └── options.nix
│   ├── bookdb/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   ├── options.nix
│   │   ├── remote-sync-receive.nix
│   │   ├── remote-sync-send.nix
│   │   └── uuids.yaml
│   ├── bookmarks/
│   │   ├── default.nix
│   │   ├── options.nix
│   │   ├── remote-sync-receive.nix
│   │   └── remote-sync-send.nix
│   ├── dashboards/
│   │   └── node-stats-detailed.json
│   ├── default.nix
│   ├── erase-your-darlings/
│   │   ├── default.nix
│   │   └── options.nix
│   ├── finder/
│   │   ├── default.nix
│   │   └── options.nix
│   ├── forgejo/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── foundryvtt/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── host-templates/
│   │   ├── default.nix
│   │   └── website-mirror/
│   │       ├── default.nix
│   │       ├── options.nix
│   │       └── resources/
│   │           ├── memo-barrucadu-co-uk.caddyfile
│   │           └── www-barrucadu-co-uk.caddyfile
│   ├── minecraft/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── oci-containers/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── options.nix
│   ├── pleroma/
│   │   ├── default.nix
│   │   ├── erase-your-darlings.nix
│   │   └── options.nix
│   ├── resolved/
│   │   ├── dashboard.json
│   │   ├── default.nix
│   │   └── options.nix
│   ├── restic-backups/
│   │   ├── default.nix
│   │   └── options.nix
│   └── torrents/
│       ├── default.nix
│       ├── erase-your-darlings.nix
│       ├── options.nix
│       └── transmission_3/
│           ├── default.nix
│           ├── transmission-3.00-miniupnpc-2.2.8.patch
│           └── transmission-3.00-openssl-3.patch
└── tools/
    └── provision-machine.sh
Download .txt
SYMBOL INDEX (22 symbols across 3 files)

FILE: hosts/nyarlathotep/jobs/hledger-export-to-victoriametrics.py
  function hledger_command (line 26) | def hledger_command(args):
  function offset_date (line 38) | def offset_date(date, years):
  function offset_price_date (line 51) | def offset_price_date(line, years):
  function offset_posting_date (line 59) | def offset_posting_date(posting, years):
  function date_to_timestamp (line 66) | def date_to_timestamp(date):
  function running_totals (line 75) | def running_totals(deltas_by_timestamp):
  function pivot (line 89) | def pivot(samples_by_timestamp):
  function convert_samples (line 101) | def convert_samples(samples):
  function preprocess_group_credits_debits (line 110) | def preprocess_group_credits_debits(postings):
  function metric_hledger_fx_rate (line 160) | def metric_hledger_fx_rate(gbp_fx_rates, credits_debits):
  function metric_hledger_balance (line 212) | def metric_hledger_balance(credits_debits):
  function metric_hledger_monthly_credits_debits (line 231) | def metric_hledger_monthly_credits_debits(credits_debits, field):
  function metric_hledger_age_of_money (line 259) | def metric_hledger_age_of_money(credits_debits):
  function metric_hledger_transactions_total (line 310) | def metric_hledger_transactions_total(postings):
  function metric_quantified_self_age (line 338) | def metric_quantified_self_age(credits_debits):

FILE: hosts/nyarlathotep/jobs/hledger-fetch-fx-rates.py
  function get_financial_times (line 13) | def get_financial_times(url):
  function get_financial_times_currency (line 40) | def get_financial_times_currency(symbol):
  function get_financial_times_fund (line 46) | def get_financial_times_fund(isin):

FILE: hosts/nyarlathotep/jobs/restic-prepare--hardlink-torrent-files.py
  function print_cmd (line 24) | def print_cmd(cmd):
  function file_ref (line 31) | def file_ref(fpath):
  function find_inodes (line 38) | def find_inodes(base):
  function traverse (line 49) | def traverse(base, inodes):
Condensed preview — 95 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,268K chars).
[
  {
    "path": ".forgejo/scripts/deploy-documentation.sh",
    "chars": 329,
    "preview": "#!/usr/bin/env bash\n\necho \"$SSH_PRIVATE_KEY\" > .ssh_private_key\ncleanup() {\n    rm .ssh_private_key\n}\ntrap cleanup EXIT\n"
  },
  {
    "path": ".forgejo/workflows/deploy-documentation.yml",
    "chars": 797,
    "preview": "on:\n  push:\n    branches: [\"master\"]\n\njobs:\n  deploy_documentation:\n    runs-on: nix\n    steps:\n      - name: Install to"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 111,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "chars": 661,
    "preview": "name: Run tests\n\non: pull_request\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6"
  },
  {
    "path": ".github/workflows/github-pages.yml",
    "chars": 1378,
    "preview": "on:\n  push:\n    branches: [\"master\"]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  pages: write\n  id-token: writ"
  },
  {
    "path": ".gitignore",
    "chars": 7,
    "preview": "/_site\n"
  },
  {
    "path": ".sops.yaml",
    "chars": 659,
    "preview": "keys:\n  - &barrucadu 'age1sdnp5uxhdtujc78penv2gntnenzcfju7est4hslz6eqgfk26u9nskkk634'\n\ncreation_rules:\n  - path_regex: h"
  },
  {
    "path": "README.markdown",
    "chars": 3348,
    "preview": "nixfiles\n========\n\nMy [NixOS][] configuration and assorted other crap, powered by [flakes][].\nClone to `/etc/nixos`.\n\nCI"
  },
  {
    "path": "docs/.gitignore",
    "chars": 133,
    "preview": "book\n\n# generated by the build script\nsrc/README.md\nsrc/hosts.md\nsrc/host-templates.md\nsrc/modules.md\nsrc/options.md\nsrc"
  },
  {
    "path": "docs/book.toml",
    "chars": 312,
    "preview": "[book]\ntitle = \"nixfiles\"\nauthors = [\"Michael Walker (barrucadu)\"]\ndescription = \"My NixOS configuration and assorted ot"
  },
  {
    "path": "docs/src/SUMMARY.md",
    "chars": 666,
    "preview": "# Summary\n\n[README](./README.md)\n\n# Reference\n\n- [Hosts](./hosts.md)\n- [Host Templates](./host-templates.md)\n- [Modules]"
  },
  {
    "path": "docs/src/runbooks/alerts/diskspacelow.md",
    "chars": 1314,
    "preview": "DiskSpaceLow\n============\n\nThis alert fires when a partition has under 10% free space remaining.\n\nThe alert will say whi"
  },
  {
    "path": "docs/src/runbooks/alerts/zpoolstatusdegraded.md",
    "chars": 1999,
    "preview": "ZPoolStatusDegraded\n===================\n\nThis alert fires when an HDD fails.\n\nThe `zpool status -x` command will say whi"
  },
  {
    "path": "docs/src/runbooks/move-a-configuration-to-a-new-machine.md",
    "chars": 3014,
    "preview": "Move a configuration to a new machine\n=====================================\n\nFollow the [set up a new host](./set-up-a-n"
  },
  {
    "path": "docs/src/runbooks/set-up-a-new-host.md",
    "chars": 4271,
    "preview": "Set up a new host\n=================\n\n> [!NOTE]\n> See also [the NixOS installation instructions](https://nixos.org/manual"
  },
  {
    "path": "docs/src/runbooks/upgrade-to-a-new-version-of-elasticsearch.md",
    "chars": 2386,
    "preview": "Upgrade to a new version of elasticsearch\n====================================\n\n\nChange the default elasticsearch versio"
  },
  {
    "path": "docs/src/runbooks/upgrade-to-a-new-version-of-postgres.md",
    "chars": 2994,
    "preview": "Upgrade to a new version of postgres\n====================================\n\n\nChange the default postgres version for a mo"
  },
  {
    "path": "flake.nix",
    "chars": 5426,
    "preview": "{\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixos-25.11-small\";\n    sops-nix = {\n      url = \"github:Mic92/so"
  },
  {
    "path": "hosts/carcosa/caddy/lainon-life/404.html",
    "chars": 1092,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>LAINON.LIFE IS DEAD</title>\n    <meta charset=\"UTF-8\">\n    <meta name=\"theme-"
  },
  {
    "path": "hosts/carcosa/caddy/www-lookwhattheshoggothdraggedin-com.caddyfile",
    "chars": 5521,
    "preview": "# Removed\nerror /files/privacy/analytics.png 410\nerror /glossary.html 410\nerror /privacy.html 410\n\n# Redirected\nredir /a"
  },
  {
    "path": "hosts/carcosa/configuration.nix",
    "chars": 12274,
    "preview": "# This is a VPS (hosted by Hetzner Cloud).\n#\n# It serves [barrucadu.co.uk][] and other services on it.  Websites are ser"
  },
  {
    "path": "hosts/carcosa/hardware.nix",
    "chars": 1014,
    "preview": "{ ... }:\n\n{\n  boot.initrd.availableKernelModules = [ \"ahci\" \"xhci_pci\" \"virtio_pci\" \"sd_mod\" \"sr_mod\" ];\n  boot.initrd.k"
  },
  {
    "path": "hosts/carcosa/secrets.yaml",
    "chars": 5200,
    "preview": "users:\n    barrucadu: ENC[AES256_GCM,data:5mAoxmMEn4zOhwDHS4cal7b0fPZb7SshS3QmfrBgLKQMJfExiRdnWvwnqTvLY/ObRTYqeWrcik+GVy"
  },
  {
    "path": "hosts/nyarlathotep/configuration.nix",
    "chars": 19084,
    "preview": "# This is my home server.\n#\n# It runs writable instances of the bookdb and bookmarks services, which have\n# any updates "
  },
  {
    "path": "hosts/nyarlathotep/dashboards/finance.json",
    "chars": 154546,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"$$hashKey\": \"object:321\",\n        \"builtIn\": 1,\n        \"datasource\""
  },
  {
    "path": "hosts/nyarlathotep/dashboards/smart-home.json",
    "chars": 18867,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\","
  },
  {
    "path": "hosts/nyarlathotep/hardware.nix",
    "chars": 938,
    "preview": "{ ... }:\n\n{\n  boot.initrd.availableKernelModules = [ \"nvme\" \"xhci_pci\" \"ahci\" \"usb_storage\" \"usbhid\" \"sd_mod\" ];\n  boot."
  },
  {
    "path": "hosts/nyarlathotep/jobs/flac-and-tag-album.sh",
    "chars": 1043,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\nfor artist in *; do\n  if [[ -d $artist ]]; then\n    pushd \"$artist\"\n    for album in *; do\n"
  },
  {
    "path": "hosts/nyarlathotep/jobs/hledger-export-to-victoriametrics.py",
    "chars": 13567,
    "preview": "#!/usr/bin/env python3\n\nimport calendar\nimport csv\nimport datetime\nimport io\nimport os\nimport subprocess\nimport sys\n\nfro"
  },
  {
    "path": "hosts/nyarlathotep/jobs/hledger-fetch-fx-rates.py",
    "chars": 1831,
    "preview": "#!/usr/bin/env python3\n\nfrom html.parser import HTMLParser\n\nimport os\nimport requests\nimport sys\nimport time\n\nDRY_RUN = "
  },
  {
    "path": "hosts/nyarlathotep/jobs/restic-prepare--fetch-youtube.py",
    "chars": 745,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Youtube \"backup\" script - generates a script to download videos.\"\"\"\n\nimport os\nimport shlex\n\n"
  },
  {
    "path": "hosts/nyarlathotep/jobs/restic-prepare--hardlink-torrent-files.py",
    "chars": 2005,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"Torrent \"backup\" script - generates a script to create the directory\nhierarchy and hardlink f"
  },
  {
    "path": "hosts/nyarlathotep/jobs/rss-to-mastodon.py",
    "chars": 2797,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"RSS-to-Mastodon (or Pleroma)\n\nRequires the API_KEY environment variable to be set.\n\nUsage:\n  "
  },
  {
    "path": "hosts/nyarlathotep/jobs/tag-podcasts.sh",
    "chars": 1506,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\nsleep 60\n\nfor m4afile in */in/*.m4a; do\n  if [[ ! -f \"$m4afile\" ]]; then\n    break\n  fi\n\n  "
  },
  {
    "path": "hosts/nyarlathotep/secrets.yaml",
    "chars": 3880,
    "preview": "users:\n    barrucadu: ENC[AES256_GCM,data:H6q5mf0vurd5FRCPftLGXvGzDep5iYlSw4gJMVCWhB8+35A8wYyE8i5qW+pNEE+TtxrXpYa9MzjWpA"
  },
  {
    "path": "hosts/yuggoth/configuration.nix",
    "chars": 2132,
    "preview": "# This is a VPS (hosted by Hetzner Cloud).\n#\n# It serves a redundant deployment of a few of my websites.\n#\n# **Alerting:"
  },
  {
    "path": "hosts/yuggoth/hardware.nix",
    "chars": 926,
    "preview": "{ ... }:\n\n{\n  boot.initrd.availableKernelModules = [ \"ahci\" \"xhci_pci\" \"virtio_pci\" \"virtio_scsi\" \"sd_mod\" \"sr_mod\" ];\n "
  },
  {
    "path": "hosts/yuggoth/secrets.yaml",
    "chars": 2921,
    "preview": "nix:\n    build_machines:\n        carcosa:\n            ssh_key: ENC[AES256_GCM,data:iHdR8yErpDSh9GHQ1nS1u6jkxDxDoQnjpElKl"
  },
  {
    "path": "scripts/backups.sh",
    "chars": 947,
    "preview": "set -Ee\n\nexport RESTIC_REPOSITORY=\"b2:barrucadu-backups-a19c48:nixfiles/restic\"\n\nCOMMAND=$1\nTARGET=$(hostname)\n\nif [[ -z"
  },
  {
    "path": "scripts/documentation.sh",
    "chars": 4021,
    "preview": "set -e\n\nsed 's#See \\[the documentation\\].*##' < README.markdown > docs/src/README.md\n\npython3 - <<'EOF' > docs/src/hosts"
  },
  {
    "path": "scripts/fmt.sh",
    "chars": 17,
    "preview": "nix fmt\n\nblack .\n"
  },
  {
    "path": "scripts/lint.sh",
    "chars": 853,
    "preview": "set -ex\n\n# TODO: add this back when the package is no longer broken\n# nix-linter -r .\n\n# SC2001: use pattern expansion o"
  },
  {
    "path": "scripts/secrets.sh",
    "chars": 213,
    "preview": "if [[ -z \"$1\" ]]; then\n  SECRETS_FILE=\"hosts/$(hostname)/secrets.yaml\"\nelif [[ -z \"$2\" ]]; then\n  SECRETS_FILE=\"hosts/${"
  },
  {
    "path": "shared/acme/default.nix",
    "chars": 1255,
    "preview": "# Manage ACME (LetsEncrypt) certificates via Route53 DNS challenge.\n#\n# **Erase your darlings:** stores certificates in "
  },
  {
    "path": "shared/acme/options.nix",
    "chars": 830,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.acme = {\n    enable = mkOption {\n      type = types.bool;\n      default ="
  },
  {
    "path": "shared/bookdb/default.nix",
    "chars": 2587,
    "preview": "# [bookdb][] is a webapp to keep track of all my books, with a public instance\n# on [bookdb.barrucadu.co.uk][].\n#\n# book"
  },
  {
    "path": "shared/bookdb/erase-your-darlings.nix",
    "chars": 240,
    "preview": "{ config, lib, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.bookdb;\n  eyd = config.nixfiles.eraseYourDarlings;\nin\n{\n  c"
  },
  {
    "path": "shared/bookdb/options.nix",
    "chars": 2622,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.bookdb = {\n    enable = mkOption {\n      type = types.bool;\n      default"
  },
  {
    "path": "shared/bookdb/remote-sync-receive.nix",
    "chars": 1982,
    "preview": "# See remote-sync-send.nix\n{ config, lib, pkgs, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.bookdb.remoteSync.receive;"
  },
  {
    "path": "shared/bookdb/remote-sync-send.nix",
    "chars": 2451,
    "preview": "# See remote-sync-receive.nix\n{ config, lib, pkgs, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.bookdb.remoteSync.send;"
  },
  {
    "path": "shared/bookdb/uuids.yaml",
    "chars": 2085,
    "preview": "# shared between nyarlathotep & carcosa\nlocations:\n- name: House\n  slug: be60be7b-a10f-42e1-8769-d43f12cad02d\n- name: Pa"
  },
  {
    "path": "shared/bookmarks/default.nix",
    "chars": 2163,
    "preview": "# [bookmarks][] is a webapp to keep track of all my bookmarks, with a public\n# instance on [bookmarks.barrucadu.co.uk][]"
  },
  {
    "path": "shared/bookmarks/options.nix",
    "chars": 2351,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.bookmarks = {\n    enable = mkOption {\n      type = types.bool;\n      defa"
  },
  {
    "path": "shared/bookmarks/remote-sync-receive.nix",
    "chars": 831,
    "preview": "# see remote-sync-send.nix\n{ config, lib, pkgs, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.bookmarks.remoteSync.recei"
  },
  {
    "path": "shared/bookmarks/remote-sync-send.nix",
    "chars": 1264,
    "preview": "# see remote-sync-receive.nix\n{ config, lib, pkgs, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.bookmarks.remoteSync.se"
  },
  {
    "path": "shared/dashboards/node-stats-detailed.json",
    "chars": 683012,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"$$hashKey\": \"object:1058\",\n        \"builtIn\": 1,\n        \"datasource"
  },
  {
    "path": "shared/default.nix",
    "chars": 11208,
    "preview": "# Common configuration enabled on all hosts.\n#\n# **Alerts:**\n#\n# - A zpool is in \"degraded\" status (alertmanager)\n{ conf"
  },
  {
    "path": "shared/erase-your-darlings/default.nix",
    "chars": 2326,
    "preview": "# Wipe `/` on boot, inspired by [\"erase your darlings\"][].\n#\n# This module is responsible for configuring standard NixOS"
  },
  {
    "path": "shared/erase-your-darlings/options.nix",
    "chars": 1521,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.eraseYourDarlings = {\n    enable = mkOption {\n      type = types.bool;\n  "
  },
  {
    "path": "shared/finder/default.nix",
    "chars": 1362,
    "preview": "# finder is a webapp to read downloaded manga.  There is no public deployment.\n#\n# finder uses a containerised elasticse"
  },
  {
    "path": "shared/finder/options.nix",
    "chars": 845,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.finder = {\n    enable = mkOption {\n      type = types.bool;\n      default"
  },
  {
    "path": "shared/forgejo/default.nix",
    "chars": 3517,
    "preview": "# [Forgejo][] is git forge (I'll never like that term).  This module sets up an\n# instance with user registration disabl"
  },
  {
    "path": "shared/forgejo/erase-your-darlings.nix",
    "chars": 248,
    "preview": "{ config, lib, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.forgejo;\n  eyd = config.nixfiles.eraseYourDarlings;\nin\n{\n  "
  },
  {
    "path": "shared/forgejo/options.nix",
    "chars": 1388,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.forgejo = {\n    enable = mkOption {\n      type = types.bool;\n      defaul"
  },
  {
    "path": "shared/foundryvtt/default.nix",
    "chars": 1883,
    "preview": "# [FoundryVTT][] is a virtual tabletop to run roleplaying games.  It is licensed\n# software and needs to be downloaded a"
  },
  {
    "path": "shared/foundryvtt/erase-your-darlings.nix",
    "chars": 256,
    "preview": "{ config, lib, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.foundryvtt;\n  eyd = config.nixfiles.eraseYourDarlings;\nin\n{"
  },
  {
    "path": "shared/foundryvtt/options.nix",
    "chars": 777,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.foundryvtt = {\n    enable = mkOption {\n      type = types.bool;\n      def"
  },
  {
    "path": "shared/host-templates/default.nix",
    "chars": 223,
    "preview": "# Template configuration for a variety of functionality.\n#\n# See [the documentation for each template][].\n#\n# [the docum"
  },
  {
    "path": "shared/host-templates/website-mirror/default.nix",
    "chars": 8691,
    "preview": "# Configures a webserver for the following domains:\n#\n# - {www,bookdb,bookmarks,memos,weeknotes,}barrucadu.co.uk\n# - {ww"
  },
  {
    "path": "shared/host-templates/website-mirror/options.nix",
    "chars": 1412,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.hostTemplates.websiteMirror = {\n    enable = mkOption {\n      type = type"
  },
  {
    "path": "shared/host-templates/website-mirror/resources/memo-barrucadu-co-uk.caddyfile",
    "chars": 28802,
    "preview": "# SUPERSEDED\n\nredir /2018-budget.html /personal-finance.html permanent\nredir /concourseci-nixos.html /ci-cd.html permane"
  },
  {
    "path": "shared/host-templates/website-mirror/resources/www-barrucadu-co-uk.caddyfile",
    "chars": 5285,
    "preview": "# Removed posts\nerror /posts/2013-05-27-a-gentle-introduction-to-parsec.html 410\nerror /posts/2014-01-07-garbage-collect"
  },
  {
    "path": "shared/minecraft/default.nix",
    "chars": 2444,
    "preview": "# [Minecraft][] Java Edition runner.  Supports multiple servers, with mods.\n# This module doesn't manage the Minecraft s"
  },
  {
    "path": "shared/minecraft/erase-your-darlings.nix",
    "chars": 253,
    "preview": "{ config, lib, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.minecraft;\n  eyd = config.nixfiles.eraseYourDarlings;\nin\n{\n"
  },
  {
    "path": "shared/minecraft/options.nix",
    "chars": 2302,
    "preview": "{ lib, pkgs, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.minecraft = {\n    enable = mkOption {\n      type = types.bool;\n    "
  },
  {
    "path": "shared/oci-containers/default.nix",
    "chars": 4964,
    "preview": "# > [!NOTE]\n# > **TODO:** Run podman containers run as a non-root user.\n#\n# An abstraction over running containers as sy"
  },
  {
    "path": "shared/oci-containers/erase-your-darlings.nix",
    "chars": 210,
    "preview": "{ config, lib, ... }:\n\nwith lib;\nlet\n  eyd = config.nixfiles.eraseYourDarlings;\nin\n{\n  config = mkIf eyd.enable {\n    ni"
  },
  {
    "path": "shared/oci-containers/options.nix",
    "chars": 4863,
    "preview": "{ lib, ... }:\n\nwith lib;\nlet\n  portOptions = {\n    host = mkOption {\n      type = types.int;\n      description = ''\n    "
  },
  {
    "path": "shared/options.nix",
    "chars": 358,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.firewall = {\n    ipBlocklistFile = mkOption {\n      type = types.nullOr t"
  },
  {
    "path": "shared/pleroma/default.nix",
    "chars": 4252,
    "preview": "# [Pleroma][] is a fediverese server.\n#\n# Pleroma uses a containerised postgres database.\n#\n# **Backups:** the postgres "
  },
  {
    "path": "shared/pleroma/erase-your-darlings.nix",
    "chars": 450,
    "preview": "{ config, lib, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.pleroma;\n  eyd = config.nixfiles.eraseYourDarlings;\n\n  # sy"
  },
  {
    "path": "shared/pleroma/options.nix",
    "chars": 1907,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.pleroma = {\n    enable = mkOption {\n      type = types.bool;\n      defaul"
  },
  {
    "path": "shared/resolved/dashboard.json",
    "chars": 43355,
    "preview": "{\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"datasource\","
  },
  {
    "path": "shared/resolved/default.nix",
    "chars": 2039,
    "preview": "# [resolved][] is a recursive DNS server for LAN DNS.\n#\n# Provides a grafana dashboard.\n#\n# [resolved]: https://github.c"
  },
  {
    "path": "shared/resolved/options.nix",
    "chars": 2428,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.resolved = {\n    enable = mkOption {\n      type = types.bool;\n      defau"
  },
  {
    "path": "shared/restic-backups/default.nix",
    "chars": 4173,
    "preview": "# Manage regular incremental, compressed, and encrypted backups with [restic][].\n#\n# Backups are uploaded to the `barruc"
  },
  {
    "path": "shared/restic-backups/options.nix",
    "chars": 2750,
    "preview": "{ lib, ... }:\n\nwith lib;\n\nlet\n  backupOptions = {\n    paths = mkOption {\n      type = types.listOf types.str;\n      defa"
  },
  {
    "path": "shared/torrents/default.nix",
    "chars": 1564,
    "preview": "# [Transmission][] is a bittorrent client.  This module configures it along with\n# a web UI.\n#\n# This module does not in"
  },
  {
    "path": "shared/torrents/erase-your-darlings.nix",
    "chars": 251,
    "preview": "{ config, lib, ... }:\n\nwith lib;\nlet\n  cfg = config.nixfiles.torrents;\n  eyd = config.nixfiles.eraseYourDarlings;\nin\n{\n "
  },
  {
    "path": "shared/torrents/options.nix",
    "chars": 1776,
    "preview": "{ lib, ... }:\n\nwith lib;\n\n{\n  options.nixfiles.torrents = {\n    enable = mkOption {\n      type = types.bool;\n      defau"
  },
  {
    "path": "shared/torrents/transmission_3/default.nix",
    "chars": 4352,
    "preview": "{\n  stdenv,\n  lib,\n  fetchFromGitHub,\n  cmake,\n  pkg-config,\n  openssl,\n  curl,\n  libevent,\n  inotify-tools,\n  systemd,\n"
  },
  {
    "path": "shared/torrents/transmission_3/transmission-3.00-miniupnpc-2.2.8.patch",
    "chars": 778,
    "preview": "diff --git a/libtransmission/upnp.c b/libtransmission/upnp.c\nindex c9e248a379...c7b2580bcb 100644\n--- a/libtransmission/"
  },
  {
    "path": "shared/torrents/transmission_3/transmission-3.00-openssl-3.patch",
    "chars": 1136,
    "preview": "From 6ee128b95bacaff20746538dc97c2b8e2b9fcc29 Mon Sep 17 00:00:00 2001\nFrom: Mike Gilbert <floppym@gentoo.org>\nDate: Sun"
  },
  {
    "path": "tools/provision-machine.sh",
    "chars": 3602,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\n# see https://nixfiles.docs.barrucadu.dev/runbooks/set-up-a-new-host.html\n\nMODE=\"$1\"\nDEVICE"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the barrucadu/nixfiles GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 95 files (1.1 MB), approximately 261.1k tokens, and a symbol index with 22 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!