aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore28
-rw-r--r--LICENSE21
-rw-r--r--README.md29
-rw-r--r--config_example.toml11
-rw-r--r--go.mod36
-rw-r--r--go.sum67
-rw-r--r--main.go103
-rw-r--r--run_host.go14
-rw-r--r--run_nonhost.go18
9 files changed, 327 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..26a2fff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+# If you prefer the allow list template instead of the deny list, see community template:
+# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
+#
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+
+# Go workspace file
+go.work
+go.work.sum
+
+# env file
+.env
+
+config.toml
+wsl-tui \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1d3c8df
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Matt Strapp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..28493ac
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+# wsl-tui
+
+A simple terminal interface to interact with having several [WSL](https://learn.microsoft.com/en-gb/windows/wsl/) distributions installed.
+
+## Config file
+
+See [example config](./config_example.toml).
+
+## Requirements
+
+- `go` 1.23
+- `wsl` if on Windows
+- `SSH` if on any other platform or on Windows built with the `nohost` tag.
+
+### Building
+
+```bash
+go build -ldflags "-s -w" -o wsl-tui
+```
+
+To use SSH on Windows instead of WSL, instead use the following:
+
+```bash
+go build -ldflags "-s -w" -tags nohost -o wsl-tui
+```
+
+## License
+
+[MIT](./LICENSE)
diff --git a/config_example.toml b/config_example.toml
new file mode 100644
index 0000000..7bcf626
--- /dev/null
+++ b/config_example.toml
@@ -0,0 +1,11 @@
+host_ip = "192.168.1.1"
+extra_ssh_args = ["-C"]
+#extra_wsl_args = []
+
+[[distribution]]
+name = "Ubuntu"
+
+[[distribution]]
+name = "Debian"
+username = "locust"
+port = 4444 \ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..bbcf054
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,36 @@
+module git.mattstrapp.net/wsl-tui
+
+go 1.23.2
+
+require (
+ github.com/adrg/xdg v0.5.1
+ github.com/charmbracelet/huh v0.6.0
+ github.com/locusts-r-us/locusts v0.0.0-20241017222834-aea14464705c
+ github.com/pelletier/go-toml/v2 v2.2.3
+)
+
+require (
+ github.com/atotto/clipboard v0.1.4 // indirect
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+ github.com/catppuccin/go v0.2.0 // indirect
+ github.com/charmbracelet/bubbles v0.20.0 // indirect
+ github.com/charmbracelet/bubbletea v1.1.0 // indirect
+ github.com/charmbracelet/lipgloss v0.13.0 // indirect
+ github.com/charmbracelet/x/ansi v0.2.3 // indirect
+ github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
+ github.com/charmbracelet/x/term v0.2.0 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-localereader v0.0.1 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+ github.com/muesli/cancelreader v0.2.2 // indirect
+ github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/sys v0.26.0 // indirect
+ golang.org/x/text v0.18.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..6405ed3
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,67 @@
+github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
+github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
+github.com/adrg/xdg v0.5.1 h1:Im8iDbEFARltY09yOJlSGu4Asjk2vF85+3Dyru8uJ0U=
+github.com/adrg/xdg v0.5.1/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
+github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
+github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
+github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
+github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
+github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
+github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
+github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
+github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8=
+github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU=
+github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
+github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
+github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
+github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
+github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
+github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
+github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
+github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
+github.com/locusts-r-us/locusts v0.0.0-20241017222834-aea14464705c h1:LSRSe0cIp8RVY2Hyn6s+t2E/YzCmzodxwvgMgPg9R+4=
+github.com/locusts-r-us/locusts v0.0.0-20241017222834-aea14464705c/go.mod h1:Na7LwqkgXUZ8d7qErSojZNiNyIQdYB7lix+7HNvJV4o=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
+github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
+github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..2b4487b
--- /dev/null
+++ b/main.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "io"
+ "log"
+ "os"
+
+ "github.com/adrg/xdg"
+ "github.com/charmbracelet/huh"
+ "github.com/locusts-r-us/locusts"
+ "github.com/pelletier/go-toml/v2"
+)
+
+type Config struct {
+ HostIP string `toml:"host_ip"`
+ WslArgs []string `toml:"extra_wsl_args"`
+ SshArgs []string `toml:"extra_ssh_args"`
+}
+
+type Distribution struct {
+ Name string `toml:"name"`
+ Username string `toml:"username"`
+ Port int `toml:"port"`
+}
+
+type ConfigFile struct {
+ Config
+ Distributions []Distribution `toml:"distribution"`
+}
+
+func main() {
+ locusts.IntroduceLocusts()
+
+ // Create config file location
+ path, err := xdg.ConfigFile("wsl-tui/config.toml")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ configFile, err := os.Open(path)
+ if err != nil {
+ // Open local config file
+ configFile, err = os.Open("config.toml")
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ configContents, err := io.ReadAll(configFile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ configFile.Close()
+
+ config := ConfigFile{}
+ err = toml.Unmarshal(configContents, &config)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // fmt.Println(config)
+
+ var distro Distribution
+
+ err = huh.NewForm(
+ huh.NewGroup(
+ huh.NewSelect[Distribution]().
+ Title("Select a distribution to launch!").
+ Value(&distro).
+ OptionsFunc(func() []huh.Option[Distribution] {
+ options := make([]huh.Option[Distribution], len(config.Distributions))
+ for i, distro := range config.Distributions {
+ options[i] = huh.Option[Distribution]{
+ Key: distro.Name,
+ Value: distro,
+ }
+ }
+ return options
+ }, &config,
+ ),
+ ),
+ ).WithTheme(huh.ThemeDracula()).Run()
+
+ if err != nil {
+ if err == huh.ErrUserAborted {
+ os.Exit(130)
+ }
+ log.Fatal(err)
+ }
+
+ cmd, err := command(distro, config.Config)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // Pass in the TTY shite
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ if err := cmd.Run(); err != nil {
+ log.Fatal(cmd.Args[0], " exited with error: ", err)
+ }
+}
diff --git a/run_host.go b/run_host.go
new file mode 100644
index 0000000..4a74a7b
--- /dev/null
+++ b/run_host.go
@@ -0,0 +1,14 @@
+//go:build windows && !nonhost
+
+package main
+
+import (
+ "os/exec"
+)
+
+// Run the native WSL command
+func command(distribution Distribution, config Config) (*exec.Cmd, error) {
+ // Append the additional arguments to the command
+ args := append([]string{"-d", distribution.Name}, config.WslArgs...)
+ return exec.Command("wsl", args...), nil
+}
diff --git a/run_nonhost.go b/run_nonhost.go
new file mode 100644
index 0000000..9742774
--- /dev/null
+++ b/run_nonhost.go
@@ -0,0 +1,18 @@
+//go:build !windows || nonhost
+
+package main
+
+import (
+ "errors"
+ "fmt"
+ "os/exec"
+ "strconv"
+)
+
+func command(distribution Distribution, config Config) (*exec.Cmd, error) {
+ if distribution.Username == "" || distribution.Port == 0 {
+ return nil, errors.New("SSH username or port not set, not running.")
+ }
+ args := append([]string{fmt.Sprintf("%s@%s", distribution.Username, config.HostIP), "-p", strconv.Itoa(distribution.Port)}, config.SshArgs...)
+ return exec.Command("ssh", args...), nil
+}