Skip to content
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

Cloudfuse service linux cmd #338

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open

Conversation

Dabnsky
Copy link
Contributor

@Dabnsky Dabnsky commented Oct 10, 2024

What type of Pull Request is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Describe your changes in brief

This adds the following commands available to the cloudfuse executable:
cloudfuse service install
cloudfuse service uninstall

Checklist

  • Tested locally
  • Added new dependencies
  • Updated documentation
  • Added tests

Related Issues

  • Related Issue #
  • Closes #

@foodprocessor foodprocessor removed the request for review from brayan-trejo October 31, 2024 17:52
@Dabnsky Dabnsky marked this pull request as ready for review November 1, 2024 17:32
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
@Dabnsky Dabnsky marked this pull request as draft November 8, 2024 21:12
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
}

folderList := strings.Split(mountPath, "/")
newFile, err := os.Create("/etc/systemd/system/" + folderList[len(folderList)-1] + ".service")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you set this as a constant to refer to at the top of the file.

cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
}

// get a list of group from reference user
groups, err := usr.GroupIds()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What groups do we need to add the user to? And why do we need to add them manually?

Copy link
Contributor Author

@Dabnsky Dabnsky Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the service user at least needs the current user added to its groups to be able to access and use mount and config file typically located at the end user's home directory. There doesn't seem to be a way around using the usermod command for this.

cmd/service_linux.go Outdated Show resolved Hide resolved
-changed collectServiceData() to extractValue() that outputs value from file for provided key

-changed setUser() to use SUDO_USER env variable to add to service user group. and adjusted permissions for service user.
-corrected typo of moutingpoint that came from cloudfuse.service template file
@Dabnsky Dabnsky marked this pull request as ready for review November 22, 2024 20:26
Copy link
Contributor

@foodprocessor foodprocessor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great improvements!!

cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
cmd/service_linux.go Outdated Show resolved Hide resolved
Comment on lines +246 to +262
mountPathInfo, err := os.Stat(mountPath)
if err != nil {
return fmt.Errorf("failed to stat file: %v", err)
}

// Get file's group ID
stat, ok = mountPathInfo.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("failed to get file system stats")
}
mountGroupID := stat.Gid

// Get configFileGroup name
mountPathGroup, err := user.LookupGroupId(fmt.Sprint(mountGroupID))
if err != nil {
return fmt.Errorf("failed to lookup group: %v", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And can we do the same for the mount directory (just make sure the service user has the access it needs)?

if err != nil {
if strings.Contains(err.Error(), "unknown user") {
//create the user
userAddCmd := exec.Command("sudo", "useradd", "-m", serviceUser)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably shouldn't use sudo in our code. The user will already run our CLI with sudo, right?

Comment on lines +274 to +291
//add group to serviceUser group
usermodCmd := exec.Command("sudo", "usermod", "-aG", configFileGroup.Name, serviceUser)
err = usermodCmd.Run()
if err != nil {
return fmt.Errorf("failed to create user due to following error: [%s]", err.Error())
}
usermodCmd = exec.Command("sudo", "usermod", "-aG", mountPathGroup.Name, serviceUser)
err = usermodCmd.Run()
if err != nil {
return fmt.Errorf("failed to create user due to following error: [%s]", err.Error())
}

//set set folder permission on the mount path
chmodCmd := exec.Command("sudo", "chmod", "770", mountPath)
err = chmodCmd.Run()
if err != nil {
return fmt.Errorf("failed set permisions on mount path due to following error: [%s]", err.Error())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can suggest these commands on error, instead of doing them ourselves.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I think that makes it a bit easier and prevents us from doing something wrong to the users system.

Comment on lines +211 to +212
folderList := strings.Split(mountPath, "/")
serviceName := folderList[len(folderList)-1] + ".service"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you get the absolute path for the mount folder, you could ensure the service name is perfectly specific and unique by just replacing slashes with dashes.
This is already the best practice / standard for mounts on startup using systemd.

Comment on lines +213 to +216
newFile, err := os.Create("/etc/systemd/system/" + serviceName)
if err != nil {
return "", fmt.Errorf("error creating new service file: [%s]", err.Error())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you overwrite it, in case they want to use the install command to update an existing service?
Or would you rather they uninstall first?

Copy link
Contributor

@jfantinhardesty jfantinhardesty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few more comments to simplify some of this. Also, I tend to agree with Michael that we probably should not run a lot of these commands ourselves, and instead suggest them to the user. Also, there are a variety of lint and spelling errors that should be fixed.

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 which remounts any active previously active mounts on startup. elevated permissions.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should say "Requires elevated permissions."

return fmt.Errorf("error: [%s]", err.Error())
}

if strings.Contains(mountPath, ".") || strings.Contains(mountPath, "..") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than checking for dots, you could also use the go function filepath.IsAbs. And then you could also call filepath.Clean to get the shortest filepath if it not an absolute path.

RunE: func(cmd *cobra.Command, args []string) error {

// get absolute path of provided relative mount path
if strings.Contains(serviceName, ".") || strings.Contains(serviceName, "..") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is mostly the same as above, you could possibly abstract this type of logic into a separate function.

if err != nil {
if strings.Contains(err.Error(), "unknown user") {
//create the user
userAddCmd := exec.Command("sudo", "useradd", "-m", serviceUser)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should user "-r" instead of "-m" since "-m" will create a user that has a home directory and we probably don't want to do that.

Comment on lines +274 to +291
//add group to serviceUser group
usermodCmd := exec.Command("sudo", "usermod", "-aG", configFileGroup.Name, serviceUser)
err = usermodCmd.Run()
if err != nil {
return fmt.Errorf("failed to create user due to following error: [%s]", err.Error())
}
usermodCmd = exec.Command("sudo", "usermod", "-aG", mountPathGroup.Name, serviceUser)
err = usermodCmd.Run()
if err != nil {
return fmt.Errorf("failed to create user due to following error: [%s]", err.Error())
}

//set set folder permission on the mount path
chmodCmd := exec.Command("sudo", "chmod", "770", mountPath)
err = chmodCmd.Run()
if err != nil {
return fmt.Errorf("failed set permisions on mount path due to following error: [%s]", err.Error())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I think that makes it a bit easier and prevents us from doing something wrong to the users system.

//--------------- command section ends

func newServiceFile(mountPath string, configPath string, serviceUser string) (string, error) {
serviceTemplate := ` [Unit]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There shouldn't be a space here before [Unit]

# User service will run as.
User={{.ServiceUser}}
# Path to the location Cloudfuse will mount to. Note this folder must currently exist.
Environment=MountingPoint={{.MountPath}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could simplify this environment section. I only did it this way to make it easy for a user to edit. But if you write it then you could just put the mountPath in the mount and unmount commands directly.

}
} else {
//add group to serviceUser group
usermodCmd := exec.Command("sudo", "usermod", "-aG", configFileGroup.Name, serviceUser)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user exists, we probably should not modify the group permissions and assume the user has them set in a way to run cloudfuse. And if there is an error we should tell them how to fix that error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants