commit 8fe3ddc38ee483444716d169aea9cb71076a0a49 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..bc5390f --- /dev/null +++ b/README.md @@ -0,0 +1,131 @@ +# 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 + - once shut down all traces effectively disappear + +### they're idempotent + - ides services cah only ever run one (1) instance of any package+config combination + - no matter how many times the devshell is opened or the launch command invoked + +### they're services + - ides runs on systemd user services - no additional process manager needed + + +## the bottom line +your dev environment now includes your service dependencies! + + +## how ? +- bring ides into your nix expression (flake input/fetchGit) +- set it up by invoking its `use` function on your nixpkgs instance +- use `mkShell` like you normally would, but with *spicy extras* +- make sure you run `et-tu` before you log out! + +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's path as %CFG% in the `args` option. + +### 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; + }; + }; + }; +} +``` + +### options +- `services: attrset of service configs`: set up your services for ides +- `noCC: bool`: sets whether to use mkShell or mkShellNoCC +- `...`: all other options are passed directly to mkShell as per usual + +#### service config attributes + - `pkg`: the package to launch as a service + - `args`: the arguments to the service. writing `%CFG%` in this will template to your config location + - `ext`: in case your service is picky about its file extension, set it here + - `config`: your service config. + +if plaintext isn't your thing, check out pkgs.writers and lib.generators +for ways to generate json, yaml, etc from nix attribute sets. + +### 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 +- `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 +- bald gang +- nixpkgs manual authors +- devenv, for the idea of flake services diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..34d083c --- /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 = (shell.shellHook or "") + '' + 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"; + }; + }; +}