func Shutdown(reason string, exitcode int) { shutdownLock.Lock() for i := 0; i < shutdownChanLen; i++ { shutdownChan <- true } status := log.New(os.Stderr, "", 0) if reason != "" { status.Printf("%s ", reason) } status.Printf("Stopping all subprocesses...\n") done := make(chan bool) for _, _ctx := range AllSyncContexts { go func(ctx *SyncContext) { if ctx.IsConnected() { ctx.KillAllSessions() // This generally shouldn't *do* anything other than // clean up the PID files, as the killing would have // been done already in KillAllSessions. ctx.KillAllViaPidfiles() ctx.Close() } done <- true }(_ctx) } for range AllSyncContexts { select { case <-done: case <-time.After(3 * time.Second): } } status.Printf("Exiting.") os.Stderr.WriteString("\n") os.Exit(exitcode) }
func printUsageInfoAndExit() { status := log.New(os.Stderr, "", 0) status.Println("") status.Println("Usage: gut sync [option]... path [{ [user@]host:path | path }]...") status.Println("") status.Println("Options:") status.Println(" --no-color: Disable ANSI colors") status.Println(" --verbose: Show all commands executed") status.Println(" --build-deps: Build gut-commands from git source instead of downloading tarball") status.Println("--build-parallel: Build gut-commands in parallel via make -j {num_cores}") status.Println("") status.Println("Examples:") status.Println(" Sync folder with one remote: gut sync ~/stuff/ [email protected]:~/stuff/") status.Println(" Sync folder with two remotes: gut sync stuff/ remotehost1.com:~/work/ [email protected]:/tmp/sync") status.Println(" Sync folders locally: gut sync ~/mywork /mnt/backup/mywork/") status.Println("Just track changes, no syncing: gut sync ~/mywork") status.Println("") os.Exit(0) }
func main() { log.EnableColorTemplate() log.AddAnsiColorCode("error", 31) log.AddAnsiColorCode("commit", 32) log.AddAnsiColorCode("path", 36) var args []string = os.Args[1:] if len(args) == 0 { fmt.Println("You must specify a gut-command, e.g. `gut sync ...`") os.Exit(1) } var cmd = args[0] if IsGitCommand(cmd) { if IsDangerousGitCommand(cmd) { if len(args) < 2 || args[1] != "--danger" { status := log.New(os.Stderr, "", 0) status.Printf("@(dim:In order to prevent damage caused by accidentally using `)gut %s ...@(dim:`)\n", cmd) status.Printf("@(dim:in cases where `)git %s ...@(dim:` was intended, you must append `)--danger@(dim:`)\n", cmd) status.Printf("@(dim:immediately after the command, i.e. `)gut %s --danger ...@(dim:`.)\n", cmd) status.Printf("@(dim:Alternatively, you could invoke) gut @(dim:directly at) @(path:%s)@(dim:.)\n", GutExePath) status.Printf("@(dim:The commands that require this flag are:) %s\n", strings.Join(DangerousGitCommands, " ")) os.Exit(1) } // Split the "--danger" flag out before handing off the args list to the gut-command: if len(args) > 2 { args = append(args[:1], args[2:]...) } else { args = args[:1] } } homeDir, err := homedir.Dir() if err != nil { log.Bail(err) } var gutExe = path.Join(homeDir, GutExePath[2:]) syscall.Exec(gutExe, append([]string{gutExe}, args...), os.Environ()) fmt.Printf("Failed to exec %s", gutExe) os.Exit(1) } autorestart.CleanUpChildZombiesQuietly() go autorestart.RestartOnChange() status := log.New(os.Stderr, "", 0) args = args[1:] parser := flags.NewParser(&OptsCommon, flags.IgnoreUnknown) var argsRemaining, err = parser.ParseArgs(args) if err != nil { printUsageInfoAndExit() } if OptsCommon.NoColor { log.DisableColor() } if OptsCommon.Version { status.Printf("gut-sync %s\n", GutVersion) os.Exit(0) } bismuth.SetVerbose(OptsCommon.Verbose) go func() { sigintChan := make(chan os.Signal, 1) signal.Notify(sigintChan, os.Interrupt) <-sigintChan Shutdown("Received SIGINT.", 1) }() go func() { sighupChan := autorestart.NotifyOnSighup() <-sighupChan Shutdown("Received SIGHUP.", 0) }() if cmd == "build" { var local = NewSyncContext() err := local.Connect() if err != nil { status.Bail(err) } err = local.CheckLocalDeps() if err != nil { status.Bail(err) } didSomething, err := EnsureBuild(local, local) if err != nil { status.Bail(err) } if !didSomething { status.Printf("@(dim:gut) " + GitVersion + " @(dim:has already been built.)\n") } } else if cmd == "sync" { var remoteArgs, err = flags.ParseArgs(&OptsSync, argsRemaining) if err != nil { printUsageInfoAndExit() } ready := make(chan error) local := NewSyncContext() err = local.ParseSyncPath(OptsSync.Positional.LocalPath) if err != nil { status.Bail(err) } if len(remoteArgs) > 0 && os.Getenv("SSH_AUTH_SOCK") == "" { log.Printf("@(error:SSH_AUTH_SOCK is not set in environment. Start up an ssh agent first before running gut-sync.)\n") Shutdown("", 1) } go func() { err = local.Connect() if err != nil { status.Bail(err) } err = local.CheckLocalDeps() if err != nil { status.Bail(err) } local.KillAllViaPidfiles() local.SaveDaemonPid("gut", os.Getpid()) ready <- nil }() remotes := []*SyncContext{} for _, remotePath := range remoteArgs { remote := NewSyncContext() remotes = append(remotes, remote) err = remote.ParseSyncPath(remotePath) if err != nil { status.Bail(err) } go func(_remote *SyncContext) { err = _remote.Connect() if err != nil { status.Printf("@(error:Failed to connect to %s: %s)\n", remote.Hostname(), err) Shutdown("", 1) } _remote.KillAllViaPidfiles() err = _remote.CheckRemoteDeps() if err != nil { status.Bail(err) } ready <- nil }(remote) } for i := 0; i < len(remotes)+1; i++ { <-ready } err = Sync(local, remotes) if err != nil { status.Bail(err) } } }