diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/eaglemode/default.nix | 15 | ||||
-rw-r--r-- | tools/eaglemode/wrapper.go | 156 |
2 files changed, 171 insertions, 0 deletions
diff --git a/tools/eaglemode/default.nix b/tools/eaglemode/default.nix index 5bab3155da1e..879235b62e05 100644 --- a/tools/eaglemode/default.nix +++ b/tools/eaglemode/default.nix @@ -9,6 +9,13 @@ let mkDesc = d: lib.concatMapStringsSep "\n" (x: "# Descr =${x}") (builtins.filter (s: s != "") (lib.splitString "\n" d)); + + configWrapper = pkgs.runCommand "eaglemode-config-wrapper" { } '' + cp ${./wrapper.go} wrapper.go + export HOME=$PWD + ${pkgs.go}/bin/go build wrapper.go + install -Dm755 wrapper $out/bin/wrapper + ''; in rec { # mkCommand creates an Eagle Mode command for the file browser. @@ -71,4 +78,12 @@ rec { lib.concatMapStringsSep "\n" (s: "cp -rT ${s} $out/\nchmod -R u+rw $out/\n") ([ "${eaglemode}/etc"] ++ extraPaths) } ''; + + # withConfig creates an Eagle Mode wrapper that runs it with the given + # configuration. + withConfig = { eaglemode ? pkgs.eaglemode, config }: pkgs.writeShellScriptBin "eaglemode" '' + set -ue + ${configWrapper}/bin/wrapper --em-config "${config}" + exec ${eaglemode}/bin/eaglemode "$@" + ''; } diff --git a/tools/eaglemode/wrapper.go b/tools/eaglemode/wrapper.go new file mode 100644 index 000000000000..841642b6d93f --- /dev/null +++ b/tools/eaglemode/wrapper.go @@ -0,0 +1,156 @@ +// Eagle Mode configuration wrapper that recreates the required directory +// structure for Eagle Mode based on the output of depot.tools.eaglemode.etcDir +// +// This will replace *all* symlinks in the Eagle Mode configuration directory, +// but it will not touch actual files. Missing folders will be created. +package main + +import ( + "flag" + "fmt" + "io/fs" + "log" + "os" + "os/user" + "path" + "path/filepath" + "strings" +) + +func configDir() (string, error) { + v := os.Getenv("EM_USER_CONFIG_DIR") + if v != "" { + return v, nil + } + + usr, err := user.Current() + if err != nil { + return "", fmt.Errorf("failed to get current user: %w", err) + } + + return path.Join(usr.HomeDir, ".eaglemode"), nil +} + +// cleanupConfig removes *all* existing symlinks in the configuration which do +// not point into the right Nix store path. +func cleanupConfig(conf string, dir string) (map[string]bool, error) { + // In case of first launch, we might have to create the directory. + _ = os.MkdirAll(dir, 0755) + c := 0 + + currentFiles := map[string]bool{} + + walker := func(p string, d fs.DirEntry, e error) error { + if e != nil { + return fmt.Errorf("could not walk %s in config directory: %w", p, e) + } + + if d.Type()&fs.ModeSymlink != 0 { + target, err := os.Readlink(p) + if err != nil { + return fmt.Errorf("could not read link for %s: %w", p, err) + } + + if !strings.HasPrefix(target, conf) { + err = os.Remove(p) + c++ + if err != nil { + return fmt.Errorf("could not remove stale link %q: %w", p, err) + } + log.Printf("removed stale symlink %q", p) + } else { + currentFiles[p] = false + } + } + + if d.Type().IsRegular() { + currentFiles[p] = true + } + + return nil + } + + err := filepath.WalkDir(dir, walker) + if err != nil { + return nil, err + } + + if c > 0 { + log.Printf("removed %v stale symlinks", c) + } + + return currentFiles, nil +} + +// linkConfig traverses the given Eagle Mode configuration and links everything +// to the expected location in the user's configuration directory. +// +// If the user placed actual files in the configuration directory at paths that +// would be overwritten, they will not be touched. +func linkConfig(conf string, dir string, existing map[string]bool) error { + walker := func(p string, d fs.DirEntry, e error) error { + if e != nil { + return fmt.Errorf("could not walk %s in config directory: %w", p, e) + } + + target := path.Join(dir, strings.TrimPrefix(p, conf)) + + if d.Type().IsDir() { + err := os.MkdirAll(target, 0755) + if err != nil { + return fmt.Errorf("could not create directory %q: %w", target, err) + } + + return nil + } + + if shadow, exists := existing[target]; exists { + if shadow { + log.Printf("WARN: file %q already exists and shadows a file from configuration", target) + } + + return nil + } + + err := os.Symlink(p, target) + if err != nil { + return fmt.Errorf("failed to link %q: %w", target, err) + } + + return nil + } + + return filepath.WalkDir(conf, walker) +} + +func main() { + emConfig := flag.String("em-config", "", "path to em-config dir") + + flag.Parse() + log.Println("verifying current Eagle Mode configuration") + + if *emConfig == "" { + log.Fatalf("Eagle Mode configuration must be given") + } + + if !strings.HasPrefix(*emConfig, "/nix/store/") { + log.Fatalf("Eagle Mode configuration must be in Nix store") + } + + dir, err := configDir() + if err != nil { + log.Fatalf("could not determine Eagle Mode config dir: %v", err) + } + + currentFiles, err := cleanupConfig(*emConfig, dir) + if err != nil { + log.Fatalf("failed to remove stale symlinks: %v", err) + } + + err = linkConfig(*emConfig, dir, currentFiles) + if err != nil { + log.Fatalf("failed to link new configuration: %v", err) + } + + log.Println("Eagle Mode configuration updated") +} |