diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index b6a12789..4412d7ea 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -16,7 +16,8 @@ alias supportedCommands = imported!`std.traits`.AliasSeq!( cmds.shard_matrix, cmds.host_info, cmds.ci, - cmds.machine_create + cmds.machine, + cmds.config, ); int main(string[] args) @@ -42,7 +43,7 @@ int main(string[] args) { infof("Running %s task", cmd.bold); - command(); + command(args[2..$]); infof("Execution Succesfull"); return 0; } diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index 2575b92b..61d12588 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -18,7 +18,7 @@ import mcl.utils.json : toJSON; Params params; -export void ci() +export void ci(string[] args) { params = parseEnv!Params; diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index 09518856..b8984b37 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -149,7 +149,7 @@ shared static this() params = parseEnv!Params; } -export void ci_matrix() +export void ci_matrix(string[] args) { createResultDirs(); nixEvalForAllSystems().array.printTableForCacheStatus(); @@ -186,7 +186,7 @@ Package[] checkCacheStatus(Package[] packages) return packages; } -export void print_table() +export void print_table(string[] args) { createResultDirs(); diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d new file mode 100755 index 00000000..4ead6f48 --- /dev/null +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -0,0 +1,125 @@ +module mcl.commands.config; + +import std.algorithm : canFind; +import std.array : array; +import std.process : ProcessPipes, Redirect, wait, environment; +import std.range : drop, front; +import std.stdio : writeln; +import std.string : indexOf; + +import mcl.utils.env : optional, parseEnv; +import mcl.utils.fetch : fetchJson; +import mcl.utils.log : errorAndExit; +import mcl.utils.nix : nix, queryStorePath; +import mcl.utils.process : execute; +import mcl.utils.string : camelCaseToCapitalCase; + +export void config(string[] args) { + if (args.length == 0) { + errorAndExit("Usage: mcl config [args]"); + } + if (!checkRepo()) { + errorAndExit("This command must be run from a repository containing a NixOS machine configuration"); + } + + string subcommand = args.front; + + switch (subcommand) { + case "sys": + sys(args.drop(1)); + break; + case "home": + home(args.drop(1)); + break; + case "start-vm": + startVM(args.drop(1)); + break; + default: + errorAndExit("Unknown config subcommand " ~ subcommand ~ ". Supported subcommands: sys, home, start-vm"); + break; + } +} + +bool checkRepo() +{ + const string[] validRepos = ["nixos-machine-config", "infra-lido"]; + string remoteOriginUrl = execute(["git", "config", "--get", "remote.origin.url"], false); + + foreach (string repo; validRepos) { + if (remoteOriginUrl.indexOf(repo) != -1) { + return true; + } + } + return false; +} + +void executeCommand(string command) { + auto exec = execute!ProcessPipes(command, true, false, Redirect.stderrToStdout); + wait(exec.pid); +} + +void edit(string type, string path) { + string editor = environment.get("EDITOR", "vim"); + string user = environment.get("USER", "root"); + writeln("Editing " ~ path ~ " configuration from: ", path); + final switch (type) { + case "system": + executeCommand(editor ~ " machines/*/" ~ path ~ "/*.nix"); + break; + case "user": + executeCommand(editor~ " users/" ~ user ~ "/gitconfig " ~ "users/" ~ user ~ "/*.nix " ~ "users/" ~ user ~ "/home-"~path~"/*.nix"); + break; + } +} + +void sys(string[] args) +{ + if ((args.length < 1 || args.length > 2) && !["apply", "edit"].canFind(args.front)) + { + errorAndExit("Usage: mcl config sys apply or mcl config sys apply \n"~ + " mcl config sys edit or mcl config sys edit "); + } + + string machineName = args.length > 1 ? args[1] : ""; + final switch (args.front) { + case "apply": + writeln("Applying system configuration from: ", machineName); + executeCommand("just switch-system " ~ machineName); + break; + case "edit": + edit("system", machineName); + break; + } +} + +void home(string[] args) +{ + if ((args.length != 2) && args.front != "apply") + { + errorAndExit("Usage: mcl config home apply \n"~ + " mcl config home edit "); + } + + auto type = args[1]; + final switch (args.front) { + case "apply": + writeln("Applying home configuration from: ", type); + executeCommand("just switch-home " ~ type); + break; + case "edit": + edit("user", type); + break; + } +} + +void startVM(string[] args) +{ + if (args.length != 1) + { + errorAndExit("Usage: mcl config start-vm "); + } + + string vmName = args.front; + writeln("Starting VM: ", vmName); + executeCommand("just start-vm " ~ vmName); +} diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 9a6a07dc..819fd4a2 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -13,7 +13,7 @@ import mcl.utils.json : tryDeserializeFromJsonFile, writeJsonFile; import mcl.commands.ci_matrix : flakeAttr, params, nixEvalJobs, SupportedSystem; -export void deploy_spec() +export void deploy_spec(string[] args) { const deploySpecFile = resultDir.buildPath("cachix-deploy-spec.json"); diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index 032f79f3..d063b7ed 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -13,7 +13,7 @@ import mcl.utils.nix : queryStorePath, nix; import mcl.utils.string : camelCaseToCapitalCase; import mcl.utils.process : execute; -export void get_fstab() +export void get_fstab(string[] args) { const params = parseEnv!Params; const machineStorePath = getCachixDeploymentStorePath(params); diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index ddbc1cd0..464a6575 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -59,7 +59,7 @@ string[string] getProcInfo(string fileOrData, bool file = true) return r; } -export void host_info() +export void host_info(string[] args) { const Params params = parseEnv!Params; diff --git a/packages/mcl/src/src/mcl/commands/machine_create.d b/packages/mcl/src/src/mcl/commands/machine.d similarity index 97% rename from packages/mcl/src/src/mcl/commands/machine_create.d rename to packages/mcl/src/src/mcl/commands/machine.d index 8aaca9f6..008d0a86 100755 --- a/packages/mcl/src/src/mcl/commands/machine_create.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -1,4 +1,4 @@ -module mcl.commands.machine_create; +module mcl.commands.machine; import std; import mcl.utils.log : prompt; @@ -221,7 +221,7 @@ struct MachineConfiguration } void createMachine(MachineType machineType, string machineName, User user) { - auto infoJSON = execute(["ssh", params.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/feat/machine_create#mcl host_info"],false, false); + auto infoJSON = execute(["ssh", params.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host_info"],false, false); auto infoJSONParsed = infoJSON.parseJSON; Info info = infoJSONParsed.fromJSON!Info; @@ -377,10 +377,17 @@ void createMachineConfiguration() Params params; -export void machine_create() +export void machine(string[] args) { params = parseEnv!Params; - createMachineConfiguration(); + switch (args.front) + { + case "create": + createMachineConfiguration(); + break; + default: + assert(0, "Unknown machine action: " ~ args.front); + } } struct Params { diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index a02a3201..4ab52cce 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -6,4 +6,5 @@ public import mcl.commands.ci_matrix : ci_matrix, print_table; public import mcl.commands.shard_matrix : shard_matrix; public import mcl.commands.ci : ci; public import mcl.commands.host_info : host_info; -public import mcl.commands.machine_create : machine_create; +public import mcl.commands.machine : machine; +public import mcl.commands.config : config; diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index 77cdc486..b4307844 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -18,7 +18,7 @@ import mcl.utils.json : toJSON; import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; -export void shard_matrix() +export void shard_matrix(string[] args) { const params = parseEnv!Params; auto matrix = generateShardMatrix(); diff --git a/packages/mcl/src/src/mcl/utils/log.d b/packages/mcl/src/src/mcl/utils/log.d index de27d93d..fde4a4e3 100644 --- a/packages/mcl/src/src/mcl/utils/log.d +++ b/packages/mcl/src/src/mcl/utils/log.d @@ -1,5 +1,14 @@ module mcl.utils.log; + +void errorAndExit(string message) { + import core.stdc.stdlib: exit; + import std.stdio : stderr; + + stderr.writeln(message); + exit(1); +} + T prompt(T)(string message, T[] options = [], string input = "unfilled") { import std.stdio : write, writeln, readln; diff --git a/packages/mcl/src/src/mcl/utils/process.d b/packages/mcl/src/src/mcl/utils/process.d index c0d55ad3..943e5ba5 100644 --- a/packages/mcl/src/src/mcl/utils/process.d +++ b/packages/mcl/src/src/mcl/utils/process.d @@ -4,34 +4,34 @@ import mcl.utils.test; import mcl.utils.tui : bold; -import std.process : ProcessPipes; +import std.process : ProcessPipes, Redirect; import std.string : split, strip; import core.sys.posix.unistd : geteuid; import std.json : JSONValue, parseJSON; bool isRoot() => geteuid() == 0; -T execute(T = string)(string args, bool printCommand = true, bool returnErr = false) if (is(T == string) || is(T == ProcessPipes) || is(T == JSONValue)) +T execute(T = string)(string args, bool printCommand = true, bool returnErr = false, Redirect redirect = Redirect.all) if (is(T == string) || is(T == ProcessPipes) || is(T == JSONValue)) { - return execute!T(args.split(" "), printCommand, returnErr); + return execute!T(args.strip.split(" "), printCommand, returnErr, redirect); } -T execute(T = string)(string[] args, bool printCommand = true, bool returnErr = false) if (is(T == string) || is(T == ProcessPipes) || is(T == JSONValue)) +T execute(T = string)(string[] args, bool printCommand = true, bool returnErr = false, Redirect redirect = Redirect.all) if (is(T == string) || is(T == ProcessPipes) || is(T == JSONValue)) { import std.exception : enforce; import std.format : format; - import std.process : pipeShell, wait, Redirect, escapeShellCommand; + import std.process : pipeShell, wait, escapeShellCommand; import std.logger : tracef, errorf, infof; import std.array : join; - import std.algorithm : map; + import std.algorithm : map, canFind; import std.conv : to; - auto cmd = args.map!escapeShellCommand.join(" "); + auto cmd = args.map!(x => x.canFind("*") ? x : x.escapeShellCommand()).join(" "); if (printCommand) { infof("\n$ `%s`", cmd.bold); } - auto res = pipeShell(cmd, Redirect.all); + auto res = pipeShell(cmd, redirect); static if (is(T == ProcessPipes)) { return res;