aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-04-29 15:20:57 -0500
committerCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-04-29 15:20:57 -0500
commit9c9a49a855951662da62bcdec45f69ddc0099e5c (patch)
tree1f7852971693d9e49d16a77aff51b87d39940b1b
parent21de12cd6e63adf463674942f1a8f9b658f47945 (diff)
changes
-rw-r--r--AGENTS.md5
-rw-r--r--hosts/ganymede/config.nix7
-rw-r--r--hosts/mercury/hjem.nix1
-rw-r--r--hosts/mercury/nixos.nix56
-rw-r--r--modules/services/nixos/default.nix2
-rw-r--r--modules/services/nixos/ganyupload/README13
-rw-r--r--modules/services/nixos/ganyupload/default.nix47
-rw-r--r--modules/services/nixos/ganyupload/go.mod3
-rw-r--r--modules/services/nixos/ganyupload/main.go104
-rw-r--r--modules/services/nixos/ganyupload/pkg.nix6
-rw-r--r--modules/services/nixos/ngircd.nix27
-rw-r--r--modules/services/options.nix13
-rw-r--r--modules/system/nixos/boot.nix2
-rw-r--r--modules/system/nixos/polkit.nix2
-rw-r--r--modules/terminal/hjem/programs/broot.nix2
-rw-r--r--modules/terminal/hjem/programs/helix.nix6
-rw-r--r--modules/user/nixos/default.nix2
-rw-r--r--pkgs/yossh/README.md129
-rw-r--r--pkgs/yossh/default.nix52
-rw-r--r--pkgs/yossh/doc/lua_doc.md118
-rw-r--r--pkgs/yossh/doc/nix.md44
-rwxr-xr-xpkgs/yossh/examples/home_wifi.lua33
-rw-r--r--pkgs/yossh/examples/mega_bastion.lua61
-rw-r--r--pkgs/yossh/examples/test.lua18
-rw-r--r--pkgs/yossh/tests/unit_tests.lua190
-rw-r--r--pkgs/yossh/yoshi.lua276
26 files changed, 271 insertions, 948 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 0c8da14..3b8f4e5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -21,7 +21,6 @@ Guidance for coding agents working in this repository.
- `lib/nix-furnace/mkSystem.nix`: system composition.
- `lib/lib.nix`: shared helpers (`my-lib`).
- `pkgs/yo/`: Ruby deployment/build helper.
-- `pkgs/yossh/`: Lua SSH tooling and tests.
- `pkgs/jta/`: Go module.
- `justfile`: command shortcuts.
@@ -77,10 +76,6 @@ Go tests (`pkgs/jta`):
- Run single test name: `go test ./... -run '^TestName$'`
- Run single test in current package: `go test . -run '^TestName$'`
-Lua tests (`pkgs/yossh`):
-- Run all defined tests: `lua pkgs/yossh/tests/unit_tests.lua`
-- Current single-test method is manual (temporarily call one test in `main()`).
-
## 7) Architectural Conventions
- Preserve homogenous module layout:
- `options.nix` for option definitions.
diff --git a/hosts/ganymede/config.nix b/hosts/ganymede/config.nix
index 03235f0..94d67ec 100644
--- a/hosts/ganymede/config.nix
+++ b/hosts/ganymede/config.nix
@@ -74,6 +74,8 @@
listenAddr = "0.0.0.0";
};
+ ngircd.enable = true;
+
qbittorrent = {
enable = true;
privateUrl = "bittorrent.ganymede";
@@ -96,6 +98,11 @@
publicUrl = "jta.williamsfam.us.com";
};
+ ganyupload = {
+ enable = true;
+ publicUrl = "upld.williamsfam.us.com";
+ };
+
caddy = {
enable = true;
envFile = config.collinux.secrets."caddy-env".path;
diff --git a/hosts/mercury/hjem.nix b/hosts/mercury/hjem.nix
index 7084251..6ecbb8e 100644
--- a/hosts/mercury/hjem.nix
+++ b/hosts/mercury/hjem.nix
@@ -7,6 +7,7 @@
prismlauncher
mpv
+ irssi
opencode
diff --git a/hosts/mercury/nixos.nix b/hosts/mercury/nixos.nix
index 7db48b4..a47375a 100644
--- a/hosts/mercury/nixos.nix
+++ b/hosts/mercury/nixos.nix
@@ -1,6 +1,7 @@
{
lib,
inputs,
+ pkgs,
...
}: {
imports = [
@@ -23,6 +24,61 @@
};
programs.kdeconnect.enable = true;
+ security.soteria.enable = true;
+
+ virtualisation.podman = {
+ enable = true;
+ dockerCompat = true;
+ };
+ users.users.collin = {
+ extraGroups = ["podman"];
+ subGidRanges = [
+ {
+ count = 65536;
+ startGid = 100000;
+ }
+ ];
+ subUidRanges = [
+ {
+ count = 65536;
+ startUid = 100000;
+ }
+ ];
+ };
+
+ services.jupyter = {
+ enable = true;
+ ip = "127.0.0.1";
+ port = 8888;
+
+ user = "collin"; # to access my files
+
+ package = pkgs.python313Packages.jupyter;
+ command = "jupyter lab --ServerApp.token='' --ServerApp.password=''";
+ password = "";
+
+ notebookDir = "~/brain/notes/schoolyear2025/physics";
+
+ kernels = {
+ python3 = let
+ python = pkgs.python313.withPackages (ps:
+ with ps; [
+ numpy
+ pandas
+ matplotlib
+ sympy
+ ipywidgets
+ ipydatagrid
+ ipykernel
+ ]);
+ in {
+ language = "python";
+ displayName = "Python (Physics)";
+ argv = ["${python}/bin/python" "-m" "ipykernel_launcher" "-f" "{connection_file}"];
+ };
+ };
+ };
+
fileSystems."/home/collin/ganymede" = {
device = "collin@ganymede:/media";
fsType = "fuse.sshfs";
diff --git a/modules/services/nixos/default.nix b/modules/services/nixos/default.nix
index 32efb1c..c3652b5 100644
--- a/modules/services/nixos/default.nix
+++ b/modules/services/nixos/default.nix
@@ -9,9 +9,11 @@
./cgit
./jta
+ ./ganyupload
./polaris.nix
./agate.nix
./minecraft.nix
+ ./ngircd.nix
./copyparty.nix
./qbittorrent.nix
];
diff --git a/modules/services/nixos/ganyupload/README b/modules/services/nixos/ganyupload/README
new file mode 100644
index 0000000..ac9a50d
--- /dev/null
+++ b/modules/services/nixos/ganyupload/README
@@ -0,0 +1,13 @@
+Ganyupload - File Upload Service
+
+Upload files using curl with a PUT request:
+
+ curl -X PUT --data-binary @myfile.txt https://upld.williamsfam.us.com/myfile.txt
+
+The server will respond with "ok" on success.
+
+You can use any path you want:
+
+ curl -X PUT --data-binary @document.pdf https://upld.williamsfam.us.com/docs/document.pdf
+
+Nested directories will be created automatically.
diff --git a/modules/services/nixos/ganyupload/default.nix b/modules/services/nixos/ganyupload/default.nix
new file mode 100644
index 0000000..0138100
--- /dev/null
+++ b/modules/services/nixos/ganyupload/default.nix
@@ -0,0 +1,47 @@
+{
+ pkgs,
+ config,
+ lib,
+ ...
+}: let
+ cfg = config.collinux.services.ganyupload;
+
+ package = pkgs.callPackage ./pkg.nix {};
+in {
+ imports = [
+ (import ../mkCaddyCfg.nix cfg)
+ ];
+
+ config = lib.mkIf cfg.enable {
+ users.groups."ganyupload" = {};
+ users.users."ganyupload" = {
+ isSystemUser = true;
+ group = "ganyupload";
+ home = "/var/lib/ganyupload";
+ createHome = true;
+ homeMode = "755";
+ };
+
+ systemd.services."ganyupload" = {
+ description = "Ganymede File Upload Service";
+ restartIfChanged = true;
+ wants = ["network-online.target" "caddy.service"];
+ after = ["network-online.target" "caddy.service"];
+
+ environment = {
+ PORT = toString cfg.port;
+ UPLOAD_DIR = "/media/ganyupload";
+ };
+
+ serviceConfig = {
+ User = "ganyupload";
+ Type = "simple";
+
+ WorkingDirectory = "/var/lib/ganyupload";
+ ExecStart = "${package}/bin/ganyupload";
+ };
+
+ wantedBy = ["multi-user.target"];
+ };
+ };
+}
diff --git a/modules/services/nixos/ganyupload/go.mod b/modules/services/nixos/ganyupload/go.mod
new file mode 100644
index 0000000..715e1b7
--- /dev/null
+++ b/modules/services/nixos/ganyupload/go.mod
@@ -0,0 +1,3 @@
+module git.ganymede/ganyupload
+
+go 1.25.5
diff --git a/modules/services/nixos/ganyupload/main.go b/modules/services/nixos/ganyupload/main.go
new file mode 100644
index 0000000..6aa219e
--- /dev/null
+++ b/modules/services/nixos/ganyupload/main.go
@@ -0,0 +1,104 @@
+package main
+
+import (
+ _ "embed"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+//go:embed README
+var readme string
+
+var uploadDir = "."
+
+func handleRoot(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ w.Write([]byte(readme))
+}
+
+func handleUpload(w http.ResponseWriter, r *http.Request) {
+ // Show README on GET /
+ if r.Method == http.MethodGet && r.URL.Path == "/" {
+ handleRoot(w, r)
+ return
+ }
+
+ if r.Method != http.MethodPut {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ // Extract filename from URL path, preventing path traversal
+ filename := strings.TrimPrefix(r.URL.Path, "/")
+ if filename == "" {
+ http.Error(w, "filename required", http.StatusBadRequest)
+ return
+ }
+
+ // Prevent path traversal
+ filename = filepath.Clean(filename)
+ if strings.HasPrefix(filename, "..") {
+ http.Error(w, "invalid filename", http.StatusBadRequest)
+ return
+ }
+
+ fullPath := filepath.Join(uploadDir, filename)
+
+ // Ensure the directory exists
+ dir := filepath.Dir(fullPath)
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ log.Printf("failed to create directory: %v", err)
+ http.Error(w, "internal server error", http.StatusInternalServerError)
+ return
+ }
+
+ // Create the file
+ f, err := os.Create(fullPath)
+ if err != nil {
+ log.Printf("failed to create file: %v", err)
+ http.Error(w, "internal server error", http.StatusInternalServerError)
+ return
+ }
+ defer f.Close()
+
+ // Copy request body to file
+ written, err := io.Copy(f, r.Body)
+ if err != nil {
+ log.Printf("failed to write file: %v", err)
+ http.Error(w, "internal server error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ w.WriteHeader(http.StatusCreated)
+ w.Write([]byte("ok\n"))
+
+ log.Printf("uploaded %s (%d bytes)", filename, written)
+}
+
+func main() {
+ if envUploadDir := os.Getenv("UPLOAD_DIR"); envUploadDir != "" {
+ uploadDir = envUploadDir
+ }
+
+ port := os.Getenv("PORT")
+ if port == "" {
+ port = "8080"
+ }
+
+ http.HandleFunc("/", handleUpload)
+
+ log.Printf("starting ganyupload on port %s, upload directory: %s", port, uploadDir)
+ if err := http.ListenAndServe(":"+port, nil); err != nil {
+ log.Fatalf("server error: %v", err)
+ }
+}
diff --git a/modules/services/nixos/ganyupload/pkg.nix b/modules/services/nixos/ganyupload/pkg.nix
new file mode 100644
index 0000000..18be8ed
--- /dev/null
+++ b/modules/services/nixos/ganyupload/pkg.nix
@@ -0,0 +1,6 @@
+{pkgs ? import <nixpkgs> {system = "x86_64-linux";}, ...}:
+pkgs.buildGoModule {
+ name = "ganyupload";
+ src = ./.;
+ vendorHash = null;
+}
diff --git a/modules/services/nixos/ngircd.nix b/modules/services/nixos/ngircd.nix
index 014dd68..d06a497 100644
--- a/modules/services/nixos/ngircd.nix
+++ b/modules/services/nixos/ngircd.nix
@@ -6,31 +6,28 @@
cfg = config.collinux.services.ngircd;
in
lib.mkIf cfg.enable {
+ networking.firewall.allowedTCPPorts = [cfg.port];
+
services.ngircd = {
enable = true;
config = ''
[Global]
- Name = irc.williamsfam.us.com
+ Name = williamsfam.us.com
Info = Ganymede IRC Chat
AdminInfo1 = Collin
- Listen = 127.0.0.1
+ Listen = 0.0.0.0
Ports = ${toString cfg.port}
- [Options]
- PAM = yes
- PAMIsOptional = no
+ [Channel]
+ Name = #general
+ AutoJoin = yes
- [Operator]
- Name = collin
- Password = # users authenticate with PAM
- Mask = *@*
+ [Options]
+ RequireAuth = no
+ Ident = no
+ AllowedHosts = *
+ PAM = no
'';
};
-
- users.users.ngircd.extraGroups = ["shadow"];
-
- security.pam.services.ngircd = {
- unixAuth = true;
- };
}
diff --git a/modules/services/options.nix b/modules/services/options.nix
index bb43180..8918781 100644
--- a/modules/services/options.nix
+++ b/modules/services/options.nix
@@ -114,6 +114,11 @@ in {
default_port = 8072;
};
+ ganyupload = webserviceOptions {
+ service_name = "ganyupload";
+ default_port = 8073;
+ };
+
copyparty =
(webserviceOptions {
service_name = "copyparty";
@@ -141,6 +146,14 @@ in {
};
};
+ ngircd = {
+ enable = mkEnableOption "ncircd IRC server";
+ port = mkOption {
+ type = lib.types.port;
+ default = 6667;
+ };
+ };
+
minecraft = {
enable = mkEnableOption "Minecraft bedrock server";
listenAddr = mkOption {
diff --git a/modules/system/nixos/boot.nix b/modules/system/nixos/boot.nix
index 709a1ca..71bac70 100644
--- a/modules/system/nixos/boot.nix
+++ b/modules/system/nixos/boot.nix
@@ -77,7 +77,7 @@ in {
enable = true;
mutable = true; # necessary for installing secrets into etc
};
- system.nixos-init.enable = true;
+ # system.nixos-init.enable = true;
# store journald logs in memory
services.journald.extraConfig = ''
diff --git a/modules/system/nixos/polkit.nix b/modules/system/nixos/polkit.nix
index 29a06f4..5bd8e33 100644
--- a/modules/system/nixos/polkit.nix
+++ b/modules/system/nixos/polkit.nix
@@ -69,7 +69,5 @@ in {
defaultDenyRule
];
};
-
- soteria.enable = true;
};
}
diff --git a/modules/terminal/hjem/programs/broot.nix b/modules/terminal/hjem/programs/broot.nix
index 4836d50..59f16aa 100644
--- a/modules/terminal/hjem/programs/broot.nix
+++ b/modules/terminal/hjem/programs/broot.nix
@@ -57,7 +57,7 @@
{
name = "justfile";
key = "ctrl-j";
- execution = ''just --choose --chooser "fzf --height=25% --color=bg:-1 --preview 'just --show {}'"'';
+ execution = "just --choose";
working_dir = "{root}";
leave_broot = false;
}
diff --git a/modules/terminal/hjem/programs/helix.nix b/modules/terminal/hjem/programs/helix.nix
index 89d18f4..db6beba 100644
--- a/modules/terminal/hjem/programs/helix.nix
+++ b/modules/terminal/hjem/programs/helix.nix
@@ -33,8 +33,10 @@
command = "${pkgs.superhtml}/bin/superhtml";
args = ["lsp"];
};
- golsp = {
- command = "${pkgs.gopls}/bin/gopls";
+ golsp.command = "${pkgs.gopls}/bin/gopls";
+ tinymist = {
+ command = "${pkgs.tinymist}/bin/tinymist";
+ config.exportPdf = "onType";
};
dhall-lsp-server.command = "${pkgs.dhall-lsp-server}/bin/dhall-lsp-server";
};
diff --git a/modules/user/nixos/default.nix b/modules/user/nixos/default.nix
index 68238d6..fbc6001 100644
--- a/modules/user/nixos/default.nix
+++ b/modules/user/nixos/default.nix
@@ -15,7 +15,7 @@ in {
extraGroups = ["networkmanager" "disks" "input" "video" "dialout" "kvm"] ++ (lib.optional cfg.isAdmin "wheel");
};
};
- services.userborn.enable = true;
+ # services.userborn.enable = true;
# sudo
security = {
diff --git a/pkgs/yossh/README.md b/pkgs/yossh/README.md
deleted file mode 100644
index 56179ae..0000000
--- a/pkgs/yossh/README.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# Yo SSH Host Initiator (Y.O.S.H.I)
-Yoshi is a lua library that wraps the `ssh` client, allowing you to define complex host configurations using the power of a full programming langage (lua) instead of the `ssh_config` dsl.
-
-Obligatory code example (`~/bin/yossh`):
-```lua
-#!/usr/bin/env lua
-yoshi = require'yoshi'
-
-local function isHome()
- local _, _, code = os.execute("nc -z -w1 192.168.50.2 2222")
- return code == 0
-end
-
-yoshi.hosts["ganymede"] = function()
- if isHome() then
- return yoshi.ssh{
- HostName = "192.168.50.2",
- Port = 2222,
- LocalForward = 8010
- }
- else
- return yoshi.ssh{
- HostName = "williamsfam.us.com",
- LocalForward = 8010,
- DynamicFoward = 9090
- }
- end
-end
-
-yoshi.hosts["io"] = function()
- if isHome() then
- return yoshi.ssh{HostName = "192.168.50.3"}
- else
- return yoshi.ssh{
- HostName = "192.168.50.3",
- User = "admin",
- ProxyJump = "collin@ganymede",
- }
- end
-end
-
-yoshi.run(arg)
-```
-
-```
-$ yossh io
-Connection to 192.168.50.2 2222 port [tcp/ethernet-ip-1] succeeded!
-+ ssh admin@192.168.50.3
-io:~$
-```
-
-## 🍄 Getting Started
-First, clone this repo, and make sure you have ssh and lua installed.
-```
-sudo apt install ssh luajit5.4 # or whatever
-git clone https://github.com/bluedragon1221/yoshi
-```
-
-Next, place `yoshi.lua` in a place that lua can find it.
-Consult the [lua documentation](https://www.lua.org/pil/8.1.html) for help.
-
-Now you can write a simple configuration.
-As an example, let's look at the equivelant of this `ssh_config` block:
-```
-Host box
- HostName 192.168.50.3
- User yoshi
-```
-
-Using Yoshi, it would look like this:
-```lua
--- yoshi_cfg.lua
-yoshi = require'yoshi'
-
-yoshi.hosts["box"] = yoshi.ssh{
- HostName = "192.168.50.3",
- User = "yoshi"
-}
-
-yoshi.run(arg)
-```
-
-> (To create more complicated configurations, read the [docs](./doc/lua_doc.md))
-
-To test your Yoshi configuration, you can run `lua yoshi_cfg.lua`.
-This would be annoying to type every time you need to access your remote server, though!
-So we'll place the script in the `$PATH` (ex. `~/bin/yossh` or something), and use a shabang to tell the script to execute with lua:
-```lua
-#!/usr/bin/env lua
-yoshi = require'yoshi'
-
-yoshi.hosts["box"] = yoshi.ssh{
- HostName = "192.168.50.3",
- User = "yoshi"
-}
-
-yoshi.run(arg)
-```
-
-Assuming `~/bin` is in the `$PATH` and you made `yossh` executable with `chmod +x`, running `yossh` in the terminal will give you a nice help page:
-```bash
-$ yossh
-yossh - smart SSH wrapper
-
-USAGE:
- yossh <host>
- yossh <user@host>
- yossh <host:port>
- yossh <user@host:port>
-
-AVAILABLE HOSTS:
- - box
-```
-
-And you can ssh into `box`:
-```bash
-$ yossh box
-+ ssh yoshi@192.168.50.3
-io:~$ whoami
-yoshi
-```
-
-## 📖 Docs
-- [`doc/lua_doc.md`](doc/lua_doc.md) contains comprehensive docs for the Yoshi lua api
-- [`doc/nix.md`](doc/nix.md) discusses packaging your Yoshi configuration with nix
-
----
-
-[![BrainMade](https://brainmade.org/88x31-dark.png)](https://brainmade.org)
diff --git a/pkgs/yossh/default.nix b/pkgs/yossh/default.nix
deleted file mode 100644
index 4ea0127..0000000
--- a/pkgs/yossh/default.nix
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- pkgs,
- inputs,
- ...
-}: let
- yossh =
- pkgs.writeText "yossh.lua"
- # lua
- ''
- yoshi = dofile'${./yoshi.lua}'
-
- local function isHome()
- local _, _, code = os.execute("nc -z -w1 192.168.50.2 2222")
- return code == 0
- end
-
- yoshi.hosts["ganymede"] = function()
- if isHome() then
- return yoshi.ssh{
- HostName = "192.168.50.2",
- DynamicForward = 9090,
- Port = 2222
- }
- else
- return yoshi.ssh{
- HostName = "williamsfam.us.com",
- DynamicForward = 9090
- }
- end
- end
-
- yoshi.hosts["io"] = function()
- if isHome() then
- return yoshi.ssh{
- HostName = "192.168.50.3",
- User = "admin",
- }
- else
- return yoshi.ssh{
- HostName = "192.168.50.3",
- User = "admin",
- ProxyJump = "collin@ganymede",
- }
- end
- end
-
- yoshi.run(arg)
- '';
-in
- pkgs.writeShellScriptBin "yossh" ''
- exec ${pkgs.lua}/bin/lua ${yossh} "$@"
- ''
diff --git a/pkgs/yossh/doc/lua_doc.md b/pkgs/yossh/doc/lua_doc.md
deleted file mode 100644
index b029f56..0000000
--- a/pkgs/yossh/doc/lua_doc.md
+++ /dev/null
@@ -1,118 +0,0 @@
-# Lua Documentation
-Lua API documentation for Yoshi
-
-## `yoshi.hosts`
-- Description: List of defined ssh hosts. Each entry must be a function that returns an `sshCommand` when called.
-- Type: table of `function(): sshCommand`
-- Example:
-```lua
-yoshi.hosts["webserver"] = function()
- -- this is just a random example to show why you might want yoshi.hosts[host] to be a function
- if os.getenv("IM_FEELING") == "sad" then
- return yoshi.ssh{HostName = "sad.webserver.local"}
- else
- return yoshi.ssh{HostName = "happy.webserver.local"}
- end
-end
-```
-
-## `yoshi.ssh`
-- Description: Constructs an `sshCommand`
-- Type: `function(table): sshCommand`
-- Example:
-```lua
-yoshi.ssh{
- HostName = "webserver.local",
- Port = 2227,
- ProxyJump = "bastion"
-}
-```
-
-## `yoshi.sshCommand.User`
-- Description: Specifies the target user in the ssh connection. Analogous to the `User` key in `ssh_config`
-- Type: string or nil
-- Example: `yoshi`, `collin123`
-
-## `yoshi.sshCommand.HostName`
-- Description: Specifies the target host in the ssh connection. The only required element to `yoshi.sshCommand`. Analogous to the `HostName` key in `ssh_config`
-- Type: string
-- Example: `192.168.0.27`, `github.com`, `140.82.114.3`
-
-## `yoshi.sshCommand.Port`
-- Description: Specifies the target port in the ssh connection. Defaults to `22` if unspecified. Analagous to the `Port` key in `ssh_config`
-- Type: integer, `0 <= i <= 65535`
-- Examples: `22`, `2222`, `2225`
-
-## `yoshi.sshCommand.RemoteCommand`
-- Description: Specifies a command to run on the target host instead of the default shell. Analagous to the `RemoteCommand` key in `ssh_config`
-- Type: string
-- Examples: `whoami`, `nixos-rebuild switch --flake /etc/nixos`, `tmux new-session -A`
-
-## `yoshi.sshCommand.SessionType`
-- Description: Specifies the type of ssh session. "none" means that no shell access is provded. Analogous to the `SessionType` key in `ssh_config`
-- Type: "subsystem", "none", or "default"
-- Examples: `"none"`
-
-## `yoshi.sshCommand.LocalForward`
-- Description: Specifies a port on the target host to forward to the local host in the format `{local_port, target_port}`. If no local port is specified, will assume host port and local port are the same. Analagous to the `LocalForward` key in `ssh_config` or the `-L` flag on the command line
-- Type: `table[2] of integer` or integer, `0 <= i <= 65535`
-- Examples: `8010`, `{8010, 80}`
-
-## `yoshi.sshCommand.DynamicForward`
-- Description: Specifies a port on the local host to serve as a SOCKS5 proxy, forwarding all traffic through the target host. Analagous to the `DynamicForward` key in `ssh_config` or the `-D` option on the command line
-- Type: integer, `0 <= i <= 65535`
-- Examples: `9090`
-
-## `yoshi.sshCommand.ProxyJump`
-- Description: Specifies a host to act as a gateway to the target host if not directly accessable by the local host. It will inherit `User`, `Port`, and `HostName` options from `yoshi.hosts[host]` if it exists. Analagous to the `ProxyJump` key in `ssh_config` or the `-J` flag on the command line
-- Type: string in the form `user@host:port`
-- Example: `box`, `yoshi@box`, `box:27`, `yoshi@box:27`
-- Full Example:
-```lua
-yoshi.hosts["bastion"] = sshCommand.ssh{
- HostName = "bastion.mywebsite.com",
- Port = 23,
-}
-
-yoshi.hosts["server"] = sshCommand.ssh{
- HostName = "192.168.0.2",
- ProxyJump = "myuser@bastion"
-}
-
-assert_eq(
- yoshi.getHost("server"):toCommand(),
- "ssh -J myuser@bastion.mywebsite.com:23 server"
-)
-```
-
-## `yoshi.sshCommand:toCommand`
-- Description: Formats `self` as a valid `ssh` command to be run in the terminal.
-- Type: `sshCommand:function(): string`
-- Example:
-```lua
-os.execute(yoshi.getHost("io"):toCommand())
-```
-
-## `yoshi.getHost`
-- Description: Looks up and evaluates `yoshi.hosts[hostname]`, returning the resulting `sshCommand`. Raises an error if the host is not found or the host function returns nil.
-- Type: `function(string): sshCommand`
-- Example:
-```lua
-yoshi.hosts["webserver"] = function()
- return yoshi.ssh{
- HostName = "webserver.local",
- Port = 22,
- ProxyJump = "bastion"
- }
-end
-
-os.execute(yoshi.getHost("webserver"):toCommand())
-```
-
-## `yoshi.run`
-- Description: Executes the ssh command associated with the given target host as provided by `arg`, a list of commandline arguments. If no arguments are provided, prints a help page. The first argument is a path of the form `[jumphost/.../]host`, where any leading path segments are used as explicit proxy jump hops. Subsequent arguments may include ssh flags such as `-D`, `-L`, `-N`, or `-- command`.
-- Type: `function(table of string, table of any)`
-- Example:
-```lua
-yoshi.run(arg, {dry_run = true}) -- arg is the global name for the lua program's commandline arguments
-```
diff --git a/pkgs/yossh/doc/nix.md b/pkgs/yossh/doc/nix.md
deleted file mode 100644
index 9631392..0000000
--- a/pkgs/yossh/doc/nix.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Packing your Yoshi configuration with nix
-If you want to declare Yoshi in your nixos configuration, here's how.
-
-First, add Yoshi to your nix flake:
-```nix
-{
- inputs = {
- yoshi = {
- url = "github:bluedragon1221/yoshi";
- flake = false;
- };
- };
-}
-```
-
-Next, write a package for Yoshi. This is how I achieved it:
-```sh
-{
- pkgs,
- inputs,
- ...
-}: let
- yossh = pkgs.writeText "yossh.lua" ''
- yoshi = dofile'${inputs.yoshi-lua}/yoshi.lua'
-
- yoshi.hosts["box"] = yoshi.ssh{
- HostName = "192.168.50.3",
- User = "yoshi"
- }
-
- yoshi.run(arg)
- '';
-in
- pkgs.writeShellScriptBin "yossh" ''
- exec ${pkgs.lua}/bin/lua ${yossh} "$@"
- ''
-```
-
-Then add the package in `environment.systemPackages`:
-```nix
-environment.systemPackages = [
- (pkgs.callPackage ./yossh.nix {inherit inputs;})
-]
-```
diff --git a/pkgs/yossh/examples/home_wifi.lua b/pkgs/yossh/examples/home_wifi.lua
deleted file mode 100755
index 8df2aad..0000000
--- a/pkgs/yossh/examples/home_wifi.lua
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-yoshi = dofile'yoshi.lua'
-
-local function isHome()
- local ok, reason, code = os.execute("nc -z -w1 192.168.50.2 2222")
- return code == 0
-end
-
-yoshi.hosts["ganymede"] = function()
- if isHome() then
- return yoshi.ssh{
- HostName = "192.168.50.2",
- Port = 2222,
- LocalForward = 8010
- }
- else
- return yoshi.ssh{
- HostName = "williamsfam.us.com",
- LocalForward = 8010,
- DynamicForward = 9090
- }
- end
-end
-
-yoshi.hosts["io"] = function()
- return yoshi.ssh{
- HostName = "192.168.50.3",
- User = "admin",
- ProxyJump = isHome() or "collin@ganymede",
- }
-end
-
-yoshi.run(arg, {dry_run = true})
diff --git a/pkgs/yossh/examples/mega_bastion.lua b/pkgs/yossh/examples/mega_bastion.lua
deleted file mode 100644
index d4eb3ee..0000000
--- a/pkgs/yossh/examples/mega_bastion.lua
+++ /dev/null
@@ -1,61 +0,0 @@
--- situation: fictional network where there are three departments:
--- - tech (172.16.10.0/24)
--- - HR (172.16.20.0/24)
--- - Secretary (172.16.40.0/24)
--- Each department has
--- - an ssh bastion server at .1:22 (only way to access other devices on the subnet from outside the subnet)
--- - a shared sftp server at .2:25
---
--- This exact yoshi script would be deployed to all personal computers inside the network.
-local yoshi = dofile"yoshi.lua"
-
-local function inNetwork()
- -- company internal landing page
- local _, _, code = os.execute("nc -z -w1 172.16.0.1 443")
- return code == 0
-end
-
-local function currDept()
- local ip_addr = io.popen("hostname -I")
- local _, _, third_octet, _ = string.gmatch(ip_addr, "%d%.%d%.%d%.%d")
-
- return third_octet
-end
-
-local depts = {
- tech = 10,
- hr = 20,
- secretary = 40
-}
-
-yoshi.hosts["global-bastion"] = yoshi.ssh{
- HostName = "bastion.company-website.com",
- Port = 2222
-}
-
-for name, subnet in pairs(depts) do
- yoshi.hosts[name.."-bastion"] = function()
- return yoshi.ssh{
- HostName = "172.16."..subnet..".1",
- ProxyJump = inNetwork() or "global-bastion"
- }
- end
-
- yoshi.hosts[name.."-fileserver"] = function()
- return yoshi.ssh{
- HostName = "172.16."..subnet..".2",
- SessionType = "none",
- LocalForward = {2225, 25},
- ProxyJump = inNetwork() and currDept() == subnet or name.."-bastion"
- }
- end
-end
-
-yoshi.hosts[name.."-db"] = yoshi.ssh{
- HostName = "172.16.10.3",
- SessionType = "none",
- LocalForward = 5432,
- ProxyJump = inNetwork() and currDept() == subnet or "tech-bastion"
-}
-
-yoshi.run(arg, {dry_run = true})
diff --git a/pkgs/yossh/examples/test.lua b/pkgs/yossh/examples/test.lua
deleted file mode 100644
index a594305..0000000
--- a/pkgs/yossh/examples/test.lua
+++ /dev/null
@@ -1,18 +0,0 @@
-yoshi = dofile'yoshi.lua'
-
-yoshi.hosts["you"] = function()
- return yoshi.ssh{
- HostName = "192.168.50.2",
- DynamicForward = 7320,
- Port = 27
- }
-end
-
-yoshi.hosts["me"] = function()
- return yoshi.ssh{
- HostName = "test.local",
- ProxyJump = "jumper@you"
- }
-end
-
-yoshi.run(arg, {dry_run = true})
diff --git a/pkgs/yossh/tests/unit_tests.lua b/pkgs/yossh/tests/unit_tests.lua
deleted file mode 100644
index 0ce044c..0000000
--- a/pkgs/yossh/tests/unit_tests.lua
+++ /dev/null
@@ -1,190 +0,0 @@
--- ChatGPT wrote my unit tests
-
-local yoshi = dofile'yoshi.lua'
-local tests = {}
-
-----------------------------------------------------------------
--- Minimal assert functions
-----------------------------------------------------------------
-
-local function fail(msg)
- error("TEST FAILED: " .. msg, 2)
-end
-
-local function assertEqual(a, b, msg)
- if a ~= b then fail(msg or string.format("%s ~= %s", tostring(a), tostring(b))) end
-end
-
-local function assertNotEqual(a, b, msg)
- if a == b then fail(msg or string.format("%s == %s", tostring(a), tostring(b))) end
-end
-
-local function assertDeepEqual(a, b, msg)
- local function compare(x, y)
- if type(x) ~= type(y) then return false end
- if type(x) ~= "table" then return x == y end
- local k_checked = {}
- for k,v in pairs(x) do
- if not compare(v, y[k]) then return false end
- k_checked[k] = true
- end
- for k,_ in pairs(y) do
- if not k_checked[k] then return false end
- end
- return true
- end
- if not compare(a,b) then fail(msg or "Tables not equal") end
-end
-
-----------------------------------------------------------------
--- Helpers
-----------------------------------------------------------------
-
--- reset hosts between tests
-local function resetHosts()
- yoshi.hosts = {}
-end
-
-----------------------------------------------------------------
--- Tests
-----------------------------------------------------------------
-
-function tests.test_constructor()
- local cmd = yoshi.ssh{
- HostName = "localhost",
- User = "me",
- Port = 2222,
- SessionType = "none",
- DynamicForward = 8080,
- LocalForward = 8000,
- RemoteCommand = "ls -la",
- }
-
- assertEqual(cmd.HostName, "localhost")
- assertEqual(cmd.User, "me")
- assertEqual(cmd.Port, 2222)
- assertEqual(cmd.SessionType, "none")
- assertEqual(cmd.DynamicForward, 8080)
- assertDeepEqual(cmd.LocalForward, {8000,8000})
- assertEqual(cmd.RemoteCommand, "ls -la")
-end
-
-function tests.test_proxyjump()
- resetHosts()
-
- yoshi.hosts["bastion"] = function()
- return yoshi.ssh{HostName="bastion.internal", User="bastionuser"}
- end
-
- local cmd = yoshi.ssh{
- HostName = "server.internal",
- User = "admin",
- ProxyJump = "bastion",
- }
-
- assertEqual(#cmd.ProxyJumps, 1)
- assertEqual(cmd.ProxyJumps[1].host, "bastion.internal")
- assertEqual(cmd.ProxyJumps[1].user, "bastionuser")
-end
-
-function tests.test_nested_proxyjump()
- resetHosts()
- yoshi.hosts["jump1"] = function()
- return yoshi.ssh{HostName="jump1.host", ProxyJump="jump2"}
- end
- yoshi.hosts["jump2"] = function()
- return yoshi.ssh{HostName="jump2.host", User="jumpuser"}
- end
-
- local cmd = yoshi.ssh{
- HostName = "target.host",
- ProxyJump = "jump1"
- }
-
- local hops = cmd.ProxyJumps
- assertEqual(#hops, 2)
- assertEqual(hops[1].host, "jump2.host")
- assertEqual(hops[1].user, "jumpuser")
- assertEqual(hops[2].host, "jump1.host")
-end
-
-function tests.test_cli_hop_override()
- resetHosts()
- yoshi.hosts["ganymede"] = function() return yoshi.ssh{HostName="192.168.50.2"} end
- yoshi.hosts["io"] = function() return yoshi.ssh{HostName="192.168.50.3", ProxyJump="ganymede"} end
-
- local arg = {"collin@ganymede/io"}
- local target = yoshi.getHost("io")
- local path = {"collin@ganymede"} -- simulate split_path(arg[1])
-
- local acc = {}
- for i = 1, #path do
- local hop = yoshi.parse_user_host_port(path[i])
- yoshi.collect_proxy_jumps(hop, acc)
- end
-
- target.ProxyJumps = acc
- assertEqual(#target.ProxyJumps, 1)
- assertEqual(target.ProxyJumps[1].user, "collin")
- assertEqual(target.ProxyJumps[1].host, "192.168.50.2")
-end
-
-function tests.test_toCommand()
- resetHosts()
- local cmd = yoshi.ssh{
- HostName = "localhost",
- User = "me",
- Port = 2222,
- DynamicForward = 8080,
- LocalForward = 8000,
- SessionType = "none",
- RemoteCommand = "ls"
- }
-
- local text = cmd:toCommand()
- assert(text:match("ssh"))
- assert(text:match("-D 8080"))
- assert(text:match("-L 8000:127.0.0.1:8000"))
- assert(text:match("-N"))
- assert(text:match("me@localhost"))
- assert(text:match("ls"))
-end
-
-function tests.test_LocalForward_tuple()
- local cmd = yoshi.ssh{
- HostName="localhost",
- LocalForward={9000,9001}
- }
- local text = cmd:toCommand()
- assert(text:match("-L 9000:127.0.0.1:9001"))
-end
-
-function tests.test_DynamicForward()
- local cmd = yoshi.ssh{
- HostName="localhost",
- DynamicForward=9999
- }
- local text = cmd:toCommand()
- assert(text:match("-D 9999"))
-end
-
-function tests.test_RemoteCommand()
- local cmd = yoshi.ssh{HostName="localhost", RemoteCommand="echo hello"}
- local text = cmd:toCommand()
- assert(text:match("echo hello"))
-end
-
-----------------------------------------------------------------
--- Run tests
-----------------------------------------------------------------
-
-local function main()
- tests.test_constructor()
- tests.test_proxyjump()
- tests.test_nested_proxyjump()
- tests.test_cli_hop_override()
- tests.test_toCommand()
- tests.test_LocalForward_tuple()
- tests.test_DynamicForward()
- tests.test_RemoteCommand ()
-end
diff --git a/pkgs/yossh/yoshi.lua b/pkgs/yossh/yoshi.lua
deleted file mode 100644
index ce2086a..0000000
--- a/pkgs/yossh/yoshi.lua
+++ /dev/null
@@ -1,276 +0,0 @@
-local SSHTarget = {}
-
-function SSHTarget:clone()
- return {
- user = self.user,
- host = self.host,
- port = self.port
- }
-end
-
-local function parse_user_host_port(input)
- local user, rest = input:match("^(.-)@(.*)$")
- if not rest then rest = input end
-
- local host, port_str = rest:match("^(.-):(%d+)$")
- local port
- if not host then
- host = rest
- else
- port = tonumber(port_str)
- end
-
- return {
- user = user,
- host = host,
- port = port,
- }
-end
-
-local function split_path(path)
- local t = {}
- for seg in path:gmatch("[^/]+") do
- table.insert(t, seg)
- end
- return t
-end
-
-local yoshi = {
- hosts = {}
-}
-
-local SSHConfig = {}
-local SSHCommand = {}
-
-function SSHCommand:clone()
- local new_proxy_jumps
- if self.proxy_jumps then
- new_proxy_jumps = {}
- for _, j in ipairs(self.proxy_jumps) do
- table.insert(new_proxy_jumps, {
- user = j.user,
- host = j.host,
- port = j.port,
- })
- end
- end
-
- return setmetatable({
- target = {
- user = self.target.user,
- host = self.target.host,
- port = self.target.port,
- },
- session_type = self.session_type,
- dynamic_forward = self.dynamic_forward,
- local_forward = self.local_forward and { self.local_forward[1], self.local_forward[2] } or nil,
- remote_command = self.remote_command,
- proxy_jumps = new_proxy_jumps,
- }, { __index = SSHCommand })
-end
-
-function SSHCommand:overlayTarget(target)
- local new = self:clone()
- if target.user then
- new.target.user = target.user
- end
-
- if target.port then
- new.target.port = target.port
- end
-
- return new
-end
-
-function yoshi.getHost(hostname)
- local base = yoshi.hosts[hostname]
- if not base then
- error("Unknown host: " .. hostname)
- end
-
- local cmd = base()
- if cmd then
- return cmd
- else
- error("Calling host returned nil")
- end
-end
-
-function yoshi.ssh(o)
- local local_forward
- local l = o.LocalForward
- if type(l) == "number" then
- local_forward = { l, l }
- else
- local_forward = l
- end
-
- local proxy_jumps = {}
- if o.ProxyJump then
- local u = parse_user_host_port(o.ProxyJump)
- local jmp = yoshi.getHost(u.host):overlayTarget(u)
-
- table.insert(proxy_jumps, jmp.target)
- if #jmp.proxy_jumps ~= 0 then
- for _, p in ipairs(jmp.proxy_jumps) do
- table.insert(proxy_jumps, p)
- end
- end
- end
-
- local self = setmetatable({
- target = {
- user = o.User,
- host = o.HostName,
- port = o.Port or 22,
- },
- session_type = o.SessionType or "default",
- dynamic_forward = o.DynamicForward,
- local_forward = local_forward,
- remote_command = o.RemoteCommand,
-
- proxy_jumps = proxy_jumps,
- }, { __index = SSHCommand })
-
- return self
-end
-
-function SSHCommand:toCommand()
- local ret = { "ssh" }
-
- if self.dynamic_forward then
- table.insert(ret, "-D")
- table.insert(ret, tostring(self.dynamic_forward))
- end
-
- if self.local_forward then
- table.insert(ret, "-L")
- local lf = self.local_forward
- table.insert(ret,
- lf[1] .. ":127.0.0.1:" .. lf[2])
-
- end
-
- if self.session_type == "none" then
- table.insert(ret, "-N")
- end
-
- if self.proxy_jumps and #self.proxy_jumps > 0 then
- local js = {}
- for _, j in ipairs(self.proxy_jumps) do
- local s = (j.user and j.user .. "@" or "") .. j.host
- if j.port and j.port ~= 22 then s = s .. ":" .. j.port end
- table.insert(js, s)
- end
- table.insert(ret, "-J")
- table.insert(ret, table.concat(js, ","))
- end
-
- if self.target.port and self.target.port ~= 22 then
- table.insert(ret, "-p")
- table.insert(ret, tostring(self.target.port))
- end
-
- table.insert(ret, (self.target.user and self.target.user .. "@" or "") .. self.target.host)
-
- if self.remote_command then
- table.insert(ret, "-t")
- table.insert(ret, "'" .. self.remote_command .. "'")
- end
-
- return table.concat(ret, " ")
-end
-
-function SSHCommand:overlayCliOpts(cli_args)
- local new = self:clone()
-
- local i = 2
- while i <= #cli_args do
- local a = cli_args[i]
-
- if a == "-D" then
- new.dynamic_forward = tonumber(cli_args[i + 1])
- i = i + 2
-
- elseif a == "-L" then
- local spec = cli_args[i + 1]
-
- local port = tonumber(spec)
- if port then
- new.local_forward = { port, port }
- i = i + 2
-
- else
- local l, r = spec:match("^(%d+):.*:(%d+)$")
- if not l or not r then
- error("Invalid -L spec: " .. spec)
- end
- new.local_forward = { tonumber(l), tonumber(r) }
- i = i + 2
- end
-
- elseif a == "-N" then
- new.session_type = "none"
- i = i + 1
-
- elseif a == "--" then
- local parts = {}
- for j = i + 1, #cli_args do
- table.insert(parts, cli_args[j])
- end
- new.remote_command = table.concat(parts, " ")
- break
-
- else
- error("Unknown or unsupported ssh option: " .. a)
- end
- end
-
- return new
-end
-
-local function show_help()
- print("Usage: ssh <[jumphost/...]host> [options] [-- command]")
- print("\nAvailable hosts:")
- for h, _ in pairs(yoshi.hosts) do
- print(" - " .. h)
- end
-end
-
-function yoshi.run(cli_opts, opts)
- if not cli_opts then
- error("Please pass `arg` to yoshi.run (ex. `yoshi.run(arg)`)")
- end
-
- if cli_opts[1] == nil then
- show_help()
- os.exit(0)
- end
-
- local path = split_path(cli_opts[1])
- local destName = path[#path]
-
- local target = yoshi.getHost(destName)
-
- local acc = {}
- if #path > 1 then
- for i = 1, #path - 1 do
- local hop = parse_user_host_port(path[i])
- table.insert(acc, {
- user = hop.user,
- host = hop.host,
- port = hop.port,
- })
- end
- target.proxy_jumps = acc
- end
-
- local cmd = target:overlayCliOpts(cli_opts)
- print("+ " .. cmd:toCommand())
-
- if not opts or not opts.dry_run then
- os.execute(cmd)
- end
-end
-
-return yoshi