about summary refs log tree commit diff
path: root/nix
diff options
context:
space:
mode:
Diffstat (limited to 'nix')
-rw-r--r--nix/hosts/caladan/darwin-configuration.nix9
-rw-r--r--nix/hosts/caladan/emacs_config.el20
-rw-r--r--nix/hosts/caladan/home_emile.nix21
-rw-r--r--nix/hosts/caladan/nvim_plugins.nix21
-rw-r--r--nix/hosts/caladan/overlay.nix5
-rw-r--r--nix/hosts/corrino/configuration.nix27
-rw-r--r--nix/hosts/corrino/secrets/garage_admin_metrics_secret.age7
-rw-r--r--nix/hosts/corrino/secrets/garage_admin_token_secret.age7
-rw-r--r--nix/hosts/corrino/secrets/garage_env.agebin0 -> 602 bytes
-rw-r--r--nix/hosts/corrino/secrets/garage_rpc_secret.agebin387 -> 387 bytes
-rw-r--r--nix/hosts/corrino/secrets/miniflux_admin_file.age7
-rw-r--r--nix/hosts/corrino/secrets/miniflux_oidc_secret.agebin0 -> 395 bytes
-rw-r--r--nix/hosts/corrino/secrets/tailscale-corrino-cert.agebin0 -> 3247 bytes
-rw-r--r--nix/hosts/corrino/secrets/tailscale-corrino-key.age8
-rw-r--r--nix/hosts/corrino/www/grafana.emile.space.nix19
-rw-r--r--nix/hosts/corrino/www/mc.emile.space.nix150
-rw-r--r--nix/hosts/corrino/www/md.emile.space.nix22
-rw-r--r--nix/hosts/corrino/www/miniflux.emile.space.nix73
-rw-r--r--nix/hosts/corrino/www/sb.emile.space.nix6
-rw-r--r--nix/hosts/corrino/www/sso.emile.space.nix29
-rw-r--r--nix/hosts/lampadas/configuration.nix63
-rw-r--r--nix/modules/libvirtnix/config.nix59
-rw-r--r--nix/modules/libvirtnix/domain.nix1126
-rw-r--r--nix/modules/libvirtnix/test.nix10
-rw-r--r--nix/modules/libvirtnix/xml.nix45
-rw-r--r--nix/pkgs/glibc-all-in-one/default.nix25
-rw-r--r--nix/templates/ctf/flake.lock141
-rw-r--r--nix/templates/ctf/flake.nix62
-rw-r--r--nix/templates/ctf/solve.py15
29 files changed, 1230 insertions, 747 deletions
diff --git a/nix/hosts/caladan/darwin-configuration.nix b/nix/hosts/caladan/darwin-configuration.nix
index ef18642..6fdbdaa 100644
--- a/nix/hosts/caladan/darwin-configuration.nix
+++ b/nix/hosts/caladan/darwin-configuration.nix
@@ -85,10 +85,10 @@
       {
         hostName = "corrino.emile.space";
         system = "x86_64-linux";
-        maxJobs = 16;
+        maxJobs = 10;
         speedFactor = 2;
 
-        # Feature	      | Derivations requiring it
+        # Feature	        | Derivations requiring it
         # ----------------|-----------------------------------------------------
         # kvm	            | Everything which builds inside a vm, like NixOS tests
         # nixos-test	    | Machine can run NixOS tests
@@ -110,8 +110,9 @@
     ];
   };
 
-  nixpkgs = {
-    config.allowUnfree = true;
+  nixpkgs.config = {
+    allowUnfree = true;
+    allowUnsupportedSystem = true;
   };
 
   services.nix-daemon.enable = true;
diff --git a/nix/hosts/caladan/emacs_config.el b/nix/hosts/caladan/emacs_config.el
index 0ed5786..01cf5bd 100644
--- a/nix/hosts/caladan/emacs_config.el
+++ b/nix/hosts/caladan/emacs_config.el
@@ -17,10 +17,7 @@
   (unless (package-installed-p package)
     (package-install package)))
 
-(when (display-graphic-p)
-  (tool-bar-mode 0)
-  (scroll-bar-mode 'left))
-
+(scroll-bar-mode -1)
 (load-theme 'leuven) ;; light theme
 (setq pixel-scroll-precision-mode 1)
 (xterm-mouse-mode 1)
@@ -42,9 +39,6 @@
                (display-buffer-no-window)
                (allow-no-window . t)))
 
-(when (not (display-graphic-p))
-      (menu-bar-mode -1))
-
 ;; general purpose emacs settings
 (use-package emacs
   :init
@@ -148,7 +142,6 @@
               completion-category-overrides '((file (styles partial-completion)))))
               
 
-
 ;; markdown mode
 ;; https://jblevins.org/projects/markdown-mode/
 (defvar markdown-command)
@@ -181,7 +174,7 @@
   :ensure nil ; no need to install it as it is built-in, but needs to be activated
   :hook (after-init . delete-selection-mode))
 
-;; Configure the Lisp program for SLIME
+;; Configure the Lisp program for SLY
 (add-to-list 'exec-path "/Users/emile/.nix-profile/bin")
 (defvar inferior-lisp-program "sbcl")
 
@@ -206,14 +199,5 @@
 (use-package breadcrumb
      :ensure t)
 
-;(setq circe-network-options
-;  '(("Libera Chat"
-;     :tls t
-;     :tls-keylist (("/Users/emile/libera.crt"
-;                    "/Users/emile/libera.key"))
-;     :sasl-external t
-;     :nick "hanemile"
-;     :channels ("#test"))))
-
 (provide '.emacs)
 ;;; emacs_config.el ends here
diff --git a/nix/hosts/caladan/home_emile.nix b/nix/hosts/caladan/home_emile.nix
index 86d6965..545c4d5 100644
--- a/nix/hosts/caladan/home_emile.nix
+++ b/nix/hosts/caladan/home_emile.nix
@@ -1,4 +1,4 @@
-{ pkgs, ... }:
+{ lib, pkgs, ... }:
 
 {
   home = {
@@ -60,6 +60,18 @@
       '';
     };
 
+    neovim = let
+      custom_plugins = pkgs.callPackage ./nvim_plugins.nix { };
+    in {
+      enable = true;
+      plugins = with pkgs.vimPlugins // custom_plugins; [
+        neovim-ayu
+        lisp.vlime
+      ];
+      extraConfig = ''
+      '';
+    };
+
     emacs = {
       enable = true;
       package = pkgs.emacs;
@@ -174,7 +186,7 @@
     nixos-rebuild
 
     # editor
-    unstable.helix
+    unstable-darwin.helix
 
     ## formatter
     nixfmt-rfc-style # official formatter for nix code
@@ -200,6 +212,7 @@
     # go foo
     go
     delve
+    gotools
 
     # c foo
     cmake
@@ -249,6 +262,10 @@
 
     drawio
 
+    # cargo rustup
+    cargo
+
+    # custom
     libc-database
 
     # blender
diff --git a/nix/hosts/caladan/nvim_plugins.nix b/nix/hosts/caladan/nvim_plugins.nix
new file mode 100644
index 0000000..7f07816
--- /dev/null
+++ b/nix/hosts/caladan/nvim_plugins.nix
@@ -0,0 +1,21 @@
+{ vimUtils, fetchgit, ... }:
+
+let
+  build = ({name, owner, rev, sha256}: vimUtils.buildVimPlugin {
+    inherit name;
+    src = fetchgit {
+      inherit rev sha256;
+      url = "https://github.com/${owner}/${name}";
+    };
+    dependencies = [];
+  });
+in {
+  lisp = {
+    vlime = build {
+      name = "vlime";
+      owner = "l04m33";
+      rev = "065b95f3ac7a455314c2bdefeb2b792f290034df";
+      sha256 = "1bmmskdwvbl6lvbnjp9lls86rz0vzmk73y644bjb9ix9ygmjbia4";
+    };
+  };
+}
diff --git a/nix/hosts/caladan/overlay.nix b/nix/hosts/caladan/overlay.nix
index c9bdd79..8f3b810 100644
--- a/nix/hosts/caladan/overlay.nix
+++ b/nix/hosts/caladan/overlay.nix
@@ -33,11 +33,6 @@
             rm tools/utils/passwd_test.go
           '';
         });
-
-        # helix-2303 = self.callPackage ../../pkgs/helix-2303 { };
-        # r2 = self.callPackage ../../pkgs/radare2-5.8.4 { };
-        # ansel = self.callPackage ../../pkgs/ansel { };
-        # typst = self.callPackage ../pkgs/radare2-5.8.4 { };
       })
     ];
     config = {
diff --git a/nix/hosts/corrino/configuration.nix b/nix/hosts/corrino/configuration.nix
index 52e9ecf..2f8954b 100644
--- a/nix/hosts/corrino/configuration.nix
+++ b/nix/hosts/corrino/configuration.nix
@@ -46,6 +46,7 @@ in
 
     ./www/tickets.emile.space.nix
     # ./www/talks.emile.space.nix
+    ./www/miniflux.emile.space.nix
     # ./www/stream.emile.space.nix
     ./www/md.emile.space.nix
     ./www/social.emile.space.nix
@@ -556,13 +557,25 @@ in
     };
 
     "/mnt/storagebox-bx11" = {
-      device = "//u331921.your-storagebox.de/backup";
-      fsType = "cifs";
-      options =
-        let
-          automount_opts = "_netdev,x-systemd.automount,noauto,x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s";
-        in
-        [ "${automount_opts},credentials=${config.age.secrets.storage_box_bx11_password.path}" ];
+      device = "u331921@u331921.your-storagebox.de:/home/backup";
+      fsType = "sshfs";
+      options = [ # Filesystem options
+        "allow_other"          # for non-root access
+        "_netdev"              # this is a network fs
+
+        # We don't mount on demand, as that will cause services like navidrome to fail
+        # as the share doesn't yet exist.
+        #"x-systemd.automount" # mount on demand, rather than boot
+
+        #"debug"               # print debug logging
+                               # warning: this causes the one-shot service to never exit
+
+        # SSH options
+        "StrictHostKeyChecking=no"  # prevent the connection from failing if the host's key hasn't been trusted yet
+        "ServerAliveInterval=15" # keep connections alive
+        "Port=23"
+        "IdentityFile=/root/.ssh/id_ed25519"
+      ];
     };
   };
 
