mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-06 02:36:38 +00:00
Reorging functions
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# To see all available options, please visit the docs:
|
# To see all available options, please visit the docs:
|
||||||
# https://docs.digpangolin.com/self-host/dns-and-networking
|
# https://docs.digpangolin.com/self-host/advanced/config-file
|
||||||
|
|
||||||
app:
|
app:
|
||||||
dashboard_url: "https://{{.DashboardDomain}}"
|
dashboard_url: "https://{{.DashboardDomain}}"
|
||||||
@@ -36,3 +36,9 @@ flags:
|
|||||||
disable_signup_without_invite: true
|
disable_signup_without_invite: true
|
||||||
disable_user_create_org: false
|
disable_user_create_org: false
|
||||||
allow_raw_resources: true
|
allow_raw_resources: true
|
||||||
|
|
||||||
|
{{if and .HybridMode .HybridId .HybridSecret}}
|
||||||
|
hybrid:
|
||||||
|
id: "{{.HybridId}}"
|
||||||
|
secret: "{{.HybridSecret}}"
|
||||||
|
{{end}}
|
||||||
332
install/containers.go
Normal file
332
install/containers.go
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitForContainer(containerName string, containerType SupportedContainer) error {
|
||||||
|
maxAttempts := 30
|
||||||
|
retryInterval := time.Second * 2
|
||||||
|
|
||||||
|
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||||
|
// Check if container is running
|
||||||
|
cmd := exec.Command(string(containerType), "container", "inspect", "-f", "{{.State.Running}}", containerName)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// If the container doesn't exist or there's another error, wait and retry
|
||||||
|
time.Sleep(retryInterval)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning := strings.TrimSpace(out.String()) == "true"
|
||||||
|
if isRunning {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container exists but isn't running yet, wait and retry
|
||||||
|
time.Sleep(retryInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("container %s did not start within %v seconds", containerName, maxAttempts*int(retryInterval.Seconds()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func installDocker() error {
|
||||||
|
// Detect Linux distribution
|
||||||
|
cmd := exec.Command("cat", "/etc/os-release")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to detect Linux distribution: %v", err)
|
||||||
|
}
|
||||||
|
osRelease := string(output)
|
||||||
|
|
||||||
|
// Detect system architecture
|
||||||
|
archCmd := exec.Command("uname", "-m")
|
||||||
|
archOutput, err := archCmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to detect system architecture: %v", err)
|
||||||
|
}
|
||||||
|
arch := strings.TrimSpace(string(archOutput))
|
||||||
|
|
||||||
|
// Map architecture to Docker's architecture naming
|
||||||
|
var dockerArch string
|
||||||
|
switch arch {
|
||||||
|
case "x86_64":
|
||||||
|
dockerArch = "amd64"
|
||||||
|
case "aarch64":
|
||||||
|
dockerArch = "arm64"
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported architecture: %s", arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
var installCmd *exec.Cmd
|
||||||
|
switch {
|
||||||
|
case strings.Contains(osRelease, "ID=ubuntu"):
|
||||||
|
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
|
||||||
|
apt-get update &&
|
||||||
|
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
|
||||||
|
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
|
||||||
|
apt-get update &&
|
||||||
|
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
`, dockerArch))
|
||||||
|
case strings.Contains(osRelease, "ID=debian"):
|
||||||
|
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
|
||||||
|
apt-get update &&
|
||||||
|
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
|
||||||
|
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
|
||||||
|
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
|
||||||
|
apt-get update &&
|
||||||
|
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
`, dockerArch))
|
||||||
|
case strings.Contains(osRelease, "ID=fedora"):
|
||||||
|
// Detect Fedora version to handle DNF 5 changes
|
||||||
|
versionCmd := exec.Command("bash", "-c", "grep VERSION_ID /etc/os-release | cut -d'=' -f2 | tr -d '\"'")
|
||||||
|
versionOutput, err := versionCmd.Output()
|
||||||
|
var fedoraVersion int
|
||||||
|
if err == nil {
|
||||||
|
if v, parseErr := strconv.Atoi(strings.TrimSpace(string(versionOutput))); parseErr == nil {
|
||||||
|
fedoraVersion = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use appropriate DNF syntax based on version
|
||||||
|
var repoCmd string
|
||||||
|
if fedoraVersion >= 41 {
|
||||||
|
// DNF 5 syntax for Fedora 41+
|
||||||
|
repoCmd = "dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo"
|
||||||
|
} else {
|
||||||
|
// DNF 4 syntax for Fedora < 41
|
||||||
|
repoCmd = "dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
|
||||||
|
dnf -y install dnf-plugins-core &&
|
||||||
|
%s &&
|
||||||
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
`, repoCmd))
|
||||||
|
case strings.Contains(osRelease, "ID=opensuse") || strings.Contains(osRelease, "ID=\"opensuse-"):
|
||||||
|
installCmd = exec.Command("bash", "-c", `
|
||||||
|
zypper install -y docker docker-compose &&
|
||||||
|
systemctl enable docker
|
||||||
|
`)
|
||||||
|
case strings.Contains(osRelease, "ID=rhel") || strings.Contains(osRelease, "ID=\"rhel"):
|
||||||
|
installCmd = exec.Command("bash", "-c", `
|
||||||
|
dnf remove -y runc &&
|
||||||
|
dnf -y install yum-utils &&
|
||||||
|
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo &&
|
||||||
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin &&
|
||||||
|
systemctl enable docker
|
||||||
|
`)
|
||||||
|
case strings.Contains(osRelease, "ID=amzn"):
|
||||||
|
installCmd = exec.Command("bash", "-c", `
|
||||||
|
yum update -y &&
|
||||||
|
yum install -y docker &&
|
||||||
|
systemctl enable docker &&
|
||||||
|
usermod -a -G docker ec2-user
|
||||||
|
`)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported Linux distribution")
|
||||||
|
}
|
||||||
|
|
||||||
|
installCmd.Stdout = os.Stdout
|
||||||
|
installCmd.Stderr = os.Stderr
|
||||||
|
return installCmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func startDockerService() error {
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
cmd := exec.Command("systemctl", "enable", "--now", "docker")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
} else if runtime.GOOS == "darwin" {
|
||||||
|
// On macOS, Docker is usually started via the Docker Desktop application
|
||||||
|
fmt.Println("Please start Docker Desktop manually on macOS.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("unsupported operating system for starting Docker service")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDockerInstalled() bool {
|
||||||
|
return isContainerInstalled("docker")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPodmanInstalled() bool {
|
||||||
|
return isContainerInstalled("podman") && isContainerInstalled("podman-compose")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isContainerInstalled(container string) bool {
|
||||||
|
cmd := exec.Command(container, "--version")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUserInDockerGroup() bool {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
// Docker group is not applicable on macOS
|
||||||
|
// So we assume that the user can run Docker commands
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Geteuid() == 0 {
|
||||||
|
return true // Root user can run Docker commands anyway
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current user is in the docker group
|
||||||
|
if dockerGroup, err := user.LookupGroup("docker"); err == nil {
|
||||||
|
if currentUser, err := user.Current(); err == nil {
|
||||||
|
if currentUserGroupIds, err := currentUser.GroupIds(); err == nil {
|
||||||
|
for _, groupId := range currentUserGroupIds {
|
||||||
|
if groupId == dockerGroup.Gid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eventually, if any of the checks fail, we assume the user cannot run Docker commands
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDockerRunning checks if the Docker daemon is running by using the `docker info` command.
|
||||||
|
func isDockerRunning() bool {
|
||||||
|
cmd := exec.Command("docker", "info")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeDockerComposeCommandWithArgs executes the appropriate docker command with arguments supplied
|
||||||
|
func executeDockerComposeCommandWithArgs(args ...string) error {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
var useNewStyle bool
|
||||||
|
|
||||||
|
if !isDockerInstalled() {
|
||||||
|
return fmt.Errorf("docker is not installed")
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCmd := exec.Command("docker", "compose", "version")
|
||||||
|
if err := checkCmd.Run(); err == nil {
|
||||||
|
useNewStyle = true
|
||||||
|
} else {
|
||||||
|
checkCmd = exec.Command("docker-compose", "version")
|
||||||
|
if err := checkCmd.Run(); err == nil {
|
||||||
|
useNewStyle = false
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if useNewStyle {
|
||||||
|
cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("docker-compose", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pullContainers pulls the containers using the appropriate command.
|
||||||
|
func pullContainers(containerType SupportedContainer) error {
|
||||||
|
fmt.Println("Pulling the container images...")
|
||||||
|
if containerType == Podman {
|
||||||
|
if err := run("podman-compose", "-f", "docker-compose.yml", "pull"); err != nil {
|
||||||
|
return fmt.Errorf("failed to pull the containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerType == Docker {
|
||||||
|
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "pull", "--policy", "always"); err != nil {
|
||||||
|
return fmt.Errorf("failed to pull the containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// startContainers starts the containers using the appropriate command.
|
||||||
|
func startContainers(containerType SupportedContainer) error {
|
||||||
|
fmt.Println("Starting containers...")
|
||||||
|
|
||||||
|
if containerType == Podman {
|
||||||
|
if err := run("podman-compose", "-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
|
||||||
|
return fmt.Errorf("failed start containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerType == Docker {
|
||||||
|
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
|
||||||
|
return fmt.Errorf("failed to start containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopContainers stops the containers using the appropriate command.
|
||||||
|
func stopContainers(containerType SupportedContainer) error {
|
||||||
|
fmt.Println("Stopping containers...")
|
||||||
|
if containerType == Podman {
|
||||||
|
if err := run("podman-compose", "-f", "docker-compose.yml", "down"); err != nil {
|
||||||
|
return fmt.Errorf("failed to stop containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerType == Docker {
|
||||||
|
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "down"); err != nil {
|
||||||
|
return fmt.Errorf("failed to stop containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// restartContainer restarts a specific container using the appropriate command.
|
||||||
|
func restartContainer(container string, containerType SupportedContainer) error {
|
||||||
|
fmt.Println("Restarting containers...")
|
||||||
|
if containerType == Podman {
|
||||||
|
if err := run("podman-compose", "-f", "docker-compose.yml", "restart"); err != nil {
|
||||||
|
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerType == Docker {
|
||||||
|
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "restart", container); err != nil {
|
||||||
|
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||||
|
}
|
||||||
74
install/input.go
Normal file
74
install/input.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readString(reader *bufio.Reader, prompt string, defaultValue string) string {
|
||||||
|
if defaultValue != "" {
|
||||||
|
fmt.Printf("%s (default: %s): ", prompt, defaultValue)
|
||||||
|
} else {
|
||||||
|
fmt.Print(prompt + ": ")
|
||||||
|
}
|
||||||
|
input, _ := reader.ReadString('\n')
|
||||||
|
input = strings.TrimSpace(input)
|
||||||
|
if input == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStringNoDefault(reader *bufio.Reader, prompt string) string {
|
||||||
|
fmt.Print(prompt + ": ")
|
||||||
|
input, _ := reader.ReadString('\n')
|
||||||
|
return strings.TrimSpace(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPassword(prompt string, reader *bufio.Reader) string {
|
||||||
|
if term.IsTerminal(int(syscall.Stdin)) {
|
||||||
|
fmt.Print(prompt + ": ")
|
||||||
|
// Read password without echo if we're in a terminal
|
||||||
|
password, err := term.ReadPassword(int(syscall.Stdin))
|
||||||
|
fmt.Println() // Add a newline since ReadPassword doesn't add one
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
input := strings.TrimSpace(string(password))
|
||||||
|
if input == "" {
|
||||||
|
return readPassword(prompt, reader)
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
} else {
|
||||||
|
// Fallback to reading from stdin if not in a terminal
|
||||||
|
return readString(reader, prompt, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBool(reader *bufio.Reader, prompt string, defaultValue bool) bool {
|
||||||
|
defaultStr := "no"
|
||||||
|
if defaultValue {
|
||||||
|
defaultStr = "yes"
|
||||||
|
}
|
||||||
|
input := readString(reader, prompt+" (yes/no)", defaultStr)
|
||||||
|
return strings.ToLower(input) == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBoolNoDefault(reader *bufio.Reader, prompt string) bool {
|
||||||
|
input := readStringNoDefault(reader, prompt+" (yes/no)")
|
||||||
|
return strings.ToLower(input) == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInt(reader *bufio.Reader, prompt string, defaultValue int) int {
|
||||||
|
input := readString(reader, prompt, fmt.Sprintf("%d", defaultValue))
|
||||||
|
if input == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
value := defaultValue
|
||||||
|
fmt.Sscanf(input, "%d", &value)
|
||||||
|
return value
|
||||||
|
}
|
||||||
402
install/main.go
402
install/main.go
@@ -10,17 +10,12 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD
|
// DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD
|
||||||
@@ -52,7 +47,9 @@ type Config struct {
|
|||||||
TraefikBouncerKey string
|
TraefikBouncerKey string
|
||||||
DoCrowdsecInstall bool
|
DoCrowdsecInstall bool
|
||||||
Secret string
|
Secret string
|
||||||
HybridMode bool
|
HybridMode bool
|
||||||
|
HybridId string
|
||||||
|
HybridSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SupportedContainer string
|
type SupportedContainer string
|
||||||
@@ -308,70 +305,6 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
return chosenContainer
|
return chosenContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
func readString(reader *bufio.Reader, prompt string, defaultValue string) string {
|
|
||||||
if defaultValue != "" {
|
|
||||||
fmt.Printf("%s (default: %s): ", prompt, defaultValue)
|
|
||||||
} else {
|
|
||||||
fmt.Print(prompt + ": ")
|
|
||||||
}
|
|
||||||
input, _ := reader.ReadString('\n')
|
|
||||||
input = strings.TrimSpace(input)
|
|
||||||
if input == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
func readStringNoDefault(reader *bufio.Reader, prompt string) string {
|
|
||||||
fmt.Print(prompt + ": ")
|
|
||||||
input, _ := reader.ReadString('\n')
|
|
||||||
return strings.TrimSpace(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readPassword(prompt string, reader *bufio.Reader) string {
|
|
||||||
if term.IsTerminal(int(syscall.Stdin)) {
|
|
||||||
fmt.Print(prompt + ": ")
|
|
||||||
// Read password without echo if we're in a terminal
|
|
||||||
password, err := term.ReadPassword(int(syscall.Stdin))
|
|
||||||
fmt.Println() // Add a newline since ReadPassword doesn't add one
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
input := strings.TrimSpace(string(password))
|
|
||||||
if input == "" {
|
|
||||||
return readPassword(prompt, reader)
|
|
||||||
}
|
|
||||||
return input
|
|
||||||
} else {
|
|
||||||
// Fallback to reading from stdin if not in a terminal
|
|
||||||
return readString(reader, prompt, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readBool(reader *bufio.Reader, prompt string, defaultValue bool) bool {
|
|
||||||
defaultStr := "no"
|
|
||||||
if defaultValue {
|
|
||||||
defaultStr = "yes"
|
|
||||||
}
|
|
||||||
input := readString(reader, prompt+" (yes/no)", defaultStr)
|
|
||||||
return strings.ToLower(input) == "yes"
|
|
||||||
}
|
|
||||||
|
|
||||||
func readBoolNoDefault(reader *bufio.Reader, prompt string) bool {
|
|
||||||
input := readStringNoDefault(reader, prompt+" (yes/no)")
|
|
||||||
return strings.ToLower(input) == "yes"
|
|
||||||
}
|
|
||||||
|
|
||||||
func readInt(reader *bufio.Reader, prompt string, defaultValue int) int {
|
|
||||||
input := readString(reader, prompt, fmt.Sprintf("%d", defaultValue))
|
|
||||||
if input == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
value := defaultValue
|
|
||||||
fmt.Sscanf(input, "%d", &value)
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectUserInput(reader *bufio.Reader) Config {
|
func collectUserInput(reader *bufio.Reader) Config {
|
||||||
config := Config{}
|
config := Config{}
|
||||||
|
|
||||||
@@ -379,6 +312,15 @@ func collectUserInput(reader *bufio.Reader) Config {
|
|||||||
fmt.Println("\n=== Basic Configuration ===")
|
fmt.Println("\n=== Basic Configuration ===")
|
||||||
config.HybridMode = readBoolNoDefault(reader, "Do you want to use hybrid mode?")
|
config.HybridMode = readBoolNoDefault(reader, "Do you want to use hybrid mode?")
|
||||||
|
|
||||||
|
if config.HybridMode {
|
||||||
|
alreadyHaveCreds := readBool(reader, "Do you already have credentials from the dashboard?", false)
|
||||||
|
|
||||||
|
if alreadyHaveCreds {
|
||||||
|
config.HybridId = readString(reader, "Enter your hybrid ID", "")
|
||||||
|
config.HybridSecret = readString(reader, "Enter your hybrid secret", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !config.HybridMode {
|
if !config.HybridMode {
|
||||||
config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
|
config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
|
||||||
|
|
||||||
@@ -501,298 +443,6 @@ func createConfigFiles(config Config) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func installDocker() error {
|
|
||||||
// Detect Linux distribution
|
|
||||||
cmd := exec.Command("cat", "/etc/os-release")
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to detect Linux distribution: %v", err)
|
|
||||||
}
|
|
||||||
osRelease := string(output)
|
|
||||||
|
|
||||||
// Detect system architecture
|
|
||||||
archCmd := exec.Command("uname", "-m")
|
|
||||||
archOutput, err := archCmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to detect system architecture: %v", err)
|
|
||||||
}
|
|
||||||
arch := strings.TrimSpace(string(archOutput))
|
|
||||||
|
|
||||||
// Map architecture to Docker's architecture naming
|
|
||||||
var dockerArch string
|
|
||||||
switch arch {
|
|
||||||
case "x86_64":
|
|
||||||
dockerArch = "amd64"
|
|
||||||
case "aarch64":
|
|
||||||
dockerArch = "arm64"
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported architecture: %s", arch)
|
|
||||||
}
|
|
||||||
|
|
||||||
var installCmd *exec.Cmd
|
|
||||||
switch {
|
|
||||||
case strings.Contains(osRelease, "ID=ubuntu"):
|
|
||||||
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
|
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
|
|
||||||
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
|
||||||
`, dockerArch))
|
|
||||||
case strings.Contains(osRelease, "ID=debian"):
|
|
||||||
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common &&
|
|
||||||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg &&
|
|
||||||
echo "deb [arch=%s signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list &&
|
|
||||||
apt-get update &&
|
|
||||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
|
||||||
`, dockerArch))
|
|
||||||
case strings.Contains(osRelease, "ID=fedora"):
|
|
||||||
// Detect Fedora version to handle DNF 5 changes
|
|
||||||
versionCmd := exec.Command("bash", "-c", "grep VERSION_ID /etc/os-release | cut -d'=' -f2 | tr -d '\"'")
|
|
||||||
versionOutput, err := versionCmd.Output()
|
|
||||||
var fedoraVersion int
|
|
||||||
if err == nil {
|
|
||||||
if v, parseErr := strconv.Atoi(strings.TrimSpace(string(versionOutput))); parseErr == nil {
|
|
||||||
fedoraVersion = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use appropriate DNF syntax based on version
|
|
||||||
var repoCmd string
|
|
||||||
if fedoraVersion >= 41 {
|
|
||||||
// DNF 5 syntax for Fedora 41+
|
|
||||||
repoCmd = "dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo"
|
|
||||||
} else {
|
|
||||||
// DNF 4 syntax for Fedora < 41
|
|
||||||
repoCmd = "dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo"
|
|
||||||
}
|
|
||||||
|
|
||||||
installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
|
|
||||||
dnf -y install dnf-plugins-core &&
|
|
||||||
%s &&
|
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
|
||||||
`, repoCmd))
|
|
||||||
case strings.Contains(osRelease, "ID=opensuse") || strings.Contains(osRelease, "ID=\"opensuse-"):
|
|
||||||
installCmd = exec.Command("bash", "-c", `
|
|
||||||
zypper install -y docker docker-compose &&
|
|
||||||
systemctl enable docker
|
|
||||||
`)
|
|
||||||
case strings.Contains(osRelease, "ID=rhel") || strings.Contains(osRelease, "ID=\"rhel"):
|
|
||||||
installCmd = exec.Command("bash", "-c", `
|
|
||||||
dnf remove -y runc &&
|
|
||||||
dnf -y install yum-utils &&
|
|
||||||
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo &&
|
|
||||||
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin &&
|
|
||||||
systemctl enable docker
|
|
||||||
`)
|
|
||||||
case strings.Contains(osRelease, "ID=amzn"):
|
|
||||||
installCmd = exec.Command("bash", "-c", `
|
|
||||||
yum update -y &&
|
|
||||||
yum install -y docker &&
|
|
||||||
systemctl enable docker &&
|
|
||||||
usermod -a -G docker ec2-user
|
|
||||||
`)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported Linux distribution")
|
|
||||||
}
|
|
||||||
|
|
||||||
installCmd.Stdout = os.Stdout
|
|
||||||
installCmd.Stderr = os.Stderr
|
|
||||||
return installCmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func startDockerService() error {
|
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
cmd := exec.Command("systemctl", "enable", "--now", "docker")
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
} else if runtime.GOOS == "darwin" {
|
|
||||||
// On macOS, Docker is usually started via the Docker Desktop application
|
|
||||||
fmt.Println("Please start Docker Desktop manually on macOS.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unsupported operating system for starting Docker service")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDockerInstalled() bool {
|
|
||||||
return isContainerInstalled("docker")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPodmanInstalled() bool {
|
|
||||||
return isContainerInstalled("podman") && isContainerInstalled("podman-compose")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isContainerInstalled(container string) bool {
|
|
||||||
cmd := exec.Command(container, "--version")
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func isUserInDockerGroup() bool {
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
// Docker group is not applicable on macOS
|
|
||||||
// So we assume that the user can run Docker commands
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Geteuid() == 0 {
|
|
||||||
return true // Root user can run Docker commands anyway
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the current user is in the docker group
|
|
||||||
if dockerGroup, err := user.LookupGroup("docker"); err == nil {
|
|
||||||
if currentUser, err := user.Current(); err == nil {
|
|
||||||
if currentUserGroupIds, err := currentUser.GroupIds(); err == nil {
|
|
||||||
for _, groupId := range currentUserGroupIds {
|
|
||||||
if groupId == dockerGroup.Gid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eventually, if any of the checks fail, we assume the user cannot run Docker commands
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// isDockerRunning checks if the Docker daemon is running by using the `docker info` command.
|
|
||||||
func isDockerRunning() bool {
|
|
||||||
cmd := exec.Command("docker", "info")
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// executeDockerComposeCommandWithArgs executes the appropriate docker command with arguments supplied
|
|
||||||
func executeDockerComposeCommandWithArgs(args ...string) error {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
var useNewStyle bool
|
|
||||||
|
|
||||||
if !isDockerInstalled() {
|
|
||||||
return fmt.Errorf("docker is not installed")
|
|
||||||
}
|
|
||||||
|
|
||||||
checkCmd := exec.Command("docker", "compose", "version")
|
|
||||||
if err := checkCmd.Run(); err == nil {
|
|
||||||
useNewStyle = true
|
|
||||||
} else {
|
|
||||||
checkCmd = exec.Command("docker-compose", "version")
|
|
||||||
if err := checkCmd.Run(); err == nil {
|
|
||||||
useNewStyle = false
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if useNewStyle {
|
|
||||||
cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("docker-compose", args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// pullContainers pulls the containers using the appropriate command.
|
|
||||||
func pullContainers(containerType SupportedContainer) error {
|
|
||||||
fmt.Println("Pulling the container images...")
|
|
||||||
if containerType == Podman {
|
|
||||||
if err := run("podman-compose", "-f", "docker-compose.yml", "pull"); err != nil {
|
|
||||||
return fmt.Errorf("failed to pull the containers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if containerType == Docker {
|
|
||||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "pull", "--policy", "always"); err != nil {
|
|
||||||
return fmt.Errorf("failed to pull the containers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// startContainers starts the containers using the appropriate command.
|
|
||||||
func startContainers(containerType SupportedContainer) error {
|
|
||||||
fmt.Println("Starting containers...")
|
|
||||||
|
|
||||||
if containerType == Podman {
|
|
||||||
if err := run("podman-compose", "-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
|
|
||||||
return fmt.Errorf("failed start containers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if containerType == Docker {
|
|
||||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
|
|
||||||
return fmt.Errorf("failed to start containers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopContainers stops the containers using the appropriate command.
|
|
||||||
func stopContainers(containerType SupportedContainer) error {
|
|
||||||
fmt.Println("Stopping containers...")
|
|
||||||
if containerType == Podman {
|
|
||||||
if err := run("podman-compose", "-f", "docker-compose.yml", "down"); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop containers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if containerType == Docker {
|
|
||||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "down"); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop containers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// restartContainer restarts a specific container using the appropriate command.
|
|
||||||
func restartContainer(container string, containerType SupportedContainer) error {
|
|
||||||
fmt.Println("Restarting containers...")
|
|
||||||
if containerType == Podman {
|
|
||||||
if err := run("podman-compose", "-f", "docker-compose.yml", "restart"); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if containerType == Docker {
|
|
||||||
if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "restart", container); err != nil {
|
|
||||||
return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
func copyFile(src, dst string) error {
|
||||||
source, err := os.Open(src)
|
source, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -818,34 +468,6 @@ func moveFile(src, dst string) error {
|
|||||||
return os.Remove(src)
|
return os.Remove(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForContainer(containerName string, containerType SupportedContainer) error {
|
|
||||||
maxAttempts := 30
|
|
||||||
retryInterval := time.Second * 2
|
|
||||||
|
|
||||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
|
||||||
// Check if container is running
|
|
||||||
cmd := exec.Command(string(containerType), "container", "inspect", "-f", "{{.State.Running}}", containerName)
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
// If the container doesn't exist or there's another error, wait and retry
|
|
||||||
time.Sleep(retryInterval)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
isRunning := strings.TrimSpace(out.String()) == "true"
|
|
||||||
if isRunning {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container exists but isn't running yet, wait and retry
|
|
||||||
time.Sleep(retryInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("container %s did not start within %v seconds", containerName, maxAttempts*int(retryInterval.Seconds()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func printSetupToken(containerType SupportedContainer, dashboardDomain string) {
|
func printSetupToken(containerType SupportedContainer, dashboardDomain string) {
|
||||||
fmt.Println("Waiting for Pangolin to generate setup token...")
|
fmt.Println("Waiting for Pangolin to generate setup token...")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user