306 lines
7.8 KiB
Go
306 lines
7.8 KiB
Go
/*
|
|
* Copyright (C) 2026 Fluxer Contributors
|
|
*
|
|
* This file is part of Fluxer.
|
|
*
|
|
* Fluxer is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Fluxer is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"fluxer.dev/dev/pkg/integrations"
|
|
"fluxer.dev/dev/pkg/utils"
|
|
)
|
|
|
|
const (
|
|
defaultComposeFile = "dev/compose.yaml"
|
|
defaultEnvFile = "dev/.env"
|
|
)
|
|
|
|
// NewRootCmd creates the root command
|
|
func NewRootCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "devctl",
|
|
Short: "Fluxer development control tool",
|
|
Long: "Docker Compose wrapper and development utilities for Fluxer.",
|
|
}
|
|
|
|
cmd.AddCommand(
|
|
NewUpCmd(),
|
|
NewDownCmd(),
|
|
NewRestartCmd(),
|
|
NewLogsCmd(),
|
|
NewPsCmd(),
|
|
NewExecCmd(),
|
|
NewShellCmd(),
|
|
|
|
NewLivekitSyncCmd(),
|
|
NewGeoIPDownloadCmd(),
|
|
NewEnsureNetworkCmd(),
|
|
)
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewUpCmd starts services
|
|
func NewUpCmd() *cobra.Command {
|
|
var detach bool
|
|
var build bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "up [services...]",
|
|
Short: "Start services",
|
|
Long: "Start all or specific services using docker compose",
|
|
RunE: func(cmd *cobra.Command, services []string) error {
|
|
args := []string{"--env-file", defaultEnvFile, "-f", defaultComposeFile, "up"}
|
|
if detach {
|
|
args = append(args, "-d")
|
|
}
|
|
if build {
|
|
args = append(args, "--build")
|
|
}
|
|
args = append(args, services...)
|
|
return runDockerCompose(args...)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVarP(&detach, "detach", "d", true, "Run in background")
|
|
cmd.Flags().BoolVar(&build, "build", false, "Build images before starting")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewDownCmd stops and removes containers
|
|
func NewDownCmd() *cobra.Command {
|
|
var volumes bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "down",
|
|
Short: "Stop and remove containers",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
dcArgs := []string{"--env-file", defaultEnvFile, "-f", defaultComposeFile, "down"}
|
|
if volumes {
|
|
dcArgs = append(dcArgs, "-v")
|
|
}
|
|
return runDockerCompose(dcArgs...)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVarP(&volumes, "volumes", "v", false, "Remove volumes")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewRestartCmd restarts services
|
|
func NewRestartCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "restart [services...]",
|
|
Short: "Restart services",
|
|
RunE: func(cmd *cobra.Command, services []string) error {
|
|
args := []string{"--env-file", defaultEnvFile, "-f", defaultComposeFile, "restart"}
|
|
args = append(args, services...)
|
|
return runDockerCompose(args...)
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewLogsCmd shows service logs
|
|
func NewLogsCmd() *cobra.Command {
|
|
var follow bool
|
|
var tail string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "logs [services...]",
|
|
Short: "Show service logs",
|
|
RunE: func(cmd *cobra.Command, services []string) error {
|
|
args := []string{"--env-file", defaultEnvFile, "-f", defaultComposeFile, "logs"}
|
|
if follow {
|
|
args = append(args, "-f")
|
|
}
|
|
if tail != "" {
|
|
args = append(args, "--tail", tail)
|
|
}
|
|
args = append(args, services...)
|
|
return runDockerCompose(args...)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVarP(&follow, "follow", "f", true, "Follow log output")
|
|
cmd.Flags().StringVarP(&tail, "tail", "n", "100", "Number of lines to show from the end")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewPsCmd lists containers
|
|
func NewPsCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "ps",
|
|
Short: "List containers",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runDockerCompose("--env-file", defaultEnvFile, "-f", defaultComposeFile, "ps")
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewExecCmd executes a command in a running container
|
|
func NewExecCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "exec SERVICE COMMAND...",
|
|
Short: "Execute a command in a running container",
|
|
Args: cobra.MinimumNArgs(2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
dcArgs := []string{"--env-file", defaultEnvFile, "-f", defaultComposeFile, "exec"}
|
|
dcArgs = append(dcArgs, args...)
|
|
return runDockerCompose(dcArgs...)
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewShellCmd opens a shell in a container
|
|
func NewShellCmd() *cobra.Command {
|
|
var shell string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "sh SERVICE",
|
|
Short: "Open a shell in a container",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
service := args[0]
|
|
return runDockerCompose("--env-file", defaultEnvFile, "-f", defaultComposeFile, "exec", service, shell)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&shell, "shell", "sh", "Shell to use (sh, bash, etc.)")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewLivekitSyncCmd syncs LiveKit configuration
|
|
func NewLivekitSyncCmd() *cobra.Command {
|
|
var envPath string
|
|
var outputPath string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "livekit-sync",
|
|
Short: "Generate LiveKit configuration from environment variables",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
env, err := utils.ParseEnvFile(envPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read env file: %w", err)
|
|
}
|
|
|
|
written, err := integrations.WriteLivekitFileFromEnv(outputPath, env)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !written {
|
|
fmt.Println("⚠️ Voice/LiveKit is disabled - no config generated")
|
|
return nil
|
|
}
|
|
|
|
fmt.Printf("✅ LiveKit config written to %s\n", outputPath)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVarP(&envPath, "env", "e", defaultEnvFile, "Environment file path")
|
|
cmd.Flags().StringVarP(&outputPath, "output", "o", "dev/livekit.yaml", "Output path")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewGeoIPDownloadCmd downloads GeoIP database
|
|
func NewGeoIPDownloadCmd() *cobra.Command {
|
|
var token string
|
|
var envPath string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "geoip-download",
|
|
Short: "Download GeoIP database from IPInfo",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return integrations.DownloadGeoIP(token, envPath)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&token, "token", "", "IPInfo API token")
|
|
cmd.Flags().StringVarP(&envPath, "env", "e", defaultEnvFile, "Env file to read token from")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// NewEnsureNetworkCmd ensures the Docker network exists
|
|
func NewEnsureNetworkCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "ensure-network",
|
|
Short: "Ensure the fluxer-shared Docker network exists",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return ensureNetwork()
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
// runDockerCompose runs a docker compose command
|
|
func runDockerCompose(args ...string) error {
|
|
cmd := exec.Command("docker", append([]string{"compose"}, args...)...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
return cmd.Run()
|
|
}
|
|
|
|
// ensureNetwork ensures the fluxer-shared network exists
|
|
func ensureNetwork() error {
|
|
checkCmd := exec.Command("docker", "network", "ls", "--format", "{{.Name}}")
|
|
output, err := checkCmd.Output()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list networks: %w", err)
|
|
}
|
|
|
|
networks := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
for _, net := range networks {
|
|
if net == "fluxer-shared" {
|
|
fmt.Println("✅ fluxer-shared network already exists")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fmt.Println("Creating fluxer-shared network...")
|
|
createCmd := exec.Command("docker", "network", "create", "fluxer-shared")
|
|
createCmd.Stdout = os.Stdout
|
|
createCmd.Stderr = os.Stderr
|
|
if err := createCmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to create network: %w", err)
|
|
}
|
|
|
|
fmt.Println("✅ fluxer-shared network created")
|
|
return nil
|
|
}
|