diff --git a/nix/hosts/corrino/secrets/garage_admin_metrics_secret.age b/nix/hosts/corrino/secrets/garage_admin_metrics_secret.age
new file mode 100644
index 0000000..e1af7da
--- /dev/null
+++ b/nix/hosts/corrino/secrets/garage_admin_metrics_secret.age
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 gvwQ2Q 7QkcpYGeeMsbW0GcXzNGPTc0jUf4ydpMiTO6ZxEIKGY
+OOxq2hMORsmUzBuoqOIPNJeLqJB0seve9PhorS6PKNs
+-> ssh-ed25519 m8VklA pF7mWG6tviFC6qD88dxoQRnXGfR0AuanVyY+bh8XgV0
+mrk4HgEs3i8y5P+BSGM1psweXpY/xO+8vK/DsXyhyiY
+--- zqEl/ZN/3jEgMZ/IbPbyTHGZJDDENLOnoQezaACeoSs
+l,±W`\6yh.
oV(?Em,;(@0dVA=4v
\ No newline at end of file
diff --git a/nix/hosts/corrino/secrets/garage_admin_token_secret.age b/nix/hosts/corrino/secrets/garage_admin_token_secret.age
new file mode 100644
index 0000000..2a18a6b
--- /dev/null
+++ b/nix/hosts/corrino/secrets/garage_admin_token_secret.age
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 gvwQ2Q hcMMVkZSsObrOFjetml2z4eH+EfnuSsna+GaXEeMUA4
+y6lFBj49cMhOGuJBpILHsykpBMpKDHZpFXR4E4zZEbg
+-> ssh-ed25519 m8VklA Z6zLilTWlGWG17Q6jBx13m3KYs3gE93TPLq0CidHeTA
+eqMN5mDMasi/Nw2y5Kgwy2COna+3zbbFTTUrD/O26ls
+--- QdVyqrTLmEcGSB37Ft3Ur0Ry9Jk9DyHFI6fo88tnsgI
+X`wY,<A$XeDGe	;െ>1nsNr_Y\`)F#{<
\ No newline at end of file
diff --git a/nix/hosts/corrino/secrets/garage_env.age b/nix/hosts/corrino/secrets/garage_env.age
new file mode 100644
index 0000000..becb511
--- /dev/null
+++ b/nix/hosts/corrino/secrets/garage_env.age
Binary files differdiff --git a/nix/hosts/corrino/secrets/garage_rpc_secret.age b/nix/hosts/corrino/secrets/garage_rpc_secret.age
index e228d0d..ce8a65a 100644
--- a/nix/hosts/corrino/secrets/garage_rpc_secret.age
+++ b/nix/hosts/corrino/secrets/garage_rpc_secret.age
Binary files differdiff --git a/nix/hosts/corrino/secrets/miniflux_admin_file.age b/nix/hosts/corrino/secrets/miniflux_admin_file.age
new file mode 100644
index 0000000..3e00b9b
--- /dev/null
+++ b/nix/hosts/corrino/secrets/miniflux_admin_file.age
@@ -0,0 +1,7 @@
+age-encryption.org/v1
+-> ssh-ed25519 gvwQ2Q OGds4NLmRiMmVjPTORP3jLe3iEkqrDyTqW4V7ceFfRk
+FFdZcsT9ZruNhpY5cb674qpQpK0qzHNwRPCfHvYaKcE
+-> ssh-ed25519 m8VklA 84XSPja8dzJEUVR5olwNONVzNn5QrsX+R+WeBHqxXDo
+5CVpnTDcO0EG3NsHdFsSABWNBIe3Xe16me13JIOlfos
+--- rlIUU/0gYwxIXmpRI5/3mmZXJ+JrG/tE/3IBtpo4uT4
+-9JNVG.l.e?&r;V#ahSJWjhvѳt;3ϫk(^QU	*czFbwPZuqĉ
\ No newline at end of file
diff --git a/nix/hosts/corrino/secrets/miniflux_oidc_secret.age b/nix/hosts/corrino/secrets/miniflux_oidc_secret.age
new file mode 100644
index 0000000..c16754e
--- /dev/null
+++ b/nix/hosts/corrino/secrets/miniflux_oidc_secret.age
Binary files differdiff --git a/nix/hosts/corrino/secrets/tailscale-corrino-cert.age b/nix/hosts/corrino/secrets/tailscale-corrino-cert.age
new file mode 100644
index 0000000..07252cc
--- /dev/null
+++ b/nix/hosts/corrino/secrets/tailscale-corrino-cert.age
Binary files differdiff --git a/nix/hosts/corrino/secrets/tailscale-corrino-key.age b/nix/hosts/corrino/secrets/tailscale-corrino-key.age
new file mode 100644
index 0000000..36c132e
--- /dev/null
+++ b/nix/hosts/corrino/secrets/tailscale-corrino-key.age
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> ssh-ed25519 gvwQ2Q P6b4m51AxFbXT3OOkgMe/BPZi3240e/Gii3weyMtPxI
+fRVIno8tPqh4F6e6TOj6YiW2uL9T3uqkro6EZ1mPXOc
+-> ssh-ed25519 m8VklA lRMfdLzmoVybkJJvTlY1lZgkMt1R0wyjA/NFcdRFKDM
+CLyqRXYetMUbsGhL8NRQ333WIy/TnJwhWX8UpxyLmbw
+--- zTjX+CIXtDurBc+TaT7zQ0xn/5Xx3mIrKkAviqMgn4c
+:$+D\[Q.pS<HQWMTLTGxZV'^pNDGF*ڢ:6`ˉ`aMdbN\ȣ	ClH7>i_!
%neUVI4sA6_kP4-=.dxqt+)4`+E2R&0__$V}Hojܾ}aB|//@L#IK3Dt
+C?4?u}7!
\ No newline at end of file
diff --git a/nix/hosts/corrino/www/grafana.emile.space.nix b/nix/hosts/corrino/www/grafana.emile.space.nix
index 22b444f..f8674a2 100644
--- a/nix/hosts/corrino/www/grafana.emile.space.nix
+++ b/nix/hosts/corrino/www/grafana.emile.space.nix
@@ -91,19 +91,30 @@
       provision = {
         dashboards.settings = { };
         datasources.settings = {
+          deleteDatasources = [
+            { name = "Prometheus"; orgId = 1; }  
+            { name = "Lampadas"; orgId = 1; }  
+          ];
           datasources = [
             {
               url = "http://localhost:${toString config.services.prometheus.port}";
               type = "prometheus";
-              name = "Prometheus";
+              name = "Prometheus Corrino";
               editable = false;
               access = "proxy"; # server = "proxy", browser = "direct"
             }
             {
-              name = "loki";
-              url = "http://${config.services.loki.configuration.common.instance_addr}:${toString config.services.loki.configuration.server.http_listen_port}";
-              type = "loki";
+              url = "http://lampadas:9009";
+              type = "prometheus";
+              name = "Prometheus Lampadas";
+              editable = false;
+              access = "proxy"; # server = "proxy", browser = "direct"
             }
+            # {
+            #   name = "loki";
+            #   url = "http://${config.services.loki.configuration.common.instance_addr}:${toString config.services.loki.configuration.server.http_listen_port}";
+            #   type = "loki";
+            # }
           ];
         };
 
diff --git a/nix/hosts/corrino/www/mc.emile.space.nix b/nix/hosts/corrino/www/mc.emile.space.nix
new file mode 100644
index 0000000..8250a1d
--- /dev/null
+++ b/nix/hosts/corrino/www/mc.emile.space.nix
@@ -0,0 +1,150 @@
+{ config, pkgs, ... }:
+
+{
+  services.minecraft-server = {
+    package = pkgs.minecraft-server;
+    serverProperties = {
+      server-port = 43000;
+
+      # 0 peaceful
+      # 1 easy
+      # 2 normal
+      # 3 hard
+      difficulty = 1;
+
+      # 0 survival
+      # 1 creative
+      # 2 adventure
+      # 5 default
+      # "spectator" spectator
+      # gamemode = "survival";
+      gamemode = 0;
+
+      max-players = 10;
+      motd = "Neurodivergenter Hexenzirkel";
+      enable-rcon = true;
+      "rcon.password" = "hunter2";
+      enable-command-block = false;
+      enable-query = false;
+      spawn-protection = 0;
+
+      white-list = true;
+    };
+    openFirewall = true;
+
+    whitelist = {
+      "emileemail" = "a7614a53-b8b8-47b7-91cf-860e7c7f325f";
+      "dodonator23" = "f93506b6-76e8-437d-927d-dceeb833a33f";
+      "ChaosAyumi" = "223040ec-ca30-4238-8b58-c81597c30426";
+      "xerunala" = "962e41c8-1da8-4592-9a2f-e36cdb20d5a6";
+      "rappet" = "588377a5-362f-4ea1-8195-9cf97dd7a884";
+    };
+
+    jvmOpts = "-Xms4092M -Xmx4092M";
+    eula = true;
+    enable = true;
+    declarative = true;
+    dataDir = "/var/lib/minecraft";
+  };
+
+  services.nginx.virtualHosts."mc.emile.space" = {
+    forceSSL = true;
+    enableACME = true;
+  };
+
+  services.bluemap = {
+    enable = true;
+
+    enableNginx = true;
+    host = "mc.emile.space";
+
+    webappSettings = {
+      enabled = true;
+      webroot = config.services.bluemap.webRoot;
+    };
+
+    # webserverSettings = {};
+    webserverSettings.enabled = false; # using nginx;
+    webRoot = "/var/lib/bluemap/web";
+
+    # coreSettings = {};
+    coreSettings.data = "/var/lib/bluemap";
+    coreSettings.metrics = false; # don't send data to the devs
+
+    storage = {
+      "file" = {
+        root = "${config.services.bluemap.webRoot}/maps";
+      };
+    };
+    # storage.<name>.storage-type
+
+    maps = let
+      worldpath = "/var/lib/minecraft/world";
+    in {
+      "overworld" = {
+        world = "${worldpath}";
+        ambient-light = 0.1;
+        cave-detection-ocean-floor = -5;
+        dimension = "minecraft:overworld";
+      };
+
+      "nether" = {
+        world = "${worldpath}/DIM-1";
+        sorting = 100;
+        sky-color = "#290000";
+        void-color = "#150000";
+        ambient-light = 0.6;
+        world-sky-light = 0;
+        remove-caves-below-y = -10000;
+        cave-detection-ocean-floor = -5;
+        cave-detection-uses-block-light = true;
+        max-y = 90;
+        dimension = "minecraft:the_nether";
+      };
+
+      "end" = {
+        world = "${worldpath}/DIM1";
+        sorting = 200;
+        sky-color = "#080010";
+        void-color = "#080010";
+        ambient-light = 0.6;
+        world-sky-light = 0;
+        remove-caves-below-y = -10000;
+        cave-detection-ocean-floor = -5;
+        dimension = "minecraft:the_end";
+      };
+    };
+
+    # A set of resourcepacks, datapacks, and mods to extract resources from, loaded in alphabetical order.
+    packs = {};
+
+    # How often to trigger rendering the map, in the format of a systemd timer onCalendar configuration. See systemd.timer(5).
+    #
+    # This one means "every three hours":
+    # *-*-* */3:00:00
+    onCalendar = "*-*-* *:00:00";
+
+    eula = true;
+
+    enableRender = true;
+
+    # The world used by the default map ruleset. If you configure your own maps you do not need to set this.
+    # defaultWorld = "${config.services.minecraft.dataDir}/world";
+     
+    addons = {};
+  };
+
+  services.restic.backups."minecraft" = {
+    repository = "/mnt/storagebox-bx11/minecraft";
+    paths = [ "/var/lib/minecraft" ];
+    timerConfig = null;
+    passwordFile = config.age.secrets.restic_password.path;
+    initialize = true;
+    pruneOpts = [
+      "--keep-daily 7"
+      "--keep-weekly 5"
+      "--keep-monthly 12"
+      "--keep-yearly 75"
+    ];
+  };
+}
diff --git a/nix/hosts/corrino/www/md.emile.space.nix b/nix/hosts/corrino/www/md.emile.space.nix
index 6088ea0..d94c06c 100644
--- a/nix/hosts/corrino/www/md.emile.space.nix
+++ b/nix/hosts/corrino/www/md.emile.space.nix
@@ -6,7 +6,7 @@
     enableACME = true;
     locations = {
       "/" = {
-        proxyPass = "http://127.0.0.1:${toString config.services.hedgedoc.settings.port}";
+        proxyPass = "http://[${config.services.hedgedoc.settings.host}]:${toString config.services.hedgedoc.settings.port}";
       };
     };
   };
