-
Notifications
You must be signed in to change notification settings - Fork 4
Cloudfuse service linux cmd #338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
71 commits
Select commit
Hold shift + click to select a range
13089bf
initial service linux file with notes
Dabnsky c60d152
added parse and collect data from cloudfuse.service file
Dabnsky a7d254e
added data collect and user create draft
Dabnsky 0cd2984
Added copy and systemctl commands to the installCmd
Dabnsky 74faea2
added wrapper for data collection from file. started composing defaul…
Dabnsky b301ade
added wrapper function to edit service file for config or mount Paths
Dabnsky 3fecd43
added sudo to commands
Dabnsky cf992a8
-fixed permission issues for mount
Dabnsky 8e86337
added service removal automation.
Dabnsky 2129c19
Added argument support and replaced default mount and config paths wi…
Dabnsky 3ebbe21
correct Short and Long description for install
Dabnsky 497706c
corrected usage
Dabnsky 439ddcb
use os.stat to check file exists
Dabnsky be9c13b
changed modifyServiceFile() to createServiceFile()
Dabnsky 60a3893
adjusted newServiceFile() to have correct paths and directly write fi…
Dabnsky 8a2c390
adjusted collectServiceData to collectServiceUser
Dabnsky 7d217ef
moved newServiceFile()
Dabnsky ce37b74
-added support for referencial path arguments
Dabnsky 08feb84
-added service user getting added to current user without sudo
Dabnsky dbce110
adjusted how the service user gets assigned the appropriate group for…
Dabnsky 429b77d
added --flag string support and added user specification support
Dabnsky ec849db
removed servOpts var
Dabnsky 4d9390a
added required flags and unsilenced usage with cobra
Dabnsky ec38957
support servcie user already existing and setting permissions as needed.
Dabnsky bf51d9b
updated long install description
Dabnsky 5ca9a39
replaced relative path support with filepath.IsAbs
Dabnsky b6bdefb
removed "error" from error msg
Dabnsky 3f78e7e
removed mount path empty check
Dabnsky 63b1be9
removed old TODO usage comment
Dabnsky ac1a870
updated short and long descriptions for uninstall
Dabnsky 43fd36d
removed space before "[unit]" in template
Dabnsky dac35d0
removed unused dir variable
Dabnsky 77a1eea
initiated err variable
Dabnsky ae54de5
corrected filepath.Abs usage
Dabnsky a07dd71
set up wrapper function for abs path
Dabnsky 2c760f7
added error return for getAbsPath()
Dabnsky a908035
added err vars to calling getAbsPath()
Dabnsky 72e00fd
moved string variables out of global scope
Dabnsky a8e503c
added a todo note for uninstall's mountPath
Dabnsky dbca1a0
added "cloudfuse-" to service filename
Dabnsky 17b6d81
moved string vars back into global scope so init() can use them
Dabnsky 0a195ae
used getAbsPath in uninstall and revised logic on finding and deletni…
Dabnsky 26aab67
corrected how serviceFile is determined for uninstall
Dabnsky 453f216
default service user value set to 'cloudfuse'
Dabnsky 3b14cd5
changed useradd -r to -m instead
Dabnsky 5113875
added TODO note on suggesting usermod commands instead of implementin…
Dabnsky 3b5b42f
replaced usermod commands with println output suggestions
Dabnsky 6bf3c5e
replaced perscriptive suggestion for a generic one
Dabnsky e4977a6
removed sudo from commands
Dabnsky d692d20
changed the service template to have mount and config set directly
Dabnsky bd54f84
removed isAbs check in getAbsPath()
Dabnsky 8e2c372
removed old TODO notes
Dabnsky 02541d3
removed "--user" from example
Dabnsky 23ebf74
changed service name to have full path with '-' characters
Dabnsky 1ddeb0a
replaced cmd running 'rm' command with os.remove.
Dabnsky 2b6a6bd
added wrapper function for generating service file name
Dabnsky 29e02cd
corrected getAbsPath argument
Dabnsky aff4253
removed spaces
Dabnsky d699358
Merge commit 'be485d5a7f3444e7d49e1ba755f72dda6bc786e5' into cloudfus…
Dabnsky 6fbaadb
removed var declaration
Dabnsky c8a7eb5
added error handle wrapper function for init()
Dabnsky 9280183
removed absPath helper function
Dabnsky cc01a7b
corrected uninstallCmd parameter from serviceName to mountPath
Dabnsky a029d67
removed new line from end of printeln string
Dabnsky bd4297c
Merge commit 'f4c829e9bff0690c81a3123615c92846d97f0591' into cloudfus…
Dabnsky 150fd6c
updated year on copyright
Dabnsky 9e4722f
removed "error" from message
Dabnsky 1a5e99c
removed 'error' from all error messages
Dabnsky 99bf7bf
fixed typo
Dabnsky c9f9379
fixed another typo
Dabnsky 887b6e3
Merge commit 'f3e7f236258dd800659bc6a83c9bcc99268c087b' into cloudfus…
Dabnsky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| //go:build linux | ||
|
|
||
| /* | ||
| Licensed under the MIT License <http://opensource.org/licenses/MIT>. | ||
|
|
||
| Copyright © 2023-2025 Seagate Technology LLC and/or its Affiliates | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| */ | ||
|
|
||
| package cmd | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "html/template" | ||
| "io/fs" | ||
| "os" | ||
| "os/exec" | ||
| "os/user" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "github.com/Seagate/cloudfuse/common" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| type serviceOptions struct { | ||
| ConfigFile string | ||
| MountPath string | ||
| ServiceUser string | ||
| } | ||
|
|
||
| // Section defining all the command that we have in secure feature | ||
| var serviceCmd = &cobra.Command{ | ||
| Use: "service", | ||
| Short: "Manage cloudfuse startup process on Linux", | ||
| Long: "Manage cloudfuse startup process on Linux", | ||
| SuggestFor: []string{"ser", "serv"}, | ||
| Example: "cloudfuse service install", | ||
| FlagErrorHandling: cobra.ExitOnError, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return errors.New("missing command options\n\nDid you mean this?\n\tcloudfuse service install\n\nRun 'cloudfuse service --help' for usage") | ||
| }, | ||
| } | ||
|
|
||
| var mountPath string | ||
| var configPath string | ||
| var serviceUser string | ||
|
|
||
| var installCmd = &cobra.Command{ | ||
| Use: "install", | ||
| Short: "Installs a service file for a single mount with Cloudfuse. Requires elevated permissions.", | ||
| Long: "Installs a service file for a single mount with Cloudfuse. Requires elevated permissions.", | ||
| SuggestFor: []string{"ins", "inst"}, | ||
| Example: "cloudfuse service install --mount-path=<path/to/mount/point> --config-file=<path/to/config/file>", | ||
| FlagErrorHandling: cobra.ExitOnError, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
|
|
||
| mountPath, err := filepath.Abs(mountPath) | ||
| if err != nil { | ||
| return fmt.Errorf("couldn't couldn't determine absolute path from string [%s]", err.Error()) | ||
| } | ||
| configPath, err := filepath.Abs(configPath) | ||
| if err != nil { | ||
| return fmt.Errorf("couldn't couldn't determine absolute path from string [%s]", err.Error()) | ||
| } | ||
|
|
||
| mountExists := common.DirectoryExists(mountPath) | ||
| if !mountExists { | ||
| return fmt.Errorf("the mount path provided does not exist") | ||
| } | ||
| _, err = os.Stat(configPath) | ||
| if errors.Is(err, fs.ErrNotExist) { | ||
| return fmt.Errorf("the config file path provided does not exist") | ||
| } | ||
| //create the new user and set permissions | ||
| err = setUser(serviceUser, mountPath, configPath) | ||
| if err != nil { | ||
| fmt.Println("could not set up service user ", err) | ||
| return err | ||
| } | ||
|
|
||
| serviceName, err := newService(mountPath, configPath, serviceUser) | ||
| if err != nil { | ||
| return fmt.Errorf("unable to create service file: [%s]", err.Error()) | ||
| } | ||
| // run systemctl daemon-reload | ||
| systemctlDaemonReloadCmd := exec.Command("systemctl", "daemon-reload") | ||
| err = systemctlDaemonReloadCmd.Run() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to run 'systemctl daemon-reload' command [%s]", err.Error()) | ||
| } | ||
| // Enable the service to start at system boot | ||
| systemctlEnableCmd := exec.Command("systemctl", "enable", serviceName) | ||
| err = systemctlEnableCmd.Run() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to run 'systemctl daemon-reload' command due to following [%s]", err.Error()) | ||
| } | ||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| var uninstallCmd = &cobra.Command{ | ||
|
foodprocessor marked this conversation as resolved.
|
||
| Use: "uninstall", | ||
| Short: "Uninstall a startup process for Cloudfuse.", | ||
| Long: "Uninstall a startup process for Cloudfuse.", | ||
| SuggestFor: []string{"uninst", "uninstal"}, | ||
| Example: "cloudfuse service uninstall --mount-path=<path/to/mount/path>", | ||
| FlagErrorHandling: cobra.ExitOnError, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| // get absolute path of provided relative mount path | ||
|
|
||
| mountPath, err := filepath.Abs(mountPath) | ||
| if err != nil { | ||
| return fmt.Errorf("couldn't determine absolute path from string [%s]", err.Error()) | ||
| } | ||
| serviceName, serviceFilePath := getService(mountPath) | ||
| if _, err := os.Stat(serviceFilePath); err == nil { | ||
| removeFileCmd := exec.Command("rm", serviceFilePath) | ||
| err := removeFileCmd.Run() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to delete "+serviceName+" file from /etc/systemd/system [%s]", err.Error()) | ||
| } | ||
| } else if os.IsNotExist(err) { | ||
| return fmt.Errorf("failed to delete "+serviceName+" file from /etc/systemd/system [%s]", err.Error()) | ||
| } | ||
| // reload daemon | ||
| systemctlDaemonReloadCmd := exec.Command("systemctl", "daemon-reload") | ||
| err = systemctlDaemonReloadCmd.Run() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to run 'systemctl daemon-reload' command [%s]", err.Error()) | ||
| } | ||
| return nil | ||
| }, | ||
| } | ||
|
|
||
| //--------------- command section ends | ||
|
|
||
| func newService(mountPath string, configPath string, serviceUser string) (string, error) { | ||
| serviceTemplate := ` | ||
| [Unit] | ||
| Description=Cloudfuse is an open source project developed to provide a virtual filesystem backed by S3 or Azure storage. | ||
| After=network-online.target | ||
| Requires=network-online.target | ||
|
|
||
| [Service] | ||
| # User service will run as. | ||
| User={{.ServiceUser}} | ||
|
|
||
| # Under the hood | ||
| Type=forking | ||
| ExecStart=/usr/bin/cloudfuse mount {{.MountPath}} --config-file={{.ConfigFile}} -o allow_other | ||
| ExecStop=/usr/bin/fusermount -u {{.MountPath}} -z | ||
|
|
||
| [Install] | ||
| WantedBy=multi-user.target | ||
| ` | ||
| config := serviceOptions{ | ||
| ConfigFile: configPath, | ||
| MountPath: mountPath, | ||
| ServiceUser: serviceUser, | ||
| } | ||
|
|
||
| tmpl, err := template.New("service").Parse(serviceTemplate) | ||
| if err != nil { | ||
| return "", fmt.Errorf("could not create a new service file: [%s]", err.Error()) | ||
| } | ||
| serviceName, serviceFilePath := getService(mountPath) | ||
| err = os.Remove(serviceFilePath) | ||
| if err != nil && !os.IsNotExist(err) { | ||
| return "", fmt.Errorf("failed to replace the service file [%s]", err.Error()) | ||
| } | ||
|
|
||
| var newFile *os.File | ||
| newFile, err = os.Create(serviceFilePath) | ||
| if err != nil { | ||
| return "", fmt.Errorf("could not create new service file: [%s]", err.Error()) | ||
| } | ||
|
|
||
| err = tmpl.Execute(newFile, config) | ||
| if err != nil { | ||
| return "", fmt.Errorf("could not create new service file: [%s]", err.Error()) | ||
| } | ||
| return serviceName, nil | ||
| } | ||
|
|
||
| func setUser(serviceUser string, mountPath string, configPath string) error { | ||
| _, err := user.Lookup(serviceUser) | ||
| if err != nil { | ||
| if strings.Contains(err.Error(), "unknown user") { | ||
| // create the user | ||
| userAddCmd := exec.Command("useradd", "-m", serviceUser) | ||
| err = userAddCmd.Run() | ||
| if err != nil { | ||
| return fmt.Errorf("failed to create user [%s]", err.Error()) | ||
| } | ||
| fmt.Println("user " + serviceUser + " has been created") | ||
| } | ||
| } | ||
| // advise on required permissions | ||
| fmt.Println("ensure the user, " + serviceUser + ", has the following access: \n" + mountPath + ": read, write, and execute \n" + configPath + ": read") | ||
| return nil | ||
| } | ||
|
|
||
| func getService(mountPath string) (string, string) { | ||
| serviceName := strings.Replace(mountPath, "/", "-", -1) | ||
| serviceFile := "cloudfuse" + serviceName + ".service" | ||
| serviceFilePath := "/etc/systemd/system/" + serviceFile | ||
| return serviceName, serviceFilePath | ||
| } | ||
|
|
||
| func markFlagErrorChk(cmd *cobra.Command, flagName string) { | ||
| if err := cmd.MarkFlagRequired(flagName); err != nil { | ||
| panic(fmt.Sprintf("Failed to mark flag as required: %v", err)) | ||
| } | ||
| } | ||
|
|
||
| func init() { | ||
| rootCmd.AddCommand(serviceCmd) | ||
| rootCmd.SilenceUsage = false | ||
| serviceCmd.AddCommand(installCmd) | ||
| installCmd.Flags().StringVar(&mountPath, "mount-path", "", "Input mount path") | ||
| installCmd.Flags().StringVar(&configPath, "config-file", "", "Input config file") | ||
| installCmd.Flags().StringVar(&serviceUser, "user", "cloudfuse", "Input service user") | ||
| markFlagErrorChk(installCmd, "mount-path") | ||
| markFlagErrorChk(installCmd, "config-file") | ||
| serviceCmd.AddCommand(uninstallCmd) | ||
| uninstallCmd.Flags().StringVar(&mountPath, "mount-path", "", "Input mount path") | ||
| markFlagErrorChk(uninstallCmd, "mount-path") | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.