Adds a new eaglemode.withConfig function that creates a specially wrapped Eagle Mode, in which a configuration script joins the user's configuration with the config passed in. This produces a fully working and configured Eagle Mode with custom stuff out of the box. Change-Id: I6282cafd0b1ac6e77bede90cc91d4ede19ee1d2f Reviewed-on: https://cl.tvl.fyi/c/depot/+/12369 Reviewed-by: azahi <azat@bahawi.net> Tested-by: BuildkiteCI Reviewed-by: emery <emery@dmz.rs>
		
			
				
	
	
		
			156 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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")
 | |
| }
 |