Skip to content

chore: propose coder dotfiles command #1696

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

Closed
wants to merge 8 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add install and symlinking
  • Loading branch information
f0ssel committed May 24, 2022
commit 17490a4d5c55dbc9bae696c28a3cc7e383058e16
135 changes: 116 additions & 19 deletions cli/dotfiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,61 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/coder/coder/cli/cliui"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
)

const (
dotfilesRepoDir = "dotfiles"
)

func dotfiles() *cobra.Command {
cmd := &cobra.Command{
Use: "dotfiles [git_repo_url]",
Args: cobra.ExactArgs(1),
Short: "Checkout and install a dotfiles repository.",
RunE: func(cmd *cobra.Command, args []string) error {
var (
gitRepo = args[0]
cfg = createConfig(cmd)
cfgDir = string(cfg)
dotfilesDir = filepath.Join(cfgDir, dotfilesRepoDir)
subcommands = []string{"clone", args[0], dotfilesRepoDir}
gitCmdDir = cfgDir
promtText = fmt.Sprintf("Cloning %s into directory %s.\n Continue?", gitRepo, dotfilesDir)
dotfilesRepoDir = "dotfiles"
gitRepo = args[0]
cfg = createConfig(cmd)
cfgDir = string(cfg)
dotfilesDir = filepath.Join(cfgDir, dotfilesRepoDir)
subcommands = []string{"clone", args[0], dotfilesRepoDir}
gitCmdDir = cfgDir
promtText = fmt.Sprintf("Cloning %s into directory %s.\n Continue?", gitRepo, dotfilesDir)
installScriptSet = []string{
"install.sh",
"install",
"bootstrap.sh",
"bootstrap",
"script/bootstrap",
"setup.sh",
"setup",
"script/setup",
}
)

_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Checking if dotfiles repository already exists...")
_, _ = fmt.Fprint(cmd.OutOrStdout(), "Checking if dotfiles repository already exists...\n")
dotfilesExists, err := dirExists(dotfilesDir)
if err != nil {
return xerrors.Errorf("checking dir %s: %w", dotfilesDir, err)
}

// if repo exists already do a git pull instead of clone
if dotfilesExists {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), fmt.Sprintf("Found dotfiles repository at %s", dotfilesDir))
_, _ = fmt.Fprint(cmd.OutOrStdout(), fmt.Sprintf("Found dotfiles repository at %s\n", dotfilesDir))
gitCmdDir = dotfilesDir
subcommands = []string{"pull", "--ff-only"}
promtText = fmt.Sprintf("Pulling latest from %s into directory %s.\n Continue?", gitRepo, dotfilesDir)
} else {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), fmt.Sprintf("Did not find dotfiles repository at %s", dotfilesDir))
_, _ = fmt.Fprint(cmd.OutOrStdout(), fmt.Sprintf("Did not find dotfiles repository at %s\n", dotfilesDir))
}

// check if git ssh command already exists so we can just wrap it
gitsshCmd := os.Getenv("GIT_SSH_COMMAND")
if gitsshCmd == "" {
gitsshCmd = "ssh"
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), fmt.Sprintf("gitssh %s", gitsshCmd))

_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: promtText,
Expand All @@ -62,27 +69,117 @@ func dotfiles() *cobra.Command {
return err
}

// ensure config dir exists
err = os.MkdirAll(gitCmdDir, 0750)
if err != nil {
return xerrors.Errorf("ensuring dir at %s: %w", gitCmdDir, err)
}

// clone or pull repo
c := exec.CommandContext(cmd.Context(), "git", subcommands...)
c.Dir = gitCmdDir
c.Env = append(os.Environ(), fmt.Sprintf(`GIT_SSH_COMMAND=%s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no`, gitsshCmd))
out, err := c.CombinedOutput()
if err != nil {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Error.Render(string(out)))
return xerrors.Errorf("running git command: %w", err)
return err
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), string(out))

// check for install scripts
files, err := os.ReadDir(dotfilesDir)
if err != nil {
return xerrors.Errorf("reading files in dir %s: %w", dotfilesDir, err)
}
_, _ = fmt.Fprint(cmd.OutOrStdout(), string(out))

// do install script if exists
// or symlink dotfiles if not
var scripts []string
var dotfiles []string
for _, f := range files {
for _, i := range installScriptSet {
if f.Name() == i {
scripts = append(scripts, f.Name())
}
}

if strings.HasPrefix(f.Name(), ".") {
dotfiles = append(dotfiles, f.Name())
}
}

// run found install scripts
if len(scripts) > 0 {
t := "Found install script(s). The following script(s) will be executed in order:\n\n"
for _, s := range scripts {
t = fmt.Sprintf("%s - %s\n", t, s)
}
t = fmt.Sprintf("%s\n Continue?", t)
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: t,
IsConfirm: true,
})
if err != nil {
return err
}

for _, s := range scripts {
_, _ = fmt.Fprint(cmd.OutOrStdout(), fmt.Sprintf("\nRunning %s...\n", s))
c := exec.CommandContext(cmd.Context(), fmt.Sprintf("./%s", s))
c.Dir = dotfilesDir
out, err := c.CombinedOutput()
if err != nil {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Error.Render(string(out)))
return xerrors.Errorf("running %s: %w", s, err)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), string(out))
}

_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Dotfiles installation complete.")
return nil
}

// otherwise symlink dotfiles
if len(dotfiles) > 0 {
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
Text: "No install scripts found, symlinking dotfiles to home directory.\n\n Continue?",
IsConfirm: true,
})
if err != nil {
return err
}

home, err := os.UserHomeDir()
if err != nil {
return xerrors.Errorf("getting user home: %w", err)
}

for _, df := range dotfiles {
from := filepath.Join(dotfilesDir, df)
to := filepath.Join(home, df)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), fmt.Sprintf("Symlinking %s to %s...\n", from, to))
// if file already exists at destination remove it
_, err := os.Lstat(to)
if err == nil {
err := os.Remove(to)
if err != nil {
return xerrors.Errorf("removing destination file %s: %w", to, err)
}
}

err = os.Symlink(from, to)
if err != nil {
return xerrors.Errorf("symlinking %s to %s: %w", from, to, err)
}
}

_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Dotfiles installation complete.")
return nil
}

_, _ = fmt.Fprintln(cmd.OutOrStdout(), "No install scripts or dotfiles found, nothing to do.")
return nil
},
}
cliui.AllowSkipPrompt(cmd)

return cmd
}
Expand Down