├── .github ├── img │ └── header.png └── workflows │ ├── commitlint.yml │ ├── golangci-lint.yml │ └── goreleaser.yml ├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── configure.go ├── default.go ├── root.go ├── shared │ └── shared.go └── update.go ├── go.mod ├── go.sum ├── install.sh ├── main.go └── models ├── config ├── config.go └── manager │ └── manager.go ├── global └── global.go └── service ├── service.go ├── servicemap └── servicemap.go └── services ├── apprise.go ├── autobrr.go ├── changedetectionio.go ├── cloudflared.go ├── gotify.go ├── homepage.go ├── huginn.go ├── jellyfin.go ├── kavita.go ├── nginx.go ├── overseerr.go ├── playwright.go ├── plex.go ├── prowlarr.go ├── qbittorrent.go ├── radarr.go ├── sabnzbd.go ├── sonarr.go ├── tautulli.go └── thelounge.go /.github/img/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TechSquidTV/uhs-cli/7269d301c765d1e68f9420f7975b8ff76521d1ea/.github/img/header.png -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Commit Messages 2 | on: [pull_request] 3 | 4 | jobs: 5 | commitlint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | - uses: wagoid/commitlint-github-action@v5 -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | permissions: 10 | contents: read 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/setup-go@v4 17 | with: 18 | go-version: '1.19' 19 | cache: false 20 | - uses: actions/checkout@v3 21 | - name: golangci-lint 22 | uses: golangci/golangci-lint-action@v3 23 | with: 24 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 25 | version: latest 26 | - name: gofmt 27 | uses: Jerome1337/gofmt-action@v1.0.5 28 | with: 29 | gofmt-flags: '-l -d' -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v4 22 | 23 | - name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v4 25 | with: 26 | # either 'goreleaser' (default) or 'goreleaser-pro' 27 | distribution: goreleaser 28 | version: latest 29 | args: release --clean 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | secrets.y* 3 | values.y* 4 | dist/ 5 | .DS_Store -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | # - windows 15 | - darwin 16 | 17 | archives: 18 | - format: tar.gz 19 | # this name template makes the OS and Arch compatible with the results of uname. 20 | name_template: >- 21 | {{ .ProjectName }}_ 22 | {{- title .Os }}_ 23 | {{- if eq .Arch "amd64" }}x86_64 24 | {{- else if eq .Arch "386" }}i386 25 | {{- else }}{{ .Arch }}{{ end }} 26 | {{- if .Arm }}v{{ .Arm }}{{ end }} 27 | # use zip for windows archives 28 | format_overrides: 29 | - goos: windows 30 | format: zip 31 | checksum: 32 | name_template: 'checksums.txt' 33 | snapshot: 34 | name_template: "{{ incpatch .Version }}-next" 35 | changelog: 36 | sort: asc 37 | filters: 38 | exclude: 39 | - '^docs:' 40 | - '^test:' 41 | 42 | # The lines beneath this are called `modelines`. See `:help modeline` 43 | # Feel free to remove those if you don't want/use them. 44 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 45 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TechSquidTV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lint install-linter test format setup prep 2 | 3 | lint: install-linter 4 | golangci-lint run -v ./... 5 | 6 | install-linter: 7 | which golangci-lint || go get -u github.com/golangci/golangci-lint/cmd/golangci-lint 8 | 9 | test: 10 | go test ./... 11 | 12 | format: 13 | go fmt ./... 14 | 15 | setup: install-linter 16 | go mod download 17 | 18 | prep: format lint test 19 | rm -rf ./bin 20 | go mod tidy -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Banner 4 |

5 | 6 |

7 | 8 | 9 | GitHub Sponsors 10 | 11 | 12 | GitHub Workflow Status 13 | 14 | 15 | Discord 16 | 17 |

18 | 19 |

20 | UltimateHomeServer CLI 21 |

22 | 23 |

24 | An interactive CLI to assist in configuring services for the UHS stack. 25 |