@@ -14,10 +14,10 @@
   # auth via authelia
   services.authelia.instances.main.settings.identity_providers.oidc.clients = [
     {
-      id = "HedgeDoc";
+      client_id = "HedgeDoc";
 
       # ; nix run nixpkgs#authelia -- crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986
-      secret = "$pbkdf2-sha512$310000$l4Kyec7Q9oY2GAhWA/xMig$P/MYFmulfgsDNyyiclUzd6le0oSiOvqCIvl4op5DkXtVTxLWlMA3ZwhJ6Z7u.OfIREuEM2htH6asxWPhBhkpNQ";
+      client_secret = "$pbkdf2-sha512$310000$l4Kyec7Q9oY2GAhWA/xMig$P/MYFmulfgsDNyyiclUzd6le0oSiOvqCIvl4op5DkXtVTxLWlMA3ZwhJ6Z7u.OfIREuEM2htH6asxWPhBhkpNQ";
       public = false;
       authorization_policy = "two_factor";
       redirect_uris = [ "https://md.emile.space/auth/oauth2/callback" ];
@@ -47,7 +47,7 @@
     environmentFile = config.age.secrets.hedgedoc_environment_variables.path;
 
     settings = {
-      host = "127.0.0.1";
+      host = "::1";
       port = config.emile.ports.md;
 
       domain = "md.emile.space";
@@ -85,6 +85,20 @@
     };
   };
 
+  services.restic.backups."hedgedoc" = {
+    repository = "/mnt/storagebox-bx11/hedgedoc";
+    paths = [ "/var/lib/hedgedoc" ];
+    timerConfig = null;
+    passwordFile = config.age.secrets.restic_password.path;
+    initialize = true;
+    pruneOpts = [
+      "--keep-daily 7"
+      "--keep-weekly 5"
+      "--keep-monthly 12"
+      "--keep-yearly 75"
+    ];
+  };
+
   # backups
   # services.restic.backups."hedgedoc" = {
   #   user = "u331921";
diff --git a/nix/hosts/corrino/www/miniflux.emile.space.nix b/nix/hosts/corrino/www/miniflux.emile.space.nix
new file mode 100644
index 0000000..f5b9817
--- /dev/null
+++ b/nix/hosts/corrino/www/miniflux.emile.space.nix
@@ -0,0 +1,73 @@
+{ config, pkgs, ... }:
+
+{
+	services.nginx.virtualHosts."miniflux.emile.space" = {
+		forceSSL = true;
+		enableACME = true;
+		locations = {
+			"/" = {
+				proxyPass = "http://${config.services.miniflux.config.LISTEN_ADDR}";
+			};
+		};
+	};
+
+  # auth via authelia
+  services.authelia.instances.main.settings.identity_providers.oidc.clients = [
+    {
+      id = "miniflux";
+
+      # ; nix run nixpkgs#authelia -- crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986
+      secret = "$pbkdf2-sha512$310000$rlOuqUDGc/kl3bw7JgcSpg$4COyNudsu/7L8qhnxfcQld5Fy.ru/JUp7RCI7dCHZMtzxRnhckW8A7uz3Xeuc7.BjCIwc4GdWusPt6.TiH6Kpw";
+      public = false;
+      authorization_policy = "two_factor";
+      redirect_uris = [ "https://miniflux.emile.space/oauth2/oidc/callback" ];
+      scopes = [
+        "openid"
+        "email"
+        "profile"
+      ];
+      grant_types = [
+        "refresh_token"
+        "authorization_code"
+      ];
+      response_types = [ "code" ];
+      response_modes = [
+        "form_post"
+        "query"
+        "fragment"
+      ];
+      token_endpoint_auth_method = "client_secret_post";
+    }
+  ];
+
+	services.miniflux = {
+		enable = true;
+		package = pkgs.miniflux;
+		config = {
+			BASE_URL = "https://miniflux.emile.space";
+
+			# Cleanup job frequency to remove old sessions and archive entries.
+		  CLEANUP_FREQUENCY = 48;
+
+			# Set to 1 to enable maintenance mode. Maintenance mode disables the web ui and show a text message to the users.
+			# MAINTENANCE_MODE = 1;
+			# MAINTENANCE_MESSAGE = "updating foo";
+			
+			OAUTH2_CLIENT_ID = "miniflux";
+			OAUTH2_CLIENT_SECRET_FILE = config.age.secrets.miniflux_oidc_secret.path;
+			OAUTH2_OIDC_DISCOVERY_ENDPOINT = "sso.emile.space";
+			OAUTH2_OIDC_PROVIDER_NAME = "authelia";
+			OAUTH2_PROVIDER = "oidc";
+			OAUTH2_REDIRECT_URL = "https://miniflux.emile.space/oauth2/oidc/callback";
+			
+		  LISTEN_ADDR = "[::1]:${toString config.emile.ports.miniflux}";
+		};
+		createDatabaseLocally = true;
+
+		# File containing the ADMIN_USERNAME and ADMIN_PASSWORD (length >= 6) in the format of an EnvironmentFile=, as described by systemd.exec(5).
+		adminCredentialsFile = config.age.secrets.miniflux_admin_file.path;
+	};
+	
+
+
+}
diff --git a/nix/hosts/corrino/www/sb.emile.space.nix b/nix/hosts/corrino/www/sb.emile.space.nix
index 1854f0e..0522e25 100644
--- a/nix/hosts/corrino/www/sb.emile.space.nix
+++ b/nix/hosts/corrino/www/sb.emile.space.nix
@@ -1,4 +1,4 @@
-{ pkgs, ... }:
+{ config, pkgs, ... }:
 
 {
   services.nginx.virtualHosts."sb.emile.space" = {
@@ -6,7 +6,7 @@
     enableACME = true;
     locations = {
       "/" = {
-        proxyPass = "http://${config.services.silverbullet.listenSddress}:${toString config.services.silverbullet.listenPort}";
+        proxyPass = "http://${config.services.silverbullet.listenAddress}:${toString config.services.silverbullet.listenPort}";
         extraConfig = ''
           ## Send a subrequest to Authelia to verify if the user is authenticated and has permission to access the resource.
           auth_request /internal/authelia/authz;
@@ -73,7 +73,7 @@
           proxy_connect_timeout 240;
         '';
       };
-
+    };
   };
 
   # auth via authelia
diff --git a/nix/hosts/corrino/www/sso.emile.space.nix b/nix/hosts/corrino/www/sso.emile.space.nix
index 44e30bb..6ffff80 100644
--- a/nix/hosts/corrino/www/sso.emile.space.nix
+++ b/nix/hosts/corrino/www/sso.emile.space.nix
@@ -141,9 +141,22 @@ in
         storage.local.path = "/var/lib/authelia-main/db.sqlite";
 
         session = {
-          domain = "sso.emile.space";
-          expiration = 3600; # 1 hour
-          inactivity = 300; # 5 minutes
+          # domain = "sso.emile.space";
+          # expiration = 3600; # 1 hour
+          # inactivity = 300; # 5 minutes
+
+          cookies = [
+            {
+              domain = "emile.space";
+              authelia_url = "https://sso.emile.space";
+              # The period of time the user can be inactive for until the session is destroyed. Useful if you want long session timers but don’t want unused devices to be vulnerable.
+              inactivity = "1h";
+              # The period of time before the cookie expires and the session is destroyed. This is overridden by remember_me when the remember me box is checked.
+              expiration = "1d";
+              # The period of time before the cookie expires and the session is destroyed when the remember me box is checked. Setting this to -1 disables this feature entirely for this session cookie domain
+              remember_me = "3M";
+            }
+          ];
         };
 
         notifier = {
@@ -196,6 +209,16 @@ in
           default_policy = "deny";
           rules = [
             {
+              # silverbullet needs access to these without auth
+              domain = "sb.emile.space";
+              policy = "bypass";
+              resources = [
+                "/.client/manifest.json$"
+                "/.client/[a-zA-Z0-9_-]+.png$"
+                "/service_worker.js$"
+              ];
+            }
+            {
               domain = "*.emile.space";
               policy = "two_factor";
             }
diff --git a/nix/hosts/lampadas/configuration.nix b/nix/hosts/lampadas/configuration.nix
index cc829d8..007f8a1 100644
--- a/nix/hosts/lampadas/configuration.nix
+++ b/nix/hosts/lampadas/configuration.nix
@@ -2,7 +2,7 @@
 # your system. Help is available in the configuration.nix(5) man page, on
 # https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
 
-{ pkgs, lib, ... }:
+{ config, pkgs, lib, ... }:
 
 let
   emile_keys = [
@@ -17,12 +17,34 @@ in
     ./hardware-configuration.nix
   ];
 
+  hardware.fancontrol = {
+    enable = true;
+    config = ''
+      # Configuration file generated by pwmconfig, changes will be lost
+      INTERVAL=10
+      DEVPATH=hwmon0=devices/platform/coretemp.0 hwmon1=devices/platform/nct6775.672
+      DEVNAME=hwmon0=coretemp hwmon1=nct6798
+      FCTEMPS=hwmon1/pwm3=hwmon0/temp2_input hwmon1/pwm2=hwmon1/temp2_input
+      FCFANS=hwmon1/pwm3=hwmon1/fan3_input hwmon1/pwm2=hwmon1/fan2_input
+      MINTEMP=hwmon1/pwm3=35 hwmon1/pwm2=35
+      MAXTEMP=hwmon1/pwm3=75 hwmon1/pwm2=75
+      MINSTART=hwmon1/pwm3=255 hwmon1/pwm2=255
+      MINSTOP=hwmon1/pwm3=30 hwmon1/pwm2=30
+      MINPWM=hwmon1/pwm3=30 hwmon1/pwm2=30
+      MAXPWM=hwmon1/pwm3=255 hwmon1/pwm2=255
+    '';
+  };
+
   boot = {
     loader = {
       systemd-boot.enable = true;
       efi.canTouchEfiVariables = true;
     };
     kernelParams = [ "ip=dhcp" ];
+    kernelModules = [
+      # fan speed modules, detected using `sensors-detect`
+      "coretemp" "nct6775"
+    ];
     initrd = {
       availableKernelModules = [ "r8169" ];
       systemd.users.root.shell = "/bin/cryptsetup-askpass";
@@ -159,10 +181,41 @@ in
     };
 
     # metric exporters
-    prometheus.exporters = {
-      node.enable = true; # port 9100
-      systemd.enable = true; # port 9558
-      smartctl.enable = true; # port 9633
+    prometheus = {
+      enable = true;
+      port = 9090;
+      listenAddress = "100.87.209.97";
+      scrapeConfigs = [
+        {
+          job_name = "node";
+          static_configs = [{
+            targets = [
+              "localhost:${toString config.services.prometheus.exporters.node.port}"
+            ];
+          }];
+        }  
+        {
+          job_name = "systemd";
+          static_configs = [{
+            targets = [
+              "localhost:${toString config.services.prometheus.exporters.systemd.port}"
+            ];
+          }];
+        }  
+        {
+          job_name = "smartctl";
+          static_configs = [{
+            targets = [
+              "localhost:${toString config.services.prometheus.exporters.smartctl.port}"
+            ];
+          }];
+        }  
+      ];
+      exporters = {
+        node.enable = true; # port 9100
+        systemd.enable = true; # port 9558
+        smartctl.enable = true; # port 9633
+      };
     };
 
     # shares
diff --git a/nix/modules/libvirtnix/config.nix b/nix/modules/libvirtnix/config.nix
new file mode 100644
index 0000000..e1a8d28
--- /dev/null
+++ b/nix/modules/libvirtnix/config.nix
@@ -0,0 +1,59 @@
+{
+  services.emile.libvirtnix = {
+    enable = true;
+    vm = {
+      "alan" = {
+        vm_type = "kvm";
+        id = "1337";
+        name = "blub";
+        uuid = "cafebabe-d474-452b-80f4-c951c39bcf74";
+
+        metadata.libosinfo = "https://libosinfo.org/xmlns/libvirt/domain/1.0";
+        metadata.libosinfo_os = "https://nixos.org/nixos/unknown";
+
+        memory.unit = "KiB";
+        memory.value = 2097152;
+
+        currentMemory.unit = "KiB";
+        currentMemory.value = 2097152;
+
+        vcpu.placement = "static";
+        vcpu.count = 3;
+
+        resource.partition = "/machine";
+
+        os = {
+          type = {
+            arch = "x86_64";
+            machine = "pc-q35-3.1";
+            value = "hvm";
+          };
+
+          loader = {
+            readonly = "yes";
+            type = "pflash";
+            value = "/usr/share/OVMF/OVMF_CODE.fd";
+          };
+
+          nvram.value = "/var/lib/libvirt/qemu/nvram/fileserver2_VARS.fd";
+
+          boot.dev = "hd";
+        };
+
+        features = {
+          acpi = true;
+          apic = true;
+          vmport = {
+            state = "off";
+          };
+        };
+
+        cpu = {
+          mode = "host-passthrough";
+          check = "none";
+          migratable = "on";
+        };
+      };
+    };
+  };
+}
diff --git a/nix/modules/libvirtnix/domain.nix b/nix/modules/libvirtnix/domain.nix
index a028c7d..22cd891 100644
--- a/nix/modules/libvirtnix/domain.nix
+++ b/nix/modules/libvirtnix/domain.nix
@@ -1,701 +1,483 @@
-{ pkgs, lib, ... }:
-
+{ config, lib, ... }:
+
+let
+  mkTag = import ./xml.nix { inherit lib; };
+  pkgs = import <nixpkgs> { };
+
+  stringOption =
+    {
+      default ? "",
+    }:
+    lib.mkOption {
+      type = lib.types.str;
+      default = "${default}";
+    };
+in
 {
-  options = with lib; {
-
-    # meta, this allows defining the package used for each vm individually, defaults should be sane
-    packages = mkOption {
-      type = types.submodule {
-        options = {
-          libvirt = mkPackageOption pkgs "libvirt" { };
-          qemu = mkPackageOption pkgs "qemu" { };
+  options = {
+    services.emile.libvirtnix =
+      let
+        cpu = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              mode = lib.mkOption {
+                type = lib.types.str;
+                example = "host-passthrough";
+                default = "";
+              };
+              check = lib.mkOption {
+                type = lib.types.str;
+                example = "none";
+                default = "";
+              };
+              migratable = lib.mkOption {
+                type = lib.types.str;
+                example = "on";
+                default = "";
+              };
+            };
+          };
         };
-      };
-    };
-
-    # attributs for the root `<domain ...>` node
-    type = mkOption {
-      type = types.enum [
-        "xen"
-        "hvm"
-        "kvm"
-        "qemu"
-        "lxc"
-      ];
-      default = "kvm";
-      example = "qemu";
-      description = ''
-        				hypervisor used for running the domain
 
-        				allowed values are driver specific, if missing, plz send PR
-
-        				hvm since since 8.1.0 and QEMU 2.12
-        			'';
-    };
-
-    id = mkOption {
-      type = types.int;
-      default = 0;
-      example = 42;
-    };
+        features = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              acpi = lib.mkEnableOption "enable acpi";
+              apic = lib.mkEnableOption "enable apic";
+
+              vmport = lib.mkOption {
+                type = lib.types.submodule {
+                  options = {
+                    state = lib.mkOption {
+                      type = lib.types.str;
+                      example = "off";
+                      default = "";
+                    };
+                  };
+                };
+              };
+            };
+          };
+        };
+      
+        os_boot = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              dev = lib.mkOption {
+                type = lib.types.str;
+                example = "hd";
+                default = "";
+              };
+            };
+          };
+        };
+      
+        os_nvram = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              value = lib.mkOption {
+                type = lib.types.str;
+                example = "/var/lib/libvirt/qemu/nvram/fileserver2_VARS.fd";
+                default = "";
+              };
+            };
+          };
+        };
+      
+        os_loader = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              readonly = lib.mkOption {
+                type = lib.types.str;
+                example = "yes";
+                default = "";
+              };
+              type = lib.mkOption {
+                type = lib.types.str;
+                example = "pflash";
+                default = "";
+              };
+              value = lib.mkOption {
+                type = lib.types.str;
+                example = "/usr/share/OVMF/OVMF_CODE.fd";
+                default = "";
+              };
+            };
+          };
+        };
+      
+        os_type = lib.mkOption {
+          type = lib.types.submodule {
+            options = {
+              arch = lib.mkOption {
+                type = lib.types.str;
+                example = "x86_64";
+                default = "";
+              };
+              machine = lib.mkOption {
+                type = lib.types.str;
+                example = "pc-q35-3.1";
+                default = "";
+              };
+              value = lib.mkOption {
+                type = lib.types.str;
+                example = "hvm";
+                default = "";
+              };
+            };
+          };
+        };
 
