Add setup script
This commit is contained in:
parent
34e3e7c819
commit
37790c850a
11 changed files with 556 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -26,6 +26,5 @@ migrations
|
||||||
package-lock.json
|
package-lock.json
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
config/
|
config/
|
||||||
config.yml
|
|
||||||
dist
|
dist
|
||||||
.dist
|
.dist
|
54
docker-compose.example.yml
Normal file
54
docker-compose.example.yml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
version: "3.7"
|
||||||
|
|
||||||
|
services:
|
||||||
|
pangolin:
|
||||||
|
image: fossorial/pangolin
|
||||||
|
container_name: pangolin
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 3001:3001
|
||||||
|
- 3000:3000
|
||||||
|
volumes:
|
||||||
|
- ./config:/app/config
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
|
||||||
|
interval: "3s"
|
||||||
|
timeout: "3s"
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
gerbil:
|
||||||
|
image: fossorial/gerbil
|
||||||
|
container_name: gerbil
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
pangolin:
|
||||||
|
condition: service_healthy
|
||||||
|
command:
|
||||||
|
- --reachableAt=http://gerbil:3003
|
||||||
|
- --generateAndSaveKeyTo=/var/config/key
|
||||||
|
- --remoteConfig=http://pangolin:3001/api/v1/gerbil/get-config
|
||||||
|
- --reportBandwidthTo=http://pangolin:3001/api/v1/gerbil/receive-bandwidth
|
||||||
|
volumes:
|
||||||
|
- ./config/:/var/config
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_MODULE
|
||||||
|
ports:
|
||||||
|
- 51820:51820/udp
|
||||||
|
- 8080:8080 # Port for traefik because of the network_mode
|
||||||
|
- 443:443 # Port for traefik because of the network_mode
|
||||||
|
- 80:80 # Port for traefik because of the network_mode
|
||||||
|
|
||||||
|
traefik:
|
||||||
|
image: traefik:v3.1
|
||||||
|
container_name: traefik
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: service:gerbil # Ports appear on the gerbil service
|
||||||
|
depends_on:
|
||||||
|
pangolin:
|
||||||
|
condition: service_healthy
|
||||||
|
command:
|
||||||
|
- --configFile=/etc/traefik/traefik_config.yml
|
||||||
|
volumes:
|
||||||
|
- ./traefik:/etc/traefik:ro # Volume to store the Traefik configuration
|
||||||
|
- ./letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
|
1
install/.gitignore
vendored
Normal file
1
install/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
installer
|
8
install/Makefile
Normal file
8
install/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o installer
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm installer
|
48
install/fs/config.yml
Normal file
48
install/fs/config.yml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
app:
|
||||||
|
base_url: https://{{.Domain}}
|
||||||
|
log_level: info
|
||||||
|
save_logs: false
|
||||||
|
|
||||||
|
server:
|
||||||
|
external_port: 3000
|
||||||
|
internal_port: 3001
|
||||||
|
next_port: 3002
|
||||||
|
internal_hostname: pangolin
|
||||||
|
secure_cookies: false
|
||||||
|
session_cookie_name: session
|
||||||
|
resource_session_cookie_name: resource_session
|
||||||
|
|
||||||
|
traefik:
|
||||||
|
cert_resolver: letsencrypt
|
||||||
|
http_entrypoint: web
|
||||||
|
https_entrypoint: websecure
|
||||||
|
prefer_wildcard_cert: false
|
||||||
|
|
||||||
|
gerbil:
|
||||||
|
start_port: 51820
|
||||||
|
base_endpoint: {{.Domain}}
|
||||||
|
use_subdomain: false
|
||||||
|
block_size: 16
|
||||||
|
subnet_group: 10.0.0.0/8
|
||||||
|
|
||||||
|
rate_limits:
|
||||||
|
global:
|
||||||
|
window_minutes: 1
|
||||||
|
max_requests: 100
|
||||||
|
{{if .EnableEmail}}
|
||||||
|
email:
|
||||||
|
smtp_host: {{.EmailSMTPHost}}
|
||||||
|
smtp_port: {{.EmailSMTPPort}}
|
||||||
|
smtp_user: {{.EmailSMTPUser}}
|
||||||
|
smtp_pass: {{.EmailSMTPPass}}
|
||||||
|
no_reply: {{.EmailNoReply}}
|
||||||
|
{{end}}
|
||||||
|
users:
|
||||||
|
server_admin:
|
||||||
|
email: {{.AdminUserEmail}}
|
||||||
|
password: {{.AdminUserPassword}}
|
||||||
|
|
||||||
|
flags:
|
||||||
|
require_email_verification: {{.EnableEmail}}
|
||||||
|
disable_signup_without_invite: {{.DisableSignupWithoutInvite}}
|
||||||
|
disable_user_create_org: {{.DisableUserCreateOrg}}
|
51
install/fs/docker-compose.yml
Normal file
51
install/fs/docker-compose.yml
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
services:
|
||||||
|
pangolin:
|
||||||
|
image: fossorial/pangolin
|
||||||
|
container_name: pangolin
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 3001:3001
|
||||||
|
- 3000:3000
|
||||||
|
volumes:
|
||||||
|
- ./config:/app/config
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
|
||||||
|
interval: "3s"
|
||||||
|
timeout: "3s"
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
gerbil:
|
||||||
|
image: fossorial/gerbil
|
||||||
|
container_name: gerbil
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
pangolin:
|
||||||
|
condition: service_healthy
|
||||||
|
command:
|
||||||
|
- --reachableAt=http://gerbil:3003
|
||||||
|
- --generateAndSaveKeyTo=/var/config/key
|
||||||
|
- --remoteConfig=http://pangolin:3001/api/v1/gerbil/get-config
|
||||||
|
- --reportBandwidthTo=http://pangolin:3001/api/v1/gerbil/receive-bandwidth
|
||||||
|
volumes:
|
||||||
|
- ./config/:/var/config
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_MODULE
|
||||||
|
ports:
|
||||||
|
- 51820:51820/udp
|
||||||
|
- 443:443 # Port for traefik because of the network_mode
|
||||||
|
- 80:80 # Port for traefik because of the network_mode
|
||||||
|
|
||||||
|
traefik:
|
||||||
|
image: traefik:v3.1
|
||||||
|
container_name: traefik
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: service:gerbil # Ports appear on the gerbil service
|
||||||
|
depends_on:
|
||||||
|
pangolin:
|
||||||
|
condition: service_healthy
|
||||||
|
command:
|
||||||
|
- --configFile=/etc/traefik/traefik_config.yml
|
||||||
|
volumes:
|
||||||
|
- ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
|
||||||
|
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
|
54
install/fs/traefik/dynamic_config.yml
Normal file
54
install/fs/traefik/dynamic_config.yml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
redirect-to-https:
|
||||||
|
redirectScheme:
|
||||||
|
scheme: https
|
||||||
|
permanent: true
|
||||||
|
|
||||||
|
routers:
|
||||||
|
# HTTP to HTTPS redirect router
|
||||||
|
main-app-router-redirect:
|
||||||
|
rule: "Host(`{{.Domain}}`)"
|
||||||
|
service: next-service
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
middlewares:
|
||||||
|
- redirect-to-https
|
||||||
|
|
||||||
|
# Next.js router (handles everything except API and WebSocket paths)
|
||||||
|
next-router:
|
||||||
|
rule: "Host(`{{.Domain}}`) && !PathPrefix(`/api/v1`)"
|
||||||
|
service: next-service
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls:
|
||||||
|
certResolver: letsencrypt
|
||||||
|
|
||||||
|
# API router (handles /api/v1 paths)
|
||||||
|
api-router:
|
||||||
|
rule: "Host(`{{.Domain}}`) && PathPrefix(`/api/v1`)"
|
||||||
|
service: api-service
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls:
|
||||||
|
certResolver: letsencrypt
|
||||||
|
|
||||||
|
# WebSocket router
|
||||||
|
ws-router:
|
||||||
|
rule: "Host(`{{.Domain}}`)"
|
||||||
|
service: api-service
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
tls:
|
||||||
|
certResolver: letsencrypt
|
||||||
|
|
||||||
|
services:
|
||||||
|
next-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://pangolin:3002" # Next.js server
|
||||||
|
|
||||||
|
api-service:
|
||||||
|
loadBalancer:
|
||||||
|
servers:
|
||||||
|
- url: "http://pangolin:3000" # API/WebSocket server
|
41
install/fs/traefik/traefik_config.yml
Normal file
41
install/fs/traefik/traefik_config.yml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
api:
|
||||||
|
insecure: true
|
||||||
|
dashboard: true
|
||||||
|
|
||||||
|
providers:
|
||||||
|
http:
|
||||||
|
endpoint: "http://pangolin:3001/api/v1/traefik-config"
|
||||||
|
pollInterval: "5s"
|
||||||
|
file:
|
||||||
|
filename: "/etc/traefik/dynamic_config.yml"
|
||||||
|
|
||||||
|
experimental:
|
||||||
|
plugins:
|
||||||
|
badger:
|
||||||
|
moduleName: "github.com/fosrl/badger"
|
||||||
|
version: "v1.0.0-beta.1"
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: "INFO"
|
||||||
|
format: "common"
|
||||||
|
|
||||||
|
certificatesResolvers:
|
||||||
|
letsencrypt:
|
||||||
|
acme:
|
||||||
|
httpChallenge:
|
||||||
|
entryPoint: web
|
||||||
|
email: "{{.LetsEncryptEmail}}"
|
||||||
|
storage: "/letsencrypt/acme.json"
|
||||||
|
caServer: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
entryPoints:
|
||||||
|
web:
|
||||||
|
address: ":80"
|
||||||
|
websecure:
|
||||||
|
address: ":443"
|
||||||
|
http:
|
||||||
|
tls:
|
||||||
|
certResolver: "letsencrypt"
|
||||||
|
|
||||||
|
serversTransport:
|
||||||
|
insecureSkipVerify: true
|
3
install/go.mod
Normal file
3
install/go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module installer
|
||||||
|
|
||||||
|
go 1.23.0
|
0
install/go.sum
Normal file
0
install/go.sum
Normal file
296
install/main.go
Normal file
296
install/main.go
Normal file
|
@ -0,0 +1,296 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed fs/*
|
||||||
|
var configFiles embed.FS
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Domain string `yaml:"domain"`
|
||||||
|
LetsEncryptEmail string `yaml:"letsEncryptEmail"`
|
||||||
|
AdminUserEmail string `yaml:"adminUserEmail"`
|
||||||
|
AdminUserPassword string `yaml:"adminUserPassword"`
|
||||||
|
DisableSignupWithoutInvite bool `yaml:"disableSignupWithoutInvite"`
|
||||||
|
DisableUserCreateOrg bool `yaml:"disableUserCreateOrg"`
|
||||||
|
EnableEmail bool `yaml:"enableEmail"`
|
||||||
|
EmailSMTPHost string `yaml:"emailSMTPHost"`
|
||||||
|
EmailSMTPPort int `yaml:"emailSMTPPort"`
|
||||||
|
EmailSMTPUser string `yaml:"emailSMTPUser"`
|
||||||
|
EmailSMTPPass string `yaml:"emailSMTPPass"`
|
||||||
|
EmailNoReply string `yaml:"emailNoReply"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
|
config := collectUserInput(reader)
|
||||||
|
createConfigFiles(config)
|
||||||
|
|
||||||
|
if !isDockerInstalled() && runtime.GOOS == "linux" {
|
||||||
|
if shouldInstallDocker() {
|
||||||
|
// ask user if they want to install docker
|
||||||
|
if readBool(reader, "Would you like to install Docker?", true) {
|
||||||
|
installDocker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDockerInstalled() {
|
||||||
|
if readBool(reader, "Would you like to install and start the containers?", true) {
|
||||||
|
pullAndStartContainers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Installation complete!")
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 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 {
|
||||||
|
config := Config{}
|
||||||
|
|
||||||
|
// Basic configuration
|
||||||
|
fmt.Println("\n=== Basic Configuration ===")
|
||||||
|
config.Domain = readString(reader, "Enter your domain name", "")
|
||||||
|
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
|
||||||
|
|
||||||
|
// Admin user configuration
|
||||||
|
fmt.Println("\n=== Admin User Configuration ===")
|
||||||
|
config.AdminUserEmail = readString(reader, "Enter admin user email", "admin@"+config.Domain)
|
||||||
|
config.AdminUserPassword = readString(reader, "Enter admin user password", "")
|
||||||
|
|
||||||
|
// Security settings
|
||||||
|
fmt.Println("\n=== Security Settings ===")
|
||||||
|
config.DisableSignupWithoutInvite = readBool(reader, "Disable signup without invite", true)
|
||||||
|
config.DisableUserCreateOrg = readBool(reader, "Disable users from creating organizations", false)
|
||||||
|
|
||||||
|
// Email configuration
|
||||||
|
fmt.Println("\n=== Email Configuration ===")
|
||||||
|
config.EnableEmail = readBool(reader, "Enable email functionality", false)
|
||||||
|
|
||||||
|
if config.EnableEmail {
|
||||||
|
config.EmailSMTPHost = readString(reader, "Enter SMTP host: ", "")
|
||||||
|
config.EmailSMTPPort = readInt(reader, "Enter SMTP port (default 587): ", 587)
|
||||||
|
config.EmailSMTPUser = readString(reader, "Enter SMTP username: ", "")
|
||||||
|
config.EmailSMTPPass = readString(reader, "Enter SMTP password: ", "")
|
||||||
|
config.EmailNoReply = readString(reader, "Enter no-reply email address: ", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if config.Domain == "" {
|
||||||
|
fmt.Println("Error: Domain name is required")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if config.LetsEncryptEmail == "" {
|
||||||
|
fmt.Println("Error: Let's Encrypt email is required")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if config.AdminUserEmail == "" || config.AdminUserPassword == "" {
|
||||||
|
fmt.Println("Error: Admin user email and password are required")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func createConfigFiles(config Config) error {
|
||||||
|
os.MkdirAll("config", 0755)
|
||||||
|
os.MkdirAll("config/letsencrypt", 0755)
|
||||||
|
os.MkdirAll("config/db", 0755)
|
||||||
|
os.MkdirAll("config/logs", 0755)
|
||||||
|
|
||||||
|
// Walk through all embedded files
|
||||||
|
err := fs.WalkDir(configFiles, "fs", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the root fs directory itself
|
||||||
|
if path == "fs" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the relative path by removing the "fs/" prefix
|
||||||
|
relPath := strings.TrimPrefix(path, "fs/")
|
||||||
|
|
||||||
|
// Create the full output path under "config/"
|
||||||
|
outPath := filepath.Join("config", relPath)
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
// Create directory
|
||||||
|
if err := os.MkdirAll(outPath, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory %s: %v", outPath, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the template file
|
||||||
|
content, err := configFiles.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse template
|
||||||
|
tmpl, err := template.New(d.Name()).Parse(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse template %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure parent directory exists
|
||||||
|
if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create parent directory for %s: %v", outPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create output file
|
||||||
|
outFile, err := os.Create(outPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s: %v", outPath, err)
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
// Execute template
|
||||||
|
if err := tmpl.Execute(outFile, config); err != nil {
|
||||||
|
return fmt.Errorf("failed to execute template %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error walking config files: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the docker-compose.yml file to the root directory
|
||||||
|
os.Rename("config/docker-compose.yml", "docker-compose.yml")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldInstallDocker() bool {
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
fmt.Print("Would you like to install Docker? (yes/no): ")
|
||||||
|
response, _ := reader.ReadString('\n')
|
||||||
|
return strings.ToLower(strings.TrimSpace(response)) == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
var installCmd *exec.Cmd
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.Contains(osRelease, "ID=ubuntu") || strings.Contains(osRelease, "ID=debian"):
|
||||||
|
installCmd = exec.Command("bash", "-c", `
|
||||||
|
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=amd64 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
|
||||||
|
`)
|
||||||
|
case strings.Contains(osRelease, "ID=fedora"):
|
||||||
|
installCmd = exec.Command("bash", "-c", `
|
||||||
|
dnf -y install dnf-plugins-core &&
|
||||||
|
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo &&
|
||||||
|
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
`)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported Linux distribution")
|
||||||
|
}
|
||||||
|
|
||||||
|
installCmd.Stdout = os.Stdout
|
||||||
|
installCmd.Stderr = os.Stderr
|
||||||
|
return installCmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDockerInstalled() bool {
|
||||||
|
cmd := exec.Command("docker", "--version")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func pullAndStartContainers() error {
|
||||||
|
containers := []string{
|
||||||
|
"traefik:v3.1",
|
||||||
|
"fossorial/pangolin:latest",
|
||||||
|
"fossorial/gerbil:latest",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
fmt.Printf("Pulling %s...\n", container)
|
||||||
|
cmd := exec.Command("docker", "pull", container)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to pull %s: %v", container, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Starting containers...")
|
||||||
|
|
||||||
|
// First try docker compose (new style)
|
||||||
|
cmd := exec.Command("docker", "compose", "-f", "docker-compose.yml", "up", "-d")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
// If docker compose fails, try docker-compose (legacy style)
|
||||||
|
if err != nil {
|
||||||
|
cmd = exec.Command("docker-compose", "-f", "docker-compose.yml", "up", "-d")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err = cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue