github.com/jstaf/onedriver@v0.14.2-0.20240420231225-f07678f9e6ef/cmd/onedriver/main.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/signal" 9 "path/filepath" 10 "strings" 11 "syscall" 12 "time" 13 14 "github.com/coreos/go-systemd/v22/unit" 15 "github.com/hanwen/go-fuse/v2/fuse" 16 "github.com/jstaf/onedriver/cmd/common" 17 "github.com/jstaf/onedriver/fs" 18 "github.com/jstaf/onedriver/fs/graph" 19 "github.com/rs/zerolog" 20 "github.com/rs/zerolog/log" 21 flag "github.com/spf13/pflag" 22 ) 23 24 func usage() { 25 fmt.Printf(`onedriver - A Linux client for Microsoft OneDrive. 26 27 This program will mount your OneDrive account as a Linux filesystem at the 28 specified mountpoint. Note that this is not a sync client - files are only 29 fetched on-demand and cached locally. Only files you actually use will be 30 downloaded. While offline, the filesystem will be read-only until 31 connectivity is re-established. 32 33 Usage: onedriver [options] <mountpoint> 34 35 Valid options: 36 `) 37 flag.PrintDefaults() 38 } 39 40 func main() { 41 log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05"}) 42 43 // setup cli parsing 44 authOnly := flag.BoolP("auth-only", "a", false, 45 "Authenticate to OneDrive and then exit.") 46 headless := flag.BoolP("no-browser", "n", false, 47 "This disables launching the built-in web browser during authentication. "+ 48 "Follow the instructions in the terminal to authenticate to OneDrive.") 49 configPath := flag.StringP("config-file", "f", common.DefaultConfigPath(), 50 "A YAML-formatted configuration file used by onedriver.") 51 logLevel := flag.StringP("log", "l", "", 52 "Set logging level/verbosity for the filesystem. "+ 53 "Can be one of: fatal, error, warn, info, debug, trace") 54 cacheDir := flag.StringP("cache-dir", "c", "", 55 "Change the default cache directory used by onedriver. "+ 56 "Will be created if the path does not already exist.") 57 wipeCache := flag.BoolP("wipe-cache", "w", false, 58 "Delete the existing onedriver cache directory and then exit. "+ 59 "This is equivalent to resetting the program.") 60 versionFlag := flag.BoolP("version", "v", false, "Display program version.") 61 debugOn := flag.BoolP("debug", "d", false, "Enable FUSE debug logging. "+ 62 "This logs communication between onedriver and the kernel.") 63 help := flag.BoolP("help", "h", false, "Displays this help message.") 64 flag.Usage = usage 65 flag.Parse() 66 67 if *help { 68 flag.Usage() 69 os.Exit(0) 70 } 71 if *versionFlag { 72 fmt.Println("onedriver", common.Version()) 73 os.Exit(0) 74 } 75 76 config := common.LoadConfig(*configPath) 77 // command line options override config options 78 if *cacheDir != "" { 79 config.CacheDir = *cacheDir 80 } 81 if *logLevel != "" { 82 config.LogLevel = *logLevel 83 } 84 85 zerolog.SetGlobalLevel(common.StringToLevel(config.LogLevel)) 86 87 // wipe cache if desired 88 if *wipeCache { 89 log.Info().Str("path", config.CacheDir).Msg("Removing cache.") 90 os.RemoveAll(config.CacheDir) 91 os.Exit(0) 92 } 93 94 // determine and validate mountpoint 95 if len(flag.Args()) == 0 { 96 flag.Usage() 97 fmt.Fprintf(os.Stderr, "\nNo mountpoint provided, exiting.\n") 98 os.Exit(1) 99 } 100 101 mountpoint := flag.Arg(0) 102 st, err := os.Stat(mountpoint) 103 if err != nil || !st.IsDir() { 104 log.Fatal(). 105 Str("mountpoint", mountpoint). 106 Msg("Mountpoint did not exist or was not a directory.") 107 } 108 if res, _ := ioutil.ReadDir(mountpoint); len(res) > 0 { 109 log.Fatal().Str("mountpoint", mountpoint).Msg("Mountpoint must be empty.") 110 } 111 112 // compute cache name as systemd would 113 absMountPath, _ := filepath.Abs(mountpoint) 114 cachePath := filepath.Join(config.CacheDir, unit.UnitNamePathEscape(absMountPath)) 115 116 // authenticate/re-authenticate if necessary 117 os.MkdirAll(cachePath, 0700) 118 authPath := filepath.Join(cachePath, "auth_tokens.json") 119 if *authOnly { 120 os.Remove(authPath) 121 graph.Authenticate(config.AuthConfig, authPath, *headless) 122 os.Exit(0) 123 } 124 125 // create the filesystem 126 log.Info().Msgf("onedriver %s", common.Version()) 127 auth := graph.Authenticate(config.AuthConfig, authPath, *headless) 128 filesystem := fs.NewFilesystem(auth, cachePath) 129 go filesystem.DeltaLoop(30 * time.Second) 130 xdgVolumeInfo(filesystem, auth) 131 132 server, err := fuse.NewServer(filesystem, mountpoint, &fuse.MountOptions{ 133 Name: "onedriver", 134 FsName: "onedriver", 135 DisableXAttrs: true, 136 MaxBackground: 1024, 137 Debug: *debugOn, 138 }) 139 if err != nil { 140 log.Fatal().Err(err).Msgf("Mount failed. Is the mountpoint already in use? "+ 141 "(Try running \"fusermount3 -uz %s\")\n", mountpoint) 142 } 143 144 // setup signal handler for graceful unmount on signals like sigint 145 sigChan := make(chan os.Signal, 1) 146 signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) 147 go fs.UnmountHandler(sigChan, server) 148 149 // serve filesystem 150 log.Info(). 151 Str("cachePath", cachePath). 152 Str("mountpoint", absMountPath). 153 Msg("Serving filesystem.") 154 server.Serve() 155 } 156 157 // xdgVolumeInfo createx .xdg-volume-info for a nice little onedrive logo in the 158 // corner of the mountpoint and shows the account name in the nautilus sidebar 159 func xdgVolumeInfo(filesystem *fs.Filesystem, auth *graph.Auth) { 160 if child, _ := filesystem.GetPath("/.xdg-volume-info", auth); child != nil { 161 return 162 } 163 log.Info().Msg("Creating .xdg-volume-info") 164 user, err := graph.GetUser(auth) 165 if err != nil { 166 log.Error().Err(err).Msg("Could not create .xdg-volume-info") 167 return 168 } 169 xdgVolumeInfo := common.TemplateXDGVolumeInfo(user.UserPrincipalName) 170 171 // just upload directly and shove it in the cache 172 // (since the fs isn't mounted yet) 173 resp, err := graph.Put( 174 graph.ResourcePath("/.xdg-volume-info")+":/content", 175 auth, 176 strings.NewReader(xdgVolumeInfo), 177 ) 178 if err != nil { 179 log.Error().Err(err).Msg("Failed to write .xdg-volume-info") 180 } 181 root, _ := filesystem.GetPath("/", auth) // cannot fail 182 inode := fs.NewInode(".xdg-volume-info", 0644, root) 183 if json.Unmarshal(resp, &inode) == nil { 184 filesystem.InsertID(inode.ID(), inode) 185 } 186 }