-    # General metadata
-    name = mkOption {
-      type = types.str;
-      example = "MyGuest";
-    };
+        os = lib.mkOption {
+          description = "os";
+          type = lib.types.submodule {
+            options = {
+              type = os_type;
+              loader = os_loader;
+              nvram = os_nvram;
+              boot = os_boot;
+            };
+          };
+        };
 
-    uuid = mkOption {
-      type = types.str;
-      example = "3e3fce45-4f53-4fa7-bb32-11f34168b82b";
-    };
+        resource = lib.mkOption {
+          description = "resource";
+          type = lib.types.submodule {
+            options = {
+              partition = lib.mkOption {
+                type = lib.types.str;
+                example = "/machine";
+                default = "";
+              };
+            };
+          };
+        };
 
-    genid = mkOption {
-      type = types.str;
-      example = "43dc0cf8-809b-4adb-9bea-a9abb5f3d90e";
-    };
+        vcpu = lib.mkOption {
+          description = "vcpu";
+          type = lib.types.submodule {
+            options = {
+              placement = lib.mkOption {
+                # TODO(emile): fill up
+                type = lib.types.enum [ "static" ];
+                example = "static";
+                default = "static";
+              };
+              count = lib.mkOption {
+                type = lib.types.int;
+                example = 4;
+                default = 1;
+              };
+            };
+          };
+        };
 
