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": [
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
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.