diff --git a/packages/cmd/gateway.go b/packages/cmd/gateway.go index 62726ec0..3be2ba82 100644 --- a/packages/cmd/gateway.go +++ b/packages/cmd/gateway.go @@ -571,7 +571,7 @@ var gatewaySystemdCmd = &cobra.Command{ Short: "Manage systemd service for Infisical gateway", Long: "Manage systemd service for Infisical gateway. Use 'systemd install' to install and enable the service.", Example: `sudo infisical gateway systemd install my-gateway --token= --domain= - sudo infisical gateway systemd uninstall`, + sudo infisical gateway systemd uninstall my-gateway`, DisableFlagsInUseLine: true, Args: cobra.NoArgs, } @@ -619,6 +619,8 @@ var gatewaySystemdInstallCmd = &cobra.Command{ enrollMethod, _ := cmd.Flags().GetString("enroll-method") + var installedServiceName string + if enrollMethod == gatewayv2.EnrollMethodToken { // --- Enrollment token path --- enrollToken, flagErr := cmd.Flags().GetString("token") @@ -642,9 +644,11 @@ var gatewaySystemdInstallCmd = &cobra.Command{ } // Install systemd service using the long-lived access token - if installErr := gatewayv2.InstallEnrolledGatewaySystemdService(enrollResp.AccessToken, domain, gatewayName, relayName, serviceLogFile); installErr != nil { + svcName, installErr := gatewayv2.InstallEnrolledGatewaySystemdService(enrollResp.AccessToken, domain, gatewayName, relayName, serviceLogFile) + if installErr != nil { util.HandleError(installErr, "Unable to install systemd service") } + installedServiceName = svcName } else if enrollMethod == gatewayv2.EnrollMethodAws { // --- AWS Auth path --- // Don't perform the AWS login at install time — the gateway does it on each service @@ -656,9 +660,11 @@ var gatewaySystemdInstallCmd = &cobra.Command{ relayName, _ := util.GetRelayName(cmd, false, "") - if installErr := gatewayv2.InstallAwsAuthGatewaySystemdService(gatewayID, domain, gatewayName, relayName, serviceLogFile); installErr != nil { + svcName, installErr := gatewayv2.InstallAwsAuthGatewaySystemdService(gatewayID, domain, gatewayName, relayName, serviceLogFile) + if installErr != nil { util.HandleError(installErr, "Unable to install systemd service") } + installedServiceName = svcName } else { // --- Machine identity token path --- token, tokenErr := util.GetInfisicalToken(cmd) @@ -675,38 +681,51 @@ var gatewaySystemdInstallCmd = &cobra.Command{ util.HandleError(relayErr, "unable to get relay name") } - if installErr := gatewayv2.InstallGatewaySystemdService(token.Token, domain, gatewayName, relayName, serviceLogFile); installErr != nil { + svcName, installErr := gatewayv2.InstallGatewaySystemdService(token.Token, domain, gatewayName, relayName, serviceLogFile) + if installErr != nil { util.HandleError(installErr, "Unable to install systemd service") } + installedServiceName = svcName } - enableCmd := exec.Command("systemctl", "enable", "infisical-gateway") + if installedServiceName == "" { + return + } + + enableCmd := exec.Command("systemctl", "enable", installedServiceName) if err := enableCmd.Run(); err != nil { util.HandleError(err, "Failed to enable systemd service") } - log.Info().Msg("Successfully installed and enabled infisical-gateway service") - log.Info().Msg("To start the service, run: sudo systemctl start infisical-gateway") + log.Info().Msgf("Successfully installed and enabled %s service", installedServiceName) + log.Info().Msgf("To start the service, run: sudo systemctl start %s", installedServiceName) }, } var gatewaySystemdUninstallCmd = &cobra.Command{ - Use: "uninstall", + Use: "uninstall [name]", Short: "Uninstall and remove systemd service for the gateway (requires sudo)", Long: "Uninstall and remove systemd service for the gateway. Must be run with sudo on Linux.", - Example: "sudo infisical gateway systemd uninstall", + Example: "sudo infisical gateway systemd uninstall my-gateway", DisableFlagsInUseLine: true, - Args: cobra.NoArgs, + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { if runtime.GOOS != "linux" { - util.HandleError(fmt.Errorf("systemd service installation is only supported on Linux")) + util.HandleError(fmt.Errorf("systemd service uninstallation is only supported on Linux")) } if os.Geteuid() != 0 { - util.HandleError(fmt.Errorf("systemd service installation requires root/sudo privileges")) + util.HandleError(fmt.Errorf("systemd service uninstallation requires root/sudo privileges")) + } + + if len(args) == 0 { + if err := gatewayv2.UninstallLegacyGatewaySystemdService(); err != nil { + util.HandleError(err, "Failed to uninstall systemd service") + } + return } - if err := gatewayv2.UninstallGatewaySystemdService(); err != nil { + if err := gatewayv2.UninstallGatewaySystemdService(args[0]); err != nil { util.HandleError(err, "Failed to uninstall systemd service") } }, diff --git a/packages/gateway-v2/enroll.go b/packages/gateway-v2/enroll.go index 94957f06..06088f2f 100644 --- a/packages/gateway-v2/enroll.go +++ b/packages/gateway-v2/enroll.go @@ -30,19 +30,14 @@ func gatewayConfPath(name string) (string, error) { return filepath.Join(homeDir, ".infisical", "gateways", name+".conf"), nil } -// loadConfKey reads a key from the named gateway's config file. Returns empty string if not found. -func loadConfKey(name, key string) (string, error) { - confPath, err := gatewayConfPath(name) - if err != nil { - return "", err - } - - data, err := os.ReadFile(confPath) +// readKeyFromConfFile reads a key=value pair from a config file at the given path. +func readKeyFromConfFile(path, key string) (string, error) { + data, err := os.ReadFile(path) if os.IsNotExist(err) { return "", nil } if err != nil { - return "", fmt.Errorf("failed to read gateway config: %w", err) + return "", fmt.Errorf("failed to read config file: %w", err) } prefix := key + "=" @@ -56,6 +51,15 @@ func loadConfKey(name, key string) (string, error) { return "", nil } +// loadConfKey reads a key from the named gateway's config file. Returns empty string if not found. +func loadConfKey(name, key string) (string, error) { + confPath, err := gatewayConfPath(name) + if err != nil { + return "", err + } + return readKeyFromConfFile(confPath, key) +} + // saveConfKey writes a key=value pair to the named gateway's config file, preserving other keys. // The file is created with 0600 permissions (owner read/write only). func saveConfKey(name, key, value string) error { diff --git a/packages/gateway-v2/systemd.go b/packages/gateway-v2/systemd.go index 622c6b9e..08128bf6 100644 --- a/packages/gateway-v2/systemd.go +++ b/packages/gateway-v2/systemd.go @@ -11,20 +11,89 @@ import ( "github.com/rs/zerolog/log" ) -func InstallGatewaySystemdService(token string, domain string, name string, relayName string, serviceLogFile string) error { +const ( + legacyServiceName = "infisical-gateway" + legacyConfigPath = "/etc/infisical/gateway.conf" + legacyServicePath = "/etc/systemd/system/infisical-gateway.service" + gatewaysConfigDir = "/etc/infisical/gateways" +) + +func serviceFilePath(name string) string { + return fmt.Sprintf("/etc/systemd/system/%s.service", name) +} + +func gatewayConfigPath(name string) string { + return filepath.Join(gatewaysConfigDir, name+".conf") +} + +type legacyInfo struct { + exists bool + gatewayName string +} + +func detectLegacyService() legacyInfo { + if _, err := os.Stat(legacyServicePath); os.IsNotExist(err) { + return legacyInfo{} + } + + name, _ := readKeyFromConfFile(legacyConfigPath, GATEWAY_NAME_ENV_NAME) + return legacyInfo{exists: true, gatewayName: name} +} + +func logLegacyWarning(svcName string) { + log.Warn().Msgf("Using legacy service name '%s'. To migrate to the new naming format, run: sudo infisical gateway systemd uninstall %s, then get a fresh install command from the Infisical dashboard.", legacyServiceName, svcName) +} + +type installResult struct { + serviceName string + configPath string + isLegacy bool +} + +func resolveInstallPaths(name string) (installResult, error) { + legacy := detectLegacyService() + + if legacy.exists && legacy.gatewayName == name { + return installResult{ + serviceName: legacyServiceName, + configPath: legacyConfigPath, + isLegacy: true, + }, nil + } + + if legacy.exists && name == legacyServiceName { + return installResult{}, fmt.Errorf("cannot use '%s' as a gateway name because it conflicts with the legacy gateway service", legacyServiceName) + } + + if legacy.exists { + log.Warn().Msgf("A legacy gateway service '%s' was found for gateway '%s'. The new gateway '%s' will be installed alongside it.", legacyServiceName, legacy.gatewayName, name) + } + + return installResult{ + serviceName: name, + configPath: gatewayConfigPath(name), + isLegacy: false, + }, nil +} + +func InstallGatewaySystemdService(token string, domain string, name string, relayName string, serviceLogFile string) (string, error) { if runtime.GOOS != "linux" { log.Info().Msg("Skipping systemd service installation - not on Linux") - return nil + return "", nil } if os.Geteuid() != 0 { log.Info().Msg("Skipping systemd service installation - not running as root/sudo") - return nil + return "", nil } - configDir := "/etc/infisical" - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %v", err) + paths, err := resolveInstallPaths(name) + if err != nil { + return "", err + } + + if err := os.MkdirAll(filepath.Dir(paths.configPath), 0755); err != nil { + return "", fmt.Errorf("failed to create config directory: %v", err) } configContent := fmt.Sprintf("INFISICAL_TOKEN=%s\n", token) @@ -39,48 +108,55 @@ func InstallGatewaySystemdService(token string, domain string, name string, rela configContent += fmt.Sprintf("%s=%s\n", RELAY_NAME_ENV_NAME, relayName) } - environmentFilePath := filepath.Join(configDir, "gateway.conf") - if err := os.WriteFile(environmentFilePath, []byte(configContent), 0600); err != nil { - return fmt.Errorf("failed to write environment file: %v", err) + if err := os.WriteFile(paths.configPath, []byte(configContent), 0600); err != nil { + return "", fmt.Errorf("failed to write environment file: %v", err) } - if err := util.WriteSystemdServiceFile(serviceLogFile, environmentFilePath, "infisical-gateway", "gateway", "Infisical Gateway Service"); err != nil { - return fmt.Errorf("failed to write systemd service file: %v", err) + if err := util.WriteSystemdServiceFile(serviceLogFile, paths.configPath, paths.serviceName, "gateway", fmt.Sprintf("Infisical Gateway Service (%s)", name)); err != nil { + return "", fmt.Errorf("failed to write systemd service file: %v", err) } - if err := util.WriteLogrotateFile(serviceLogFile, "infisical-gateway"); err != nil { - return fmt.Errorf("failed to write logrotate file: %v", err) + if err := util.WriteLogrotateFile(serviceLogFile, paths.serviceName); err != nil { + return "", fmt.Errorf("failed to write logrotate file: %v", err) } reloadCmd := exec.Command("systemctl", "daemon-reload") if err := reloadCmd.Run(); err != nil { - return fmt.Errorf("failed to reload systemd: %v", err) + return "", fmt.Errorf("failed to reload systemd: %v", err) } - log.Info().Msg("Successfully installed systemd service") - log.Info().Msg("To start the service, run: sudo systemctl start infisical-gateway") - log.Info().Msg("To enable the service on boot, run: sudo systemctl enable infisical-gateway") + if paths.isLegacy { + logLegacyWarning(name) + } - return nil + log.Info().Msgf("Successfully installed systemd service '%s'", paths.serviceName) + log.Info().Msgf("To start the service, run: sudo systemctl start %s", paths.serviceName) + log.Info().Msgf("To enable the service on boot, run: sudo systemctl enable %s", paths.serviceName) + + return paths.serviceName, nil } // InstallEnrolledGatewaySystemdService installs the systemd service for a gateway that was // enrolled via the enrollment token flow. It writes the long-lived gateway access token // (not a machine identity token) into the environment file. -func InstallEnrolledGatewaySystemdService(accessToken string, domain string, name string, relayName string, serviceLogFile string) error { +func InstallEnrolledGatewaySystemdService(accessToken string, domain string, name string, relayName string, serviceLogFile string) (string, error) { if runtime.GOOS != "linux" { log.Info().Msg("Skipping systemd service installation - not on Linux") - return nil + return "", nil } if os.Geteuid() != 0 { log.Info().Msg("Skipping systemd service installation - not running as root/sudo") - return nil + return "", nil + } + + paths, err := resolveInstallPaths(name) + if err != nil { + return "", err } - configDir := "/etc/infisical" - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %v", err) + if err := os.MkdirAll(filepath.Dir(paths.configPath), 0755); err != nil { + return "", fmt.Errorf("failed to create config directory: %v", err) } configContent := fmt.Sprintf("%s=%s\n", INFISICAL_GATEWAY_ACCESS_TOKEN_KEY, accessToken) @@ -94,29 +170,32 @@ func InstallEnrolledGatewaySystemdService(accessToken string, domain string, nam configContent += fmt.Sprintf("%s=%s\n", RELAY_NAME_ENV_NAME, relayName) } - environmentFilePath := filepath.Join(configDir, "gateway.conf") - if err := os.WriteFile(environmentFilePath, []byte(configContent), 0600); err != nil { - return fmt.Errorf("failed to write environment file: %v", err) + if err := os.WriteFile(paths.configPath, []byte(configContent), 0600); err != nil { + return "", fmt.Errorf("failed to write environment file: %v", err) } - if err := util.WriteSystemdServiceFile(serviceLogFile, environmentFilePath, "infisical-gateway", "gateway", "Infisical Gateway Service"); err != nil { - return fmt.Errorf("failed to write systemd service file: %v", err) + if err := util.WriteSystemdServiceFile(serviceLogFile, paths.configPath, paths.serviceName, "gateway", fmt.Sprintf("Infisical Gateway Service (%s)", name)); err != nil { + return "", fmt.Errorf("failed to write systemd service file: %v", err) } - if err := util.WriteLogrotateFile(serviceLogFile, "infisical-gateway"); err != nil { - return fmt.Errorf("failed to write logrotate file: %v", err) + if err := util.WriteLogrotateFile(serviceLogFile, paths.serviceName); err != nil { + return "", fmt.Errorf("failed to write logrotate file: %v", err) } reloadCmd := exec.Command("systemctl", "daemon-reload") if err := reloadCmd.Run(); err != nil { - return fmt.Errorf("failed to reload systemd: %v", err) + return "", fmt.Errorf("failed to reload systemd: %v", err) } - log.Info().Msg("Successfully installed systemd service") - log.Info().Msg("To start the service, run: sudo systemctl start infisical-gateway") - log.Info().Msg("To enable the service on boot, run: sudo systemctl enable infisical-gateway") + if paths.isLegacy { + logLegacyWarning(name) + } - return nil + log.Info().Msgf("Successfully installed systemd service '%s'", paths.serviceName) + log.Info().Msgf("To start the service, run: sudo systemctl start %s", paths.serviceName) + log.Info().Msgf("To enable the service on boot, run: sudo systemctl enable %s", paths.serviceName) + + return paths.serviceName, nil } // InstallAwsAuthGatewaySystemdService installs the systemd service for a gateway using AWS Auth. @@ -124,20 +203,24 @@ func InstallEnrolledGatewaySystemdService(accessToken string, domain string, nam // fresh STS-signed login on each service start using whatever AWS credentials it can resolve // (instance role, env vars, shared profile). We just persist the gateway id, domain, and name // so `gateway start` can re-authenticate. -func InstallAwsAuthGatewaySystemdService(gatewayID string, domain string, name string, relayName string, serviceLogFile string) error { +func InstallAwsAuthGatewaySystemdService(gatewayID string, domain string, name string, relayName string, serviceLogFile string) (string, error) { if runtime.GOOS != "linux" { log.Info().Msg("Skipping systemd service installation - not on Linux") - return nil + return "", nil } if os.Geteuid() != 0 { log.Info().Msg("Skipping systemd service installation - not running as root/sudo") - return nil + return "", nil + } + + paths, err := resolveInstallPaths(name) + if err != nil { + return "", err } - configDir := "/etc/infisical" - if err := os.MkdirAll(configDir, 0755); err != nil { - return fmt.Errorf("failed to create config directory: %v", err) + if err := os.MkdirAll(filepath.Dir(paths.configPath), 0755); err != nil { + return "", fmt.Errorf("failed to create config directory: %v", err) } configContent := fmt.Sprintf("%s=%s\n", INFISICAL_GATEWAY_ID_KEY, gatewayID) @@ -152,32 +235,35 @@ func InstallAwsAuthGatewaySystemdService(gatewayID string, domain string, name s configContent += fmt.Sprintf("%s=%s\n", RELAY_NAME_ENV_NAME, relayName) } - environmentFilePath := filepath.Join(configDir, "gateway.conf") - if err := os.WriteFile(environmentFilePath, []byte(configContent), 0600); err != nil { - return fmt.Errorf("failed to write environment file: %v", err) + if err := os.WriteFile(paths.configPath, []byte(configContent), 0600); err != nil { + return "", fmt.Errorf("failed to write environment file: %v", err) } - if err := util.WriteSystemdServiceFile(serviceLogFile, environmentFilePath, "infisical-gateway", "gateway", "Infisical Gateway Service"); err != nil { - return fmt.Errorf("failed to write systemd service file: %v", err) + if err := util.WriteSystemdServiceFile(serviceLogFile, paths.configPath, paths.serviceName, "gateway", fmt.Sprintf("Infisical Gateway Service (%s)", name)); err != nil { + return "", fmt.Errorf("failed to write systemd service file: %v", err) } - if err := util.WriteLogrotateFile(serviceLogFile, "infisical-gateway"); err != nil { - return fmt.Errorf("failed to write logrotate file: %v", err) + if err := util.WriteLogrotateFile(serviceLogFile, paths.serviceName); err != nil { + return "", fmt.Errorf("failed to write logrotate file: %v", err) } reloadCmd := exec.Command("systemctl", "daemon-reload") if err := reloadCmd.Run(); err != nil { - return fmt.Errorf("failed to reload systemd: %v", err) + return "", fmt.Errorf("failed to reload systemd: %v", err) + } + + if paths.isLegacy { + logLegacyWarning(name) } - log.Info().Msg("Successfully installed systemd service") - log.Info().Msg("To start the service, run: sudo systemctl start infisical-gateway") - log.Info().Msg("To enable the service on boot, run: sudo systemctl enable infisical-gateway") + log.Info().Msgf("Successfully installed systemd service '%s'", paths.serviceName) + log.Info().Msgf("To start the service, run: sudo systemctl start %s", paths.serviceName) + log.Info().Msgf("To enable the service on boot, run: sudo systemctl enable %s", paths.serviceName) - return nil + return paths.serviceName, nil } -func UninstallGatewaySystemdService() error { +func UninstallGatewaySystemdService(name string) error { if runtime.GOOS != "linux" { log.Info().Msg("Skipping systemd service uninstallation - not on Linux") return nil @@ -188,42 +274,60 @@ func UninstallGatewaySystemdService() error { return nil } - // Stop the service if it's running - stopCmd := exec.Command("systemctl", "stop", "infisical-gateway") + namedServicePath := serviceFilePath(name) + svcName := name + configPath := gatewayConfigPath(name) + isLegacy := false + + if _, err := os.Stat(namedServicePath); os.IsNotExist(err) { + legacy := detectLegacyService() + if !legacy.exists || legacy.gatewayName != name { + return fmt.Errorf("no gateway service found for '%s'", name) + } + svcName = legacyServiceName + configPath = legacyConfigPath + isLegacy = true + log.Warn().Msgf("Removing legacy service '%s' for gateway '%s'", legacyServiceName, name) + } + + stopCmd := exec.Command("systemctl", "stop", svcName) if err := stopCmd.Run(); err != nil { log.Warn().Msgf("Failed to stop service: %v", err) } - // Disable the service - disableCmd := exec.Command("systemctl", "disable", "infisical-gateway") + disableCmd := exec.Command("systemctl", "disable", svcName) if err := disableCmd.Run(); err != nil { log.Warn().Msgf("Failed to disable service: %v", err) } - // Remove the service file - servicePath := "/etc/systemd/system/infisical-gateway.service" - if err := os.Remove(servicePath); err != nil && !os.IsNotExist(err) { + svcFilePath := namedServicePath + if isLegacy { + svcFilePath = legacyServicePath + } + if err := os.Remove(svcFilePath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to remove systemd service file: %v", err) } - // Remove the legacy configuration file - configPath := "/etc/infisical/gateway.conf" if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to remove config file: %v", err) } - // Remove per-gateway config files from enrollment flow - gatewaysDir := "/etc/infisical/gateways" - if err := os.RemoveAll(gatewaysDir); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to remove gateways config directory: %v", err) - } - - // Reload systemd to apply changes reloadCmd := exec.Command("systemctl", "daemon-reload") if err := reloadCmd.Run(); err != nil { return fmt.Errorf("failed to reload systemd: %v", err) } - log.Info().Msg("Successfully uninstalled Infisical Gateway systemd service") + log.Info().Msgf("Successfully uninstalled gateway service '%s'", svcName) return nil } + +func UninstallLegacyGatewaySystemdService() error { + legacy := detectLegacyService() + if !legacy.exists { + return fmt.Errorf("no legacy gateway service found. Please provide a gateway name: sudo infisical gateway systemd uninstall ") + } + if legacy.gatewayName == "" { + return fmt.Errorf("legacy gateway service found but has no gateway name in config") + } + return UninstallGatewaySystemdService(legacy.gatewayName) +}