commit 90985f332f862c7c72144d295c6773397dca2a1b Author: atagen Date: Tue Jan 21 18:10:33 2025 +1100 beware diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f121bdb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +name diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e9d60e --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# ides +## (idempotent devshell ephemeral services) + +ides provides automatic idempotent launching of ephemeral services +in your devshell, +right here, right now. + + +## what ? + +### it's automatic + + - ides will instantly launch all your declared services, + as soon as you enter the.. + +### devshell + + - works just like regular mkShell + - full support for `shell.nix`, `flake.nix`, and `direnv` + +### it's ephemeral + + - ides packages and configs are present only in the nix store, + and once shut down all traces effectively disappear + +### they're idempotent + + - ides services cah only ever run one (1) instance of any package+config combination, + regardless of how many times the devshell is opened, or the launch command invoked + +### they're services + + - ides runs on systemd user services - no impostor process manager needed + + +## the bottom line + +your dev environment now includes your service dependencies! + + +## how ? + +here's how: + +### service configuration (caddy.nix) +```nix +{ + pkg = pkgs.caddy; + args = "run -c %CFG% --adapter caddyfile"; + config = '' + http://*:8080 { + respond "hello" + } + ''; +} +``` + +we template the provided config into %CFG% so you can feed it to the service as needed + +### classic nix(tm) + +```nix +let + pkgs = import {}; + ides = import (fetchGit { + url = "https://git.atagen.co/atagen/ides"; + }); + mkShell = ides.use pkgs; +in + mkShell { + noCC = true; + services.caddy = import ./caddy.nix; + } +``` + +### flake enjoyers + +```nix +{ + inputs = { + ides.url = "git+https://git.atagen.co/atagen/ides"; + }; + outputs = { + nixpkgs, + ides, + ... + }: let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + mkShell = ides.lib.use pkgs; + in { + devShells.x86_64-linux.default = mkShell { + noCC = true; + services = { + caddy = import ./caddy.nix; + }; + }; + }; +} +``` + +### cli + +in case you need manual control, an ides shell provides commands: + +- `ides`: raise the service set manually +- `et-tu`: shut down the service set manually +- `restart`: do both of the above in succession + +## why not reuse (nixpkgs/hm/...) module system ? + +ides was originally conceived with this in mind, but in practice, +it is rather difficult to decouple the module systems from the +deployments they are intended to fulfill. +occasional prodding is ongoing, and some activity appears to have +begin in nixpkgs to modularise services, which would allow ides +to take full advantage of the enormous nixos ecosystem. + +## acknowledgements +me +the bald gang +nixpkgs manual writers + +devenv probably diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..658be8c --- /dev/null +++ b/default.nix @@ -0,0 +1,68 @@ +{ + use = pkgs: { + inherit pkgs; + __functor = self: shell: let + inherit (pkgs) writeText writeShellScriptBin; + inherit (pkgs.lib) getExe foldlAttrs; + inherit (builtins) hashString removeAttrs; + + noCC = shell.noCC or false; + + mkWorks = { + pkg, + args ? "", + config, + ext ? "", + }: let + bin = getExe pkg; + + name = pkg.pname; + unitName = "shell-${name}-${cfgHash}"; + + cfgHash = hashString "sha256" config; + finalConf = writeText "config-${name}-${cfgHash}${ext}" config; + + finalArgs = builtins.replaceStrings ["%CFG%"] [finalConf.outPath] args; + in { + runner = '' + echo "[ides]: Starting ${name}.." + systemd-run --user -G -u ${unitName} ${bin} ${finalArgs} + ''; + cleaner = '' + echo "[ides]: Stopping ${name}.." + systemctl --user stop ${unitName} + ''; + }; + + works = + foldlAttrs (acc: name: svc: let + pair = mkWorks svc; + in { + runners = acc.runners + pair.runner; + cleaners = acc.cleaners + pair.cleaner; + }) { + runners = ""; + cleaners = ""; + } (shell.services or {}); + + runners = writeShellScriptBin "ides" works.runners; + cleaners = writeShellScriptBin "et-tu" (works.cleaners + + '' + systemctl --user reset-failed + ''); + restart = writeShellScriptBin "restart" "et-tu; ides"; + + final = + (removeAttrs shell ["services" "noCC"]) + // { + nativeBuildInputs = (shell.nativeBuildInputs or []) ++ [runners cleaners restart]; + shellHook = '' + ides + ''; + }; + in + if noCC + then self.pkgs.mkShellNoCC final + else self.pkgs.mkShell final; + }; +} diff --git a/example/caddy.nix b/example/caddy.nix new file mode 100644 index 0000000..f33f0fd --- /dev/null +++ b/example/caddy.nix @@ -0,0 +1,9 @@ +{ + pkg = pkgs.caddy; + args = "run -c %CFG% --adapter caddyfile"; + config = '' + http://*:8080 { + respond "hello" + } + ''; +} diff --git a/example/flake.nix b/example/flake.nix new file mode 100644 index 0000000..a5ed4bc --- /dev/null +++ b/example/flake.nix @@ -0,0 +1,20 @@ +{ + inputs = { + ides.url = "git+https://git.atagen.co/atagen/ides"; + }; + outputs = { + nixpkgs, + ides, + ... + }: let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + mkShell = ides.lib.use pkgs; + in { + devShells.x86_64-linux.default = mkShell { + noCC = true; + services = { + caddy = import ./caddy.nix; + }; + }; + }; +} diff --git a/example/shell.nix b/example/shell.nix new file mode 100644 index 0000000..45a6b0d --- /dev/null +++ b/example/shell.nix @@ -0,0 +1,11 @@ +let + pkgs = import {}; + ides = import (fetchGit { + url = "https://git.atagen.co/atagen/ides"; + }); + mkShell = ides.use pkgs; +in + mkShell { + noCC = true; + services.caddy = import ./caddy.nix; + } diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e203823 --- /dev/null +++ b/flake.nix @@ -0,0 +1,9 @@ +{ + outputs = {...}: { + lib = import ./default.nix; + templates.default = { + path = ./example; + description = "the ides template"; + }; + }; +} diff --git a/ides.nix b/ides.nix new file mode 100644 index 0000000..658be8c --- /dev/null +++ b/ides.nix @@ -0,0 +1,68 @@ +{ + use = pkgs: { + inherit pkgs; + __functor = self: shell: let + inherit (pkgs) writeText writeShellScriptBin; + inherit (pkgs.lib) getExe foldlAttrs; + inherit (builtins) hashString removeAttrs; + + noCC = shell.noCC or false; + + mkWorks = { + pkg, + args ? "", + config, + ext ? "", + }: let + bin = getExe pkg; + + name = pkg.pname; + unitName = "shell-${name}-${cfgHash}"; + + cfgHash = hashString "sha256" config; + finalConf = writeText "config-${name}-${cfgHash}${ext}" config; + + finalArgs = builtins.replaceStrings ["%CFG%"] [finalConf.outPath] args; + in { + runner = '' + echo "[ides]: Starting ${name}.." + systemd-run --user -G -u ${unitName} ${bin} ${finalArgs} + ''; + cleaner = '' + echo "[ides]: Stopping ${name}.." + systemctl --user stop ${unitName} + ''; + }; + + works = + foldlAttrs (acc: name: svc: let + pair = mkWorks svc; + in { + runners = acc.runners + pair.runner; + cleaners = acc.cleaners + pair.cleaner; + }) { + runners = ""; + cleaners = ""; + } (shell.services or {}); + + runners = writeShellScriptBin "ides" works.runners; + cleaners = writeShellScriptBin "et-tu" (works.cleaners + + '' + systemctl --user reset-failed + ''); + restart = writeShellScriptBin "restart" "et-tu; ides"; + + final = + (removeAttrs shell ["services" "noCC"]) + // { + nativeBuildInputs = (shell.nativeBuildInputs or []) ++ [runners cleaners restart]; + shellHook = '' + ides + ''; + }; + in + if noCC + then self.pkgs.mkShellNoCC final + else self.pkgs.mkShell final; + }; +}