Ctrl K
ComunidadGuías…

agent/installer.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//go:build installer

package main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"text/template"

	"github.com/shellhub-io/shellhub/pkg/uuid"
	"github.com/spf13/cobra"
)

const (
	agentEnvFile     = "/etc/shellhub-agent.env"
	agentServiceFile = "/etc/systemd/system/shellhub-agent.service"
	agentServiceName = "shellhub-agent"
)

var agentServiceTemplate = `[Unit]
Description=ShellHub Agent
After=local-fs.target network-online.target time-sync.target
Wants=network-online.target
Requires=local-fs.target

[Service]
EnvironmentFile=/etc/shellhub-agent.env
ExecStart={{.BinaryPath}}
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
`

type installerConfig struct {
	ServerAddress     string
	TenantID          string
	PrivateKey        string
	ProvisioningKey   string
	PreferredHostname string
	PreferredIdentity string
	KeepaliveInterval uint
}

func registerInstallerCommands(rootCmd *cobra.Command) {
	installCmd := &cobra.Command{ //nolint:exhaustruct
		Use:          "install",
		Short:        "Install ShellHub agent as a systemd service",
		SilenceUsage: true,
		RunE: func(cmd *cobra.Command, _ []string) error {
			flags := cmd.Flags()

			serverAddress, _ := flags.GetString("server-address")
			tenantID, _ := flags.GetString("tenant-id")
			privateKey, _ := flags.GetString("private-key")
			preferredHostname, _ := flags.GetString("preferred-hostname")
			preferredIdentity, _ := flags.GetString("preferred-identity")

			provisioningKey, _ := flags.GetString("provisioning-key")
			if provisioningKey != "" {
				if _, err := uuid.Parse(provisioningKey); err != nil {
					return fmt.Errorf("invalid provisioning key: %q is not a valid UUID", provisioningKey)
				}
			}

			var keepaliveInterval uint
			if flags.Changed("keepalive-interval") {
				keepaliveInterval, _ = flags.GetUint("keepalive-interval")
			}

			if err := agentInstall(installerConfig{
				ServerAddress:     serverAddress,
				TenantID:          tenantID,
				PrivateKey:        privateKey,
				ProvisioningKey:   provisioningKey,
				PreferredHostname: preferredHostname,
				PreferredIdentity: preferredIdentity,
				KeepaliveInterval: keepaliveInterval,
			}); err != nil {
				return fmt.Errorf("installation failed: %w", err)
			}

			fmt.Println("ShellHub agent installed successfully.")
			fmt.Println("Check status: systemctl status shellhub-agent")
			fmt.Println("View logs:    journalctl -u shellhub-agent -f")

			return nil
		},
	}

	installCmd.Flags().String("server-address", "", "ShellHub server address")
	installCmd.Flags().String("tenant-id", "", "Namespace tenant ID")
	installCmd.Flags().String("private-key", "/etc/shellhub.key", "Path to the agent private key file")
	installCmd.Flags().String("provisioning-key", "", "Provisioning key used to enroll the device")
	installCmd.Flags().String("preferred-hostname", "", "Preferred device hostname")
	installCmd.Flags().String("preferred-identity", "", "Preferred device identity")
	installCmd.Flags().Uint("keepalive-interval", 30, "Keepalive interval in seconds")
	installCmd.MarkFlagRequired("server-address") //nolint:errcheck
	installCmd.MarkFlagsOneRequired("tenant-id", "provisioning-key")

	rootCmd.AddCommand(installCmd)

	rootCmd.AddCommand(&cobra.Command{ //nolint:exhaustruct
		Use:   "uninstall",
		Short: "Uninstall ShellHub agent systemd service",
		RunE: func(_ *cobra.Command, _ []string) error {
			if err := agentUninstall(); err != nil {
				return fmt.Errorf("uninstallation failed: %w", err)
			}

			fmt.Println("ShellHub agent uninstalled successfully.")
			fmt.Println("Note: the binary and private key were not removed.")

			return nil
		},
	})
}

func agentInstall(cfg installerConfig) error {
	if os.Geteuid() != 0 {
		return fmt.Errorf("must be run as root")
	}

	if err := exec.Command("systemctl", "show-environment").Run(); err != nil {
		return fmt.Errorf("systemd is not available on this system")
	}

	exec.Command("systemctl", "disable", "--now", agentServiceName).Run() //nolint:errcheck

	exe, err := os.Executable()
	if err != nil {
		return fmt.Errorf("failed to resolve executable path: %w", err)
	}

	binaryPath, err := filepath.EvalSymlinks(exe)
	if err != nil {
		return fmt.Errorf("failed to resolve symlinks: %w", err)
	}

	if err := writeAgentEnvFile(cfg); err != nil {
		return fmt.Errorf("failed to write env file: %w", err)
	}

	if err := writeAgentServiceFile(binaryPath); err != nil {
		return fmt.Errorf("failed to write service file: %w", err)
	}

	if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil {
		return fmt.Errorf("failed to reload systemd daemon: %w", err)
	}

	if err := exec.Command("systemctl", "enable", "--now", agentServiceName).Run(); err != nil {
		return fmt.Errorf("failed to enable service: %w", err)
	}

	return nil
}

func writeAgentEnvFile(cfg installerConfig) error {
	var buf bytes.Buffer

	fmt.Fprintf(&buf, "SHELLHUB_SERVER_ADDRESS=%s\n", cfg.ServerAddress)
	fmt.Fprintf(&buf, "SHELLHUB_PRIVATE_KEY=%s\n", cfg.PrivateKey)

	if cfg.TenantID != "" {
		fmt.Fprintf(&buf, "SHELLHUB_TENANT_ID=%s\n", cfg.TenantID)
	}

	if cfg.ProvisioningKey != "" {
		fmt.Fprintf(&buf, "SHELLHUB_PROVISIONING_KEY=%s\n", cfg.ProvisioningKey)
	}

	if cfg.PreferredHostname != "" {
		fmt.Fprintf(&buf, "SHELLHUB_PREFERRED_HOSTNAME=%s\n", cfg.PreferredHostname)
	}

	if cfg.PreferredIdentity != "" {
		fmt.Fprintf(&buf, "SHELLHUB_PREFERRED_IDENTITY=%s\n", cfg.PreferredIdentity)
	}

	if cfg.KeepaliveInterval != 0 {
		fmt.Fprintf(&buf, "SHELLHUB_KEEPALIVE_INTERVAL=%d\n", cfg.KeepaliveInterval)
	}

	return os.WriteFile(agentEnvFile, buf.Bytes(), 0o600)
}

func writeAgentServiceFile(binaryPath string) error {
	tmpl, err := template.New("service").Parse(agentServiceTemplate)
	if err != nil {
		return err
	}

	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, struct{ BinaryPath string }{binaryPath}); err != nil {
		return err
	}

	return os.WriteFile(agentServiceFile, buf.Bytes(), 0o644)
}

func agentUninstall() error {
	if os.Geteuid() != 0 {
		return fmt.Errorf("must be run as root")
	}

	exec.Command("systemctl", "disable", "--now", agentServiceName).Run() //nolint:errcheck

	if err := os.Remove(agentServiceFile); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to remove service file: %w", err)
	}

	if err := os.Remove(agentEnvFile); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("failed to remove env file: %w", err)
	}

	if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil {
		return fmt.Errorf("failed to reload systemd daemon: %w", err)
	}

	return nil
}