diff --git a/.envrc b/.envrc
index 527c643..30bb3c7 100644
--- a/.envrc
+++ b/.envrc
@@ -1,2 +1,2 @@
 watch_file ./devshells/satellite.nix
-use flake .#satellite
+use flake .#satellite --accept-flake-config
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..0a4be5c
--- /dev/null
+++ b/justfile
@@ -0,0 +1,159 @@
+[private]
+default:
+  @just --list
+
+hostname := `hostname`
+
+# {{{ Nixos rebuilds
+[doc("Wrapper around `nixos-rebuild`, taking care of the generic arguments")]
+[group("nix")]
+nixos-rebuild action="rebuild" host=hostname:
+  #!/usr/bin/env python3
+  import subprocess
+
+  host = "{{host}}"
+  users = {
+    'tethys': 'adrielus',
+    'lapetus': 'adrielus',
+    'calypso': 'moon',
+  }
+
+  args = [
+    "nixos-rebuild",
+    "{{action}}",
+    "--show-trace",
+    "--fast",
+    "--accept-flake-config",
+    "--flake", 
+    ".#{{host}}"
+  ]
+
+  if "{{host}}" == "{{hostname}}": 
+    print("๐Ÿงฌ Switching nixos configuration (locally) for '{{BLUE + host + NORMAL}}'")
+    args.prepend("sudo")
+  else:
+    print("๐Ÿงฌ Switching nixos configuration (remotely) for '{{BLUE + host + NORMAL}}'")
+    args.append("--use-remote-sudo")
+    args += ["--target-host", f"{users[host]}@{host}"]
+
+  subprocess.run(args, check=True)
+  print("๐Ÿš€ All done!")
+# }}}
+# {{{ Miscellaneous nix commands
+[doc("Build the custom ISO provided by the flake")]
+[group("nix")]
+build-iso:
+  nix build .#nixosConfigurations.iso.config.system.build.isoImage
+
+[doc("Bumps flake inputs that usually need to be as up to date as possible")]
+[group("nix")]
+bump-common:
+  nix flake update \
+    nixpkgs-unstable \
+    neovim-nightly-overlay \
+    firefox-addons \
+    base16-schemes
+# }}}
+
+# {{{ Age / sops related thingies
+[doc("Save the user's SSH key as a key usable by sops")]
+[group("secrets")]
+ssh-to-age:
+  @echo "๐Ÿ“ Creating sops directory" >&2
+  mkdir -p ~/.config/sops/age
+
+  @echo "๐Ÿ”‘ Converting ssh key to age" >&2
+  ssh-to-age -private-key -i ~/.ssh/id_ed25519 > ~/.config/sops/age/keys.txt
+
+[doc("Print the public age key used by sops on this machine")]
+[group("secrets")]
+age-public-key: ssh-to-age
+  @echo "๐Ÿ”‘ Printing public age key" >&2
+  age-keygen -y ~/.config/sops/age/keys.txt
+
+[doc("Rekey every secrets file in the repository")]
+[group("secrets")]
+sops-rekey:
+  #!/usr/bin/env python3
+  import glob
+  import subprocess
+
+  paths = glob.glob("./**/secrets.yaml", recursive=True)
+  for file in paths:
+    print(f"๐Ÿ”‘ Rekeying {file}")
+    subprocess.run(["sops", "updatekeys", "--yes", file], check=True)
+
+  print(f"๐Ÿš€ Successfully rekeyed {len(paths)} files!")
+
+[doc("Export keys to the hermes USB device")]
+[group("secrets")]
+export-keys:
+  #!/usr/bin/env bash
+  set -euo pipefail # Fail on errors and whatnot
+
+  dir=/hermes/secrets/{{hostname}}/
+  mkdir -p $dir
+
+  cp /persist/state/etc/ssh/ssh* $dir
+  cp /home/*/.ssh/id* $dir
+
+  # Perhaps I should ask this as a prompt instead?
+  touch $dir/disk.key
+  echo "๐Ÿ’ซ Don't forget to provide a disk encryption key!"
+# }}}
+# {{{ Rsync
+# TODO: move this to some sort of oneshot service
+[doc("Give every machine access to the restic backups")]
+[group("secrets")]
+update-rsync-keys:
+  #!/usr/bin/env bash
+  set -euo pipefail # Fail on errors and whatnot
+  shopt -s nullglob # Make globs expand to [] if no match
+
+  keys=(hosts/nixos/*/keys/*.pub)
+
+  if [ "${#keys[@]}" -eq 0 ]; then
+    echo "โŒ No SSH public keys found. Exiting." >&2
+    exit 1
+  fi
+
+  tmpfile=$(mktemp)
+  url=$(cat hosts/nixos/common/optional/services/restic/url.txt)
+
+  echo "๐Ÿ”‘ Copying ${#keys[@]} keys to $url"
+  cat ${keys[@]} > $tmpfile
+  scp $tmpfile $url:.ssh/authorized_keys
+
+  rm -f $tmpfile
+  echo "๐Ÿš€ Successfully updated rsync.net SSH keys!"
+# }}}
+
+# {{{ DNS
+[doc("Prints the differences between the current and desired DNS records")]
+[group("dns")]
+dns-diff:
+  nix run .#octodns-sync --
+
+[doc("Syncs DNS records using octodns")]
+[group("dns")]
+dns-push:
+  nix run .#octodns-sync -- --doit
+
+[doc("Clears every DNS record")]
+[group("dns")]
+dns-clear zoneid bearer:
+  #!/usr/bin/env bash
+  set -euo pipefail # Fail on errors and whatnot
+
+  # Taken from https://developers.cloudflare.com/dns/zone-setups/troubleshooting/delete-all-records/
+  curl --silent "https://api.cloudflare.com/client/v4/zones/{{zoneid}}/dns_records?per_page=50000" \
+  --header "Authorization: Bearer {{bearer}}" \
+  | jq --raw-output '.result[].id' | while read id
+  do
+    echo "๐Ÿงน Deleting '$id' record in zone '{{zoneid}}'"
+    curl --silent --request DELETE "https://api.cloudflare.com/client/v4/zones/{{zoneid}}/dns_records/$id" \
+  --header "Authorization: Bearer {{bearer}}"
+  done
+
+  echo "๐Ÿš€ All done!"
+# }}}
diff --git a/scripts/age-public-key.sh b/scripts/age-public-key.sh
deleted file mode 100755
index c6298ed..0000000
--- a/scripts/age-public-key.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/env bash
-nix-shell -p age --run "age-keygen -y ~/.config/sops/age/keys.txt"
diff --git a/scripts/build-iso.sh b/scripts/build-iso.sh
deleted file mode 100755
index 4a595ae..0000000
--- a/scripts/build-iso.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/env bash
-nix build .#nixosConfigurations.iso.config.system.build.isoImage
diff --git a/scripts/dns/delete-all-records.sh b/scripts/dns/delete-all-records.sh
deleted file mode 100755
index 565c1f6..0000000
--- a/scripts/dns/delete-all-records.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-zoneid=$1
-bearer=$2
-
-# Taken from https://developers.cloudflare.com/dns/zone-setups/troubleshooting/delete-all-records/
-curl --silent "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records?per_page=50000" \
---header "Authorization: Bearer $bearer" \
-| jq --raw-output '.result[].id' | while read id
-do
-  echo "๐Ÿงน Deleting '$id' record in zone '$zoneid'"
-  curl --silent --request DELETE "https://api.cloudflare.com/client/v4/zones/$zoneid/dns_records/$id" \
---header "Authorization: Bearer $bearer"
-done
-
-echo "๐Ÿš€ All done!"
diff --git a/scripts/github/.gitignore b/scripts/github/.gitignore
deleted file mode 100644
index f500967..0000000
--- a/scripts/github/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-repos.json
diff --git a/scripts/github/README.md b/scripts/github/README.md
deleted file mode 100755
index 84cb349..0000000
--- a/scripts/github/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This dir contains a collection of helpers I have used when changing my github username. In particular, I created a github org with my old name, forked all my repos there, automatically adding disclaimers to the readme about the location moving. That way, a malicious user cannot create an account with my old name and inject malicious code into locations like the old url for my nvim plugin, which some people might still be using.
diff --git a/scripts/github/fetch-repos.sh b/scripts/github/fetch-repos.sh
deleted file mode 100755
index 064919e..0000000
--- a/scripts/github/fetch-repos.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-gh repo list --limit 200 --json name,description,url,sshUrl,owner,visibility,isArchived,isFork \
-  | jq > ./repos.json
diff --git a/scripts/github/rename.py b/scripts/github/rename.py
deleted file mode 100755
index 7ddc8d2..0000000
--- a/scripts/github/rename.py
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env nix-shell
-#!nix-shell -p python3 -i python3
-import json
-import os
-import subprocess
-import tempfile
-import shutil
-
-file_path = './repos.json'
-general_org = "temporarymoon" # Get it? Because forks involve many trees, which like, form a canopy or whatever... :/
-fork_org = "starlitcanopy"
-future_name = "prescientmoon"
-current_name = "mateiadrielrafael"
-
-with open(file_path, 'r') as file:
-    data = json.load(file)
-
-print(f"Parsed {len(data)} repos")
-
-with tempfile.TemporaryDirectory() as temp_dir:
-    print(f"Temporary directory: {temp_dir}")
-    for repo in data:
-        name = repo["name"]
-        url = repo["url"]
-        org = fork_org if repo['isFork'] else general_org
-        fork = f"{org}/{name}"
-
-        os.chdir(temp_dir)
-
-        if repo["isFork"]:
-            subprocess.run(f"gh api repos/{current_name}/{name}/transfer -f new_owner={org}", shell=True)
-        else:
-            subprocess.run(f"gh repo fork {url} --clone --org {org} --default-branch-only", shell=True)
-            os.chdir(f"{temp_dir}/{name}")
-
-            # Create the readme if it doesn't exist
-            if not os.path.exists("README.md"):
-                with open("README.md", 'w') as file:
-                    file.write('')
-
-            # Read the existing content of the readme
-            with open("README.md", 'r') as file:
-                existing_content = file.read()
-
-            future = f"{future_name}/{name}"
-
-            # Add disclaimer at top
-            text_to_prepend = f"# ๐Ÿšง This repo has been moved to [{future}](https://github.com/{future}) ๐Ÿšง\n"
-
-            with open("README.md", 'w') as file:
-                file.write(text_to_prepend + existing_content)
-
-            # Commit changes
-            subprocess.run("git add .", shell=True)
-            subprocess.run("git commit -m 'Added movement notice to readme [skip-ci]'", shell=True)
-            subprocess.run("git push", shell=True)
-
-            # Fix visibility and archive repo
-            visibility = repo["visibility"]
-            if visibility != "public":
-                subprocess.run(f"gh repo edit {fork} --visibility {visibility}", shell=True)
-            subprocess.run(f"gh repo archive {fork} --yes", shell=True)
-            shutil.rmtree(f"{temp_dir}/{name}")
-
-        print(f"Done moving to {fork}")
diff --git a/scripts/live.sh b/scripts/live.sh
index 580e102..ad55e2a 100755
--- a/scripts/live.sh
+++ b/scripts/live.sh
@@ -2,6 +2,8 @@
 #!nix-shell ../devshells/bootstrap/shell.nix
 #!nix-shell -i bash
 
+# TODO: convert to justfile
+
 # Check if at least one argument is provided
 if [ "$#" != "2" ] && [ "$#" != "3" ]; then
     echo "โ“ Usage: $0 <host> <disko-mode> [action]"
diff --git a/scripts/rebuild.sh b/scripts/rebuild.sh
deleted file mode 100755
index 5977212..0000000
--- a/scripts/rebuild.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-sudo nixos-rebuild switch \
-  --show-trace \
-  --fast \
-  --accept-flake-config \
-  --flake .#$(hostname)
diff --git a/scripts/repl.sh b/scripts/repl.sh
deleted file mode 100755
index c8cfb4f..0000000
--- a/scripts/repl.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/usr/bin/env bash
-nix repl .#nixosConfigurations.$1.config
diff --git a/scripts/save-keys.sh b/scripts/save-keys.sh
deleted file mode 100755
index 40c2644..0000000
--- a/scripts/save-keys.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-dir=/hermes/secrets/$(hostname)/
-mkdir -p $dir
-cp /persist/state/etc/ssh/ssh* $dir
-cp /home/*/.ssh/id* $dir
-touch $dir/disk.key
-
-echo "๐Ÿ’ซ Don't forget to provide a disk encryption key!"
diff --git a/scripts/setup-rsync-ssh.sh b/scripts/setup-rsync-ssh.sh
deleted file mode 100755
index 8a59a41..0000000
--- a/scripts/setup-rsync-ssh.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-# Create tmp file
-tmpfile=$(mktemp)
-
-# Concat files
-cat hosts/nixos/*/keys/*.pub > $tmpfile
-
-# Copy concat result
-scp $tmpfile $(cat hosts/nixos/common/optional/services/restic/url.txt):.ssh/authorized_keys
-
-# Cleanup file
-rm -rf $tmpfile
diff --git a/scripts/sops-rekey.sh b/scripts/sops-rekey.sh
deleted file mode 100755
index aa59227..0000000
--- a/scripts/sops-rekey.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env nix-shell
-#!nix-shell -p sops -i bash
-
-# https://askubuntu.com/questions/1010707/how-to-enable-the-double-star-globstar-operator
-# Enable the ** operator
-shopt -s globstar
-
-for file in ./**/secrets.yaml; do
-  echo "๐Ÿ”‘ Rekeying $file"
-  sops updatekeys --yes $file
-done
-
-echo "๐Ÿš€ All done!"
diff --git a/scripts/ssh-to-age.sh b/scripts/ssh-to-age.sh
deleted file mode 100755
index 875c544..0000000
--- a/scripts/ssh-to-age.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-echo "๐Ÿ“ Creating sops directory"
-mkdir -p ~/.config/sops/age
-
-echo "๐Ÿ”‘ Converting ssh key to age"
-nix-shell -p ssh-to-age --run "ssh-to-age -private-key -i ~/.ssh/id_ed25519 > ~/.config/sops/age/keys.txt"
-
-echo "๐Ÿš€ All done"