From a7f100038dfff0062dee19b546176c79c1edff24 Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Thu, 5 Feb 2026 13:32:06 +0200 Subject: [PATCH] Fixed system service sudoer error on debros user --- pkg/environments/production/orchestrator.go | 7 ++ pkg/environments/production/provisioner.go | 47 ++++++++ pkg/namespace/cluster_manager.go | 103 +---------------- pkg/namespace/systemd_spawner.go | 116 +++++++++++++++++++- pkg/olric/client.go | 22 +++- pkg/systemd/manager.go | 78 ++++++++++--- scripts/extract-deploy.sh | 16 ++- systemd/debros-namespace-gateway@.service | 3 +- systemd/debros-namespace-olric@.service | 5 +- systemd/debros-namespace-rqlite@.service | 6 +- 10 files changed, 265 insertions(+), 138 deletions(-) diff --git a/pkg/environments/production/orchestrator.go b/pkg/environments/production/orchestrator.go index 3b92c61..cb84608 100644 --- a/pkg/environments/production/orchestrator.go +++ b/pkg/environments/production/orchestrator.go @@ -256,6 +256,13 @@ func (ps *ProductionSetup) Phase2ProvisionEnvironment() error { ps.logf(" ✓ Deployment sudoers configured") } + // Set up namespace sudoers (allows debros user to manage debros-namespace-* services) + if err := ps.userProvisioner.SetupNamespaceSudoers(); err != nil { + ps.logf(" ⚠️ Failed to setup namespace sudoers: %v", err) + } else { + ps.logf(" ✓ Namespace sudoers configured") + } + // Set up WireGuard sudoers (allows debros user to manage WG peers) if err := ps.userProvisioner.SetupWireGuardSudoers(); err != nil { ps.logf(" ⚠️ Failed to setup wireguard sudoers: %v", err) diff --git a/pkg/environments/production/provisioner.go b/pkg/environments/production/provisioner.go index 33b534c..7c5dc7d 100644 --- a/pkg/environments/production/provisioner.go +++ b/pkg/environments/production/provisioner.go @@ -224,6 +224,53 @@ debros ALL=(ALL) NOPASSWD: /bin/rm -f /etc/systemd/system/orama-deploy-*.service return nil } +// SetupNamespaceSudoers configures the debros user with permissions needed for +// managing namespace cluster services via systemd. +func (up *UserProvisioner) SetupNamespaceSudoers() error { + sudoersFile := "/etc/sudoers.d/debros-namespaces" + + // Check if already configured + if _, err := os.Stat(sudoersFile); err == nil { + return nil // Already configured + } + + sudoersContent := `# DeBros Network - Namespace Cluster Management Permissions +# Allows debros user to manage systemd services for namespace clusters + +# Systemd service management for debros-namespace-* services +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl daemon-reload +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl start debros-namespace-* +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl stop debros-namespace-* +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart debros-namespace-* +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl enable debros-namespace-* +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl disable debros-namespace-* +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl status debros-namespace-* +debros ALL=(ALL) NOPASSWD: /usr/bin/systemctl is-active debros-namespace-* + +# Service file management (tee to write, rm to remove) +debros ALL=(ALL) NOPASSWD: /usr/bin/tee /etc/systemd/system/debros-namespace-*.service +debros ALL=(ALL) NOPASSWD: /bin/rm -f /etc/systemd/system/debros-namespace-*.service + +# Environment file management for namespace services +debros ALL=(ALL) NOPASSWD: /usr/bin/tee /home/debros/.orama/namespace/*/env/* +debros ALL=(ALL) NOPASSWD: /usr/bin/mkdir -p /home/debros/.orama/namespace/*/env +` + + // Write sudoers rule + if err := os.WriteFile(sudoersFile, []byte(sudoersContent), 0440); err != nil { + return fmt.Errorf("failed to create namespace sudoers rule: %w", err) + } + + // Validate sudoers file + cmd := exec.Command("visudo", "-c", "-f", sudoersFile) + if err := cmd.Run(); err != nil { + os.Remove(sudoersFile) // Clean up on validation failure + return fmt.Errorf("namespace sudoers rule validation failed: %w", err) + } + + return nil +} + // SetupWireGuardSudoers configures the debros user with permissions to manage WireGuard func (up *UserProvisioner) SetupWireGuardSudoers() error { sudoersFile := "/etc/sudoers.d/debros-wireguard" diff --git a/pkg/namespace/cluster_manager.go b/pkg/namespace/cluster_manager.go index 419112d..4c9a0e6 100644 --- a/pkg/namespace/cluster_manager.go +++ b/pkg/namespace/cluster_manager.go @@ -21,7 +21,6 @@ import ( "github.com/DeBrosOfficial/network/pkg/systemd" "github.com/google/uuid" "go.uber.org/zap" - "gopkg.in/yaml.v3" ) // ClusterManagerConfig contains configuration for the cluster manager @@ -164,57 +163,9 @@ func (cm *ClusterManager) spawnRQLiteWithSystemd(ctx context.Context, cfg rqlite return cm.systemdSpawner.SpawnRQLite(ctx, cfg.Namespace, cfg.NodeID, cfg) } -// spawnOlricWithSystemd generates config and spawns Olric via systemd +// spawnOlricWithSystemd spawns Olric via systemd (config creation now handled by spawner) func (cm *ClusterManager) spawnOlricWithSystemd(ctx context.Context, cfg olric.InstanceConfig) error { - // Generate Olric config file (YAML) - configDir := filepath.Join(cm.baseDataDir, cfg.Namespace, "configs") - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) - } - - configPath := filepath.Join(configDir, fmt.Sprintf("olric-%s.yaml", cfg.NodeID)) - - // Generate Olric YAML config - type olricServerConfig struct { - BindAddr string `yaml:"bindAddr"` - BindPort int `yaml:"bindPort"` - } - type olricMemberlistConfig struct { - Environment string `yaml:"environment"` - BindAddr string `yaml:"bindAddr"` - BindPort int `yaml:"bindPort"` - Peers []string `yaml:"peers,omitempty"` - } - type olricConfig struct { - Server olricServerConfig `yaml:"server"` - Memberlist olricMemberlistConfig `yaml:"memberlist"` - PartitionCount uint64 `yaml:"partitionCount"` - } - - config := olricConfig{ - Server: olricServerConfig{ - BindAddr: cfg.BindAddr, - BindPort: cfg.HTTPPort, - }, - Memberlist: olricMemberlistConfig{ - Environment: "lan", - BindAddr: cfg.BindAddr, - BindPort: cfg.MemberlistPort, - Peers: cfg.PeerAddresses, - }, - PartitionCount: 12, // Optimized for namespace clusters (vs 256 default) - } - - configBytes, err := yaml.Marshal(config) - if err != nil { - return fmt.Errorf("failed to marshal Olric config: %w", err) - } - - if err := os.WriteFile(configPath, configBytes, 0644); err != nil { - return fmt.Errorf("failed to write Olric config: %w", err) - } - - // Start via systemd + // SystemdSpawner now handles config file creation return cm.systemdSpawner.SpawnOlric(ctx, cfg.Namespace, cfg.NodeID, cfg) } @@ -234,55 +185,9 @@ func (cm *ClusterManager) writePeersJSON(dataDir string, peers []rqlite.RaftPeer return os.WriteFile(peersFile, data, 0644) } -// spawnGatewayWithSystemd generates config and spawns Gateway via systemd +// spawnGatewayWithSystemd spawns Gateway via systemd (config creation now handled by spawner) func (cm *ClusterManager) spawnGatewayWithSystemd(ctx context.Context, cfg gateway.InstanceConfig) error { - // Generate Gateway config file (YAML) - configDir := filepath.Join(cm.baseDataDir, cfg.Namespace, "configs") - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %w", err) - } - - configPath := filepath.Join(configDir, fmt.Sprintf("gateway-%s.yaml", cfg.NodeID)) - - // Build Gateway YAML config - type gatewayYAMLConfig struct { - ListenAddr string `yaml:"listen_addr"` - ClientNamespace string `yaml:"client_namespace"` - RQLiteDSN string `yaml:"rqlite_dsn"` - GlobalRQLiteDSN string `yaml:"global_rqlite_dsn,omitempty"` - DomainName string `yaml:"domain_name"` - OlricServers []string `yaml:"olric_servers"` - OlricTimeout string `yaml:"olric_timeout"` - IPFSClusterAPIURL string `yaml:"ipfs_cluster_api_url"` - IPFSAPIURL string `yaml:"ipfs_api_url"` - IPFSTimeout string `yaml:"ipfs_timeout"` - IPFSReplicationFactor int `yaml:"ipfs_replication_factor"` - } - - gatewayConfig := gatewayYAMLConfig{ - ListenAddr: fmt.Sprintf(":%d", cfg.HTTPPort), - ClientNamespace: cfg.Namespace, - RQLiteDSN: cfg.RQLiteDSN, - GlobalRQLiteDSN: cfg.GlobalRQLiteDSN, - DomainName: cfg.BaseDomain, - OlricServers: cfg.OlricServers, - OlricTimeout: cfg.OlricTimeout.String(), - IPFSClusterAPIURL: cfg.IPFSClusterAPIURL, - IPFSAPIURL: cfg.IPFSAPIURL, - IPFSTimeout: cfg.IPFSTimeout.String(), - IPFSReplicationFactor: cfg.IPFSReplicationFactor, - } - - configBytes, err := yaml.Marshal(gatewayConfig) - if err != nil { - return fmt.Errorf("failed to marshal Gateway config: %w", err) - } - - if err := os.WriteFile(configPath, configBytes, 0644); err != nil { - return fmt.Errorf("failed to write Gateway config: %w", err) - } - - // Start via systemd + // SystemdSpawner now handles config file creation return cm.systemdSpawner.SpawnGateway(ctx, cfg.Namespace, cfg.NodeID, cfg) } diff --git a/pkg/namespace/systemd_spawner.go b/pkg/namespace/systemd_spawner.go index 2c2183c..570d355 100644 --- a/pkg/namespace/systemd_spawner.go +++ b/pkg/namespace/systemd_spawner.go @@ -3,6 +3,8 @@ package namespace import ( "context" "fmt" + "os" + "path/filepath" "time" "github.com/DeBrosOfficial/network/pkg/gateway" @@ -10,6 +12,7 @@ import ( "github.com/DeBrosOfficial/network/pkg/rqlite" "github.com/DeBrosOfficial/network/pkg/systemd" "go.uber.org/zap" + "gopkg.in/yaml.v3" ) // SystemdSpawner spawns namespace cluster processes using systemd services @@ -80,10 +83,62 @@ func (s *SystemdSpawner) SpawnOlric(ctx context.Context, namespace, nodeID strin zap.String("namespace", namespace), zap.String("node_id", nodeID)) - // Generate environment file (Olric uses OLRIC_SERVER_CONFIG env var, set in systemd unit) + // Create config directory + configDir := filepath.Join(s.namespaceBase, namespace, "configs") + if err := os.MkdirAll(configDir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + configPath := filepath.Join(configDir, fmt.Sprintf("olric-%s.yaml", nodeID)) + + // Generate Olric YAML config + type olricServerConfig struct { + BindAddr string `yaml:"bindAddr"` + BindPort int `yaml:"bindPort"` + } + type olricMemberlistConfig struct { + Environment string `yaml:"environment"` + BindAddr string `yaml:"bindAddr"` + BindPort int `yaml:"bindPort"` + Peers []string `yaml:"peers,omitempty"` + } + type olricConfig struct { + Server olricServerConfig `yaml:"server"` + Memberlist olricMemberlistConfig `yaml:"memberlist"` + PartitionCount uint64 `yaml:"partitionCount"` + } + + config := olricConfig{ + Server: olricServerConfig{ + BindAddr: cfg.BindAddr, + BindPort: cfg.HTTPPort, + }, + Memberlist: olricMemberlistConfig{ + Environment: "lan", + BindAddr: cfg.BindAddr, + BindPort: cfg.MemberlistPort, + Peers: cfg.PeerAddresses, + }, + PartitionCount: 12, // Optimized for namespace clusters (vs 256 default) + } + + configBytes, err := yaml.Marshal(config) + if err != nil { + return fmt.Errorf("failed to marshal Olric config: %w", err) + } + + if err := os.WriteFile(configPath, configBytes, 0644); err != nil { + return fmt.Errorf("failed to write Olric config: %w", err) + } + + s.logger.Info("Created Olric config file", + zap.String("path", configPath), + zap.String("namespace", namespace), + zap.String("node_id", nodeID)) + + // Generate environment file with Olric config path envVars := map[string]string{ - // No additional env vars needed - Olric reads from config file - // The config file path is set in the systemd unit file + "OLRIC_SERVER_CONFIG": configPath, } if err := s.systemdMgr.GenerateEnvFile(namespace, nodeID, systemd.ServiceTypeOlric, envVars); err != nil { @@ -113,9 +168,60 @@ func (s *SystemdSpawner) SpawnGateway(ctx context.Context, namespace, nodeID str zap.String("namespace", namespace), zap.String("node_id", nodeID)) - // Generate environment file + // Create config directory + configDir := filepath.Join(s.namespaceBase, namespace, "configs") + if err := os.MkdirAll(configDir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + configPath := filepath.Join(configDir, fmt.Sprintf("gateway-%s.yaml", nodeID)) + + // Build Gateway YAML config + type gatewayYAMLConfig struct { + ListenAddr string `yaml:"listen_addr"` + ClientNamespace string `yaml:"client_namespace"` + RQLiteDSN string `yaml:"rqlite_dsn"` + GlobalRQLiteDSN string `yaml:"global_rqlite_dsn,omitempty"` + DomainName string `yaml:"domain_name"` + OlricServers []string `yaml:"olric_servers"` + OlricTimeout string `yaml:"olric_timeout"` + IPFSClusterAPIURL string `yaml:"ipfs_cluster_api_url"` + IPFSAPIURL string `yaml:"ipfs_api_url"` + IPFSTimeout string `yaml:"ipfs_timeout"` + IPFSReplicationFactor int `yaml:"ipfs_replication_factor"` + } + + gatewayConfig := gatewayYAMLConfig{ + ListenAddr: fmt.Sprintf(":%d", cfg.HTTPPort), + ClientNamespace: cfg.Namespace, + RQLiteDSN: cfg.RQLiteDSN, + GlobalRQLiteDSN: cfg.GlobalRQLiteDSN, + DomainName: cfg.BaseDomain, + OlricServers: cfg.OlricServers, + OlricTimeout: cfg.OlricTimeout.String(), + IPFSClusterAPIURL: cfg.IPFSClusterAPIURL, + IPFSAPIURL: cfg.IPFSAPIURL, + IPFSTimeout: cfg.IPFSTimeout.String(), + IPFSReplicationFactor: cfg.IPFSReplicationFactor, + } + + configBytes, err := yaml.Marshal(gatewayConfig) + if err != nil { + return fmt.Errorf("failed to marshal Gateway config: %w", err) + } + + if err := os.WriteFile(configPath, configBytes, 0644); err != nil { + return fmt.Errorf("failed to write Gateway config: %w", err) + } + + s.logger.Info("Created Gateway config file", + zap.String("path", configPath), + zap.String("namespace", namespace), + zap.String("node_id", nodeID)) + + // Generate environment file with Gateway config path envVars := map[string]string{ - // Gateway uses config file, no additional env vars needed + "GATEWAY_CONFIG": configPath, } if err := s.systemdMgr.GenerateEnvFile(namespace, nodeID, systemd.ServiceTypeGateway, envVars); err != nil { diff --git a/pkg/olric/client.go b/pkg/olric/client.go index 1e63432..5492c5a 100644 --- a/pkg/olric/client.go +++ b/pkg/olric/client.go @@ -6,6 +6,7 @@ import ( "time" olriclib "github.com/olric-data/olric" + "github.com/olric-data/olric/config" "go.uber.org/zap" ) @@ -33,14 +34,23 @@ func NewClient(cfg Config, logger *zap.Logger) (*Client, error) { servers = []string{"localhost:3320"} } - client, err := olriclib.NewClusterClient(servers) - if err != nil { - return nil, fmt.Errorf("failed to create Olric cluster client: %w", err) - } - timeout := cfg.Timeout if timeout == 0 { - timeout = 10 * time.Second + timeout = 30 * time.Second // Increased default timeout for slow SCAN operations + } + + // Configure client with increased timeouts for slow operations + clientCfg := &config.Client{ + DialTimeout: 5 * time.Second, + ReadTimeout: timeout, // 30s default - enough for slow SCAN operations + WriteTimeout: timeout, + MaxRetries: 1, // Reduce retries to 1 to avoid excessive delays + Authentication: &config.Authentication{}, // Initialize to prevent nil pointer + } + + client, err := olriclib.NewClusterClient(servers, olriclib.WithConfig(clientCfg)) + if err != nil { + return nil, fmt.Errorf("failed to create Olric cluster client: %w", err) } return &Client{ diff --git a/pkg/systemd/manager.go b/pkg/systemd/manager.go index 4c936a1..78a0f5f 100644 --- a/pkg/systemd/manager.go +++ b/pkg/systemd/manager.go @@ -47,12 +47,24 @@ func (m *Manager) StartService(namespace string, serviceType ServiceType) error zap.String("service", svcName), zap.String("namespace", namespace)) - cmd := exec.Command("systemctl", "start", svcName) - if output, err := cmd.CombinedOutput(); err != nil { + cmd := exec.Command("sudo", "-n", "systemctl", "start", svcName) + m.logger.Debug("Executing systemctl command", + zap.String("cmd", cmd.String()), + zap.Strings("args", cmd.Args)) + + output, err := cmd.CombinedOutput() + if err != nil { + m.logger.Error("Failed to start service", + zap.String("service", svcName), + zap.Error(err), + zap.String("output", string(output)), + zap.String("cmd", cmd.String())) return fmt.Errorf("failed to start %s: %w\nOutput: %s", svcName, err, string(output)) } - m.logger.Info("Service started successfully", zap.String("service", svcName)) + m.logger.Info("Service started successfully", + zap.String("service", svcName), + zap.String("output", string(output))) return nil } @@ -63,7 +75,7 @@ func (m *Manager) StopService(namespace string, serviceType ServiceType) error { zap.String("service", svcName), zap.String("namespace", namespace)) - cmd := exec.Command("systemctl", "stop", svcName) + cmd := exec.Command("sudo", "-n", "systemctl", "stop", svcName) if output, err := cmd.CombinedOutput(); err != nil { // Don't error if service is already stopped or doesn't exist if strings.Contains(string(output), "not loaded") || strings.Contains(string(output), "inactive") { @@ -84,7 +96,7 @@ func (m *Manager) RestartService(namespace string, serviceType ServiceType) erro zap.String("service", svcName), zap.String("namespace", namespace)) - cmd := exec.Command("systemctl", "restart", svcName) + cmd := exec.Command("sudo", "-n", "systemctl", "restart", svcName) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to restart %s: %w\nOutput: %s", svcName, err, string(output)) } @@ -100,7 +112,7 @@ func (m *Manager) EnableService(namespace string, serviceType ServiceType) error zap.String("service", svcName), zap.String("namespace", namespace)) - cmd := exec.Command("systemctl", "enable", svcName) + cmd := exec.Command("sudo", "-n", "systemctl", "enable", svcName) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to enable %s: %w\nOutput: %s", svcName, err, string(output)) } @@ -116,7 +128,7 @@ func (m *Manager) DisableService(namespace string, serviceType ServiceType) erro zap.String("service", svcName), zap.String("namespace", namespace)) - cmd := exec.Command("systemctl", "disable", svcName) + cmd := exec.Command("sudo", "-n", "systemctl", "disable", svcName) if output, err := cmd.CombinedOutput(); err != nil { // Don't error if service is already disabled or doesn't exist if strings.Contains(string(output), "not loaded") { @@ -133,24 +145,47 @@ func (m *Manager) DisableService(namespace string, serviceType ServiceType) erro // IsServiceActive checks if a namespace service is active func (m *Manager) IsServiceActive(namespace string, serviceType ServiceType) (bool, error) { svcName := m.serviceName(namespace, serviceType) - cmd := exec.Command("systemctl", "is-active", svcName) + cmd := exec.Command("sudo", "-n", "systemctl", "is-active", svcName) output, err := cmd.CombinedOutput() + outputStr := strings.TrimSpace(string(output)) + m.logger.Debug("Checking service status", + zap.String("service", svcName), + zap.String("status", outputStr), + zap.Error(err)) + if err != nil { - // is-active returns exit code 3 if service is inactive - if strings.TrimSpace(string(output)) == "inactive" || strings.TrimSpace(string(output)) == "failed" { + // is-active returns exit code 3 if service is inactive/activating + if outputStr == "inactive" || outputStr == "failed" { + m.logger.Debug("Service is not active", + zap.String("service", svcName), + zap.String("status", outputStr)) return false, nil } - return false, fmt.Errorf("failed to check service status: %w\nOutput: %s", err, string(output)) + // "activating" means the service is starting - return false to wait longer, but no error + if outputStr == "activating" { + m.logger.Debug("Service is still activating", + zap.String("service", svcName)) + return false, nil + } + m.logger.Error("Failed to check service status", + zap.String("service", svcName), + zap.Error(err), + zap.String("output", outputStr)) + return false, fmt.Errorf("failed to check service status: %w\nOutput: %s", err, outputStr) } - return strings.TrimSpace(string(output)) == "active", nil + isActive := outputStr == "active" + m.logger.Debug("Service status check complete", + zap.String("service", svcName), + zap.Bool("active", isActive)) + return isActive, nil } // ReloadDaemon reloads systemd daemon configuration func (m *Manager) ReloadDaemon() error { m.logger.Info("Reloading systemd daemon") - cmd := exec.Command("systemctl", "daemon-reload") + cmd := exec.Command("sudo", "-n", "systemctl", "daemon-reload") if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to reload systemd daemon: %w\nOutput: %s", err, string(output)) } @@ -193,7 +228,7 @@ func (m *Manager) StartAllNamespaceServices(namespace string) error { // ListNamespaceServices returns all namespace services currently registered in systemd func (m *Manager) ListNamespaceServices() ([]string, error) { - cmd := exec.Command("systemctl", "list-units", "--all", "--no-legend", "debros-namespace-*@*.service") + cmd := exec.Command("sudo", "-n", "systemctl", "list-units", "--all", "--no-legend", "debros-namespace-*@*.service") output, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("failed to list namespace services: %w\nOutput: %s", err, string(output)) @@ -225,7 +260,7 @@ func (m *Manager) StopAllNamespaceServicesGlobally() error { for _, svc := range services { m.logger.Info("Stopping service", zap.String("service", svc)) - cmd := exec.Command("systemctl", "stop", svc) + cmd := exec.Command("sudo", "-n", "systemctl", "stop", svc) if output, err := cmd.CombinedOutput(); err != nil { m.logger.Warn("Failed to stop service", zap.String("service", svc), @@ -258,7 +293,13 @@ func (m *Manager) CleanupOrphanedProcesses() error { // GenerateEnvFile creates the environment file for a namespace service func (m *Manager) GenerateEnvFile(namespace, nodeID string, serviceType ServiceType, envVars map[string]string) error { envDir := filepath.Join(m.namespaceBase, namespace) + m.logger.Debug("Creating env directory", + zap.String("dir", envDir)) + if err := os.MkdirAll(envDir, 0755); err != nil { + m.logger.Error("Failed to create env directory", + zap.String("dir", envDir), + zap.Error(err)) return fmt.Errorf("failed to create env directory: %w", err) } @@ -278,7 +319,14 @@ func (m *Manager) GenerateEnvFile(namespace, nodeID string, serviceType ServiceT content.WriteString(fmt.Sprintf("%s=%s\n", key, value)) } + m.logger.Debug("Writing env file", + zap.String("file", envFile), + zap.Int("size", content.Len())) + if err := os.WriteFile(envFile, []byte(content.String()), 0644); err != nil { + m.logger.Error("Failed to write env file", + zap.String("file", envFile), + zap.Error(err)) return fmt.Errorf("failed to write env file: %w", err) } diff --git a/scripts/extract-deploy.sh b/scripts/extract-deploy.sh index 81ac928..b9401c6 100755 --- a/scripts/extract-deploy.sh +++ b/scripts/extract-deploy.sh @@ -49,21 +49,25 @@ if [ -d "$SRC_DIR/bin-linux" ]; then for bin in orama-node gateway identity rqlite-mcp olric-server orama; do if [ -f "$SRC_DIR/bin-linux/$bin" ]; then - cp "$SRC_DIR/bin-linux/$bin" "$BIN_DIR/$bin" - chmod +x "$BIN_DIR/$bin" + # Atomic rename: copy to temp, then move (works even if binary is running) + cp "$SRC_DIR/bin-linux/$bin" "$BIN_DIR/$bin.tmp" + chmod +x "$BIN_DIR/$bin.tmp" + mv -f "$BIN_DIR/$bin.tmp" "$BIN_DIR/$bin" echo " ✓ $bin → $BIN_DIR/$bin" fi done if [ -f "$SRC_DIR/bin-linux/coredns" ]; then - cp "$SRC_DIR/bin-linux/coredns" /usr/local/bin/coredns - chmod +x /usr/local/bin/coredns + cp "$SRC_DIR/bin-linux/coredns" /usr/local/bin/coredns.tmp + chmod +x /usr/local/bin/coredns.tmp + mv -f /usr/local/bin/coredns.tmp /usr/local/bin/coredns echo " ✓ coredns → /usr/local/bin/coredns" fi if [ -f "$SRC_DIR/bin-linux/caddy" ]; then - cp "$SRC_DIR/bin-linux/caddy" /usr/bin/caddy - chmod +x /usr/bin/caddy + cp "$SRC_DIR/bin-linux/caddy" /usr/bin/caddy.tmp + chmod +x /usr/bin/caddy.tmp + mv -f /usr/bin/caddy.tmp /usr/bin/caddy echo " ✓ caddy → /usr/bin/caddy" fi diff --git a/systemd/debros-namespace-gateway@.service b/systemd/debros-namespace-gateway@.service index f80d85a..1fc46de 100644 --- a/systemd/debros-namespace-gateway@.service +++ b/systemd/debros-namespace-gateway@.service @@ -13,7 +13,8 @@ WorkingDirectory=/home/debros EnvironmentFile=/home/debros/.orama/data/namespaces/%i/gateway.env -ExecStart=/home/debros/bin/gateway --config /home/debros/.orama/data/namespaces/%i/configs/gateway-${NODE_ID}.yaml +# Use shell to properly expand NODE_ID from env file +ExecStart=/bin/sh -c 'exec /home/debros/bin/gateway --config ${GATEWAY_CONFIG}' TimeoutStopSec=30s KillMode=mixed diff --git a/systemd/debros-namespace-olric@.service b/systemd/debros-namespace-olric@.service index 741daf9..c770718 100644 --- a/systemd/debros-namespace-olric@.service +++ b/systemd/debros-namespace-olric@.service @@ -11,11 +11,10 @@ User=debros Group=debros WorkingDirectory=/home/debros -# Olric reads config from environment variable -Environment="OLRIC_SERVER_CONFIG=/home/debros/.orama/data/namespaces/%i/configs/olric-${NODE_ID}.yaml" +# Olric reads config from environment variable (set in env file) EnvironmentFile=/home/debros/.orama/data/namespaces/%i/olric.env -ExecStart=/usr/local/bin/olric-server +ExecStart=/home/debros/bin/olric-server TimeoutStopSec=30s KillMode=mixed diff --git a/systemd/debros-namespace-rqlite@.service b/systemd/debros-namespace-rqlite@.service index 9b0724f..9b1fe2f 100644 --- a/systemd/debros-namespace-rqlite@.service +++ b/systemd/debros-namespace-rqlite@.service @@ -14,14 +14,14 @@ WorkingDirectory=/home/debros # Environment file contains namespace-specific config EnvironmentFile=/home/debros/.orama/data/namespaces/%i/rqlite.env -# Start rqlited with args from environment -ExecStart=/usr/local/bin/rqlited \ +# Start rqlited with args from environment (using shell to properly expand JOIN_ARGS) +ExecStart=/bin/sh -c 'exec /usr/local/bin/rqlited \ -http-addr ${HTTP_ADDR} \ -raft-addr ${RAFT_ADDR} \ -http-adv-addr ${HTTP_ADV_ADDR} \ -raft-adv-addr ${RAFT_ADV_ADDR} \ ${JOIN_ARGS} \ - /home/debros/.orama/data/namespaces/%i/rqlite/${NODE_ID} + /home/debros/.orama/data/namespaces/%i/rqlite/${NODE_ID}' # Graceful shutdown TimeoutStopSec=30s