-    title = mkOption {
-      type = types.str;
-      example = "A short description - title - of the domain";
-    };
+        mem = lib.mkOption {
+          description = "memory";
+          type = lib.types.submodule {
+            options = {
+              unit = lib.mkOption {
+                # TODO(emile): fill up
+                type = lib.types.enum [
+                  "KiB"
+                  "MiB"
+                  "GiB"
+                  "TiB"
+                ];
+                example = "KiB";
+                default = "KiB";
+              };
+              value = lib.mkOption {
+                type = lib.types.int;
+                example = 2097152;
+                default = 1000;
+              };
+            };
+          };
+        };
 
-    description = mkOption {
-      type = types.str;
-      example = "Some human readable description";
-    };
+        memory = mem;
+        currentMemory = mem;
+
+        metadata = lib.mkOption {
+          description = "metadata submodule";
+          type = lib.types.submodule {
+            options = {
+              libosinfo = lib.mkOption {
+                type = lib.types.str;
+                example = "https://libosinfo.org/xmlns/libvirt/domain/1.0";
+                default = "";
+              };
+              libosinfo_os = lib.mkOption {
+                type = lib.types.str;
+                example = "https://nixos.org/nixos/unknown";
+                default = "";
+              };
+            };
+          };
+        };
 
-    metadata = mkOption {
-      type = types.str;
-      default = "";
-      example = ''
-        			  <metadata>
-        			    <app1:foo xmlns:app1="http://app1.org/app1/">..</app1:foo>
-        			    <app2:bar xmlns:app2="http://app1.org/app2/">..</app2:bar>
-        			  </metadata>
-        			'';
-    };
+        vm = lib.types.submodule {
+          options = {
+            vm_type = stringOption { default = "kvm"; };
+            id = stringOption { };
+            name = stringOption { };
+            uuid = stringOption { };
+
+            inherit
+              metadata
+              memory
+              currentMemory
+              vcpu
+              resource
+              os
+              features
+              cpu
+              ;
+          };
+        };
+      in
+      {
+        enable = lib.mkEnableOption "Enable r2wars-web";
+      
+
+        # Temporary output we write the domain to, in order to inspect the correctness of
+        # the generated xml
+        output.domain = lib.mkOption { type = lib.types.path; };
+
+        # An attrset of VMs
+        vm = lib.mkOption {
+          description = "VMs to define";
+          type = lib.types.attrsOf vm;
+        };
+      };
+  };
 
