Fixed system service sudoer error on debros user

This commit is contained in:
anonpenguin23 2026-02-05 13:32:06 +02:00
parent c855b790f8
commit a7f100038d
10 changed files with 265 additions and 138 deletions

View File

@ -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)

View File

@ -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"

View File

@ -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)
}

View File

@ -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 {

View File

@ -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{

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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