From 8fe319e525270ab000682c12d03ae27dcf6c29d9 Mon Sep 17 00:00:00 2001 From: Daylin Morgan Date: Wed, 28 Aug 2024 11:08:58 -0500 Subject: [PATCH] oizys nim wip --- flake.lock | 103 ++++++++----- flake.nix | 1 + hosts/othalan/default.nix | 3 +- lib/default.nix | 13 +- pkgs/oizys-nim/.envrc | 1 + pkgs/oizys-nim/.gitignore | 9 ++ pkgs/oizys-nim/config.nims | 8 + pkgs/oizys-nim/default.nix | 12 ++ pkgs/oizys-nim/nimble.lock | 36 +++++ pkgs/oizys-nim/oizys.nimble | 16 ++ pkgs/oizys-nim/src/oizys.nim | 128 ++++++++++++++++ pkgs/oizys-nim/src/oizys.nim.cfg | 1 + pkgs/oizys-nim/src/oizys/context.nim | 54 +++++++ pkgs/oizys-nim/src/oizys/exec.nim | 43 ++++++ pkgs/oizys-nim/src/oizys/github.nim | 39 +++++ pkgs/oizys-nim/src/oizys/ignored.txt | 21 +++ pkgs/oizys-nim/src/oizys/logging.nim | 108 +++++++++++++ pkgs/oizys-nim/src/oizys/nix.nim | 217 +++++++++++++++++++++++++++ pkgs/oizys-nim/src/oizys/overlay.nim | 64 ++++++++ pkgs/oizys-nim/src/oizys/spin.nim | 189 +++++++++++++++++++++++ pkgs/oizys-nim/todo.md | 8 + 21 files changed, 1035 insertions(+), 39 deletions(-) create mode 100644 pkgs/oizys-nim/.envrc create mode 100644 pkgs/oizys-nim/.gitignore create mode 100644 pkgs/oizys-nim/config.nims create mode 100644 pkgs/oizys-nim/default.nix create mode 100644 pkgs/oizys-nim/nimble.lock create mode 100644 pkgs/oizys-nim/oizys.nimble create mode 100644 pkgs/oizys-nim/src/oizys.nim create mode 100644 pkgs/oizys-nim/src/oizys.nim.cfg create mode 100644 pkgs/oizys-nim/src/oizys/context.nim create mode 100644 pkgs/oizys-nim/src/oizys/exec.nim create mode 100644 pkgs/oizys-nim/src/oizys/github.nim create mode 100644 pkgs/oizys-nim/src/oizys/ignored.txt create mode 100644 pkgs/oizys-nim/src/oizys/logging.nim create mode 100644 pkgs/oizys-nim/src/oizys/nix.nim create mode 100644 pkgs/oizys-nim/src/oizys/overlay.nim create mode 100644 pkgs/oizys-nim/src/oizys/spin.nim create mode 100644 pkgs/oizys-nim/todo.md diff --git a/flake.lock b/flake.lock index fc9025f..bfa7703 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ ] }, "locked": { - "lastModified": 1724273991, - "narHash": "sha256-+aUSOXKGpS5CRm1oTitgNAr05ThQNbKIXalZHl3nC6Y=", + "lastModified": 1724781866, + "narHash": "sha256-ItgACCJCwn8Rx7p8hJBpnU9eCtrdmkg4AbqMZL/rXlY=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "9a3161ad4c78dc420d1cbb3aae638222608c7de4", + "rev": "7cc3d3179c06caf3769afb3eb0c69aa55676c96a", "type": "github" }, "original": { @@ -323,11 +323,11 @@ "xdph": "xdph" }, "locked": { - "lastModified": 1724701003, - "narHash": "sha256-kTceEi5B4t2u5oG3bKnGgZkHxFTsvYAUiAmyYA/6Y3o=", + "lastModified": 1724850433, + "narHash": "sha256-WSjYdnlg6Qq0xrjCgL/WJ7a+kEWQtQmYrw0TV4ykppY=", "ref": "refs/heads/main", - "rev": "eb42adc4c090918ad6be9fcb24066da8cdfd9bd0", - "revCount": 5145, + "rev": "98e99cd03df5b4421f72f2a3f2d7de53f8261f1f", + "revCount": 5152, "submodules": true, "type": "git", "url": "https://github.com/hyprwm/Hyprland/" @@ -504,11 +504,11 @@ "lix": { "flake": false, "locked": { - "lastModified": 1724714224, - "narHash": "sha256-Fn72c2ycgYERAdwWEPmIUhuOmgOwuvZhTNdqOuiR0uw=", - "rev": "0dc486a5bf218870aafd5552586ab4330881647e", + "lastModified": 1724863402, + "narHash": "sha256-2xrZAo4m0SNklS71RCfWzNXu79OAZMrHNpe6RGOh4OY=", + "rev": "422550fd68a5877534b1ca577fc3c7d89b6706dd", "type": "tarball", - "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/0dc486a5bf218870aafd5552586ab4330881647e.tar.gz?rev=0dc486a5bf218870aafd5552586ab4330881647e" + "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/422550fd68a5877534b1ca577fc3c7d89b6706dd.tar.gz?rev=422550fd68a5877534b1ca577fc3c7d89b6706dd" }, "original": { "type": "tarball", @@ -538,11 +538,29 @@ "url": "https://git.lix.systems/lix-project/nixos-module/archive/main.tar.gz" } }, + "nim2nix": { + "inputs": { + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1724878933, + "narHash": "sha256-VP0+Lal3jJJqDH1EzQX73rP9Ue8ZTIyAErJvDz6PQSg=", + "owner": "daylinmorgan", + "repo": "nim2nix", + "rev": "5153c772e4c6a4f5645efa85ce536fe1c5063ebb", + "type": "github" + }, + "original": { + "owner": "daylinmorgan", + "repo": "nim2nix", + "type": "github" + } + }, "nix-eval-jobs": { "inputs": { "flake-parts": "flake-parts", "nix-github-actions": "nix-github-actions", - "nixpkgs": "nixpkgs_5", + "nixpkgs": "nixpkgs_6", "treefmt-nix": "treefmt-nix" }, "locked": { @@ -630,7 +648,7 @@ "inputs": { "flake-compat": "flake-compat", "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_4" }, "locked": { "lastModified": 1724664098, @@ -682,14 +700,14 @@ "flake-compat": "flake-compat_2", "lib-aggregate": "lib-aggregate", "nix-eval-jobs": "nix-eval-jobs", - "nixpkgs": "nixpkgs_6" + "nixpkgs": "nixpkgs_7" }, "locked": { - "lastModified": 1724746994, - "narHash": "sha256-krOFIWrWMW44GBpjjKKo4dcM6oPXI9YpFj+Wqf2PwxU=", + "lastModified": 1724858360, + "narHash": "sha256-Z78j0lWk4j8U5jC34S2ON37I5Z4r9yJt1orN4PBIra0=", "owner": "nix-community", "repo": "nixpkgs-wayland", - "rev": "f42d86203af2967f81c50cf4f0722c013bc22170", + "rev": "4fad2594f6dc0c0ac3b58272b7060b51d460b607", "type": "github" }, "original": { @@ -715,6 +733,22 @@ } }, "nixpkgs_3": { + "locked": { + "lastModified": 1724479785, + "narHash": "sha256-pP3Azj5d6M5nmG68Fu4JqZmdGt4S4vqI5f8te+E/FTw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "d0e1602ddde669d5beb01aec49d71a51937ed7be", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { "locked": { "lastModified": 1724316499, "narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=", @@ -730,13 +764,13 @@ "type": "github" } }, - "nixpkgs_4": { + "nixpkgs_5": { "locked": { - "lastModified": 1724395761, - "narHash": "sha256-zRkDV/nbrnp3Y8oCADf5ETl1sDrdmAW6/bBVJ8EbIdQ=", + "lastModified": 1724748588, + "narHash": "sha256-NlpGA4+AIf1dKNq76ps90rxowlFXUsV9x7vK/mN37JM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ae815cee91b417be55d43781eb4b73ae1ecc396c", + "rev": "a6292e34000dc93d43bccf78338770c1c5ec8a99", "type": "github" }, "original": { @@ -746,7 +780,7 @@ "type": "github" } }, - "nixpkgs_5": { + "nixpkgs_6": { "locked": { "lastModified": 1723221148, "narHash": "sha256-7pjpeQlZUNQ4eeVntytU3jkw9dFK3k1Htgk2iuXjaD8=", @@ -762,7 +796,7 @@ "type": "github" } }, - "nixpkgs_6": { + "nixpkgs_7": { "locked": { "lastModified": 1724479785, "narHash": "sha256-pP3Azj5d6M5nmG68Fu4JqZmdGt4S4vqI5f8te+E/FTw=", @@ -809,11 +843,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1724759134, - "narHash": "sha256-E4nBGiBGkdB1e01rGLAEiDhM5cKMnVbRPUXwXf3CjaU=", + "lastModified": 1724859070, + "narHash": "sha256-/jeh/OJm5+gmg+dQxpWetL9jgjk+zGe7uCRLvbNvukA=", "owner": "roc-lang", "repo": "roc", - "rev": "7811d8876865dc9927d707edb8fd8a426a595a0e", + "rev": "479feca39618a58770db68b44072be1f29df8d84", "type": "github" }, "original": { @@ -830,9 +864,10 @@ "hyprman": "hyprman", "lix": "lix", "lix-module": "lix-module", + "nim2nix": "nim2nix", "nix-index-database": "nix-index-database", "nixos-wsl": "nixos-wsl", - "nixpkgs": "nixpkgs_4", + "nixpkgs": "nixpkgs_5", "nixpkgs-wayland": "nixpkgs-wayland", "pixi": "pixi", "roc": "roc", @@ -1040,11 +1075,11 @@ ] }, "locked": { - "lastModified": 1723841055, - "narHash": "sha256-ykjmNEXFJ4lrW1LeB4dVsNlPKlNB0VWNnOTp1sLbOqQ=", + "lastModified": 1724782685, + "narHash": "sha256-/gzaaWVqXbnj78pNC7cOW/MDZidO127v9QRpAyBgczU=", "ref": "refs/heads/main", - "rev": "eca60579ef577ad45aa1a371f006697ddc2611d6", - "revCount": 5, + "rev": "5289c14095ec7fe0676a0dd3c3084ead095da1a5", + "revCount": 6, "type": "git", "url": "https://git.dayl.in/daylin/utils.git" }, @@ -1092,11 +1127,11 @@ ] }, "locked": { - "lastModified": 1724718400, - "narHash": "sha256-i83icUP6218jkNLBiHj2JyhKrVbgkU2mOGFvpFjw0WM=", + "lastModified": 1724847079, + "narHash": "sha256-bbcvuJqmjShg6Uet6CptnxkuPBRL2drQmvnaXi832Xk=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "20a8e0316531f825284ef15fe20b4f9848fbb4e4", + "rev": "ce690c0c732a9627a9736f145d1739d56d4905c4", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index df81c58..8774b4a 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,7 @@ zig-overlay.url = "github:mitchellh/zig-overlay"; zls.url = "github:zigtools/zls"; + nim2nix.url = "github:daylinmorgan/nim2nix"; pixi.url = "github:daylinmorgan/pixi-flake"; f1multiviewer.url = "github:daylinmorgan/f1multiviewer-flake"; tsm.url = "github:daylinmorgan/tsm?dir=nix"; diff --git a/hosts/othalan/default.nix b/hosts/othalan/default.nix index 736d72c..300649e 100644 --- a/hosts/othalan/default.nix +++ b/hosts/othalan/default.nix @@ -12,7 +12,8 @@ nix-ld = enabled // { overkill = enabled; }; - languages = "misc|nim|node|nushell|python|roc|tex|zig" |> listify; + # languages = "misc|nim|node|nushell|python|roc|tex|zig" |> listify; + languages = "misc|nim|node|nushell|python|roc|tex" |> listify; } // ( '' diff --git a/lib/default.nix b/lib/default.nix index a688e84..96b1288 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -9,7 +9,10 @@ let inherit (import ./generators.nix { inherit lib self inputs; }) mkIso mkSystem; #supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"]; supportedSystems = [ "x86_64-linux" ]; - forAllSystems = f: genAttrs supportedSystems (system: f (import nixpkgs { inherit system; })); + forAllSystems = f: genAttrs supportedSystems (system: f (import nixpkgs { + inherit system; + overlays = [inputs.nim2nix.overlays.default]; + })); inheritFlakePkgs = pkgs: flakes: @@ -27,6 +30,7 @@ let pkgs: rec { default = oizys-cli; + oizys-nim = pkgs.callPackage ../pkgs/oizys-nim { }; oizys-cli = pkgs.callPackage ../pkgs/oizys { }; iso = mkIso.config.system.build.isoImage; roc = (pkgsFromSystem pkgs.system "roc").full; @@ -39,10 +43,11 @@ let ); devShells = forAllSystems (pkgs: { - default = pkgs.mkShell { + oizys = pkgs.mkShell { packages = with pkgs; [ - git - deadnix + openssl + nim + nimble ]; }; }); diff --git a/pkgs/oizys-nim/.envrc b/pkgs/oizys-nim/.envrc new file mode 100644 index 0000000..612ebbd --- /dev/null +++ b/pkgs/oizys-nim/.envrc @@ -0,0 +1 @@ +use flake "../..#oizys" diff --git a/pkgs/oizys-nim/.gitignore b/pkgs/oizys-nim/.gitignore new file mode 100644 index 0000000..a465e32 --- /dev/null +++ b/pkgs/oizys-nim/.gitignore @@ -0,0 +1,9 @@ +nimbledeps +nimble.paths +nimble.develop + +# binaries +oizys* +!src/oizys +!src/oizys.nim* +oizys.out diff --git a/pkgs/oizys-nim/config.nims b/pkgs/oizys-nim/config.nims new file mode 100644 index 0000000..bbf9469 --- /dev/null +++ b/pkgs/oizys-nim/config.nims @@ -0,0 +1,8 @@ +task build, "build oizys": + selfExec "c -o:oizys src/oizys.nim" + +# begin Nimble config (version 2) +--noNimblePath +when withDir(thisDir(), system.fileExists("nimble.paths")): + include "nimble.paths" +# end Nimble config diff --git a/pkgs/oizys-nim/default.nix b/pkgs/oizys-nim/default.nix new file mode 100644 index 0000000..ac8f43a --- /dev/null +++ b/pkgs/oizys-nim/default.nix @@ -0,0 +1,12 @@ +{ + lib, + openssl, + buildNimblePackage, +}: +buildNimblePackage { + name = "oizys"; + verions = "unstable"; + src = lib.cleanSource ./.; + nativeBuildInputs = [ openssl ]; + nimbleDepsHash = "sha256-Eheeve4MbB3v1oVj0mB36Mv2Q3vJGLEbbShds1af23g="; +} diff --git a/pkgs/oizys-nim/nimble.lock b/pkgs/oizys-nim/nimble.lock new file mode 100644 index 0000000..cd4c542 --- /dev/null +++ b/pkgs/oizys-nim/nimble.lock @@ -0,0 +1,36 @@ +{ + "version": 2, + "packages": { + "bbansi": { + "version": "0.1.1", + "vcsRevision": "9a85d9ed028f06f1ed1ee6851480a51408a6004e", + "url": "https://github.com/daylinmorgan/bbansi", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "b338433f9a7a1b788b7583674c2b14096ced29ee" + } + }, + "cligen": { + "version": "1.7.0", + "vcsRevision": "4193f802796f15559c81c6dd56724d6f20345917", + "url": "https://github.com/c-blake/cligen.git", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "300bd7fdb6e48d2d98e34ed0661206b50331e99c" + } + }, + "jsony": { + "version": "1.1.5", + "vcsRevision": "ea811bec7fa50f5abd3088ba94cda74285e93f18", + "url": "https://github.com/treeform/jsony", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "6aeb83e7481ca8686396a568096054bc668294df" + } + } + }, + "tasks": {} +} diff --git a/pkgs/oizys-nim/oizys.nimble b/pkgs/oizys-nim/oizys.nimble new file mode 100644 index 0000000..e56a821 --- /dev/null +++ b/pkgs/oizys-nim/oizys.nimble @@ -0,0 +1,16 @@ +# Package + +version = "0.1.0" +author = "Daylin Morgan" +description = "nix begat oizys" +license = "MIT" +srcDir = "src" +bin = @["oizys"] + + +# Dependencies + +requires "nim >= 2.0.8" +requires "cligen" +requires "jsony" +requires "https://github.com/daylinmorgan/bbansi#9a85d9e" diff --git a/pkgs/oizys-nim/src/oizys.nim b/pkgs/oizys-nim/src/oizys.nim new file mode 100644 index 0000000..05d3506 --- /dev/null +++ b/pkgs/oizys-nim/src/oizys.nim @@ -0,0 +1,128 @@ +## nix begat oizys +import std/[os, tables, sequtils, strformat,] + +import cligen, bbansi +import oizys/[context, github, nix, overlay, logging] + + +addHandler( + newFancyConsoleLogger( + levelThreshold=lvlAll, + useStderr = true, + fmtPrefix = $bb"[b magenta]oizys" + ) +) + +overlay: + proc pre( + flake: string = "", + host: seq[string] = @[], + debug: bool = false, + resetCache: bool = false, + rest: seq[string], + ) = + if not debug: setLogFilter(lvlInfo) + updateContext(host, flake, debug, resetCache) + + proc dry(minimal: bool = false) = + ## dry run build + nixBuildHostDry(minimal, rest) + + proc output(yes: bool = false) = + ## output + echo nixosConfigAttrs().join(" ") + + proc update(yes: bool = false) = + ## *TBD* update and run nixos-rebuild + fatal "not implemented" + + proc build(minimal: bool = false) = + ## nix build + nixBuild(minimal, rest) + + proc cache(name: string = "daylin") = + ## *TBD* build and push to cachix + fatal "not implemented" + + proc osCmd() = + ## nixos-rebuild + if len(rest) == 0: quit "please provide subcmd" + let subcmd = rest[0] + if subcmd notin nixosSubcmds: + error ( + &"unknown nixos-rebuild subcmd: {subcmd}\nexpected one of: \n" & + nixosSubcmds.mapIt(" " & it).join("\n") + ) + quit QuitFailure + nixosRebuild(subcmd, rest[1..^1]) + + proc ci(`ref`: string = "main") = + ## *TBD* trigger GHA update flow + if rest.len == 0: + fatal "expected workflow file name"; quit QuitFailure + createDispatch(rest[0], `ref`) + +proc checkExes() = + if findExe("nix") == "": + quit("oizys requires nix", QuitFailure) + +proc `//`(t1: Table[string, string], t2: Table[string, string]): Table[string, string] = + # nix style table merge + for k, v in t1.pairs(): result[k] = v + for k, v in t2.pairs(): result[k] = v + +proc setupCligen() = + let isColor = getEnv("NO_COLOR") == "" + if clCfg.useMulti == "": + clCfg.useMulti = + if isColor: + "${doc}\e[1mUsage\e[m:\n $command {SUBCMD} [sub-command options & parameters]\n\n\e[1msubcommands\e[m:\n$subcmds" + else: + "${doc}Usage:\n $command {SUBCMD} [sub-command options & parameters]\n\nsubcommands:\n$subcmds" + if not isColor: return + if clCfg.helpAttr.len == 0: + clCfg.helpAttr = {"cmd": "\e[1;36m", "clDescrip": "", "clDflVal": "\e[33m", + "clOptKeys": "\e[32m", "clValType": "\e[31m", "args": "\e[3m"}.toTable() + clCfg.helpAttrOff = {"cmd": "\e[m", "clDescrip": "\e[m", "clDflVal": "\e[m", + "clOptKeys": "\e[m", "clValType": "\e[m", "args": "\e[m"}.toTable() + # clCfg.use does nothing? + clCfg.useHdr = "\e[1mUsage\e[m:\n " + +when isMainModule: + checkExes() + setupCligen() + let (optOpen, optClose) = + if getEnv("NO_COLOR") == "": ("\e[1m","\e[m") + else: ("","") + let + usage = &"$command [flags]\n$doc{optOpen}Options{optClose}:\n$options" + osUsage = &"$command [subcmd] [flags]\n$doc{optOpen}Options{optClose}:\n$options" + + const + sharedHelp = { + "flake" : "path/to/flake", + "host" : "host(s) to build", + "debug" : "enable debug mode", + "resetCache" : "set cache timeout to 0" + }.toTable() + updateHelp = { + "yes" : "skip all confirmation prompts" + }.toTable() // sharedHelp + ciHelp = { + "ref" : "git ref/branch/tag to trigger workflow on" + }.toTable() + cacheHelp = { + "name" : "name of cachix binary cache" + }.toTable() // sharedHelp + + # setting clCfg.use wasn't working? + dispatchMulti( + [build, help = sharedHelp, usage = usage], + [cache, help = cacheHelp, usage = usage], + [ci, help = ciHelp, usage = usage], + [dry, help = sharedHelp, usage = usage], + [osCmd, help = sharedHelp, usage = osUsage, cmdName = "os"], + [output, help = sharedHelp, usage = usage], + [update, help = updateHelp, usage = usage], + ) + diff --git a/pkgs/oizys-nim/src/oizys.nim.cfg b/pkgs/oizys-nim/src/oizys.nim.cfg new file mode 100644 index 0000000..521e21d --- /dev/null +++ b/pkgs/oizys-nim/src/oizys.nim.cfg @@ -0,0 +1 @@ +-d:ssl diff --git a/pkgs/oizys-nim/src/oizys/context.nim b/pkgs/oizys-nim/src/oizys/context.nim new file mode 100644 index 0000000..240039a --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/context.nim @@ -0,0 +1,54 @@ +import std/[logging, os, strformat, strutils] +from std/nativesockets import getHostname + +type + OizysContext* = object + flake, host: string + hosts: seq[string] + debug: bool + ci: bool + resetCache: bool + +proc initContext*(): OizysContext = + result.hosts = @[getHostname()] + result.flake = "github:daylinmorgan/oizys" + let localDir = getHomeDir() / "oizys" + if localDir.dirExists: + result.flake = localDir + let envVar = getEnv("OIZYS_DIR") + if envVar != "": + result.flake = envVar + result.ci = getEnv("GITHUB_STEP_SUMMARY") != "" + +var oc = initContext() +proc checkPath(s: string): string = + ## fail if path doesn't exist + if not s.dirExists: + error fmt"flake path: {s} does not exist" + quit() + s + +# public api ------------------------------------- + +proc updateContext*( + host: seq[string], + flake: string, + debug: bool, + resetCache: bool +) = + oc.debug = debug + oc.resetCache = resetCache + if host.len > 0: + oc.hosts = host + if flake != "": + oc.flake = + if flake.startsWith("github") or flake.startsWith("git+"): flake + else: checkPath(flake.normalizedPath().absolutePath()) + +proc getHosts*(): seq[string] = return oc.hosts +proc getFlake*(): string = return oc.flake +proc isDebug*(): bool = return oc.debug +proc isResetCache*(): bool = return oc.resetCache +proc isCi*(): bool = return oc.ci + + diff --git a/pkgs/oizys-nim/src/oizys/exec.nim b/pkgs/oizys-nim/src/oizys/exec.nim new file mode 100644 index 0000000..1ca222a --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/exec.nim @@ -0,0 +1,43 @@ +import std/[ + osproc, strformat, + strutils, streams, logging +] + +import ./spin + + +func addArgs*(cmd: var string, args: seq[string]) = + cmd &= " " & args.join(" ") + +func addArg*(cmd: var string, arg: string) = + cmd &= " " & arg + +proc runCmd*(cmd: string): int = + debug fmt"running cmd: {cmd}" + execCmd cmd + +proc runCmdCapt*(cmd: string): tuple[stdout, stderr: string, exitCode: int] = + let args = cmd.splitWhitespace() + let p = startProcess(args[0], args = args[1..^1], options = {poUsePath}) + result = ( + readAll p.outputStream, + readAll p.errorStream, + waitForExit p + ) + close p + +proc runCmdCaptWithSpinner*(cmd: string, msg: string = ""): tuple[output, err: string] = + debug fmt"running command: {cmd}" + withSpinner(msg): + let (output, err, code) = runCmdCapt(cmd) + if code != 0: + stderr.writeLine("stdout\n" & output) + stderr.writeLine("stderr\n" & err) + error fmt"{cmd} had non zero exit" + quit code + return (output, err) + +proc quitWithCmd*(cmd: string) = + debug cmd + quit(execCmd cmd) + diff --git a/pkgs/oizys-nim/src/oizys/github.nim b/pkgs/oizys-nim/src/oizys/github.nim new file mode 100644 index 0000000..89d6001 --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/github.nim @@ -0,0 +1,39 @@ +import std/[httpclient,logging, os, strformat, strutils, json] + +var ghToken = getEnv("GITHUB_TOKEN") + +#[curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer " \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches \ + -d '{"ref":"topic-branch","inputs":{"name":"Mona the Octocat","home":"San Francisco, CA"}}' +]# + +proc postGhApi(url: string, body: JsonNode) = + if ghToken == "": fatal "GITHUB_TOKEN not set"; quit QuitFailure + let client = newHttpClient() + client.headers = newHttpHeaders({ + "Accept" : "application/vnd.github+json", + "Authorization" : fmt"Bearer {ghToken}", + "X-GitHub-Api-Version": "2022-11-28", + }) + echo $body + let response = client.post(url, body = $body) + info fmt"Status: {response.status}" + +proc createDispatch*(workflowFileName: string, `ref`: string) = + ## https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event + var workflow = + if workflowFileName.endsWith(".yml") or workflowFileName.endsWith(".yaml"): workflowFileName + else: workflowFileName & ".yml" + info fmt"creating dispatch event for {workflow}" + postGhApi( + fmt"https://api.github.com/repos/daylinmorgan/oizys/actions/workflows/{workflow}/dispatches", + %*{ + "ref": `ref`, + } + ) + + diff --git a/pkgs/oizys-nim/src/oizys/ignored.txt b/pkgs/oizys-nim/src/oizys/ignored.txt new file mode 100644 index 0000000..7693d95 --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/ignored.txt @@ -0,0 +1,21 @@ +ld-library-path +builder.pl +profile +system-path +nixos-help +nixos-install +nixos-version +nixos-manual-html +nixos-rebuild +nixos-configuration-reference-manpage +nixos-generate-config +nixos-enter +nixos-container +nixos-build-vms +nixos-wsl-version +nixos-wsl-welcome-message +nixos-wsl-welcome +restic-gdrive +gitea +lock +code diff --git a/pkgs/oizys-nim/src/oizys/logging.nim b/pkgs/oizys-nim/src/oizys/logging.nim new file mode 100644 index 0000000..e6b8039 --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/logging.nim @@ -0,0 +1,108 @@ +import std/[logging,strutils] +export logging + +import bbansi + +var + handlers {.threadvar.}: seq[Logger] + +#[ +Level* = enum ## \ + lvlAll, ## All levels active + lvlDebug, ## Debug level and above are active + lvlInfo, ## Info level and above are active + lvlNotice, ## Notice level and above are active + lvlWarn, ## Warn level and above are active + lvlError, ## Error level and above are active + lvlFatal, ## Fatal level and above are active + lvlNone ## No levels active; nothing is logged +]# + +type + FancyConsoleLogger* = ref object of Logger + ## A logger that writes log messages to the console. + ## + ## Create a new ``FancyConsoleLogger`` with the `newFancyConsoleLogger proc + ## <#newConsoleLogger>`_. + ## + useStderr*: bool ## If true, writes to stderr; otherwise, writes to stdout + flushThreshold*: Level ## Only messages that are at or above this + ## threshold will be flushed immediately + fmtPrefix: string + fmtSep: string + fmtStrs: array[Level, string] + + +const defaultFlushThreshold = lvlAll + +proc genFmtStr( + fmtPrefix, fmtSep, fmtSuffix, levelBb: string, + level: Level + ): string = + var parts: seq[string] + if fmtPrefix != "": parts.add fmtPrefix + parts.add $LevelNames[level].bb(levelBb) + return parts.join(fmtSep) & fmtSuffix + + +proc newFancyConsoleLogger*( + levelThreshold = lvlAll, + fmtPrefix= "", + fmtSep = "|", + fmtSuffix ="| ", + useStderr = false, + flushThreshold = defaultFlushThreshold, + debugBb: string = "faint", + infoBb: string = "bold", + noticeBb: string = "bold", + warnBb: string = "bold yellow", + errorBb: string = "bold red", + fatalBb: string = "bold red" + ): FancyConsoleLogger = + ## Creates a new `ColoredConsoleLogger<#ConsoleLogger>`_. + new result + let fmtStrs: array[Level, string] = [ + genFmtStr(fmtPrefix,fmtSep, fmtSuffix, "", lvlAll), + genFmtStr(fmtPrefix,fmtSep, fmtSuffix, debugBb, lvlDebug), + genFmtStr(fmtPrefix,fmtSep, fmtSuffix, infobb, lvlInfo), + genFmtStr(fmtPrefix,fmtSep, fmtSuffix, noticeBb, lvlNotice), + genFmtStr(fmtPrefix,fmtSep, fmtSuffix, warnBb, lvlWarn), + genFmtStr(fmtPrefix,fmtSep, fmtSuffix, errorBb, lvlError), + genFmtStr(fmtPrefix,fmtSep, fmtSuffix, fatalBb, lvlFatal), + genFmtStr(fmtPrefix, fmtSep, fmtSuffix, "", lvlNone) + ] + result.fmtPrefix = fmtPrefix + result.fmtSep = fmtSep + result.levelThreshold = levelThreshold + result.flushThreshold = flushThreshold + result.useStderr = useStderr + result.fmtStrs = fmtStrs + + +method log*(logger: FancyConsoleLogger, level: Level, args: varargs[string, `$`]) {.gcsafe.} = + ## Logs to the console with the given `FancyConsoleLogger<#ConsoleLogger>`_ only. + ## + ## This method ignores the list of registered handlers. + ## + ## Whether the message is logged depends on both the ConsoleLogger's + ## ``levelThreshold`` field and the global log filter set using the + ## `setLogFilter proc<#setLogFilter,Level>`_. + ## + ## **Note:** Only error and fatal messages will cause the output buffer + ## to be flushed immediately by default. Set ``flushThreshold`` when creating + ## the logger to change this. + + if level >= logger.levelThreshold: + let ln = substituteLog(logger.fmtStrs[level], level, args) + when defined(js): {.fatal: "handler does note support JS".} + try: + let handle = + if logger.useStderr: stderr + else: stdout + writeLine(handle, ln) + if level >= logger.flushThreshold: flushFile(handle) + except IOError: + discard + +proc addHandlers*(handler: Logger) = + handlers.add(handler) diff --git a/pkgs/oizys-nim/src/oizys/nix.nim b/pkgs/oizys-nim/src/oizys/nix.nim new file mode 100644 index 0000000..5b93933 --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/nix.nim @@ -0,0 +1,217 @@ +import std/[ + algorithm, json, + enumerate, os, osproc, sequtils, strformat, + strutils, sugar, logging, tables +] +import bbansi, jsony +import ./[context, exec] + + +proc nixCommand(cmd: string): string = + result = "nix" + if isResetCache(): + result.addArg "--narinfo-cache-negative-ttl 0" + result.addArg "--log-format multiline" + result.addArg cmd + +proc nixosConfigAttrs*(): seq[string] = + for host in getHosts(): + result.add fmt"{getFlake()}#nixosConfigurations.{host}.config.system.build.toplevel" + +const nixosSubcmds* = + """switch boot test build dry-build dry-activate edit + repl build-vm build-vm-with-bootloader list-generations""".splitWhitespace() + +proc nixosRebuild*(subcmd: string, rest: seq[string] = @[]) = + var cmd = fmt"sudo nixos-rebuild {subcmd} --flake {getFlake()} --log-format multiline" + if getHosts().len > 1: + error "nixos-rebuild only supports one host" + quit QuitFailure + cmd.addArgs rest + quitWithCmd cmd + +type + Derivation = object + storePath, hash, name: string + + DryRunOutput = object + toBuild: seq[Derivation] + toFetch: seq[Derivation] + +func toDerivation(pkg: string): Derivation = + let path = pkg.strip() + let s = path.split("-", 1) + result.storePath = path + result.hash = s[0].rsplit("/")[^1] + result.name = s[^1].replace(".drv","") + +func toDerivations(lines: seq[string]): seq[Derivation] = + for pkg in lines: + result.add (toDerivation pkg) + +proc cmpDrv(x, y: Derivation): int = + cmp(x.name, y.name) + +proc parseDryRunOutput(err: string): DryRunOutput = + let lines = err.strip().splitLines() + let theseLines = collect: + for i, line in enumerate(lines): + if line.startswith("these"): i + + case theseLines.len: + of 2: + let (firstIdx, secondIdx) = (theseLines[0], theseLines[1]) + result.toBuild = lines[(firstIdx + 1) .. (secondIdx - 1)].toDerivations() + result.toFetch = lines[(secondIdx + 1) .. ^1].toDerivations() + of 1: + let idx = theseLines[0] + let line = lines[idx] + let drvs = lines[idx .. ^1].toDerivations() + if line.contains("built:"): + result.toBuild = drvs + elif line.contains("will be fetched"): + result.toFetch =drvs + else: + fatal "expected on of the lines to contain built or fetched check the output below" + stderr.writeLine err + quit() + of 0: + info "nothing to do"; + quit(QuitSuccess) + else: + fatal "unexpected output from nix" + stderr.writeLine err + quit() + + result.toBuild.sort(cmpDrv) + result.toFetch.sort(cmpDrv) + +proc trunc(s: string, limit: int): string = + if s.len <= limit: + s + else: + s[0..(limit-4)] & "..." + +proc display(msg: string, drvs: seq[Derivation]) = + echo fmt"{msg}: [bold cyan]{drvs.len()}[/]".bb + let maxLen = min(max drvs.mapIt(it.name.len), 40) + for drv in drvs: + echo " ", drv.name.trunc(maxLen).alignLeft(maxLen), " ", drv.hash.bb("faint") + +proc display(output: DryRunOutput) = + if isDebug(): + display("to fetch", output.toFetch) + else: + echo fmt"to fetch: [bold cyan]{output.toFetch.len()}[/]".bb + display("to build", output.toBuild) + +proc toBuildNixosConfiguration(): seq[string] = + var cmd = nixCommand("build") + cmd.addArg "--dry-run" + cmd.addArgs nixosConfigAttrs() + let (_, err) = runCmdCaptWithSpinner(cmd, "running dry run build for: " & getHosts().join(" ")) + let output = parseDryRunOutput err + return output.toBuild.mapIt(it.storePath) + +type + NixDerivation = object + inputDrvs: Table[string, JsonNode] + name: string + +proc evaluateDerivations(drvs: seq[string]): Table[string,NixDerivation] = + var cmd = "nix derivation show -r" + cmd.addArgs drvs + let (output, _) = + runCmdCaptWithSpinner(cmd, "evaluating derivations") + output.fromJson(Table[string,NixDerivation]) + + +# TODO: replace asserts in this proc +proc findSystemPaths(drvs: Table[string, NixDerivation]): seq[string] = + let hosts = getHosts() + let systemDrvs = collect( + for k in drvs.keys(): + if k.split("-",1)[1].startswith("nixos-system-"): k + ) + + assert len(hosts) == len(systemDrvs) + for name in systemDrvs: + for drv in drvs[name].inputDrvs.keys(): + if drv.endsWith("system-path.drv"): + result.add drv + + assert len(hosts) == len(result) + +func isIgnored(drv: string): bool = + const ignoredPackages = (slurp "ignored.txt").splitLines() + drv.split("-", 1)[1].replace(".drv","") in ignoredPackages + +proc systemPathDrvsToBuild(): seq[string] = + let toBuild = toBuildNixosConfiguration() + let drvs = evaluateDerivations(nixosConfigAttrs()) + let systemPaths = findSystemPaths(drvs) + var inputDrvs: seq[string] + for p in systemPaths: + inputDrvs &= drvs[p].inputDrvs.keys().toSeq() + result = collect( + for drv in inputDrvs: + if (drv in toBuild) and (not drv.isIgnored()): + drv & "^*" + ) + +func splitDrv(drv: string): tuple[name, hash:string] = + let s = drv.split("-", 1) + (s[1].replace(".drv^*",""),s[0].split("/")[^1]) + +proc writeDervationsToStepSummary(drvs: seq[string]) = + let rows = collect( + for drv in drvs: + let (name,hash) = splitDrv(drv) + fmt"| {name} | {hash} |" + ) + let summaryFilePath = getEnv("GITHUB_STEP_SUMMARY") + if summaryFilePath == "": + fatal "no github step summary found" + quit QuitFailure + let output = open(summaryFilePath,fmAppend) + output.writeLine("| derivation | hash |\n|---|---|") + output.writeLine(rows.join("\n")) + +proc nixBuild*(minimal: bool, rest: seq[string]) = + var cmd = nixCommand("build") + if minimal: + debug "populating args with derivations not built/cached" + let drvs = systemPathDrvsToBuild() + if drvs.len == 0: + info "nothing to build" + quit "exiting...", QuitSuccess + cmd.addArgs drvs + cmd.addArg "--no-link" + if isCi(): + writeDervationsToStepSummary drvs + cmd.addArgs rest + quitWithCmd cmd + + +proc nixBuildHostDry*(minimal: bool, rest: seq[string]) = + var cmd = nixCommand("build") + if minimal: + debug "populating args with derivations not built/cached" + let drvs = systemPathDrvsToBuild() + if drvs.len == 0: + info "nothing to build" + quit "exiting...", QuitSuccess + cmd.addArgs drvs + cmd.addArg "--no-link" + if isCi(): + writeDervationsToStepSummary drvs + else: + cmd.addArgs nixosConfigAttrs() + cmd.addArg "--dry-run" + cmd.addArgs rest + let (_, err) = + runCmdCaptWithSpinner(cmd, "evaluating derivation for: " & getHosts().join(" ")) + let output = parseDryRunOutput err + display output + + diff --git a/pkgs/oizys-nim/src/oizys/overlay.nim b/pkgs/oizys-nim/src/oizys/overlay.nim new file mode 100644 index 0000000..b506c1c --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/overlay.nim @@ -0,0 +1,64 @@ +import std/macros + +type + OverlayKind = enum + oPre + oPost + OverlayProc = object + node: NimNode + kind: OverlayKind + + +proc applyOverlay(child: NimNode, overlayProc: OverlayProc) = + let node = overlayProc.node + for p in node.params: + if p.kind == nnkIdentDefs: + child.params.add copyNimTree(p) + case overlayProc.kind: + of oPre: + let startIdx = if child.body[0].kind == nnkCommentStmt: 1 else: 0 + for i in countdown(node.body.len()-1, 0): + child.body.insert(startIdx, copyNimTree(node.body[i])) + of oPost: + for stmt in node.body.children(): + child.body.add copyNimTree(stmt) + + +macro overlay*(x: untyped): untyped = + ##[ + apply pre and post operations to procs: + ```nim + overlay: + proc pre(a: bool) = + echo "before" + proc post(c: bool) = + echo "after" + proc mine(b: bool) = + echo "inside mine" + ``` + would result in: + ```nim + proc pre(a: bool; b: bool; c: bool) = + echo "before" + echo "inside mine" + echo "after" + ``` + ]## + result = newStmtList() + var overlays: seq[OverlayProc] + for child in x.children(): + case child.kind: + of nnkProcDef: + case ($child.name): + of "pre": overlays.add OverlayProc(node: child, kind: oPre) + of "post": overlays.add OverlayProc(node: child, kind: oPost) + else: result.add child + else: result.add child + + if overlays.len == 0: + error "failed to create overlays: didn't find proc pre() or proc post()" + + for i, child in result.pairs(): + if child.kind == nnkProcDef: + for overlay in overlays: + applyOverlay(child, overlay) diff --git a/pkgs/oizys-nim/src/oizys/spin.nim b/pkgs/oizys-nim/src/oizys/spin.nim new file mode 100644 index 0000000..a705368 --- /dev/null +++ b/pkgs/oizys-nim/src/oizys/spin.nim @@ -0,0 +1,189 @@ +import std/[os, locks, sequtils, terminal] + +# https://github.com/molnarmark/colorize +proc reset(): string = "\e[0m" + +# foreground colors +proc fgRed*(s: string): string = "\e[31m" & s & reset() +proc fgBlack*(s: string): string = "\e[30m" & s & reset() +proc fgGreen*(s: string): string = "\e[32m" & s & reset() +proc fgYellow*(s: string): string = "\e[33m" & s & reset() +proc fgBlue*(s: string): string = "\e[34m" & s & reset() +proc fgMagenta*(s: string): string = "\e[35m" & s & reset() +proc fgCyan*(s: string): string = "\e[36m" & s & reset() +proc fgLightGray*(s: string): string = "\e[37m" & s & reset() +proc fgDarkGray*(s: string): string = "\e[90m" & s & reset() +proc fgLightRed*(s: string): string = "\e[91m" & s & reset() +proc fgLightGreen*(s: string): string = "\e[92m" & s & reset() +proc fgLightYellow*(s: string): string = "\e[93m" & s & reset() +proc fgLightBlue*(s: string): string = "\e[94m" & s & reset() +proc fgLightMagenta*(s: string): string = "\e[95m" & s & reset() +proc fgLightCyan*(s: string): string = "\e[96m" & s & reset() +proc fgWhite*(s: string): string = "\e[97m" & s & reset() + +# background colors +proc bgBlack*(s: string): string = "\e[40m" & s & reset() +proc bgRed*(s: string): string = "\e[41m" & s & reset() +proc bgGreen*(s: string): string = "\e[42m" & s & reset() +proc bgYellow*(s: string): string = "\e[43m" & s & reset() +proc bgBlue*(s: string): string = "\e[44m" & s & reset() +proc bgMagenta*(s: string): string = "\e[45m" & s & reset() +proc bgCyan*(s: string): string = "\e[46m" & s & reset() +proc bgLightGray*(s: string): string = "\e[47m" & s & reset() +proc bgDarkGray*(s: string): string = "\e[100m" & s & reset() +proc bgLightRed*(s: string): string = "\e[101m" & s & reset() +proc bgLightGreen*(s: string): string = "\e[102m" & s & reset() +proc bgLightYellow*(s: string): string = "\e[103m" & s & reset() +proc bgLightBlue*(s: string): string = "\e[104m" & s & reset() +proc bgLightMagenta*(s: string): string = "\e[105m" & s & reset() +proc bgLightCyan*(s: string): string = "\e[106m" & s & reset() +proc bgWhite*(s: string): string = "\e[107m" & s & reset() + +# formatting functions +proc bold*(s: string): string = "\e[1m" & s & reset() +proc underline*(s: string): string = "\e[4m" & s & reset() +proc hidden*(s: string): string = "\e[8m" & s & reset() +proc invert*(s: string): string = "\e[7m" & s & reset() + +type + SpinnerKind* = enum + Dots + Spinner* = object + interval*: int + frames*: seq[string] + +proc makeSpinner*(interval: int, frames: seq[string]): Spinner = + Spinner(interval: interval, frames: frames) + +const Spinners*: array[SpinnerKind, Spinner] = [ + # Dots + Spinner(interval: 80, frames: @["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"]), +] + + +type + Spinny = ref object + t: Thread[Spinny] + lock: Lock + text: string + running: bool + frames: seq[string] + frame: string + interval: int + customSymbol: bool + + EventKind = enum + Stop, StopSuccess, StopError, + SymbolChange, TextChange, + + SpinnyEvent = object + kind: EventKind + payload: string + +var spinnyChannel: Channel[SpinnyEvent] + +proc newSpinny*(text: string, s: Spinner): Spinny = + Spinny( + text: text, + running: true, + frames: s.frames, + customSymbol: false, + interval: s.interval + ) + +proc newSpinny*(text: string, spinType: SpinnerKind): Spinny = + newSpinny(text, Spinners[spinType]) + +proc setSymbolColor*(spinny: Spinny, color: proc(x: string): string) = + spinny.frames = mapIt(spinny.frames, color(it)) + +proc setSymbol*(spinny: Spinny, symbol: string) = + spinnyChannel.send(SpinnyEvent(kind: SymbolChange, payload: symbol)) + +proc setText*(spinny: Spinny, text: string) = + spinnyChannel.send(SpinnyEvent(kind: TextChange, payload: text)) + +proc handleEvent(spinny: Spinny, eventData: SpinnyEvent): bool = + result = true + case eventData.kind + of Stop: + result = false + of SymbolChange: + spinny.customSymbol = true + spinny.frame = eventData.payload + of TextChange: + spinny.text = eventData.payload + of StopSuccess: + spinny.customSymbol = true + spinny.frame = "✔".bold.fgGreen + spinny.text = eventData.payload.bold.fgGreen + of StopError: + spinny.customSymbol = true + spinny.frame = "✖".bold.fgRed + spinny.text = eventData.payload.bold.fgRed + +proc spinnyLoop(spinny: Spinny) {.thread.} = + var frameCounter = 0 + + while spinny.running: + let data = spinnyChannel.tryRecv() + if data.dataAvailable: + # If we received a Stop event + if not spinny.handleEvent(data.msg): + spinnyChannel.close() + # This is required so we can reopen the same channel more than once + # See https://github.com/nim-lang/Nim/issues/6369 + spinnyChannel = default(typeof(spinnyChannel)) + # TODO: Do we need spinny.running at all? + spinny.running = false + break + + stdout.flushFile() + if not spinny.customSymbol: + spinny.frame = spinny.frames[frameCounter] + + withLock spinny.lock: + eraseLine() + stdout.write(spinny.frame & " " & spinny.text) + stdout.flushFile() + + sleep(spinny.interval) + + if frameCounter >= spinny.frames.len - 1: + frameCounter = 0 + else: + frameCounter += 1 + +proc start*(spinny: Spinny) = + initLock(spinny.lock) + spinnyChannel.open() + createThread(spinny.t, spinnyLoop, spinny) + +proc stop(spinny: Spinny, kind: EventKind, payload = "") = + spinnyChannel.send(SpinnyEvent(kind: kind, payload: payload)) + spinnyChannel.send(SpinnyEvent(kind: Stop)) + joinThread(spinny.t) + eraseLine stdout + flushFile stdout + + +proc stop*(spinny: Spinny) = + spinny.stop(Stop) + +proc success*(spinny: Spinny, msg: string) = + spinny.stop(StopSuccess, msg) + +proc error*(spinny: Spinny, msg: string) = + spinny.stop(StopError, msg) + +template withSpinner*(msg: string = "", body: untyped): untyped = + var spinner {.inject.} = newSpinny(msg, Dots) + spinner.setSymbolColor(fgBlue) + start spinner + body + stop spinner + +template withSpinner*(body: untyped): untyped = + withSpinner("", body) + + diff --git a/pkgs/oizys-nim/todo.md b/pkgs/oizys-nim/todo.md new file mode 100644 index 0000000..c26a63c --- /dev/null +++ b/pkgs/oizys-nim/todo.md @@ -0,0 +1,8 @@ +# oizys-nim todo's + +- [x] nix commands including dry runs +- [ ] gh api commands + - [ ] ci <- start with the easier one + - [ ] update + +