-    os = mkOption {
-      type = types.submodule {
-        options = {
-          firmware = mkOption {
-            type = types.enum [
-              "bios"
-              "efi"
+  config = lib.mkIf config.services.emile.libvirtnix.enable {
+    services.emile.libvirtnix =
+      let
+        vm = config.services.emile.libvirtnix.vm;
+
+        cpu =
+          vm_name:
+          mkTag {
+            name = "cpu";
+            args = let
+              mode = vm.${vm_name}.cpu.mode;
+              check = vm.${vm_name}.cpu.check;
+              migratable= vm.${vm_name}.cpu.migratable;
+            in [
+              (if (mode != null)
+               then {key = "mode"; val = mode;}
+               else "")
+              (if (check != null)
+               then {key = "check"; val = check;}
+               else "")
+              (if (migratable != null)
+               then {key = "migratable"; val = migratable;}
+               else "")
             ];
-            default = "efi";
-            example = "bios";
+            closing = false;
           };
-          type = mkOption {
-            type = types.enum [
-              "hvm"
-              "linux"
+        
+        features =
+          vm_name:
+          mkTag {
+            name = "features";
+            children = let
+              acpi = vm_name: mkTag {
+                name = "acpi";
+                closing = false;
+              };
+              apic = vm_name: mkTag {
+                name = "apic";
+                closing = false;
+              };
+              vmport = vm_name: mkTag {
+                name = "vmport";
+                args = [
+                  { key = "state"; val = vm.${vm_name}.features.vmport.state; }
+                ];
+                closing = false;
+              };
+            in [
+              (if (vm.${vm_name}.features.acpi != false) then (acpi vm_name) else "")
+              (if (vm.${vm_name}.features.apic != false) then (apic vm_name) else "")
+              (if (vm.${vm_name}.features.vmport != null) then (vmport vm_name) else "")
             ];
-            default = "hvm";
-            example = "linux";
           };
-          arch = mkOption { type = types.str; };
-          machine = mkOption { type = types.str; };
-
-          # TODO(emile): features
-          # Search for the following...
-          # > When using firmware auto-selection there are different features enabled in the firmwares
-          # ... here: https://libvirt.org/formatdomain.html
-          # can't bother to implement this now
 
-          # features = mkOption {
-          # 	type = types.submodule {
-          # 		options = {
-          # 			enabled = {
-          # 				type = types.enum [ "yes" "no" ];
-          # 			};
-          # 			name = {};
-          # 		};
-          # 	};
-          # };
-
-          # such as:
-          # <loader readonly='yes' secure='yes' type='pflash'>/usr/share/OVMF/OVMF_CODE.fd</loader>
-          loader = mkOption {
-            type = types.submodule {
-              options = {
-                readonly = mkOption {
-                  type = types.enum [
-                    "yes"
-                    "no"
-                  ];
-                };
-                type = mkOption {
-                  type = types.enum [
-                    "rom"
-                    "pflash"
-                  ];
-                };
-                secure = mkOption {
-                  type = types.enum [
-                    "yes"
-                    "no"
-                  ];
-                };
-                stateless = mkOption {
-                  type = types.enum [
-                    "yes"
-                    "no"
-                  ];
-                };
-                format = mkOption {
-                  type = types.enum [
-                    "raw"
-                    "qcow2"
-                  ];
-                };
-                value = mkOption {
-                  type = types.str;
-                  example = "/usr/share/OVMF/OVMF_CODE.fd";
-                };
+        os =
+          vm_name:
+          mkTag {
+            name = "os";
+            children = let
+              os_type = vm_name: mkTag {
+                name = "type";
+                args = [
+                  { key = "arch"; val = vm.${vm_name}.os.type.arch; }
+                  { key = "machine"; val = vm.${vm_name}.os.type.machine; }
+                ];
+                value = vm.${vm_name}.os.type.value;
               };
-            };
+              os_loader = vm_name: mkTag {
+                name = "loader";
+                args = [
+                  { key = "readonly"; val = vm.${vm_name}.os.loader.readonly; }
+                  { key = "pflash"; val = vm.${vm_name}.os.loader.type; }
+                ];
+                value = vm.${vm_name}.os.loader.value;
+              };
+              os_nvram = vm_name: mkTag {
+                name = "nvram";
+                value = vm.${vm_name}.os.nvram.value;
+              };
+              os_boot = vm_name: mkTag {
+                name = "type";
+                args = [
+                  { key = "dev"; val = vm.${vm_name}.os.boot.dev; }
+                ];
+                closing = false;
+              };
+            in [
+              (os_type vm_name)
+              (os_loader vm_name)
+              (os_nvram vm_name)
+              (os_boot vm_name)
+            ];
           };
 
-          # <nvram type='network'>
-          #   <source protocol='iscsi' name='iqn.2013-07.com.example:iscsi-nopool/0'>
-          #     <host name='example.com' port='6000'/>
-          #     <auth username='myname'>
-          #       <secret type='iscsi' usage='mycluster_myname'/>
-          #     </auth>
-          #   </source>
-          # </nvram>
-
-          # nvram = {
-          # 	type = "network";
-          # 	source = {
-          # 		protocol = "iscsi";
-          # 		name = "iqn.2013-07.com.example:iscsi-nopool/0";
-          # 	};
-          # };
-
-          nvram = mkOption {
-            type = types.submodule {
-              options = {
-                type = mkOption {
-                  type = types.enum [
-                    "file"
-                    "block"
-                    "dir"
-                    "network"
-                    "volume"
-                    "nvme"
-                    "vhostuser"
-                    "vhostvdpa"
-                  ];
-                  default = "";
-                };
-
-                source = mkOption {
-                  type = types.submodule {
-                    options = {
-                      # TODO(emile): figure out how to conditionally allow setting the options below
-                      #              if the type defined above has been set
-
-                      # if type == file
-                      file = mkOption { type = types.str; };
-                      fdgroup = mkOption { type = types.str; };
-
-                      # if type == block
-                      dev = mkOption { type = types.str; };
-
-                      # if type == dir
-                      dir = mkOption { type = types.str; };
-
-                      # if type == network
-                      protocol = mkOption {
-                        type = types.enum [
-                          "nbd"
-                          "iscsi"
-                          "rbd"
-                          "sheepdog"
-                          "gluster"
-                          "vxhs"
-                          "nfs"
-                          "http"
-                          "https"
-                          "ftp"
-                          "ftps"
-                          "tftp"
-                          "ssh"
-                        ];
-                      };
-                      name = mkOption { type = types.str; };
-                      tls = mkOption {
-                        type = types.enum [
-                          "yes"
-                          "no"
-                        ];
-                      };
-                      tlsHostname = mkOption { type = types.str; };
-                      query = mkOption { type = types.str; };
-
-                      # if type == volume
-                      pool = mkOption { type = types.str; };
-                      volume = mkOption { type = types.str; };
-                      mode = mkOption {
-                        type = types.enum [
-                          "direct"
-                          "host"
-                        ];
-                        default = "host";
-                      };
-
-                      # if type == nvme
-                      type = mkOption {
-                        type = types.enum [ "pci" ];
-                        default = "pci";
-                        description = ''
-                          													When the type is `nvme`, only `pci` is supported
-                          													When the type is `vhostuser`, only `unix` is supported
-
-                          													(I've got not clue how to model this is nix, it's essentially an attribute that is overloaded based on another value)
-                          												'';
-                      };
-                      managed = mkOption {
-                        type = types.enum [
-                          "yes"
-                          "no"
-                        ];
-                      };
-                      namespace = mkOption {
-                        type = types.int;
-                        default = 0;
-                      };
-
-                      # if type == vhostuser
-                      # type = see the type in the nvme section above
-                      path = mkOption { type = types.str; };
-
-                      # if type == vhostvdpa
-                      # dev = (defined above in the "type == block" section)
-
-                      # if type == file
-                      # if type == block
-                      # if type == volume
-                      seclabel = mkOption { type = types.str; };
-
-                      index = mkOption {
-                        type = types.int;
-                        default = 0;
-                      };
-
-                      # TODO(emile): implement checks here
-                      # start here and scroll down a bit to the table
-                      # https://libvirt.org/formatdomain.html#hard-drives-floppy-disks-cdroms
-                      # if type == network
-                      host = mkOption {
-                        type = types.submodule {
-                          options = {
-                            name = mkOption { type = types.str; };
-                            port = mkOption {
-                              type = types.int;
-                              default = 0;
-                            };
-                            transport = mkOption { type = types.str; };
-                            socket = mkOption { type = types.str; };
-
-                          };
-                        };
-                      };
-
-                      snapshot = mkOption {
-                        type = types.submodule {
-                          options = {
-                            name = mkOption { type = types.str; };
-                          };
-                        };
-                      };
-
-                      config = mkOption {
-                        type = types.submodule {
-                          options = {
-                            file = mkOption { type = types.str; };
-                          };
-                        };
-                      };
-
-                      # Since 3.9.0, the auth element is supported for a disk type "network" that is using a source element with the protocol attributes "rbd", "iscsi", or "ssh".
-                      auth = mkOption {
-                        type = types.submodule {
-                          options = {
-                            type = mkOption {
-                              # type = types.enum [ "chap" ];
-                              # freeform, yet "chap" is one of the allowed options, I don't know others (yet)
-                              type = types.str;
-                            };
-                            username = mkOption { type = types.str; };
-
-                            # https://libvirt.org/formatsecret.html
-                            secret = mkOption {
-                              type = types.submodule {
-                                options = {
-                                  ephemeral = mkOption {
-                                    type = types.enum [
-                                      "yes"
-                                      "no"
-                                    ];
-                                    default = "no";
-                                  };
-                                  private = mkOption {
-                                    type = types.enum [
-                                      "yes"
-                                      "no"
-                                    ];
-                                    default = "no";
-                                  };
-
-                                  uuid = types.submodule {
-                                    options = {
-                                      value = mkOption {
-                                        type = types.str;
-                                        default = "";
-                                      };
-                                    };
-                                  };
-                                  description = types.submodule {
-                                    options = {
-                                      value = mkOption {
-                                        type = types.str;
-                                        default = "";
-                                      };
-                                    };
-                                  };
-
-                                  usage = types.submodule {
-                                    options = {
-                                      type = mkOption {
-                                        type = types.enum [
-                                          "volume"
-                                          "ceph"
-                                          "iscsi"
-                                          "tls"
-                                          "vtpm"
-                                        ];
-                                        default = "";
-                                      };
-                                      value = mkOption {
-                                        type = types.str;
-                                        default = "";
-                                      };
-
-                                      name = mkOption {
-                                        type = types.submodule {
-                                          options = {
-                                            value = mkOption { type = types.str; };
-                                          };
-                                        };
-                                      };
-
-                                      volume = mkOption {
-                                        type = types.submodule {
-                                          options = {
-                                            value = mkOption { type = types.str; };
-                                          };
-                                        };
-                                      };
-
-                                      # when using the "iscsi" type
-                                      target = mkOption {
-                                        type = types.submodule {
-                                          options = {
-                                            value = mkOption { type = types.str; };
-                                          };
-                                        };
-                                      };
-
-                                    };
-                                  };
-
-                                };
-                              };
-                            }; # end of secret
-
-                          };
-                        };
-                      }; # end of auth
-
-                      # https://libvirt.org/formatstorageencryption.html
-                      encryption = mkOption {
-                        type = types.submodule {
-                          options = {
-                            type = mkOption { type = types.str; };
-
-                            # mandatory
-                            format = mkOption {
-                              type = types.enum [
-                                "default"
-                                "qcow"
-                                "luks"
-                                "luks2"
-                                "luks-any"
-                              ];
-                            };
-
-                            engine = mkOption {
-                              type = types.enum [
-                                "qemu"
-                                "librbd"
-                              ];
-                            };
-
-                            secrets = mkOption {
-                              type = types.listOf (mkOption {
-                                type = types.submodule {
-                                  options = {
-
-                                    # mandatory
-                                    type = mkOption { type = types.enum [ "volume" ]; };
-
-                                    # uuid or usage
-                                    uuid = mkOption { type = types.str; };
-                                    usage = mkOption { type = types.str; };
-
-                                  };
-                                };
-                              });
-                            }; # end of secrets
-
-                            cipher = mkOption {
-                              type = types.submodule {
-                                options = {
-                                  name = mkOption {
-                                    type = types.str;
-                                    example = "'aes', 'des', 'cast5', 'serpent', 'twofish', etc.";
-                                  };
-                                  size = mkOption {
-                                    type = types.str;
-                                    example = "'256', '192', '128', etc.";
-                                  };
-                                  mode = mkOption {
-                                    type = types.str;
-                                    example = "'cbc', 'xts', 'ecb', etc.";
-                                  };
-                                  hash = mkOption {
-                                    type = types.str;
-                                    example = "'md5', 'sha1', 'sha256', etc.";
-                                  };
-                                };
-                              };
-                            }; # end of cipher
-
-                            ivgen = mkOption {
-                              type = types.submodule {
-                                options = {
-                                  name = mkOption {
-                                    type = types.str;
-                                    example = "'plain', 'plain64', 'essiv', etc.";
-                                  };
-                                  hash = mkOption {
-                                    type = types.str;
-                                    example = "'md5', 'sha1', 'sha256'";
-                                  };
-                                };
-                              };
-                            }; # end of ivygen
-                          };
-                        };
-                      }; # end of encryption
-
-                      # TODO(emile): reservations
-                      # Looking at the following, it seems like this can use the source element recursively
-                      # Haven't looked into how to define recursive nix options yet...
-                      # https://github.com/virt-manager/virt-manager/blob/5ddd3456a0ca9836a98fc6ca4f0b2eaab268bf47/tests/data/cli/compare/virt-install-many-devices.xml#L398-L400
-
-                      # TODO(emile): initiator
-
-                      # Based on this here:
-                      # https://github.com/virt-manager/virt-manager/blob/5ddd3456a0ca9836a98fc6ca4f0b2eaab268bf47/tests/data/cli/compare/virt-install-many-devices.xml#L440
-                      address = mkOption {
-                        type = types.submodule {
-                          options = {
-                            domain = mkOption { type = types.int; };
-                            bus = mkOption { type = types.int; };
-                            slot = mkOption { type = types.int; };
-                            function = mkOption { type = types.int; };
-                          };
-                        };
-                      }; # end of address
-
-                      # TODO(emile): slices
-                      # Didn't find any usage of it, why is there documentation for it then?
-
-                      ssl = mkOption {
-                        type = types.submodule {
-                          options = {
-                            verify = mkOption {
-                              type = types.enum [
-                                "yes"
-                                "no"
-                              ];
-                            };
-                          };
-                        };
-                      }; # end of ssl
-
-                      # TODO(emile): cookies for http and https
-
-                      readahead = mkOption {
-                        type = types.submodule {
-                          options = {
-                            size = mkOption { type = types.int; };
-                          };
-                        };
-                      }; # end of readahead
-
-                      timeout = mkOption {
-                        type = types.submodule {
-                          options = {
-                            seconds = mkOption { type = types.int; };
-                          };
-                        };
-                      }; # end of timeout
-
-                      identity = mkOption {
-                        type = types.submodule {
-                          options = {
-                            user = mkOption { type = types.str; };
-                            group = mkOption { type = types.str; };
-
-                            # if ssh
-                            # required
-                            username = mkOption { type = types.str; };
-
-                            # one of these:
-                            agentsock = mkOption { type = types.str; };
-                            keyfile = mkOption { type = types.str; };
-                          };
-                        };
-                      }; # end of identity
-
-                      # disk type "vhostuser"
-                      reconnect = mkOption {
-                        type = types.submodule {
-                          options = {
-                            # mandatory
-                            enabled = mkOption {
-                              type = types.enum [
-                                "yes"
-                                "no"
-                              ];
-                            };
-                            timeout = mkOption {
-                              type = types.int;
-                              description = "seconds";
-                            };
-
-                            # optional for disk type network and protocol nbd
-                            delay = mkOption {
-                              type = types.int;
-                              default = 0;
-                              description = "seconds";
-                            };
-                          };
-                        };
-                      }; # end of reconnect
-
-                      # disk type "ssh"
-                      knownHosts = mkOption {
-                        type = types.submodule {
-                          options = {
-                            path = mkOption { type = types.str; };
-                          };
-                        };
-                      }; # end of knownHosts
-
-                      dataStore = mkOption {
-                        type = types.submodule {
-                          options = {
-                            # TODO(emile): can accept the same types as a `source` element
-                            type = mkOption { type = types.str; };
-
-                            # TODO(emile): can accept format and source subelements
-                            # this is (once again) recursive for the source, stil need to figure
-                            # out how to handle this, just not now
-                          };
-                        };
-                      }; # end of dataStore
+        resource =
+          vm_name:
+          mkTag {
+            name = "resource";
+            children = [
+              (mkTag {
+                name = "partition";
+                value = "${toString vm.${vm_name}.resource.partition}";
+              })
+            ];
+          };
 
-                      startupPolicy = mkOption {
-                        type = types.enum [
-                          "mandatory"
-                          "requisite"
-                          "optional"
-                        ];
-                        default = "mandatory";
-                      }; # end of startupPolicy
+        vcpu =
+          vm_name:
+          mkTag {
+            name = "vcpu";
+            args = [
+              {
+                key = "placement";
+                val = vm.${vm_name}.vcpu.placement;
+              }
+            ];
+            value = "${toString vm.${vm_name}.vcpu.count}";
+          };
 
-                      backingStore = mkOption {
-                        type = types.submodule {
-                          options = {
-                            type = mkOption {
-                              # TODO(emile): can accept the same types as a `source` element
-                              type = types.str;
-                            };
+        currentMemory =
+          vm_name:
+          mkTag {
+            name = "currentMemory";
+            args = [
+              {
+                key = "unit";
+                val = vm.${vm_name}.currentMemory.unit;
+              }
+            ];
+            value = "${toString vm.${vm_name}.currentMemory.value}";
+          };
+        memory =
+          vm_name:
+          mkTag {
+            name = "memory";
+            args = [
+              {
+                key = "unit";
+                val = vm.${vm_name}.memory.unit;
+              }
+            ];
+            value = "${toString vm.${vm_name}.memory.value}";
+          };
 
-                            index = mkOption {
-                              # TODO(emile): figure out if this can just be an int
-                              type = types.str;
-                            };
+        libosinfo_os =
+          vm_name:
+          mkTag {
+            name = "libosinfo:os";
+            args = [
+              {
+                key = "id";
+                val = vm.${vm_name}.metadata.libosinfo_os;
+              }
+            ];
+            closing = false;
+          };
 
-                            # TODO(emile): can use the following sub elements:
-                            # - format
-                            # - source
-                            # - backingStore
-                          };
-                        };
-                      }; # end of backingStore
+        libosinfo_libosinfo =
+          vm_name:
+          mkTag {
+            name = "libosinfo:libosinfo";
+            args = [
+              {
+                key = "xmlns:libosinfo";
+                val = vm.${vm_name}.metadata.libosinfo;
+              }
+            ];
+            children = [
+              (libosinfo_os vm_name)
+            ];
+          };
 
-                      mirror = mkOption {
-                        type = types.submodule {
-                          options = {
-                            api = mkOption {
-                              type = types.enum [
-                                "copy"
-                                "active-commit"
-                              ];
-                            };
-                            ready = mkOption {
-                              type = types.enum [
-                                "yes"
-                                "abort"
-                                "pivot"
-                              ];
-                            };
-                            # TODO(emile): can use the following sub elements:
-                            # - type (disk types)
-                            # - format
-                            # - source
-                            # - file
-                          };
-                        };
-                      }; # end of mirror
+        metadata =
+          vm_name:
+          mkTag {
+            name = "metadata";
+            children = [ (libosinfo_libosinfo vm_name) ];
+          };
 
-                    };
-                  };
-                }; # end of source
+        uuid =
+          vm_name:
+          mkTag {
+            name = "uuid";
+            value = vm.${vm_name}.uuid;
+          };
 
-              };
-            };
-          }; # end of nvram
+        name =
+          vm_name:
+          mkTag {
+            name = "name";
+            value = vm.${vm_name}.name;
+          };
 
-        };
+        # the vm_name has to be passed in, as we want to accesses the value for the given vm when
+        # generating the config
+        domain =
+          vm_name:
+          mkTag {
+            name = "domain";
+            args = [
+              {
+                key = "type";
+                val = vm.${vm_name}.vm_type;
+              }
+              {
+                key = "id";
+                val = vm.${vm_name}.id;
+              }
+            ];
+            children = [
+              (name vm_name)
+              (uuid vm_name)
+              (metadata vm_name)
+              (memory vm_name)
+              (currentMemory vm_name)
+              (vcpu vm_name)
+              (resource vm_name)
+              (os vm_name)
+              (features vm_name)
+              (cpu vm_name)
+            ];
+          };
+      in
+      {
+        output.domain = pkgs.writeText "libvirt-domain-config.xml" (domain "alan");
       };
-    };
-
-    memory = lib.mkOption {
-      type = lib.types.int;
-      default = 1024;
-      example = 2048;
-      description = ''
-        The amount of memory to provide to the VM
-      '';
-    };
   };
 }
diff --git a/nix/modules/libvirtnix/test.nix b/nix/modules/libvirtnix/test.nix
new file mode 100644
index 0000000..8542b7e
--- /dev/null
+++ b/nix/modules/libvirtnix/test.nix
@@ -0,0 +1,10 @@
+let
+	pkgs = import <nixpkgs> {};
+in
+	pkgs.lib.evalModules {
+		modules = [
+			# ./secret.nix
+			./domain.nix
+			./config.nix
+		];
+	}
diff --git a/nix/modules/libvirtnix/xml.nix b/nix/modules/libvirtnix/xml.nix
new file mode 100644
index 0000000..134a878
--- /dev/null
+++ b/nix/modules/libvirtnix/xml.nix
@@ -0,0 +1,45 @@
+{ lib, ... }:
+
+# takes a few args and creats a valid xml tag pair out of it
+#
+# testTag = mkTag {
+#   name = "name";
+#   args = [
+#     {
+#       key = "arg1";
+#       val = "arg1val";
+#     }
+#     {
+#       key = "arg2";
+#       val = "arg2val";
+#     }
+#   ];
+#   value = "qwe";
+#   children = [
+#     (mkTag { name = "nested"; args = []; value = "qwe"; children = [];})
+#   ];
+# };
+#
+# <name arg1=arg1val arg2=arg2val>
+#   value
+#   {children}
+# </name>
+{
+  name, # name of the tag to be used, such as `secret`, `description`, ...
+  args ? [ ], # args, [ { key="a"; val="b"; } { key="c"; val="d"; } ]
+  value ? "", # the value to place in the middle
+  children ? [ ], # the child elements
+  closing ? true, # add a closing tag
+}:
+let
+  args_str =
+    " " + lib.strings.concatStrings (lib.strings.intersperse " " (map (x: "${x.key}='${x.val}'") args));
+  child_evaled = lib.strings.concatStrings children;
+
+  cond = condition: value: if condition then value else "";
+
+  closingTag = if closing == true then "</${name}>" else "";
+in
+"<${name}${
+  lib.optionalString (args != [ ]) args_str
+}${cond (closing == false) "/"}>${value}${child_evaled}${lib.optionalString (closing) closingTag}"
diff --git a/nix/pkgs/glibc-all-in-one/default.nix b/nix/pkgs/glibc-all-in-one/default.nix
new file mode 100644
index 0000000..bbb0824
--- /dev/null
+++ b/nix/pkgs/glibc-all-in-one/default.nix
@@ -0,0 +1,25 @@
+{ pkgs ? import <nixpkgs> {}, lib, fetchFromGitHub }:
+
+pkgs.stdenv.mkDerivation rec {
+  name = "glibc-all-in-one";
+  version = "master";
+
+  src = fetchFromGitHub {
+    owner = "fr0ster";
+    repo = "glibc-all-in-one";
+    rev = version;
+    sha256 = "Zysjhr76TenMarnoKo+M8DrTNbsnaXSoFZO1puPVoxU=";
+  };
+
+  buildPhase = '''';
+
+  installPhase = ''
+  '';
+
+  meta = {
+    description = "";
+    homepage = "https://github.com/fr0ster/glibc-all-in-one";
+    license = lib.licenses.mit;
+    maintainers = with lib.maintainers; [ hanemile ];
+  };
+ }
diff --git a/nix/templates/ctf/flake.lock b/nix/templates/ctf/flake.lock
new file mode 100644
index 0000000..b756d8d
--- /dev/null
+++ b/nix/templates/ctf/flake.lock
@@ -0,0 +1,141 @@
+{
+  "nodes": {
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1740603184,
+        "narHash": "sha256-t+VaahjQAWyA+Ctn2idyo1yxRIYpaDxMgHkgCNiMJa4=",
+        "ref": "nixos-24.11",
+        "rev": "f44bd8ca21e026135061a0a57dcf3d0775b67a49",
+        "shallow": true,
+        "type": "git",
+        "url": "ssh://git@github.com/nixos/nixpkgs.git"
+      },
+      "original": {
+        "ref": "nixos-24.11",
+        "shallow": true,
+        "type": "git",
+        "url": "ssh://git@github.com/nixos/nixpkgs.git"
+      }
+    },
+    "nixpkgs_2": {
+      "locked": {
+        "lastModified": 1736241350,
+        "narHash": "sha256-CHd7yhaDigUuJyDeX0SADbTM9FXfiWaeNyY34FL1wQU=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "8c9fd3e564728e90829ee7dbac6edc972971cd0f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "pwndbg": {
+      "inputs": {
+        "nixpkgs": "nixpkgs_2",
+        "pyproject-build-systems": "pyproject-build-systems",
+        "pyproject-nix": "pyproject-nix",
+        "uv2nix": "uv2nix"
+      },
+      "locked": {
+        "lastModified": 1740333626,
+        "narHash": "sha256-OcwULIZcWOC1FNGa0SNGtyMyfbwTsBj17LBPpGOZL78=",
+        "ref": "refs/heads/dev",
+        "rev": "ef090ebf5eb75713b1f97c3d9aa3d7be636b0c3a",
+        "revCount": 2284,
+        "type": "git",
+        "url": "ssh://git@github.com/pwndbg/pwndbg"
+      },
+      "original": {
+        "type": "git",
+        "url": "ssh://git@github.com/pwndbg/pwndbg"
+      }
+    },
+    "pyproject-build-systems": {
+      "inputs": {
+        "nixpkgs": [
+          "pwndbg",
+          "nixpkgs"
+        ],
+        "pyproject-nix": [
+          "pwndbg",
+          "pyproject-nix"
+        ],
+        "uv2nix": [
+          "pwndbg",
+          "uv2nix"
+        ]
+      },
+      "locked": {
+        "lastModified": 1737338290,
+        "narHash": "sha256-gnXlfFEHA+/jMH7R+7y3JxrI3WfOjgBhzzJNuFW70UU=",
+        "owner": "pyproject-nix",
+        "repo": "build-system-pkgs",
+        "rev": "e1487e5cefda0c7990bdd2e660bee20971680e45",
+        "type": "github"
+      },
+      "original": {
+        "owner": "pyproject-nix",
+        "repo": "build-system-pkgs",
+        "type": "github"
+      }
+    },
+    "pyproject-nix": {
+      "inputs": {
+        "nixpkgs": [
+          "pwndbg",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1738204167,
+        "narHash": "sha256-J5M2sj3x4ocM93shScT/3Z4XWHZhwwW1NyQK+C+8Mys=",
+        "owner": "pyproject-nix",
+        "repo": "pyproject.nix",
+        "rev": "0d9f4b90cee1b5c5d6c142ef22de1e246e003ccc",
+        "type": "github"
+      },
+      "original": {
+        "owner": "pyproject-nix",
+        "repo": "pyproject.nix",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "nixpkgs": "nixpkgs",
+        "pwndbg": "pwndbg"
+      }
+    },
+    "uv2nix": {
+      "inputs": {
+        "nixpkgs": [
+          "pwndbg",
+          "nixpkgs"
+        ],
+        "pyproject-nix": [
+          "pwndbg",
+          "pyproject-nix"
+        ]
+      },
+      "locked": {
+        "lastModified": 1738653454,
+        "narHash": "sha256-tAFX8mPZtZ+zVE/+bwPC3U+u5MxjpNP0gG24DG26jVs=",
+        "owner": "pyproject-nix",
+        "repo": "uv2nix",
+        "rev": "05b0c148bc53aebc6a906b6d0ac41dde5954cd47",
+        "type": "github"
+      },
+      "original": {
+        "owner": "pyproject-nix",
+        "repo": "uv2nix",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/nix/templates/ctf/flake.nix b/nix/templates/ctf/flake.nix
index da21034..f185bb7 100644
--- a/nix/templates/ctf/flake.nix
+++ b/nix/templates/ctf/flake.nix
@@ -1,14 +1,20 @@
 {
-  description = "ctf";
+  description = ''
+    One Flake to rule them all^W^Wcommon CTF problems, namely broken infa.
+
+    Usage:
+    ; nix flake init -t git+https://github.com/hanemile/hefe\#ctf
+  '';
   nixConfig.bash-prompt = "\[ctf\]; ";
 
   inputs = {
-    nixpkgs.url = "git+https://github.com/NixOS/nixpkgs";
+    nixpkgs.url = "git+ssh://git@github.com/nixos/nixpkgs.git?shallow=1&ref=nixos-24.11";
+    pwndbg.url = "git+ssh://git@github.com/pwndbg/pwndbg";
   };
 
   # Flake outputs
   outputs =
-    { nixpkgs, ... }:
+    { nixpkgs, pwndbg, ... }@inputs:
     let
       # Systems supported
       allSystems = [
@@ -21,30 +27,46 @@
       # Helper to provide system-specific attributes
       nameValuePair = name: value: { inherit name value; };
       genAttrs = names: f: builtins.listToAttrs (map (n: nameValuePair n (f n)) names);
-      forAllSystems = f: genAttrs allSystems (system: f { pkgs = import nixpkgs { inherit system; }; });
+      forAllSystems = f: genAttrs allSystems (system: f {
+        pkgs = import nixpkgs { inherit system; };
+        pwndbg = inputs.pwndbg.packages.${system}.default;
+      });
     in
     {
       # Development environment output
       devShells = forAllSystems (
-        { pkgs }:
+        { pkgs, pwndbg }:
         {
           default =
-            let
-              python = pkgs.python311; # Use Python 3.11
-            in
             pkgs.mkShell {
-              packages =
-                with pkgs;
-                [ qemu ]
-                ++ [
-                  # Python plus helper tools
-                  (python.withPackages (
-                    ps: with ps; [
-                      pwntools
-                      pycryptodome
-                    ]
-                  ))
-                ];
+              shellHook = ''
+                cat << EOF > solve.py
+                from pwn import *
+
+                context.gdbinit="${pwndbg}/share/pwndbg/gdbinit.py"
+
+                # exe = ELF("./a.out")
+
+                p = remote("138.199.213.51", 31335)
+                #p = gdb.debug(exe.path, gdbscript=''''
+                #                break main
+                #                c
+                #              '''')
+
+                p.sendlineafter(b"> ", b"asd")
+
+                p.interactive()
+                EOF
+              '';
+              packages = [
+                  pkgs.gcc
+                  pwndbg
+                  (pkgs.python311.withPackages ( ps: with ps; [
+                    pwntools
+                    pwndbg
+                    pycryptodome
+                  ]))
+              ];
             };
         }
       );
diff --git a/nix/templates/ctf/solve.py b/nix/templates/ctf/solve.py
new file mode 100644
index 0000000..acc4a75
--- /dev/null
+++ b/nix/templates/ctf/solve.py
@@ -0,0 +1,15 @@
+from pwn import *
+
+context.gdbinit="/nix/store/jhvjf5drzzqq54xghzz94h0a6wsn1fs1-pwndbg/share/pwndbg/gdbinit.py"
+
+# exe = ELF("./a.out")
+
+p = remote("138.199.213.51", 31335)
+#p = gdb.debug(exe.path, gdbscript='''
+#                break main
+#                c
+#              ''')
+
+p.sendlineafter(b"> ", b"asd")
+
+p.interactive()