26 | 27 | 28 | **Beta:** This CLI is currently in active development and is subject to change. 29 | 30 | 31 | # Getting Started 32 | 33 | ## Installation 34 | 35 | 36 | ### Binary 37 | 38 | Utilize the install script to download the latest release for your platform. 39 | 40 | ```bash 41 | wget https://raw.githubusercontent.com/TechSquidTV/uhs-cli/main/install.sh 42 | ``` 43 | 44 | ```bash 45 | chmod +x install.sh 46 | ``` 47 | 48 | ```bash 49 | ./install.sh 50 | ``` 51 | 52 | ### Go 53 | 54 | ```bash 55 | go install github.com/techsquidtv/uhs-cli@latest 56 | ``` 57 | 58 | ## Usage 59 | 60 | ```bash 61 | Usage: 62 | uhs [command] 63 | 64 | Available Commands: 65 | configure Configure your UHS instance 66 | default Get the default configuration for UHS 67 | help Help about any command 68 | 69 | Flags: 70 | -h, --help help for uhs-cli 71 | -t, --toggle Help message for toggle 72 | ``` 73 | 74 | Generate configurations for individual services: 75 | 76 | ```bash 77 | uhs-cli configure -o values.yaml 78 | ``` 79 | 80 | Generate a default configuration for all services: 81 | 82 | ```bash 83 | uhs-cli default -o values.yaml 84 | ``` -------------------------------------------------------------------------------- /cmd/configure.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 TechSquidTV 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "sort" 10 | 11 | "github.com/AlecAivazis/survey/v2" 12 | "github.com/spf13/cobra" 13 | "github.com/techsquidtv/uhs-cli/cmd/shared" 14 | "github.com/techsquidtv/uhs-cli/models/config" 15 | "github.com/techsquidtv/uhs-cli/models/global" 16 | "github.com/techsquidtv/uhs-cli/models/service/servicemap" 17 | ) 18 | 19 | // Return each key from the default config map 20 | var serviceNames = servicemap.Keys(servicemap.Registered) 21 | 22 | // configureCmd represents the configure command 23 | var configureCmd = &cobra.Command{ 24 | Use: "configure", 25 | Short: "Configure your UHS instance", 26 | Long: `Customize and configure your desired services for your UHS instance.`, 27 | ValidArgs: serviceNames, 28 | Run: func(cmd *cobra.Command, args []string) { 29 | // Create a new instance of the UHSConfig struct 30 | var selectedServices []string 31 | if len(args) > 0 { 32 | selectedServices = args 33 | } 34 | uhsConfig := config.Config{ 35 | Global: global.NewGlobal(), 36 | Services: make(config.ServicesConfig), 37 | } 38 | sort.Strings(serviceNames) 39 | 40 | if len(selectedServices) == 0 { 41 | // Prompt user to select services to enable 42 | serviceSelectPrompt := &survey.MultiSelect{ 43 | Message: "Select services to enable:", 44 | Options: serviceNames, 45 | } 46 | err := survey.AskOne(serviceSelectPrompt, &selectedServices) 47 | if err != nil { 48 | fmt.Println(err.Error()) 49 | return 50 | } 51 | } 52 | // Validate selected services 53 | if len(selectedServices) == 0 { 54 | fmt.Println("No services selected. Exiting...") 55 | os.Exit(0) 56 | } 57 | serviceListString := "" 58 | for _, service := range selectedServices { 59 | serviceListString += fmt.Sprintf(" - %v\n", service) 60 | } 61 | 62 | validateSelectionPrompt := &survey.Confirm{ 63 | Message: fmt.Sprintf("You have selected the following services:\n%v\n Is this correct?", serviceListString), 64 | } 65 | var validateSelection bool 66 | err := survey.AskOne(validateSelectionPrompt, &validateSelection) 67 | if err != nil { 68 | fmt.Println(err.Error()) 69 | return 70 | } 71 | // If selection is not valid, exit 72 | if !validateSelection { 73 | fmt.Println("Exiting...") 74 | os.Exit(0) 75 | 76 | } 77 | // Execute configuration for each selected service 78 | for _, serviceName := range selectedServices { 79 | fmt.Println("Configuring " + serviceName + "...") 80 | service := servicemap.Registered[serviceName]() 81 | service.Configure() 82 | uhsConfig.Services[serviceName] = service 83 | } 84 | // Output 85 | outputFile, err := cmd.Flags().GetString("output") 86 | if err != nil { 87 | fmt.Println(err.Error()) 88 | return 89 | } 90 | err = shared.Output(outputFile, &uhsConfig) 91 | if err != nil { 92 | fmt.Println(err.Error()) 93 | return 94 | } 95 | }, 96 | } 97 | 98 | func init() { 99 | rootCmd.AddCommand(configureCmd) 100 | configureCmd.PersistentFlags().StringP("output", "o", "", "Output file path") 101 | 102 | // Here you will define your flags and configuration settings. 103 | 104 | // Cobra supports Persistent Flags which will work for this command 105 | // and all subcommands, e.g.: 106 | // configureCmd.PersistentFlags().String("foo", "", "A help for foo") 107 | 108 | // Cobra supports local flags which will only run when this command 109 | // is called directly, e.g.: 110 | // configureCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 111 | } 112 | -------------------------------------------------------------------------------- /cmd/default.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 TechSquidTV 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/spf13/cobra" 10 | "github.com/techsquidtv/uhs-cli/cmd/shared" 11 | "github.com/techsquidtv/uhs-cli/models/config" 12 | "github.com/techsquidtv/uhs-cli/models/global" 13 | "github.com/techsquidtv/uhs-cli/models/service/servicemap" 14 | ) 15 | 16 | // defaultCmd represents the default command 17 | var defaultCmd = &cobra.Command{ 18 | Use: "default", 19 | Short: "Get the default configuration for UHS", 20 | Long: `Get the default configuration for UHS. 21 | This will output the default configuration file, meant to be overwritten manually of via the configure command.`, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | uhsConfig := config.Config{ 24 | Global: global.NewGlobal(), 25 | Services: make(config.ServicesConfig), 26 | } 27 | 28 | // Set services to default 29 | for k, v := range servicemap.Registered { 30 | uhsConfig.Services[k] = v() 31 | } 32 | // Output 33 | outputFile, err := cmd.Flags().GetString("output") 34 | if err != nil { 35 | fmt.Println(err.Error()) 36 | return 37 | } 38 | err = shared.Output(outputFile, &uhsConfig) 39 | if err != nil { 40 | fmt.Println(err.Error()) 41 | return 42 | } 43 | }, 44 | } 45 | 46 | func init() { 47 | rootCmd.AddCommand(defaultCmd) 48 | defaultCmd.PersistentFlags().StringP("output", "o", "", "Output file path") 49 | // Here you will define your flags and configuration settings. 50 | 51 | // Cobra supports Persistent Flags which will work for this command 52 | // and all subcommands, e.g.: 53 | // defaultCmd.PersistentFlags().String("foo", "", "A help for foo") 54 | 55 | // Cobra supports local flags which will only run when this command 56 | // is called directly, e.g.: 57 | // defaultCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 58 | } 59 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // rootCmd represents the base command when called without any subcommands 13 | var rootCmd = &cobra.Command{ 14 | Use: "uhs", 15 | Short: "Create and customize configuration files for UltimateHomeServer", 16 | Long: `Utilize the UHS CLI to customize your deployments of UltimateHomeServer. 17 | Interactively select the services you want to deploy, and the CLI will generate the necessary configuration files for you.`, 18 | // Uncomment the following line if your bare application 19 | // has an action associated with it: 20 | // Run: func(cmd *cobra.Command, args []string) { }, 21 | } 22 | 23 | // Execute adds all child commands to the root command and sets flags appropriately. 24 | // This is called by main.main(). It only needs to happen once to the rootCmd. 25 | func Execute() { 26 | err := rootCmd.Execute() 27 | if err != nil { 28 | os.Exit(1) 29 | } 30 | } 31 | 32 | func init() { 33 | // Here you will define your flags and configuration settings. 34 | // Cobra supports persistent flags, which, if defined here, 35 | // will be global for your application. 36 | 37 | // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.uhs-cli.yaml)") 38 | 39 | // Cobra also supports local flags, which will only run 40 | // when this action is called directly. 41 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 42 | } 43 | -------------------------------------------------------------------------------- /cmd/shared/shared.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/techsquidtv/uhs-cli/models/config" 8 | "gopkg.in/yaml.v3" 9 | ) 10 | 11 | func Output(filePath string, config *config.Config) error { 12 | yamlConfig, err := yaml.Marshal(config) 13 | if err != nil { 14 | return err 15 | } 16 | if filePath == "" { 17 | fmt.Println(string(yamlConfig)) 18 | return nil 19 | } else { 20 | err = os.WriteFile(filePath, yamlConfig, 0644) 21 | if err != nil { 22 | return err 23 | } 24 | fmt.Println("Configuration complete! Your configuration file has been saved to " + filePath + ".") 25 | } 26 | return nil 27 | } 28 | 29 | func Input(filePath string, config *config.Config) error { 30 | inputFile, err := os.ReadFile(filePath) 31 | if err != nil { 32 | fmt.Printf("Unable to read file: %v", err) 33 | return err 34 | } 35 | err = yaml.Unmarshal(inputFile, config) 36 | if err != nil { 37 | fmt.Printf("Unable to unmarshal data: %v", err) 38 | return err 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /cmd/update.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 TechSquidTV 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "sort" 10 | 11 | "github.com/AlecAivazis/survey/v2" 12 | "github.com/spf13/cobra" 13 | "github.com/techsquidtv/uhs-cli/cmd/shared" 14 | "github.com/techsquidtv/uhs-cli/models/config" 15 | "github.com/techsquidtv/uhs-cli/models/service/servicemap" 16 | ) 17 | 18 | // updateCmd represents the update command 19 | var updateCmd = &cobra.Command{ 20 | Use: "update", 21 | Short: "Update your UHS instance configuration", 22 | Long: `Customize and update your desired services for your UHS instance.`, 23 | ValidArgs: serviceNames, 24 | Run: func(cmd *cobra.Command, args []string) { 25 | // Validate files were provided 26 | inputFile, err := cmd.Flags().GetString("input") 27 | if err != nil { 28 | fmt.Println(err.Error()) 29 | return 30 | } 31 | outputFile, err := cmd.Flags().GetString("output") 32 | if err != nil { 33 | fmt.Println(err.Error()) 34 | return 35 | } 36 | var cfg config.Config 37 | cfg.Services = make(config.ServicesConfig) 38 | err = shared.Input(inputFile, &cfg) 39 | if err != nil { 40 | fmt.Println(err.Error()) 41 | return 42 | } 43 | var selectedServices []string 44 | if len(args) > 0 { 45 | selectedServices = args 46 | } 47 | sort.Strings(serviceNames) 48 | 49 | if len(selectedServices) == 0 { 50 | // Prompt user to select services to update 51 | serviceSelectPrompt := &survey.MultiSelect{ 52 | Message: "Select services to modify. If a selected service wasn't present in the input config, it will be added:", 53 | Options: serviceNames, 54 | } 55 | err := survey.AskOne(serviceSelectPrompt, &selectedServices) 56 | if err != nil { 57 | fmt.Println(err.Error()) 58 | return 59 | } 60 | } 61 | // Validate selected services 62 | if len(selectedServices) == 0 { 63 | fmt.Println("No services selected to update. Exiting...") 64 | os.Exit(0) 65 | } 66 | 67 | validateSelectionPrompt := &survey.Confirm{ 68 | Message: fmt.Sprintf("You have selected the following services:\n%v\n Is this correct?", selectedServices), 69 | } 70 | var validateSelection bool 71 | err = survey.AskOne(validateSelectionPrompt, &validateSelection) 72 | if err != nil { 73 | fmt.Println(err.Error()) 74 | return 75 | } 76 | // If selection is not valid, exit 77 | // TODO: Make this flow better. Have user go through selection process again. 78 | if !validateSelection { 79 | fmt.Println("Exiting...") 80 | os.Exit(0) 81 | 82 | } 83 | // Execute configuration for each selected service 84 | for _, serviceName := range selectedServices { 85 | fmt.Println("Updating " + serviceName + "...") 86 | service := servicemap.Registered[serviceName]() 87 | service.Configure() 88 | cfg.Services[serviceName] = service 89 | } 90 | 91 | err = shared.Output(outputFile, &cfg) 92 | if err != nil { 93 | fmt.Println(err.Error()) 94 | return 95 | } 96 | }, 97 | } 98 | 99 | func init() { 100 | rootCmd.AddCommand(updateCmd) 101 | updateCmd.PersistentFlags().StringP("output", "o", "", "Output file path") 102 | updateCmd.PersistentFlags().StringP("input", "i", "", "Input file path") 103 | 104 | // Here you will define your flags and configuration settings. 105 | 106 | // Cobra supports Persistent Flags which will work for this command 107 | // and all subcommands, e.g.: 108 | // updateCmd.PersistentFlags().String("foo", "", "A help for foo") 109 | 110 | // Cobra supports local flags which will only run when this command 111 | // is called directly, e.g.: 112 | // updateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 113 | } 114 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/techsquidtv/uhs-cli 2 | 3 | go 1.19 4 | 5 | require github.com/AlecAivazis/survey/v2 v2.3.6 6 | 7 | require ( 8 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 9 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 10 | github.com/mattn/go-colorable v0.1.2 // indirect 11 | github.com/mattn/go-isatty v0.0.8 // indirect 12 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 13 | github.com/spf13/cobra v1.7.0 14 | github.com/spf13/pflag v1.0.5 // indirect 15 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect 16 | golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect 17 | golang.org/x/text v0.3.3 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw= 2 | github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= 3 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 4 | github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 6 | github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= 7 | github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= 12 | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= 13 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 14 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 15 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 16 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 17 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 18 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 19 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 20 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 21 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= 22 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 26 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 27 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 28 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 29 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 30 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 31 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 32 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 33 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 34 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= 36 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= 38 | golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= 39 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 40 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 41 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 43 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 44 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 45 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 46 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the latest version 4 | VERSION=$(curl -s https://api.github.com/repos/TechSquidTV/uhs-cli/releases/latest | grep tag_name | cut -d '"' -f 4) 5 | RED='\033[0;31m' 6 | NC='\033[0m' # No Color 7 | 8 | # The operating system and architecture 9 | OS=$(uname) 10 | ARCH=$(uname -m) 11 | 12 | # The URL of the release 13 | URL="https://github.com/TechSquidTV/uhs-cli/releases/download/${VERSION}/uhs-cli_${OS}_${ARCH}.tar.gz" 14 | 15 | # Download and extract the binary 16 | wget $URL 17 | mkdir uhs-cli_${OS}_${ARCH} 18 | tar -xvzf uhs-cli_${OS}_${ARCH}.tar.gz -C uhs-cli_${OS}_${ARCH} 19 | 20 | # Move the binary to a location in your PATH 21 | if [ ! -d ~/.local/bin ]; then 22 | mkdir -p ~/.local/bin 23 | fi 24 | mv uhs-cli_${OS}_${ARCH}/uhs-cli ~/.local/bin/uhs 25 | 26 | # Ensure the binary is executable 27 | chmod +x ~/.local/bin/uhs 28 | 29 | # Clean up the downloaded file and the extracted directory 30 | rm uhs-cli_${OS}_${ARCH}.tar.gz 31 | rm -rf uhs-cli_${OS}_${ARCH} 32 | 33 | echo "Installed uhs-cli version ${VERSION}" 34 | echo "Run 'uhs --help' to get started" 35 | echo 36 | # Check if the binary is in your PATH 37 | if ! command -v uhs &> /dev/null; then 38 | echo "${RED}[WARNING]${NC} uhs could not be found in your PATH. Please add '\${HOME}/.local/bin/' to your PATH." 39 | fi 40 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 NAME HERE 3 | */ 4 | package main 5 | 6 | import "github.com/techsquidtv/uhs-cli/cmd" 7 | 8 | func main() { 9 | cmd.Execute() 10 | } 11 | -------------------------------------------------------------------------------- /models/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/techsquidtv/uhs-cli/models/config/manager" 7 | "github.com/techsquidtv/uhs-cli/models/service/servicemap" 8 | "gopkg.in/yaml.v3" 9 | ) 10 | 11 | type Config struct { 12 | Global manager.Configurer `yaml:"global,omitempty"` 13 | Services ServicesConfig `yaml:"services"` 14 | } 15 | 16 | type ServicesConfig map[string]manager.Configurer 17 | 18 | func (sc ServicesConfig) UnmarshalYAML(unmarshal func(any) error) error { 19 | services := make(map[string]any) 20 | if err := unmarshal(&services); err != nil { 21 | fmt.Println("could not unmarshal:", err) 22 | return err 23 | } 24 | for k, v := range services { 25 | b, err := yaml.Marshal(v) // remarshal this specific service so we can unmarshal it properly. 26 | if err != nil { 27 | fmt.Println("Error marshalling:", err) 28 | continue 29 | } 30 | service, err := unmarshalService(k, b) 31 | if err != nil { 32 | fmt.Println("Failed to unmarshal service", err) 33 | continue 34 | } 35 | sc[k] = service 36 | } 37 | return nil 38 | } 39 | 40 | func unmarshalService(key string, data []byte) (manager.Configurer, error) { 41 | v, ok := servicemap.Registered[key] 42 | if !ok { 43 | return nil, fmt.Errorf("unregistered service name: %q", key) 44 | } 45 | sc := v() 46 | if err := sc.Fill(data); err != nil { 47 | return nil, err 48 | } 49 | return sc, nil 50 | } 51 | -------------------------------------------------------------------------------- /models/config/manager/manager.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | // Configurer represents the config manager interface. 4 | type Configurer interface { 5 | // Configure the service config based on user specification 6 | Configure() 7 | // Fill populates the service config 8 | Fill([]byte) error 9 | } 10 | -------------------------------------------------------------------------------- /models/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | ) 9 | 10 | type Global struct { 11 | TZ string `yaml:"tz"` 12 | Network Network `yaml:"network"` 13 | Domain string `yaml:"domain"` 14 | Certs Certs `yaml:"certs"` 15 | } 16 | 17 | type Network struct { 18 | Gateway string `yaml:"gateway"` 19 | } 20 | 21 | type Certs struct { 22 | SSLCertificateKey string `yaml:"ssl_certificate_key"` 23 | SSLCertificate string `yaml:"ssl_certificate"` 24 | SSLDHParam string `yaml:"ssl_dhparam"` 25 | } 26 | 27 | func NewGlobal() manager.Configurer { 28 | return &Global{ 29 | TZ: "America/New_York", 30 | Domain: "UltimateHomeServer.com", 31 | Network: Network{ 32 | Gateway: "192.168.1.1", 33 | }, 34 | Certs: Certs{ 35 | SSLCertificateKey: "/etc/letsencrypt/live/${DOMAIN}/privkey.pem", 36 | SSLCertificate: "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem", 37 | SSLDHParam: "/etc/letsencrypt/certs/dhparam.pem", 38 | }, 39 | } 40 | } 41 | 42 | func (c *Global) Configure() { 43 | inputDomain := &survey.Input{ 44 | Message: "Enter your domain:", 45 | Default: c.Domain, 46 | } 47 | err := survey.AskOne(inputDomain, &c.Domain) 48 | if err != nil { 49 | fmt.Println(err.Error()) 50 | } 51 | 52 | inputTz := &survey.Input{ 53 | Message: "Enter your timezone:", 54 | Default: c.TZ, 55 | } 56 | err = survey.AskOne(inputTz, &c.TZ) 57 | if err != nil { 58 | fmt.Println(err.Error()) 59 | } 60 | 61 | inputGateway := &survey.Input{ 62 | Message: "Enter your network gateway:", 63 | Default: c.Network.Gateway, 64 | } 65 | err = survey.AskOne(inputGateway, &c.Network.Gateway) 66 | if err != nil { 67 | fmt.Println(err.Error()) 68 | } 69 | 70 | inputSSLCertificateKey := &survey.Input{ 71 | Message: "Enter your SSL certificate key path:", 72 | Default: c.Certs.SSLCertificateKey, 73 | } 74 | err = survey.AskOne(inputSSLCertificateKey, &c.Certs.SSLCertificateKey) 75 | if err != nil { 76 | fmt.Println(err.Error()) 77 | } 78 | 79 | inputSSLCertificate := &survey.Input{ 80 | Message: "Enter your SSL certificate path:", 81 | Default: c.Certs.SSLCertificate, 82 | } 83 | err = survey.AskOne(inputSSLCertificate, &c.Certs.SSLCertificate) 84 | if err != nil { 85 | fmt.Println(err.Error()) 86 | } 87 | 88 | inputSSLDHParam := &survey.Input{ 89 | Message: "Enter your SSL DHParam path:", 90 | Default: c.Certs.SSLDHParam, 91 | } 92 | err = survey.AskOne(inputSSLDHParam, &c.Certs.SSLDHParam) 93 | if err != nil { 94 | fmt.Println(err.Error()) 95 | } 96 | } 97 | 98 | func (c *Global) Fill(data []byte) error { return nil } 99 | -------------------------------------------------------------------------------- /models/service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | type Ports struct { 4 | Http *int `yaml:"http,omitempty"` 5 | Https *int `yaml:"https,omitempty"` 6 | Udp *int `yaml:"udp,omitempty"` 7 | P2p *int `yaml:"p2p,omitempty"` 8 | P2pudp *int `yaml:"p2pudp,omitempty"` 9 | } 10 | 11 | type Image struct { 12 | Repository string `yaml:"repository"` 13 | Tag string `yaml:"tag"` 14 | PullPolicy string `yaml:"pullPolicy"` 15 | } 16 | 17 | type Service struct { 18 | Enabled bool `yaml:"enabled"` 19 | ReplicaCount int `yaml:"replicaCount"` 20 | Image Image `yaml:"image"` 21 | Ports Ports `yaml:"ports,omitempty"` 22 | } 23 | -------------------------------------------------------------------------------- /models/service/servicemap/servicemap.go: -------------------------------------------------------------------------------- 1 | package servicemap 2 | 3 | import ( 4 | "github.com/techsquidtv/uhs-cli/models/config/manager" 5 | "github.com/techsquidtv/uhs-cli/models/service/services" 6 | ) 7 | 8 | var Registered = map[string]func() manager.Configurer{ 9 | "qbittorrent": services.NewQbittorrent, 10 | "plex": services.NewPlex, 11 | "sonarr": services.NewSonarr, 12 | "radarr": services.NewRadarr, 13 | "sabnzbd": services.NewSabnzbd, 14 | "cloudflared": services.NewCloudflared, 15 | "overseerr": services.NewOverseerr, 16 | "autobrr": services.NewAutobrr, 17 | "prowlarr": services.NewProwlarr, 18 | "kavita": services.NewKavita, 19 | "gotify": services.NewGotify, 20 | "tautulli": services.NewTautulli, 21 | "playwright": services.NewPlaywright, 22 | "thelounge": services.NewThelounge, 23 | "apprise": services.NewApprise, 24 | "changedetectionio": services.NewChangedetectionio, 25 | "huginn": services.NewHuginn, 26 | "nginx": services.NewNginx, 27 | "homepage": services.NewHomepage, 28 | "jellyfin": services.NewJellyfin, 29 | } 30 | 31 | // Keys is a utility function which returns the (string) keys of a map. 32 | func Keys(m map[string]func() manager.Configurer) []string { 33 | var r []string 34 | for k := range m { 35 | r = append(r, k) 36 | } 37 | return r 38 | } 39 | -------------------------------------------------------------------------------- /models/service/services/apprise.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type apprise struct { 13 | service.Service `yaml:",inline"` 14 | appriseOptions `yaml:",inline"` 15 | } 16 | 17 | type appriseOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewApprise() manager.Configurer { 23 | http := 8000 24 | 25 | return &apprise{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "lscr.io/linuxserver/apprise-api", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | appriseOptions: appriseOptions{ 39 | Config: "/opt/apprise/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *apprise) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your Apprise config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *apprise) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/autobrr.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type autobrr struct { 13 | service.Service `yaml:",inline"` 14 | autobrrOptions `yaml:",inline"` 15 | } 16 | 17 | type autobrrOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewAutobrr() manager.Configurer { 23 | http := 7474 24 | 25 | return &autobrr{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "ghcr.io/autobrr/autobrr", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | autobrrOptions: autobrrOptions{ 39 | Config: "/opt/autobrr/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *autobrr) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your Autobrr config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *autobrr) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/changedetectionio.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type changedetectionio struct { 13 | service.Service `yaml:",inline"` 14 | changedetectionioOptions `yaml:",inline"` 15 | } 16 | 17 | type changedetectionioOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewChangedetectionio() manager.Configurer { 23 | http := 5000 24 | 25 | return &changedetectionio{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "ghcr.io/dgtlmoon/changedetection.io", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | changedetectionioOptions: changedetectionioOptions{ 39 | Config: "/opt/changedetectionio/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *changedetectionio) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your Changedetectionio config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *changedetectionio) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/cloudflared.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type cloudflared struct { 13 | service.Service `yaml:",inline"` 14 | cloudflaredOptions `yaml:",inline"` 15 | } 16 | 17 | type cloudflaredOptions struct { 18 | Tunnel string `yaml:"tunnel"` 19 | Domain string `yaml:"domain"` 20 | URL string `yaml:"url"` 21 | Target string `yaml:"target"` 22 | } 23 | 24 | // Return default values for service 25 | func NewCloudflared() manager.Configurer { 26 | 27 | return &cloudflared{ 28 | Service: service.Service{ 29 | Enabled: false, 30 | ReplicaCount: 1, 31 | Image: service.Image{ 32 | Repository: "cloudflare/cloudflared", 33 | Tag: "latest", 34 | PullPolicy: "IfNotPresent", // For stability 35 | }, 36 | Ports: service.Ports{}, 37 | }, 38 | cloudflaredOptions: cloudflaredOptions{ 39 | Tunnel: "example-tunnel", 40 | Domain: "example.com", 41 | URL: "http://foo.example.com", 42 | Target: "localhost:8080", 43 | }, 44 | } 45 | } 46 | 47 | func (s *cloudflared) Configure() { 48 | s.Enabled = true 49 | inputTunnel := &survey.Input{ 50 | Message: "Enter the name of your tunnel:", 51 | Default: s.Tunnel, 52 | } 53 | err := survey.AskOne(inputTunnel, &s.Tunnel) 54 | if err != nil { 55 | fmt.Println(err.Error()) 56 | } 57 | 58 | inputDomain := &survey.Input{ 59 | Message: "Enter the domain you want to use:", 60 | Default: s.Domain, 61 | } 62 | err = survey.AskOne(inputDomain, &s.Domain) 63 | if err != nil { 64 | fmt.Println(err.Error()) 65 | } 66 | 67 | inputURL := &survey.Input{ 68 | Message: "Enter the URL you want to use:", 69 | Default: s.URL, 70 | } 71 | err = survey.AskOne(inputURL, &s.URL) 72 | if err != nil { 73 | fmt.Println(err.Error()) 74 | } 75 | 76 | inputTarget := &survey.Input{ 77 | Message: "Enter the target you want to use:", 78 | Default: s.Target, 79 | } 80 | err = survey.AskOne(inputTarget, &s.Target) 81 | if err != nil { 82 | fmt.Println(err.Error()) 83 | } 84 | } 85 | 86 | func (s *cloudflared) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 87 | -------------------------------------------------------------------------------- /models/service/services/gotify.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type gotify struct { 13 | service.Service `yaml:",inline"` 14 | gotifyOptions `yaml:",inline"` 15 | } 16 | 17 | type gotifyOptions struct { 18 | Data string `yaml:"data"` 19 | } 20 | 21 | // Return default values for service 22 | func NewGotify() manager.Configurer { 23 | http := 80 24 | 25 | return &gotify{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "ghcr.io/gotify/server", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | gotifyOptions: gotifyOptions{ 39 | Data: "/opt/gotify/data", 40 | }, 41 | } 42 | } 43 | 44 | func (s *gotify) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your Gotify data folder:", 48 | Default: s.Data, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Data) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *gotify) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/homepage.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/techsquidtv/uhs-cli/models/config/manager" 5 | "github.com/techsquidtv/uhs-cli/models/service" 6 | "gopkg.in/yaml.v3" 7 | ) 8 | 9 | type homepage struct { 10 | service.Service `yaml:",inline"` 11 | homepageOptions `yaml:",inline"` 12 | } 13 | 14 | type homepageOptions struct { 15 | Bookmarks []homepageBookmarkGroup `yaml:"bookmarks"` 16 | Services []homepageServiceGroup `yaml:"services"` 17 | Widgets []homepageWidget `yaml:"widgets"` 18 | Settings map[any]any `yaml:"settings"` 19 | } 20 | 21 | // Return default values for service 22 | func NewHomepage() manager.Configurer { 23 | http := 3000 24 | 25 | bookmarksConfig := []homepageBookmarkGroup{ 26 | { 27 | "Development": []homepageBookmark{ 28 | createHomepageBookmark("github", "mdi-github", "gh", "https://github.com"), 29 | }, 30 | }, 31 | { 32 | "Media": []homepageBookmark{ 33 | createHomepageBookmark("youtube", "mdi-youtube", "yt", "https://youtube.com"), 34 | createHomepageBookmark("plex", "mdi-plex", "plex", "https://app.plex.tv/desktop"), 35 | }, 36 | }, 37 | } 38 | servicesConfig := []homepageServiceGroup{ 39 | { 40 | "Media": []homepageService{ 41 | createHomepageService("plex", "mdi-plex", "https://app.plex.tv/desktop", "Plex"), 42 | }, 43 | }, 44 | } 45 | widgetConfig := []homepageWidget{ 46 | createHomepageWidget("search", map[string]any{ 47 | "provider": "[duckduckgo, google]", 48 | "focus": true, 49 | "target": "_blank", 50 | }), 51 | } 52 | settingsConfig := map[any]any{ 53 | "title": "Homepage", 54 | "theme": "dark", 55 | } 56 | 57 | return &homepage{ 58 | Service: service.Service{ 59 | Enabled: false, 60 | ReplicaCount: 1, 61 | Image: service.Image{ 62 | Repository: "ghcr.io/benphelps/homepage", 63 | Tag: "latest", 64 | PullPolicy: "Always", 65 | }, 66 | Ports: service.Ports{ 67 | Http: &http, 68 | }, 69 | }, 70 | homepageOptions: homepageOptions{ 71 | Bookmarks: bookmarksConfig, 72 | Services: servicesConfig, 73 | Widgets: widgetConfig, 74 | Settings: settingsConfig, 75 | }, 76 | } 77 | } 78 | 79 | func (s *homepage) Configure() { 80 | s.Enabled = true 81 | } 82 | 83 | func (s *homepage) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 84 | 85 | ////////////// 86 | // Bookmark // 87 | ////////////// 88 | 89 | type homepageBookmarkGroup map[string][]homepageBookmark 90 | type homepageBookmark map[string][]homepageBookmarkOptions 91 | 92 | type homepageBookmarkOptions struct { 93 | Icon *string `yaml:"icon,omitempty"` 94 | Abbr *string `yaml:"abbr,omitempty"` 95 | Href string `yaml:"href"` 96 | } 97 | 98 | func createHomepageBookmark(name, icon, abbr, href string) homepageBookmark { 99 | return homepageBookmark{ 100 | name: []homepageBookmarkOptions{ 101 | { 102 | Icon: &icon, 103 | Abbr: &abbr, 104 | Href: href, 105 | }, 106 | }, 107 | } 108 | } 109 | 110 | ///////////// 111 | // Service // 112 | ///////////// 113 | 114 | type homepageServiceGroup map[string][]homepageService 115 | type homepageService map[string][]homepageServiceOptions 116 | 117 | type homepageServiceOptions struct { 118 | Icon *string `yaml:"icon,omitempty"` 119 | Href string `yaml:"href"` 120 | Description *string `yaml:"description,omitempty"` 121 | } 122 | 123 | func createHomepageService(name, icon, href, description string) homepageService { 124 | return homepageService{ 125 | name: []homepageServiceOptions{ 126 | { 127 | Icon: &icon, 128 | Href: href, 129 | Description: &description, 130 | }, 131 | }, 132 | } 133 | } 134 | 135 | //////////// 136 | // Widget // 137 | //////////// 138 | 139 | type homepageWidget map[string]map[string]any 140 | 141 | func createHomepageWidget(name string, resources map[string]any) homepageWidget { 142 | return homepageWidget{ 143 | name: resources, 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /models/service/services/huginn.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type huginn struct { 13 | service.Service `yaml:",inline"` 14 | huginnOptions `yaml:",inline"` 15 | } 16 | 17 | type huginnOptions struct { 18 | Data string `yaml:"data"` 19 | Invitation_code string `yaml:"invitation_code"` 20 | App_secret_token string `yaml:"app_secret_token"` 21 | } 22 | 23 | // Return default values for service 24 | func NewHuginn() manager.Configurer { 25 | http := 3000 26 | 27 | return &huginn{ 28 | Service: service.Service{ 29 | Enabled: false, 30 | ReplicaCount: 1, 31 | Image: service.Image{ 32 | Repository: "ghcr.io/huginn/huginn", 33 | Tag: "latest", 34 | PullPolicy: "Always", 35 | }, 36 | Ports: service.Ports{ 37 | Http: &http, 38 | }, 39 | }, 40 | huginnOptions: huginnOptions{ 41 | Data: "/opt/huginn/data", 42 | Invitation_code: "invite-me", 43 | App_secret_token: "", 44 | }, 45 | } 46 | } 47 | 48 | func (s *huginn) Configure() { 49 | s.Enabled = true 50 | inputDataPath := &survey.Input{ 51 | Message: "Enter the path to your Huginn data folder:", 52 | Default: s.Data, 53 | } 54 | err := survey.AskOne(inputDataPath, &s.Data) 55 | if err != nil { 56 | fmt.Println(err.Error()) 57 | } 58 | inputInvitationCode := &survey.Input{ 59 | Message: "Enter the invitation code for your Huginn instance:", 60 | Default: s.Invitation_code, 61 | } 62 | err = survey.AskOne(inputInvitationCode, &s.Invitation_code) 63 | if err != nil { 64 | fmt.Println(err.Error()) 65 | } 66 | inputAppSecretToken := &survey.Input{ 67 | Message: "Enter the app secret token for your Huginn instance:", 68 | Default: s.App_secret_token, 69 | } 70 | err = survey.AskOne(inputAppSecretToken, &s.App_secret_token) 71 | if err != nil { 72 | fmt.Println(err.Error()) 73 | } 74 | } 75 | 76 | func (s *huginn) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 77 | -------------------------------------------------------------------------------- /models/service/services/jellyfin.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type jellyfin struct { 13 | service.Service `yaml:",inline"` 14 | jellyfinOptions `yaml:",inline"` 15 | } 16 | 17 | type jellyfinOptions struct { 18 | Config string `yaml:"config"` 19 | Library string `yaml:"library"` 20 | } 21 | 22 | // Return default values for service 23 | func NewJellyfin() manager.Configurer { 24 | http := 8096 25 | udp := 7359 26 | 27 | return &jellyfin{ 28 | Service: service.Service{ 29 | Enabled: false, 30 | ReplicaCount: 1, 31 | Image: service.Image{ 32 | Repository: "ghcr.io/onedr0p/jellyfin", 33 | Tag: "rolling", 34 | PullPolicy: "Always", 35 | }, 36 | Ports: service.Ports{ 37 | Http: &http, 38 | Udp: &udp, 39 | }, 40 | }, 41 | jellyfinOptions: jellyfinOptions{ 42 | Config: "/opt/jellyfin/config", 43 | Library: "/data/library", 44 | }, 45 | } 46 | } 47 | 48 | func (s *jellyfin) Configure() { 49 | s.Enabled = true 50 | inputConfigPath := &survey.Input{ 51 | Message: "Enter the path to your jellyfin config folder:", 52 | Default: s.Config, 53 | } 54 | err := survey.AskOne(inputConfigPath, &s.Config) 55 | if err != nil { 56 | fmt.Println(err.Error()) 57 | } 58 | 59 | inputLibraryPath := &survey.Input{ 60 | Message: "Enter the path to your jellyfin library folder:", 61 | Default: s.Library, 62 | } 63 | 64 | err = survey.AskOne(inputLibraryPath, &s.Library) 65 | if err != nil { 66 | fmt.Println(err.Error()) 67 | } 68 | } 69 | 70 | func (s *jellyfin) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 71 | -------------------------------------------------------------------------------- /models/service/services/kavita.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type kavita struct { 13 | service.Service `yaml:",inline"` 14 | kavitaOptions `yaml:",inline"` 15 | } 16 | 17 | type kavitaOptions struct { 18 | Config string `yaml:"config"` 19 | Library string `yaml:"library"` 20 | } 21 | 22 | // Return default values for service 23 | func NewKavita() manager.Configurer { 24 | http := 5000 25 | 26 | return &kavita{ 27 | Service: service.Service{ 28 | Enabled: false, 29 | ReplicaCount: 1, 30 | Image: service.Image{ 31 | Repository: "kizaing/kavita", 32 | Tag: "latest", 33 | PullPolicy: "Always", 34 | }, 35 | Ports: service.Ports{ 36 | Http: &http, 37 | }, 38 | }, 39 | kavitaOptions: kavitaOptions{ 40 | Config: "/opt/kavita/config", 41 | Library: "/data/library/books", 42 | }, 43 | } 44 | } 45 | 46 | func (s *kavita) Configure() { 47 | s.Enabled = true 48 | inputConfigPath := &survey.Input{ 49 | Message: "Enter the path to your Kavita config folder:", 50 | Default: s.Config, 51 | } 52 | err := survey.AskOne(inputConfigPath, &s.Config) 53 | if err != nil { 54 | fmt.Println(err.Error()) 55 | } 56 | inputLibraryPath := &survey.Input{ 57 | Message: "Enter the path to your Kavita library folder:", 58 | Default: s.Library, 59 | } 60 | err = survey.AskOne(inputLibraryPath, &s.Library) 61 | if err != nil { 62 | fmt.Println(err.Error()) 63 | } 64 | } 65 | 66 | func (s *kavita) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 67 | -------------------------------------------------------------------------------- /models/service/services/nginx.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type nginx struct { 13 | service.Service `yaml:",inline"` 14 | nginxOptions `yaml:",inline"` 15 | } 16 | 17 | type nginxOptions struct { 18 | PublicPath string `yaml:"public_path"` 19 | Domain string `yaml:"domain"` 20 | } 21 | 22 | // Return default values for service 23 | func NewNginx() manager.Configurer { 24 | http := 80 25 | https := 443 26 | 27 | return &nginx{ 28 | Service: service.Service{ 29 | Enabled: false, 30 | ReplicaCount: 1, 31 | Image: service.Image{ 32 | Repository: "nginx", 33 | Tag: "latest", 34 | PullPolicy: "Always", 35 | }, 36 | Ports: service.Ports{ 37 | Http: &http, 38 | Https: &https, 39 | }, 40 | }, 41 | nginxOptions: nginxOptions{ 42 | PublicPath: "/opt/nginx/public", 43 | Domain: "example.com", 44 | }, 45 | } 46 | } 47 | 48 | func (s *nginx) Configure() { 49 | s.Enabled = true 50 | inputPublicPath := &survey.Input{ 51 | Message: "Enter the path to your Nginx public folder:", 52 | Default: s.PublicPath, 53 | } 54 | err := survey.AskOne(inputPublicPath, &s.PublicPath) 55 | if err != nil { 56 | fmt.Println(err.Error()) 57 | } 58 | 59 | inputDomain := &survey.Input{ 60 | Message: "Enter the domain you want to use:", 61 | Default: s.Domain, 62 | } 63 | 64 | err = survey.AskOne(inputDomain, &s.Domain) 65 | if err != nil { 66 | fmt.Println(err.Error()) 67 | } 68 | } 69 | 70 | func (s *nginx) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 71 | -------------------------------------------------------------------------------- /models/service/services/overseerr.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type overseerr struct { 13 | service.Service `yaml:",inline"` 14 | overseerrOptions `yaml:",inline"` 15 | } 16 | 17 | type overseerrOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewOverseerr() manager.Configurer { 23 | http := 5055 24 | 25 | return &overseerr{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "lscr.io/linuxserver/overseerr", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | overseerrOptions: overseerrOptions{ 39 | Config: "/opt/overseerr/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *overseerr) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your overseerr config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *overseerr) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/playwright.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type playwright struct { 13 | service.Service `yaml:",inline"` 14 | playwrightOptions `yaml:",inline"` 15 | } 16 | 17 | type playwrightOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewPlaywright() manager.Configurer { 23 | http := 3000 24 | 25 | return &playwright{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "browserless/chrome", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | playwrightOptions: playwrightOptions{ 39 | Config: "/opt/playwright/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *playwright) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your Playwright config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *playwright) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/plex.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type plex struct { 13 | service.Service `yaml:",inline"` 14 | plexOptions `yaml:",inline"` 15 | } 16 | 17 | type plexOptions struct { 18 | Config string `yaml:"config"` 19 | Library string `yaml:"library"` 20 | } 21 | 22 | // Return default values for service 23 | func NewPlex() manager.Configurer { 24 | http := 32400 25 | 26 | return &plex{ 27 | Service: service.Service{ 28 | Enabled: false, 29 | ReplicaCount: 1, 30 | Image: service.Image{ 31 | Repository: "lscr.io/linuxserver/plex", 32 | Tag: "latest", 33 | PullPolicy: "Always", 34 | }, 35 | Ports: service.Ports{ 36 | Http: &http, 37 | }, 38 | }, 39 | plexOptions: plexOptions{ 40 | Config: "/opt/plex/config", 41 | Library: "/data/library", 42 | }, 43 | } 44 | } 45 | 46 | func (s *plex) Configure() { 47 | s.Enabled = true 48 | inputConfigPath := &survey.Input{ 49 | Message: "Enter the path to your plex config folder:", 50 | Default: s.Config, 51 | } 52 | err := survey.AskOne(inputConfigPath, &s.Config) 53 | if err != nil { 54 | fmt.Println(err.Error()) 55 | } 56 | 57 | inputLibraryPath := &survey.Input{ 58 | Message: "Enter the path to your plex library folder:", 59 | Default: s.Library, 60 | } 61 | err = survey.AskOne(inputLibraryPath, &s.Library) 62 | if err != nil { 63 | fmt.Println(err.Error()) 64 | } 65 | } 66 | 67 | func (s *plex) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 68 | -------------------------------------------------------------------------------- /models/service/services/prowlarr.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type prowlarr struct { 13 | service.Service `yaml:",inline"` 14 | prowlarrOptions `yaml:",inline"` 15 | } 16 | 17 | type prowlarrOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewProwlarr() manager.Configurer { 23 | http := 9696 24 | 25 | return &prowlarr{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "lscr.io/linuxserver/prowlarr", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | prowlarrOptions: prowlarrOptions{ 39 | Config: "/opt/prowlarr/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *prowlarr) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your prowlarr config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *prowlarr) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/qbittorrent.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type qbittorrent struct { 13 | service.Service `yaml:",inline"` 14 | qbittorrentOptions `yaml:",inline"` 15 | } 16 | 17 | type qbittorrentOptions struct { 18 | Config string `yaml:"config"` 19 | Data string `yaml:"data"` 20 | } 21 | 22 | // Return default values for service 23 | func NewQbittorrent() manager.Configurer { 24 | http := 8080 25 | p2p := 6881 26 | p2pudp := 6881 27 | 28 | // Create a new Qbittorrent instance 29 | return &qbittorrent{ 30 | Service: service.Service{ 31 | Enabled: false, 32 | ReplicaCount: 1, 33 | Image: service.Image{ 34 | Repository: "lscr.io/linuxserver/qbittorrent", 35 | Tag: "latest", 36 | PullPolicy: "Always", 37 | }, 38 | Ports: service.Ports{ 39 | Http: &http, 40 | P2p: &p2p, 41 | P2pudp: &p2pudp, 42 | }, 43 | }, 44 | qbittorrentOptions: qbittorrentOptions{ 45 | Config: "/opt/qbittorrent/config", 46 | Data: "/data/torrents", 47 | }, 48 | } 49 | } 50 | 51 | func (s *qbittorrent) Configure() { 52 | s.Enabled = true 53 | inputConfigPath := &survey.Input{ 54 | Message: "Enter the path to your qbittorrent config folder:", 55 | Default: s.Config, 56 | } 57 | err := survey.AskOne(inputConfigPath, &s.Config) 58 | if err != nil { 59 | fmt.Println(err.Error()) 60 | } 61 | 62 | inputDataPath := &survey.Input{ 63 | Message: "Enter the path to your torrent data folder:", 64 | Default: s.Data, 65 | } 66 | err = survey.AskOne(inputDataPath, &s.Data) 67 | if err != nil { 68 | fmt.Println(err.Error()) 69 | } 70 | } 71 | 72 | func (s *qbittorrent) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 73 | -------------------------------------------------------------------------------- /models/service/services/radarr.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type radarr struct { 13 | service.Service `yaml:",inline"` 14 | radarrOptions `yaml:",inline"` 15 | } 16 | 17 | type radarrOptions struct { 18 | Config string `yaml:"config"` 19 | Data string `yaml:"data"` 20 | } 21 | 22 | // Return default values for service 23 | func NewRadarr() manager.Configurer { 24 | http := 7878 25 | 26 | return &radarr{ 27 | Service: service.Service{ 28 | Enabled: false, 29 | ReplicaCount: 1, 30 | Image: service.Image{ 31 | Repository: "lscr.io/linuxserver/radarr", 32 | Tag: "latest", 33 | PullPolicy: "Always", 34 | }, 35 | Ports: service.Ports{ 36 | Http: &http, 37 | }, 38 | }, 39 | radarrOptions: radarrOptions{ 40 | Config: "/opt/radarr/config", 41 | Data: "/data", 42 | }, 43 | } 44 | } 45 | 46 | func (s *radarr) Configure() { 47 | s.Enabled = true 48 | inputConfigPath := &survey.Input{ 49 | Message: "Enter the path to your radarr config folder:", 50 | Default: s.Config, 51 | } 52 | err := survey.AskOne(inputConfigPath, &s.Config) 53 | if err != nil { 54 | fmt.Println(err.Error()) 55 | } 56 | 57 | inputDataPath := &survey.Input{ 58 | Message: "Enter the path to your top level media folder:", 59 | Default: s.Data, 60 | } 61 | err = survey.AskOne(inputDataPath, &s.Data) 62 | if err != nil { 63 | fmt.Println(err.Error()) 64 | } 65 | } 66 | 67 | func (s *radarr) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 68 | -------------------------------------------------------------------------------- /models/service/services/sabnzbd.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type sabnzbd struct { 13 | service.Service `yaml:",inline"` 14 | sabnzbdOptions `yaml:",inline"` 15 | } 16 | 17 | type sabnzbdOptions struct { 18 | Config string `yaml:"config"` 19 | Data string `yaml:"data"` 20 | } 21 | 22 | // Return default values for service 23 | func NewSabnzbd() manager.Configurer { 24 | http := 8080 25 | 26 | // Create a new Sabnzbd instance 27 | return &sabnzbd{ 28 | Service: service.Service{ 29 | Enabled: false, 30 | ReplicaCount: 1, 31 | Image: service.Image{ 32 | Repository: "lscr.io/linuxserver/sabnzbd", 33 | Tag: "latest", 34 | PullPolicy: "Always", 35 | }, 36 | Ports: service.Ports{ 37 | Http: &http, 38 | }, 39 | }, 40 | sabnzbdOptions: sabnzbdOptions{ 41 | Config: "/opt/sabnzbd/config", 42 | Data: "/data/usenet", 43 | }, 44 | } 45 | } 46 | 47 | func (s *sabnzbd) Configure() { 48 | s.Enabled = true 49 | inputConfigPath := &survey.Input{ 50 | Message: "Enter the path to your sabnzbd config folder:", 51 | Default: s.Config, 52 | } 53 | err := survey.AskOne(inputConfigPath, &s.Config) 54 | if err != nil { 55 | fmt.Println(err.Error()) 56 | } 57 | 58 | inputDataPath := &survey.Input{ 59 | Message: "Enter the path to your usenet data folder:", 60 | Default: s.Data, 61 | } 62 | err = survey.AskOne(inputDataPath, &s.Data) 63 | if err != nil { 64 | fmt.Println(err.Error()) 65 | } 66 | } 67 | 68 | func (s *sabnzbd) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 69 | -------------------------------------------------------------------------------- /models/service/services/sonarr.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type sonarr struct { 13 | service.Service `yaml:",inline"` 14 | sonarrOptions `yaml:",inline"` 15 | } 16 | 17 | type sonarrOptions struct { 18 | Config string `yaml:"config"` 19 | Data string `yaml:"data"` 20 | } 21 | 22 | // Return default values for service 23 | func NewSonarr() manager.Configurer { 24 | http := 8989 25 | 26 | return &sonarr{ 27 | Service: service.Service{ 28 | Enabled: false, 29 | ReplicaCount: 1, 30 | Image: service.Image{ 31 | Repository: "lscr.io/linuxserver/sonarr", 32 | Tag: "latest", 33 | PullPolicy: "Always", 34 | }, 35 | Ports: service.Ports{ 36 | Http: &http, 37 | }, 38 | }, 39 | sonarrOptions: sonarrOptions{ 40 | Config: "/opt/sonarr/config", 41 | Data: "/data", 42 | }, 43 | } 44 | } 45 | 46 | func (s *sonarr) Configure() { 47 | s.Enabled = true 48 | inputConfigPath := &survey.Input{ 49 | Message: "Enter the path to your Sonarr config folder:", 50 | Default: s.Config, 51 | } 52 | err := survey.AskOne(inputConfigPath, &s.Config) 53 | if err != nil { 54 | fmt.Println(err.Error()) 55 | } 56 | 57 | inputDataPath := &survey.Input{ 58 | Message: "Enter the path to your top level media folder:", 59 | Default: s.Data, 60 | } 61 | err = survey.AskOne(inputDataPath, &s.Data) 62 | if err != nil { 63 | fmt.Println(err.Error()) 64 | } 65 | } 66 | 67 | func (s *sonarr) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 68 | -------------------------------------------------------------------------------- /models/service/services/tautulli.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type tautulli struct { 13 | service.Service `yaml:",inline"` 14 | tautulliOptions `yaml:",inline"` 15 | } 16 | 17 | type tautulliOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewTautulli() manager.Configurer { 23 | http := 8181 24 | 25 | return &tautulli{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "lscr.io/linuxserver/tautulli", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | tautulliOptions: tautulliOptions{ 39 | Config: "/opt/tautulli/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *tautulli) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your Tautulli config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *tautulli) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | -------------------------------------------------------------------------------- /models/service/services/thelounge.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/AlecAivazis/survey/v2" 7 | "github.com/techsquidtv/uhs-cli/models/config/manager" 8 | "github.com/techsquidtv/uhs-cli/models/service" 9 | "gopkg.in/yaml.v3" 10 | ) 11 | 12 | type thelounge struct { 13 | service.Service `yaml:",inline"` 14 | theloungeOptions `yaml:",inline"` 15 | } 16 | 17 | type theloungeOptions struct { 18 | Config string `yaml:"config"` 19 | } 20 | 21 | // Return default values for service 22 | func NewThelounge() manager.Configurer { 23 | http := 9000 24 | 25 | return &thelounge{ 26 | Service: service.Service{ 27 | Enabled: false, 28 | ReplicaCount: 1, 29 | Image: service.Image{ 30 | Repository: "lscr.io/linuxserver/thelounge", 31 | Tag: "latest", 32 | PullPolicy: "Always", 33 | }, 34 | Ports: service.Ports{ 35 | Http: &http, 36 | }, 37 | }, 38 | theloungeOptions: theloungeOptions{ 39 | Config: "/opt/thelounge/config", 40 | }, 41 | } 42 | } 43 | 44 | func (s *thelounge) Configure() { 45 | s.Enabled = true 46 | inputConfigPath := &survey.Input{ 47 | Message: "Enter the path to your Thelounge config folder:", 48 | Default: s.Config, 49 | } 50 | err := survey.AskOne(inputConfigPath, &s.Config) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | } 54 | } 55 | 56 | func (s *thelounge) Fill(data []byte) error { return yaml.Unmarshal(data, s) } 57 | --------------------------------------------------------------------------------