e071084ebe
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled
270 lines
6.4 KiB
Go
270 lines
6.4 KiB
Go
// Package initcmd provides the micro init command for server setup
|
|
package initcmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
"go-micro.dev/v6/cmd"
|
|
)
|
|
|
|
const systemdTemplate = `[Unit]
|
|
Description=Micro service: %%i
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=%s
|
|
Group=%s
|
|
WorkingDirectory=%s
|
|
ExecStart=%s/bin/%%i
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
EnvironmentFile=-%s/config/%%i.env
|
|
|
|
# Logging
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
SyslogIdentifier=micro-%%i
|
|
|
|
# Security hardening
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=%s/data
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`
|
|
|
|
// Init initializes a server to receive micro deployments
|
|
func Init(c *cli.Context) error {
|
|
if !c.Bool("server") {
|
|
return fmt.Errorf("usage: micro init --server\n\nInitialize this machine to receive micro deployments")
|
|
}
|
|
|
|
// Check if we're on Linux
|
|
if runtime.GOOS != "linux" {
|
|
return fmt.Errorf("micro init --server is only supported on Linux")
|
|
}
|
|
|
|
// Check for remote init
|
|
remoteHost := c.String("remote")
|
|
if remoteHost != "" {
|
|
return initRemote(c, remoteHost)
|
|
}
|
|
|
|
basePath := c.String("path")
|
|
userName := c.String("user")
|
|
|
|
fmt.Println("Initializing micro server...")
|
|
fmt.Println()
|
|
|
|
// Check if running as root (needed for systemd and creating users)
|
|
if os.Geteuid() != 0 {
|
|
return fmt.Errorf(`micro init --server requires root privileges.
|
|
|
|
Run with sudo:
|
|
sudo micro init --server`)
|
|
}
|
|
|
|
// Create user if needed
|
|
if userName == "micro" {
|
|
if err := createMicroUser(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Create directories
|
|
fmt.Println("Creating directories:")
|
|
dirs := []string{
|
|
filepath.Join(basePath, "bin"),
|
|
filepath.Join(basePath, "data"),
|
|
filepath.Join(basePath, "config"),
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create %s: %w", dir, err)
|
|
}
|
|
fmt.Printf(" ✓ %s\n", dir)
|
|
}
|
|
|
|
// Set ownership
|
|
if userName != "root" {
|
|
u, err := user.Lookup(userName)
|
|
if err != nil {
|
|
return fmt.Errorf("user %s not found: %w", userName, err)
|
|
}
|
|
|
|
// chown -R user:user /opt/micro
|
|
chownCmd := exec.Command("chown", "-R", fmt.Sprintf("%s:%s", u.Username, u.Username), basePath)
|
|
if err := chownCmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to set ownership: %w", err)
|
|
}
|
|
}
|
|
|
|
fmt.Println()
|
|
|
|
// Create systemd template
|
|
fmt.Println("Creating systemd template:")
|
|
unitContent := fmt.Sprintf(systemdTemplate, userName, userName, basePath, basePath, basePath, basePath)
|
|
unitPath := "/etc/systemd/system/micro@.service"
|
|
|
|
if err := os.WriteFile(unitPath, []byte(unitContent), 0644); err != nil {
|
|
return fmt.Errorf("failed to write systemd unit: %w", err)
|
|
}
|
|
fmt.Printf(" ✓ %s\n", unitPath)
|
|
|
|
// Reload systemd
|
|
reloadCmd := exec.Command("systemctl", "daemon-reload")
|
|
if err := reloadCmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to reload systemd: %w", err)
|
|
}
|
|
fmt.Println(" ✓ systemd daemon-reload")
|
|
|
|
// Write marker file so deploy can detect initialization
|
|
markerPath := filepath.Join(basePath, ".micro-initialized")
|
|
if err := os.WriteFile(markerPath, []byte("1\n"), 0644); err != nil {
|
|
return fmt.Errorf("failed to write marker: %w", err)
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Println("Server ready!")
|
|
fmt.Println()
|
|
fmt.Println(" Deploy from your machine:")
|
|
fmt.Printf(" micro deploy user@%s\n", getHostname())
|
|
fmt.Println()
|
|
fmt.Println(" Manage services:")
|
|
fmt.Println(" sudo systemctl status micro@myservice")
|
|
fmt.Println(" sudo journalctl -u micro@myservice -f")
|
|
fmt.Println()
|
|
|
|
return nil
|
|
}
|
|
|
|
func createMicroUser() error {
|
|
// Check if user exists
|
|
if _, err := user.Lookup("micro"); err == nil {
|
|
return nil // user already exists
|
|
}
|
|
|
|
fmt.Println("Creating micro user:")
|
|
createCmd := exec.Command("useradd", "--system", "--no-create-home", "--shell", "/bin/false", "micro")
|
|
if err := createCmd.Run(); err != nil {
|
|
// Check if it's just because user already exists
|
|
if _, lookupErr := user.Lookup("micro"); lookupErr == nil {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to create micro user: %w", err)
|
|
}
|
|
fmt.Println(" ✓ Created user 'micro'")
|
|
return nil
|
|
}
|
|
|
|
func initRemote(c *cli.Context, host string) error {
|
|
fmt.Printf("Initializing micro on %s...\n\n", host)
|
|
|
|
// Check SSH connectivity first
|
|
if err := checkSSH(host); err != nil {
|
|
return err
|
|
}
|
|
|
|
basePath := c.String("path")
|
|
userName := c.String("user")
|
|
|
|
// Run micro init --server on remote
|
|
initCmd := fmt.Sprintf("sudo micro init --server --path %s --user %s", basePath, userName)
|
|
|
|
sshCmd := exec.Command("ssh", host, initCmd)
|
|
sshCmd.Stdout = os.Stdout
|
|
sshCmd.Stderr = os.Stderr
|
|
|
|
if err := sshCmd.Run(); err != nil {
|
|
return fmt.Errorf("remote init failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkSSH(host string) error {
|
|
// Quick SSH test
|
|
testCmd := exec.Command("ssh", "-o", "ConnectTimeout=5", "-o", "BatchMode=yes", host, "echo ok")
|
|
output, err := testCmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
return fmt.Errorf(`✗ Cannot connect to %s
|
|
|
|
SSH connection failed. Check that:
|
|
• The server is reachable: ping %s
|
|
• SSH is configured: ssh %s
|
|
• Your key is added: ssh-add -l
|
|
|
|
Common fixes:
|
|
• Add SSH key: ssh-copy-id %s
|
|
• Check hostname in ~/.ssh/config
|
|
|
|
Error: %s`, host, host, host, host, strings.TrimSpace(string(output)))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getHostname() string {
|
|
name, err := os.Hostname()
|
|
if err != nil {
|
|
return "this-server"
|
|
}
|
|
return name
|
|
}
|
|
|
|
func init() {
|
|
cmd.Register(&cli.Command{
|
|
Name: "init",
|
|
Usage: "Initialize micro for development or server deployment",
|
|
Description: `Initialize micro on a server to receive deployments.
|
|
|
|
Server setup:
|
|
sudo micro init --server
|
|
|
|
This creates:
|
|
• /opt/micro/bin/ - service binaries
|
|
• /opt/micro/data/ - persistent data
|
|
• /opt/micro/config/ - environment files
|
|
• systemd template for managing services
|
|
|
|
Remote setup:
|
|
micro init --server --remote user@host
|
|
|
|
After init, deploy with:
|
|
micro deploy user@host`,
|
|
Action: Init,
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "server",
|
|
Usage: "Initialize as a deployment server",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "path",
|
|
Usage: "Base path for micro (default: /opt/micro)",
|
|
Value: "/opt/micro",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "user",
|
|
Usage: "User to run services as (default: micro)",
|
|
Value: "micro",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "remote",
|
|
Usage: "Initialize a remote server via SSH",
|
|
},
|
|
},
|
|
})
|
|
}
|