From 59a6969c25fa1e91f4afb801b89470b8b0b8610a Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Sat, 14 Sep 2024 11:41:58 +0100 Subject: [PATCH] feat: download configs from external sources Download and install configurations from external sources. At the moment downloading from external git repositories is only supported. Download and install the Neovim configuration for sparrow. Add an external script to install lazy.nvim. --- hosts/sparrow.json | 8 ++ magefiles/external.go | 102 +++++++++++++++++++++ magefiles/internal/config/config.go | 59 +++++++----- scripts/install_lazy.nvim | 19 ++++ scripts/{install-neovim => install_neovim} | 0 5 files changed, 163 insertions(+), 25 deletions(-) create mode 100644 magefiles/external.go create mode 100755 scripts/install_lazy.nvim rename scripts/{install-neovim => install_neovim} (100%) diff --git a/hosts/sparrow.json b/hosts/sparrow.json index e7d7e99..809fe4d 100644 --- a/hosts/sparrow.json +++ b/hosts/sparrow.json @@ -43,6 +43,14 @@ "Projects" ] }, + "externalConfigurations": [ + { + "label": "Neovim", + "gitRepoURL": "https://codeflow.dananglin.me.uk/linux-home/nvim.d.git", + "gitRef": "746f07efdb203a46a7a02ada987cd97b68fa92d6", + "gitRepoPath": "nvim" + } + ], "git": { "gpgSign": false, "user": { diff --git a/magefiles/external.go b/magefiles/external.go new file mode 100644 index 0000000..c49567d --- /dev/null +++ b/magefiles/external.go @@ -0,0 +1,102 @@ +//go:build mage + +package main + +import ( + "fmt" + "os" + "path/filepath" + "slices" + "strings" + + "codeflow.dananglin.me.uk/linux-home/manager/magefiles/internal/config" + "codeflow.dananglin.me.uk/linux-home/manager/magefiles/internal/walk" + "github.com/magefile/mage/sh" +) + +// Externalconfigs downloads and manages neovim configuration from a remote git repository. +func Externalconfigs() error { + cfg, err := config.NewConfig() + if err != nil { + return fmt.Errorf( + "unable to load the configuration: %w", + err, + ) + } + + homeConfigDir, err := os.UserConfigDir() + if err != nil { + return fmt.Errorf( + "unable to get the user's home configuration directory: %w", + err, + ) + } + + for _, externalConfig := range slices.All(cfg.ExternalConfigurations) { + if err := manageExternalConfig(externalConfig, homeConfigDir); err != nil { + return fmt.Errorf("received an error while processing %s: %w", externalConfig.Label, err) + } + } + + return nil +} + +func manageExternalConfig(cfg config.ExternalConfig, homeConfigDir string) error { + clonedRepo, err := downloadRepo(cfg.GitRepoURL, cfg.GitRef) + if err != nil { + return fmt.Errorf("unable to clone the git repository: %w", err) + } + + defer os.RemoveAll(clonedRepo) + + fmt.Println("Git repository cloned to:", clonedRepo) + + validationFunc := func(relativePath string) bool { + split := strings.SplitN(relativePath, "/", 2) + + if len(split) < 1 { + return false + } + + rootPath := split[0] + + return rootPath == cfg.GitRepoPath + } + + if err = filepath.WalkDir( + clonedRepo, + walk.CopyFiles(homeConfigDir, clonedRepo, rootManagedDir, validationFunc), + ); err != nil { + return fmt.Errorf("received an error while copying the files: %w", err) + } + + return nil +} + +func downloadRepo(repoURL, repoRef string) (string, error) { + cloneDir, err := os.MkdirTemp("/tmp", "config-") + if err != nil { + return "", fmt.Errorf("unable to create the temporary directory: %w", err) + } + + git := sh.RunCmd("git", "-C", cloneDir) + + commands := [][]string{ + {"init"}, + {"remote", "add", "origin", repoURL}, + {"fetch", "origin", repoRef}, + {"checkout", "FETCH_HEAD"}, + } + + for _, command := range slices.All(commands) { + if err := git(command...); err != nil { + return "", err + } + } + + if err := os.RemoveAll(filepath.Join(cloneDir, ".git")); err != nil { + return "", fmt.Errorf("unable to remove the .git folder: %w", err) + } + + return cloneDir, nil +} diff --git a/magefiles/internal/config/config.go b/magefiles/internal/config/config.go index d9bb9cb..ad35a27 100644 --- a/magefiles/internal/config/config.go +++ b/magefiles/internal/config/config.go @@ -8,52 +8,60 @@ import ( "strings" ) -const configDir string = "hosts" +const dir string = "hosts" type Config struct { - ManagedConfigurations []string `json:"managedConfigurations"` - BashProfile ConfigBashProfile `json:"bashProfile"` - Directories ConfigDirectories `json:"directories"` - Git ConfigGit `json:"git"` + ManagedConfigurations []string `json:"managedConfigurations"` + BashProfile BashProfile `json:"bashProfile"` + Directories Directories `json:"directories"` + Git Git `json:"git"` + ExternalConfigurations []ExternalConfig `json:"externalConfigurations"` } -type ConfigDirectories struct { +type Directories struct { UseDefaultDirectories bool `json:"useDefaultDirectories"` IncludeXDGDirectories bool `json:"includeXDGDirectories"` AdditionalDirectories []string `json:"additionalDirectories"` } -type ConfigGit struct { - GpgSign bool `json:"gpgSign"` - User ConfigGitUser `json:"user"` +type Git struct { + GpgSign bool `json:"gpgSign"` + User GitUser `json:"user"` } -type ConfigGitUser struct { +type GitUser struct { Email string `json:"email"` Name string `json:"name"` SigningKey string `json:"signingKey"` } -type ConfigBashProfile struct { - Manage bool `json:"manage"` - Filename string `json:"filename"` - SessionPaths []ConfigBashProfileSessionPath `json:"sessionPaths"` - XdgDirectories map[string]string `json:"xdgDirectories"` - EnvironmentVariables map[string]string `json:"environmentVariables"` - Aliases map[string]string `json:"aliases"` - Commands []ConfigBashProfileCommand `json:"commands"` +type BashProfile struct { + Manage bool `json:"manage"` + Filename string `json:"filename"` + SessionPaths []BashProfileSessionPath `json:"sessionPaths"` + XdgDirectories map[string]string `json:"xdgDirectories"` + EnvironmentVariables map[string]string `json:"environmentVariables"` + Aliases map[string]string `json:"aliases"` + Commands []BashProfileCommand `json:"commands"` } -type ConfigBashProfileSessionPath struct { +type BashProfileSessionPath struct { Path string `json:"path"` Description string `json:"description"` } -type ConfigBashProfileCommand struct { +type BashProfileCommand struct { Command string `json:"command"` Description string `json:"description"` } +type ExternalConfig struct { + Label string `json:"label"` + GitRepoURL string `json:"gitRepoURL"` + GitRef string `json:"gitRef"` + GitRepoPath string `json:"gitRepoPath"` +} + func NewConfig() (Config, error) { cfg := defaultConfig() @@ -89,24 +97,25 @@ func configFilePath() (string, error) { identifier := hostnameParts[1] - return filepath.Join(configDir, identifier+".json"), nil + return filepath.Join(dir, identifier+".json"), nil } func defaultConfig() Config { return Config{ - Directories: ConfigDirectories{ + Directories: Directories{ UseDefaultDirectories: true, IncludeXDGDirectories: true, AdditionalDirectories: []string{}, }, - Git: ConfigGit{ + Git: Git{ GpgSign: false, - User: ConfigGitUser{ + User: GitUser{ Email: "", Name: "", SigningKey: "", }, }, - ManagedConfigurations: []string{}, + ManagedConfigurations: []string{}, + ExternalConfigurations: []ExternalConfig{}, } } diff --git a/scripts/install_lazy.nvim b/scripts/install_lazy.nvim new file mode 100755 index 0000000..3f340b5 --- /dev/null +++ b/scripts/install_lazy.nvim @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +LAZY_NVIM_SOURCE="https://github.com/folke/lazy.nvim.git" +LAZY_NVIM_DESTINATION="${XDG_DATA_HOME}/nvim/lazy/lazy.nvim" +LAZY_STATE_DIR="${XDG_STATE_HOME}/nvim/lazy" + +mkdir -p "${LAZY_STATE_DIR}" + +if [ -d "${LAZY_NVIM_DESTINATION}/.git" ]; then + echo "INFO: ${LAZY_NVIM_DESTINATION} is already present" + exit 0 +fi + +echo "INFO: Cloning ${LAZY_NVIM_SOURCE} to ${LAZY_NVIM_DESTINATION}..." +git clone --filter=blob:none --single-branch "${LAZY_NVIM_SOURCE}" "${LAZY_NVIM_DESTINATION}" diff --git a/scripts/install-neovim b/scripts/install_neovim similarity index 100% rename from scripts/install-neovim rename to scripts/install_neovim