Bug fixing

This commit is contained in:
anonpenguin23 2026-02-18 11:20:16 +02:00
parent 83804422c4
commit 4f1709e136
14 changed files with 453 additions and 9 deletions

View File

@ -218,6 +218,37 @@ func (store *EnhancedCredentialStore) SetDefaultCredential(gatewayURL string, in
return true
}
// RemoveCredentialByNamespace removes the credential for a specific namespace from a gateway.
// Returns true if a credential was removed.
func (store *EnhancedCredentialStore) RemoveCredentialByNamespace(gatewayURL, namespace string) bool {
gwCreds := store.Gateways[gatewayURL]
if gwCreds == nil || len(gwCreds.Credentials) == 0 {
return false
}
for i, cred := range gwCreds.Credentials {
if cred.Namespace == namespace {
// Remove this credential from the slice
gwCreds.Credentials = append(gwCreds.Credentials[:i], gwCreds.Credentials[i+1:]...)
// Fix indices if they now point beyond the slice
if len(gwCreds.Credentials) == 0 {
gwCreds.DefaultIndex = 0
gwCreds.LastUsedIndex = 0
} else {
if gwCreds.DefaultIndex >= len(gwCreds.Credentials) {
gwCreds.DefaultIndex = len(gwCreds.Credentials) - 1
}
if gwCreds.LastUsedIndex >= len(gwCreds.Credentials) {
gwCreds.LastUsedIndex = gwCreds.DefaultIndex
}
}
return true
}
}
return false
}
// ClearAllCredentials removes all credentials
func (store *EnhancedCredentialStore) ClearAllCredentials() {
store.Gateways = make(map[string]*GatewayCredentials)

View File

@ -27,6 +27,15 @@ var deleteCmd = &cobra.Command{
},
}
var listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List namespaces owned by the current wallet",
Run: func(cmd *cobra.Command, args []string) {
cli.HandleNamespaceCommand([]string{"list"})
},
}
var repairCmd = &cobra.Command{
Use: "repair <namespace>",
Short: "Repair an under-provisioned namespace cluster",
@ -39,6 +48,7 @@ var repairCmd = &cobra.Command{
func init() {
deleteCmd.Flags().Bool("force", false, "Skip confirmation prompt")
Cmd.AddCommand(listCmd)
Cmd.AddCommand(deleteCmd)
Cmd.AddCommand(repairCmd)
}

View File

@ -29,6 +29,8 @@ func HandleNamespaceCommand(args []string) {
fs.BoolVar(&force, "force", false, "Skip confirmation prompt")
_ = fs.Parse(args[1:])
handleNamespaceDelete(force)
case "list":
handleNamespaceList()
case "repair":
if len(args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: orama namespace repair <namespace_name>\n")
@ -48,12 +50,14 @@ func showNamespaceHelp() {
fmt.Printf("Namespace Management Commands\n\n")
fmt.Printf("Usage: orama namespace <subcommand>\n\n")
fmt.Printf("Subcommands:\n")
fmt.Printf(" list - List namespaces owned by the current wallet\n")
fmt.Printf(" delete - Delete the current namespace and all its resources\n")
fmt.Printf(" repair <namespace> - Repair an under-provisioned namespace cluster (add missing nodes)\n")
fmt.Printf(" help - Show this help message\n\n")
fmt.Printf("Flags:\n")
fmt.Printf(" --force - Skip confirmation prompt (delete only)\n\n")
fmt.Printf("Examples:\n")
fmt.Printf(" orama namespace list\n")
fmt.Printf(" orama namespace delete\n")
fmt.Printf(" orama namespace delete --force\n")
fmt.Printf(" orama namespace repair anchat\n")
@ -122,10 +126,12 @@ func handleNamespaceDelete(force bool) {
// Confirm deletion
if !force {
fmt.Printf("This will permanently delete namespace '%s' and all its resources:\n", namespace)
fmt.Printf(" - All deployments and their processes\n")
fmt.Printf(" - RQLite cluster (3 nodes)\n")
fmt.Printf(" - Olric cache cluster (3 nodes)\n")
fmt.Printf(" - Gateway instances\n")
fmt.Printf(" - API keys and credentials\n\n")
fmt.Printf(" - API keys and credentials\n")
fmt.Printf(" - IPFS content and DNS records\n\n")
fmt.Printf("Type the namespace name to confirm: ")
scanner := bufio.NewScanner(os.Stdin)
@ -174,5 +180,88 @@ func handleNamespaceDelete(force bool) {
}
fmt.Printf("Namespace '%s' deleted successfully.\n", namespace)
// Clean up local credentials for the deleted namespace
if store.RemoveCredentialByNamespace(gatewayURL, namespace) {
if err := store.Save(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: failed to clean up local credentials: %v\n", err)
} else {
fmt.Printf("Local credentials for '%s' cleared.\n", namespace)
}
}
fmt.Printf("Run 'orama auth login' to create a new namespace.\n")
}
func handleNamespaceList() {
// Load credentials
store, err := auth.LoadEnhancedCredentials()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load credentials: %v\n", err)
os.Exit(1)
}
gatewayURL := getGatewayURL()
creds := store.GetDefaultCredential(gatewayURL)
if creds == nil || !creds.IsValid() {
fmt.Fprintf(os.Stderr, "Not authenticated. Run 'orama auth login' first.\n")
os.Exit(1)
}
// Make GET request to namespace list endpoint
url := fmt.Sprintf("%s/v1/namespace/list", gatewayURL)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+creds.APIKey)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
resp, err := client.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to gateway: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != http.StatusOK {
errMsg := "unknown error"
if e, ok := result["error"].(string); ok {
errMsg = e
}
fmt.Fprintf(os.Stderr, "Failed to list namespaces: %s\n", errMsg)
os.Exit(1)
}
namespaces, _ := result["namespaces"].([]interface{})
if len(namespaces) == 0 {
fmt.Println("No namespaces found.")
return
}
activeNS := creds.Namespace
fmt.Printf("Namespaces (%d):\n\n", len(namespaces))
for _, ns := range namespaces {
nsMap, _ := ns.(map[string]interface{})
name, _ := nsMap["name"].(string)
status, _ := nsMap["cluster_status"].(string)
marker := " "
if name == activeNS {
marker = "* "
}
fmt.Printf("%s%-20s cluster: %s\n", marker, name, status)
}
fmt.Printf("\n* = active namespace\n")
}

View File

@ -10,7 +10,7 @@ import (
// DownloadFile downloads a file from a URL to a destination path
func DownloadFile(url, dest string) error {
cmd := exec.Command("wget", "-q", url, "-O", dest)
cmd := exec.Command("wget", "-q", "-4", url, "-O", dest)
if err := cmd.Run(); err != nil {
return fmt.Errorf("download failed: %w", err)
}

View File

@ -149,6 +149,28 @@ func (ps *ProductionSetup) IsAnyoneClient() bool {
return ps.isAnyoneClient
}
// disableConflictingAnyoneService stops, disables, and removes a conflicting
// Anyone service file. A node must run either relay or client, never both.
// This is best-effort: errors are logged but do not abort the operation.
func (ps *ProductionSetup) disableConflictingAnyoneService(serviceName string) {
unitPath := filepath.Join("/etc/systemd/system", serviceName)
if _, err := os.Stat(unitPath); os.IsNotExist(err) {
return // Nothing to clean up
}
ps.logf(" Removing conflicting Anyone service: %s", serviceName)
if err := ps.serviceController.StopService(serviceName); err != nil {
ps.logf(" ⚠️ Warning: failed to stop %s: %v", serviceName, err)
}
if err := ps.serviceController.DisableService(serviceName); err != nil {
ps.logf(" ⚠️ Warning: failed to disable %s: %v", serviceName, err)
}
if err := ps.serviceController.RemoveServiceUnit(serviceName); err != nil {
ps.logf(" ⚠️ Warning: failed to remove %s: %v", serviceName, err)
}
}
// Phase1CheckPrerequisites performs initial environment validation
func (ps *ProductionSetup) Phase1CheckPrerequisites() error {
ps.logf("Phase 1: Checking prerequisites...")
@ -605,18 +627,27 @@ func (ps *ProductionSetup) Phase5CreateSystemdServices(enableHTTPS bool) error {
ps.logf(" ✓ Node service created: orama-node.service (with embedded gateway)")
// Anyone Relay service (only created when --anyone-relay flag is used)
// A node must run EITHER relay OR client, never both. When writing one
// mode's service, we remove the other to prevent conflicts (they share
// the same anon binary and would fight over ports).
if ps.IsAnyoneRelay() {
anyoneUnit := ps.serviceGenerator.GenerateAnyoneRelayService()
if err := ps.serviceController.WriteServiceUnit("orama-anyone-relay.service", anyoneUnit); err != nil {
return fmt.Errorf("failed to write Anyone Relay service: %w", err)
}
ps.logf(" ✓ Anyone Relay service created (operator mode, ORPort: %d)", ps.anyoneRelayConfig.ORPort)
ps.disableConflictingAnyoneService("orama-anyone-client.service")
} else if ps.IsAnyoneClient() {
anyoneUnit := ps.serviceGenerator.GenerateAnyoneClientService()
if err := ps.serviceController.WriteServiceUnit("orama-anyone-client.service", anyoneUnit); err != nil {
return fmt.Errorf("failed to write Anyone client service: %w", err)
}
ps.logf(" ✓ Anyone client service created (SocksPort 9050)")
ps.disableConflictingAnyoneService("orama-anyone-relay.service")
} else {
// Neither mode configured — clean up both
ps.disableConflictingAnyoneService("orama-anyone-client.service")
ps.disableConflictingAnyoneService("orama-anyone-relay.service")
}
// CoreDNS service (only for nameserver nodes)

View File

@ -433,6 +433,24 @@ func (sc *SystemdController) StopService(name string) error {
return nil
}
// DisableService disables a service from starting on boot
func (sc *SystemdController) DisableService(name string) error {
cmd := exec.Command("systemctl", "disable", name)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to disable service %s: %w", name, err)
}
return nil
}
// RemoveServiceUnit removes a systemd unit file from disk
func (sc *SystemdController) RemoveServiceUnit(name string) error {
unitPath := filepath.Join(sc.systemdDir, name)
if err := os.Remove(unitPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove unit file %s: %w", name, err)
}
return nil
}
// StatusService gets the status of a service
func (sc *SystemdController) StatusService(name string) (bool, error) {
cmd := exec.Command("systemctl", "is-active", "--quiet", name)

View File

@ -136,6 +136,9 @@ type Gateway struct {
// Namespace delete handler
namespaceDeleteHandler http.Handler
// Namespace list handler
namespaceListHandler http.Handler
// Peer discovery for namespace gateways (libp2p mesh formation)
peerDiscovery *PeerDiscovery
@ -630,11 +633,21 @@ func (g *Gateway) SetNamespaceDeleteHandler(h http.Handler) {
g.namespaceDeleteHandler = h
}
// SetNamespaceListHandler sets the handler for namespace list requests.
func (g *Gateway) SetNamespaceListHandler(h http.Handler) {
g.namespaceListHandler = h
}
// GetORMClient returns the RQLite ORM client for external use (e.g., by ClusterManager)
func (g *Gateway) GetORMClient() rqlite.Client {
return g.ormClient
}
// GetIPFSClient returns the IPFS client for external use (e.g., by namespace delete handler)
func (g *Gateway) GetIPFSClient() ipfs.IPFSClient {
return g.ipfsClient
}
// setOlricClient atomically sets the Olric client and reinitializes cache handlers.
func (g *Gateway) setOlricClient(client *olric.Client) {
g.olricMu.Lock()

View File

@ -3,9 +3,11 @@ package namespace
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
"github.com/DeBrosOfficial/network/pkg/ipfs"
"github.com/DeBrosOfficial/network/pkg/rqlite"
"go.uber.org/zap"
)
@ -19,14 +21,16 @@ type NamespaceDeprovisioner interface {
type DeleteHandler struct {
deprovisioner NamespaceDeprovisioner
ormClient rqlite.Client
ipfsClient ipfs.IPFSClient // can be nil
logger *zap.Logger
}
// NewDeleteHandler creates a new delete handler
func NewDeleteHandler(dp NamespaceDeprovisioner, orm rqlite.Client, logger *zap.Logger) *DeleteHandler {
func NewDeleteHandler(dp NamespaceDeprovisioner, orm rqlite.Client, ipfsClient ipfs.IPFSClient, logger *zap.Logger) *DeleteHandler {
return &DeleteHandler{
deprovisioner: dp,
ormClient: orm,
ipfsClient: ipfsClient,
logger: logger.With(zap.String("component", "namespace-delete-handler")),
}
}
@ -80,14 +84,20 @@ func (h *DeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
zap.Int64("namespace_id", namespaceID),
)
// Deprovision the cluster (stops processes, deallocates ports, deletes DB records)
// 1. Deprovision the cluster (stops infra + deployment processes, deallocates ports, deletes DNS)
if err := h.deprovisioner.DeprovisionCluster(r.Context(), namespaceID); err != nil {
h.logger.Error("Failed to deprovision cluster", zap.Error(err))
writeDeleteResponse(w, http.StatusInternalServerError, map[string]interface{}{"error": err.Error()})
return
}
// Delete API keys, ownership records, and namespace record
// 2. Unpin IPFS content (must run before global table cleanup to read CID list)
h.unpinNamespaceContent(r.Context(), ns)
// 3. Clean up global tables that use namespace TEXT (not FK cascade)
h.cleanupGlobalTables(r.Context(), ns)
// 4. Delete API keys, ownership records, and namespace record (FK cascade handles children)
h.ormClient.Exec(r.Context(), "DELETE FROM wallet_api_keys WHERE namespace_id = ?", namespaceID)
h.ormClient.Exec(r.Context(), "DELETE FROM api_keys WHERE namespace_id = ?", namespaceID)
h.ormClient.Exec(r.Context(), "DELETE FROM namespace_ownership WHERE namespace_id = ?", namespaceID)
@ -101,6 +111,71 @@ func (h *DeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
})
}
// unpinNamespaceContent unpins all IPFS content owned by the namespace.
// Best-effort: individual failures are logged but do not abort deletion.
func (h *DeleteHandler) unpinNamespaceContent(ctx context.Context, ns string) {
if h.ipfsClient == nil {
h.logger.Debug("IPFS client not available, skipping IPFS cleanup")
return
}
type cidRow struct {
CID string `db:"cid"`
}
var rows []cidRow
if err := h.ormClient.Query(ctx, &rows,
"SELECT cid FROM ipfs_content_ownership WHERE namespace = ?", ns); err != nil {
h.logger.Warn("Failed to query IPFS content for namespace",
zap.String("namespace", ns), zap.Error(err))
return
}
if len(rows) == 0 {
return
}
h.logger.Info("Unpinning IPFS content for namespace",
zap.String("namespace", ns),
zap.Int("cid_count", len(rows)))
for _, row := range rows {
if err := h.ipfsClient.Unpin(ctx, row.CID); err != nil {
h.logger.Warn("Failed to unpin CID (best-effort)",
zap.String("cid", row.CID),
zap.String("namespace", ns),
zap.Error(err))
}
}
}
// cleanupGlobalTables deletes orphaned records from global tables that reference
// the namespace by TEXT name (not by integer FK, so CASCADE doesn't help).
// Best-effort: individual failures are logged but do not abort deletion.
func (h *DeleteHandler) cleanupGlobalTables(ctx context.Context, ns string) {
tables := []struct {
table string
column string
}{
{"global_deployment_subdomains", "namespace"},
{"ipfs_content_ownership", "namespace"},
{"functions", "namespace"},
{"function_secrets", "namespace"},
{"namespace_sqlite_databases", "namespace"},
{"namespace_quotas", "namespace"},
{"home_node_assignments", "namespace"},
}
for _, t := range tables {
query := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", t.table, t.column)
if _, err := h.ormClient.Exec(ctx, query, ns); err != nil {
h.logger.Warn("Failed to clean up global table (best-effort)",
zap.String("table", t.table),
zap.String("namespace", ns),
zap.Error(err))
}
}
}
func writeDeleteResponse(w http.ResponseWriter, status int, resp map[string]interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)

View File

@ -0,0 +1,91 @@
package namespace
import (
"encoding/json"
"net/http"
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
"github.com/DeBrosOfficial/network/pkg/rqlite"
"go.uber.org/zap"
)
// ListHandler handles namespace list requests
type ListHandler struct {
ormClient rqlite.Client
logger *zap.Logger
}
// NewListHandler creates a new namespace list handler
func NewListHandler(orm rqlite.Client, logger *zap.Logger) *ListHandler {
return &ListHandler{
ormClient: orm,
logger: logger.With(zap.String("component", "namespace-list-handler")),
}
}
// ServeHTTP handles GET /v1/namespace/list
func (h *ListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeListResponse(w, http.StatusMethodNotAllowed, map[string]interface{}{"error": "method not allowed"})
return
}
// Get current namespace from auth context
ns := ""
if v := r.Context().Value(ctxkeys.NamespaceOverride); v != nil {
if s, ok := v.(string); ok {
ns = s
}
}
if ns == "" {
writeListResponse(w, http.StatusUnauthorized, map[string]interface{}{"error": "not authenticated"})
return
}
// Look up the owner wallet from the current namespace
type ownerRow struct {
OwnerID string `db:"owner_id"`
}
var owners []ownerRow
if err := h.ormClient.Query(r.Context(), &owners,
`SELECT owner_id FROM namespace_ownership
WHERE namespace_id = (SELECT id FROM namespaces WHERE name = ? LIMIT 1)
LIMIT 1`, ns); err != nil || len(owners) == 0 {
h.logger.Warn("Failed to resolve namespace owner",
zap.String("namespace", ns), zap.Error(err))
writeListResponse(w, http.StatusInternalServerError, map[string]interface{}{"error": "failed to resolve namespace owner"})
return
}
ownerID := owners[0].OwnerID
// Query all namespaces owned by this wallet
type nsRow struct {
Name string `db:"name" json:"name"`
CreatedAt string `db:"created_at" json:"created_at"`
ClusterStatus string `db:"cluster_status" json:"cluster_status"`
}
var namespaces []nsRow
if err := h.ormClient.Query(r.Context(), &namespaces,
`SELECT n.name, n.created_at, COALESCE(nc.status, 'none') as cluster_status
FROM namespaces n
JOIN namespace_ownership no2 ON no2.namespace_id = n.id
LEFT JOIN namespace_clusters nc ON nc.namespace_id = n.id
WHERE no2.owner_id = ?
ORDER BY n.created_at DESC`, ownerID); err != nil {
h.logger.Error("Failed to list namespaces", zap.Error(err))
writeListResponse(w, http.StatusInternalServerError, map[string]interface{}{"error": "failed to list namespaces"})
return
}
writeListResponse(w, http.StatusOK, map[string]interface{}{
"namespaces": namespaces,
"count": len(namespaces),
})
}
func writeListResponse(w http.ResponseWriter, status int, resp map[string]interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(resp)
}

View File

@ -81,6 +81,11 @@ func (g *Gateway) Routes() http.Handler {
mux.Handle("/v1/namespace/delete", g.namespaceDeleteHandler)
}
// namespace list (authenticated — lists namespaces owned by the current wallet)
if g.namespaceListHandler != nil {
mux.Handle("/v1/namespace/list", g.namespaceListHandler)
}
// network
mux.HandleFunc("/v1/network/status", g.networkStatusHandler)
mux.HandleFunc("/v1/network/peers", g.networkPeersHandler)

View File

@ -306,11 +306,15 @@ func (s *SystemdSpawner) SaveClusterState(namespace string, data []byte) error {
return nil
}
// StopAll stops all services for a namespace
// StopAll stops all services for a namespace, including deployment processes
func (s *SystemdSpawner) StopAll(ctx context.Context, namespace string) error {
s.logger.Info("Stopping all namespace services via systemd",
zap.String("namespace", namespace))
// Stop deployment processes first (they depend on the cluster services)
s.systemdMgr.StopDeploymentServicesForNamespace(namespace)
// Then stop infrastructure services (Gateway → Olric → RQLite)
return s.systemdMgr.StopAllNamespaceServices(namespace)
}

View File

@ -88,10 +88,14 @@ func (n *Node) startHTTPGateway(ctx context.Context) error {
spawnHandler := namespacehandlers.NewSpawnHandler(systemdSpawner, n.logger.Logger)
apiGateway.SetSpawnHandler(spawnHandler)
// Wire namespace delete handler
deleteHandler := namespacehandlers.NewDeleteHandler(clusterManager, ormClient, n.logger.Logger)
// Wire namespace delete handler (with IPFS client for content unpinning)
deleteHandler := namespacehandlers.NewDeleteHandler(clusterManager, ormClient, apiGateway.GetIPFSClient(), n.logger.Logger)
apiGateway.SetNamespaceDeleteHandler(deleteHandler)
// Wire namespace list handler
nsListHandler := namespacehandlers.NewListHandler(ormClient, n.logger.Logger)
apiGateway.SetNamespaceListHandler(nsListHandler)
n.logger.ComponentInfo(logging.ComponentNode, "Namespace cluster provisioning enabled",
zap.String("base_domain", clusterCfg.BaseDomain),
zap.String("base_data_dir", baseDataDir))

View File

@ -273,6 +273,79 @@ func (m *Manager) StopAllNamespaceServicesGlobally() error {
return nil
}
// StopDeploymentServicesForNamespace stops all deployment systemd units for a given namespace.
// Deployment units follow the naming pattern: orama-deploy-{namespace}-{name}.service
// (with dots replaced by hyphens, matching process/manager.go:getServiceName).
// This is best-effort: individual failures are logged but do not abort the operation.
func (m *Manager) StopDeploymentServicesForNamespace(namespace string) {
// Match the sanitization from deployments/process/manager.go:getServiceName
sanitizedNS := strings.ReplaceAll(namespace, ".", "-")
pattern := fmt.Sprintf("orama-deploy-%s-*", sanitizedNS)
m.logger.Info("Stopping deployment services for namespace",
zap.String("namespace", namespace),
zap.String("pattern", pattern))
cmd := exec.Command("systemctl", "list-units", "--type=service", "--all", "--no-pager", "--no-legend", pattern)
output, err := cmd.CombinedOutput()
if err != nil {
m.logger.Warn("Failed to list deployment services",
zap.String("namespace", namespace),
zap.Error(err))
return
}
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
stopped := 0
for _, line := range lines {
if line == "" {
continue
}
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
svc := fields[0]
// Stop the service
if stopOut, stopErr := exec.Command("systemctl", "stop", svc).CombinedOutput(); stopErr != nil {
m.logger.Warn("Failed to stop deployment service",
zap.String("service", svc),
zap.Error(stopErr),
zap.String("output", string(stopOut)))
}
// Disable the service
if disOut, disErr := exec.Command("systemctl", "disable", svc).CombinedOutput(); disErr != nil {
m.logger.Warn("Failed to disable deployment service",
zap.String("service", svc),
zap.Error(disErr),
zap.String("output", string(disOut)))
}
// Remove the service file
serviceFile := filepath.Join(m.systemdDir, svc)
if !strings.HasSuffix(serviceFile, ".service") {
serviceFile += ".service"
}
if rmErr := os.Remove(serviceFile); rmErr != nil && !os.IsNotExist(rmErr) {
m.logger.Warn("Failed to remove deployment service file",
zap.String("file", serviceFile),
zap.Error(rmErr))
}
stopped++
m.logger.Info("Stopped deployment service", zap.String("service", svc))
}
if stopped > 0 {
m.ReloadDaemon()
m.logger.Info("Deployment services cleanup complete",
zap.String("namespace", namespace),
zap.Int("stopped", stopped))
}
}
// CleanupOrphanedProcesses finds and kills any orphaned namespace processes not managed by systemd
// This is for cleaning up after migration from old exec.Command approach
func (m *Manager) CleanupOrphanedProcesses() error {

View File

@ -12,7 +12,7 @@ WorkingDirectory=/opt/orama
# Olric reads config from environment variable (set in env file)
EnvironmentFile=/opt/orama/.orama/data/namespaces/%i/olric.env
ExecStart=/opt/orama/bin/olric-server
ExecStart=/usr/local/bin/olric-server
TimeoutStopSec=30s
KillMode=mixed