mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-29 22:47:47 -04:00
1 parent
2f19ccf707
commit
f0867798e6
6 files changed
+327
-24
No files matched your search
+14
-6
@@ -157,17 +157,25 @@ curl -fsSL https://install.danklinux.com | sh
|
||||
|
||||
**Headless (unattended):**
|
||||
|
||||
Headless mode requires cached sudo credentials. Run `sudo -v` first:
|
||||
Headless mode requires cached credentials or a passwordless rule for your privilege escalation tool (sudo, doas, or run0). With sudo, run `sudo -v` first:
|
||||
|
||||
```bash
|
||||
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c niri -t ghostty -y
|
||||
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c hyprland -t kitty --include-deps dms-greeter -y
|
||||
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c hyprland -t kitty --dms-greeter -y
|
||||
sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c niri -t ghostty --git-deps niri,quickshell --all-features -y
|
||||
```
|
||||
|
||||
| Flag | Short | Description |
|
||||
|------|-------|-------------|
|
||||
| `--compositor <niri|hyprland>` | `-c` | Compositor/WM to install (required for headless) |
|
||||
| `--term <ghostty|kitty|alacritty>` | `-t` | Terminal emulator (required for headless) |
|
||||
| `--compositor <niri\|hyprland\|mango>` | `-c` | Compositor/WM to install (required for headless) |
|
||||
| `--term <ghostty\|kitty\|alacritty>` | `-t` | Terminal emulator (required for headless) |
|
||||
| `--privesc <sudo\|doas\|run0>` | | Privilege escalation tool (default: autodetect) |
|
||||
| `--git` | | Install the git version of every dependency that has one |
|
||||
| `--git-deps <name,...>` | | Install the git version of specific dependencies (e.g. `niri,quickshell`) |
|
||||
| `--all-features` | | Enable all optional dependencies (`dms-greeter`, `danksearch`, `dankcalendar`) |
|
||||
| `--dms-greeter` | | Install dms-greeter |
|
||||
| `--danksearch` | | Install danksearch and enable its user indexing service |
|
||||
| `--dankcalendar` | | Install dankcalendar |
|
||||
| `--include-deps <name,...>` | | Enable optional dependencies (e.g. `dms-greeter`) |
|
||||
| `--exclude-deps <name,...>` | | Skip specific dependencies |
|
||||
| `--replace-configs <name,...>` | | Replace specific configuration files (mutually exclusive with `--replace-configs-all`) |
|
||||
@@ -176,7 +184,7 @@ sudo -v && curl -fsSL https://install.danklinux.com | sh -s -- -c hyprland -t ki
|
||||
|
||||
Headless mode requires `--yes` to proceed; without it, the installer exits with an error.
|
||||
Configuration files are not replaced by default unless `--replace-configs` or `--replace-configs-all` is specified.
|
||||
`dms-greeter` is disabled by default; use `--include-deps dms-greeter` to enable it.
|
||||
Optional packages (`dms-greeter`, `danksearch`, `dankcalendar`) are disabled by default; enable them with their dedicated flags, `--include-deps`, or `--all-features`.
|
||||
|
||||
When no flags are provided, `dankinstall` launches the interactive TUI.
|
||||
|
||||
@@ -185,7 +193,7 @@ When no flags are provided, `dankinstall` launches the interactive TUI.
|
||||
Headless mode activates when `--compositor` or `--term` is provided.
|
||||
|
||||
- Both `--compositor` and `--term` are required; providing only one results in an error.
|
||||
- Headless-only flags (`--include-deps`, `--exclude-deps`, `--replace-configs`, `--replace-configs-all`, `--yes`) are rejected in TUI mode.
|
||||
- Headless-only flags (`--privesc`, `--git`, `--git-deps`, `--all-features`, `--include-deps`, `--exclude-deps`, `--replace-configs`, `--replace-configs-all`, `--yes`, `--danksearch`, `--dankcalendar`, `--dms-greeter`) are rejected in TUI mode.
|
||||
- Positional arguments are not accepted.
|
||||
|
||||
### Log file location
|
||||
|
||||
@@ -18,6 +18,10 @@ var Version = "dev"
|
||||
var (
|
||||
compositor string
|
||||
term string
|
||||
privescTool string
|
||||
gitAll bool
|
||||
gitDeps []string
|
||||
allFeatures bool
|
||||
includeDeps []string
|
||||
excludeDeps []string
|
||||
replaceConfigs []string
|
||||
@@ -25,6 +29,7 @@ var (
|
||||
yes bool
|
||||
danksearch bool
|
||||
dankcalendar bool
|
||||
dmsGreeter bool
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
@@ -35,8 +40,8 @@ var rootCmd = &cobra.Command{
|
||||
Without flags, it launches an interactive TUI. Providing either --compositor
|
||||
or --term activates headless (unattended) mode, which requires both flags.
|
||||
|
||||
Headless mode requires cached sudo credentials. Run 'sudo -v' beforehand, or
|
||||
configure passwordless sudo for your user.`,
|
||||
Headless mode requires cached credentials or a passwordless rule for your
|
||||
privilege escalation tool (sudo, doas, or run0).`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: runDankinstall,
|
||||
SilenceErrors: true,
|
||||
@@ -46,6 +51,10 @@ configure passwordless sudo for your user.`,
|
||||
func init() {
|
||||
rootCmd.Flags().StringVarP(&compositor, "compositor", "c", "", "Compositor/WM to install: niri, hyprland, or mango (enables headless mode)")
|
||||
rootCmd.Flags().StringVarP(&term, "term", "t", "", "Terminal emulator to install: ghostty, kitty, or alacritty (enables headless mode)")
|
||||
rootCmd.Flags().StringVar(&privescTool, "privesc", "", "Privilege escalation tool: sudo, doas, or run0 (default: autodetect)")
|
||||
rootCmd.Flags().BoolVar(&gitAll, "git", false, "Install the git version of every dep that has one")
|
||||
rootCmd.Flags().StringSliceVar(&gitDeps, "git-deps", []string{}, "Deps to install the git version of (e.g. niri,quickshell)")
|
||||
rootCmd.Flags().BoolVar(&allFeatures, "all-features", false, "Enable all optional deps (dms-greeter, danksearch, dankcalendar)")
|
||||
rootCmd.Flags().StringSliceVar(&includeDeps, "include-deps", []string{}, "Optional deps to enable (e.g. dms-greeter)")
|
||||
rootCmd.Flags().StringSliceVar(&excludeDeps, "exclude-deps", []string{}, "Deps to skip during installation")
|
||||
rootCmd.Flags().StringSliceVar(&replaceConfigs, "replace-configs", []string{}, "Deploy only named configs (e.g. niri,ghostty)")
|
||||
@@ -53,6 +62,7 @@ func init() {
|
||||
rootCmd.Flags().BoolVarP(&yes, "yes", "y", false, "Auto-confirm all prompts")
|
||||
rootCmd.Flags().BoolVar(&danksearch, "danksearch", false, "Install danksearch and enable its user indexing service")
|
||||
rootCmd.Flags().BoolVar(&dankcalendar, "dankcalendar", false, "Install dankcalendar")
|
||||
rootCmd.Flags().BoolVar(&dmsGreeter, "dms-greeter", false, "Install dms-greeter")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -73,6 +83,10 @@ func runDankinstall(cmd *cobra.Command, args []string) error {
|
||||
if !headlessMode {
|
||||
// Reject headless-only flags when running in TUI mode.
|
||||
headlessOnly := []string{
|
||||
"privesc",
|
||||
"git",
|
||||
"git-deps",
|
||||
"all-features",
|
||||
"include-deps",
|
||||
"exclude-deps",
|
||||
"replace-configs",
|
||||
@@ -80,6 +94,7 @@ func runDankinstall(cmd *cobra.Command, args []string) error {
|
||||
"yes",
|
||||
"danksearch",
|
||||
"dankcalendar",
|
||||
"dms-greeter",
|
||||
}
|
||||
var set []string
|
||||
for _, name := range headlessOnly {
|
||||
@@ -110,6 +125,10 @@ func runHeadless() error {
|
||||
cfg := headless.Config{
|
||||
Compositor: compositor,
|
||||
Terminal: term,
|
||||
PrivescTool: privescTool,
|
||||
GitAll: gitAll,
|
||||
GitDeps: gitDeps,
|
||||
AllFeatures: allFeatures,
|
||||
IncludeDeps: includeDeps,
|
||||
ExcludeDeps: excludeDeps,
|
||||
ReplaceConfigs: replaceConfigs,
|
||||
@@ -117,6 +136,7 @@ func runHeadless() error {
|
||||
Yes: yes,
|
||||
DankSearch: danksearch,
|
||||
DankCalendar: dankcalendar,
|
||||
DmsGreeter: dmsGreeter,
|
||||
}
|
||||
|
||||
runner := headless.NewRunner(cfg)
|
||||
|
||||
+2
-7
@@ -47,6 +47,7 @@ printf "%bInstalling Dankinstall %s for %s...%b\n" "$GREEN" "$LATEST_VERSION" "$
|
||||
|
||||
# Download and install
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap 'cd / && rm -rf "$TEMP_DIR"' EXIT
|
||||
cd "$TEMP_DIR" || exit 1
|
||||
|
||||
# Download the gzipped binary and its checksum
|
||||
@@ -67,8 +68,6 @@ if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then
|
||||
printf "Expected: %s\n" "$EXPECTED_CHECKSUM"
|
||||
printf "Got: %s\n" "$ACTUAL_CHECKSUM"
|
||||
printf "The downloaded file may be corrupted or tampered with\n"
|
||||
cd - >/dev/null
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -79,8 +78,4 @@ chmod +x installer
|
||||
|
||||
# Execute the installer
|
||||
printf "%bRunning installer...%b\n" "$GREEN" "$NC"
|
||||
./installer
|
||||
|
||||
# Cleanup
|
||||
cd - >/dev/null
|
||||
rm -rf "$TEMP_DIR"
|
||||
./installer "$@"
|
||||
@@ -101,6 +101,7 @@ type Distribution interface {
|
||||
|
||||
// Package Mapping
|
||||
GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping
|
||||
GetPackageMappingWithVariants(wm deps.WindowManager, variants map[string]deps.PackageVariant) map[string]PackageMapping
|
||||
|
||||
// Prerequisites
|
||||
InstallPrerequisites(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error
|
||||
|
||||
@@ -34,8 +34,12 @@ var orderedConfigNames = []string{"niri", "hyprland", "ghostty", "kitty", "alacr
|
||||
|
||||
// Config holds all CLI parameters for unattended installation.
|
||||
type Config struct {
|
||||
Compositor string // "niri" or "hyprland"
|
||||
Terminal string // "ghostty", "kitty", or "alacritty"
|
||||
Compositor string // "niri", "hyprland", or "mango"
|
||||
Terminal string // "ghostty", "kitty", or "alacritty"
|
||||
PrivescTool string // "sudo", "doas", or "run0"; empty means autodetect
|
||||
GitAll bool // git variant for every dep that supports it
|
||||
GitDeps []string // deps to force the git variant of
|
||||
AllFeatures bool // enable all optional deps
|
||||
IncludeDeps []string
|
||||
ExcludeDeps []string
|
||||
ReplaceConfigs []string // specific configs to deploy (e.g. "niri", "ghostty")
|
||||
@@ -43,6 +47,7 @@ type Config struct {
|
||||
Yes bool
|
||||
DankSearch bool // install danksearch and enable its user service
|
||||
DankCalendar bool // install dankcalendar
|
||||
DmsGreeter bool // install dms-greeter
|
||||
}
|
||||
|
||||
// Runner orchestrates unattended (headless) installation.
|
||||
@@ -68,6 +73,10 @@ func (r *Runner) GetLogChan() <-chan string {
|
||||
func (r *Runner) Run() error {
|
||||
r.log("Starting headless installation")
|
||||
|
||||
if err := r.applyPrivescTool(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. Parse compositor and terminal selections
|
||||
wm, err := r.parseWindowManager()
|
||||
if err != nil {
|
||||
@@ -112,14 +121,16 @@ func (r *Runner) Run() error {
|
||||
return fmt.Errorf("dependency detection failed: %w", err)
|
||||
}
|
||||
|
||||
reinstallItems, err := r.applyGitVariants(dependencies, gitVariantChecker(distro, wm, dependencies))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. Apply include/exclude filters and build the disabled-items map.
|
||||
// Headless mode does not currently collect any explicit reinstall selections,
|
||||
// so keep reinstallItems nil instead of constructing an always-empty map.
|
||||
disabledItems, err := r.buildDisabledItems(dependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var reinstallItems map[string]bool
|
||||
|
||||
// Print dependency summary
|
||||
fmt.Fprintln(os.Stdout, "\nDependencies:")
|
||||
@@ -129,6 +140,9 @@ func (r *Runner) Run() error {
|
||||
if disabledItems[dep.Name] {
|
||||
marker = " SKIP "
|
||||
status = "(disabled)"
|
||||
} else if reinstallItems[dep.Name] {
|
||||
marker = " RE "
|
||||
status = "(will reinstall)"
|
||||
} else {
|
||||
switch dep.Status {
|
||||
case deps.StatusInstalled:
|
||||
@@ -283,15 +297,96 @@ func (r *Runner) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) applyPrivescTool() error {
|
||||
if r.cfg.PrivescTool == "" {
|
||||
return nil
|
||||
}
|
||||
switch tool := privesc.Tool(strings.ToLower(r.cfg.PrivescTool)); tool {
|
||||
case privesc.ToolSudo, privesc.ToolDoas, privesc.ToolRun0:
|
||||
if err := privesc.SetTool(tool); err != nil {
|
||||
return fmt.Errorf("--privesc: %w", err)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid --privesc value %q: must be 'sudo', 'doas', or 'run0'", r.cfg.PrivescTool)
|
||||
}
|
||||
}
|
||||
|
||||
// CanToggle is not enough: some distros discard the variant (hyprland on Arch maps to stable either way)
|
||||
func gitVariantChecker(distro distros.Distribution, wm deps.WindowManager, dependencies []deps.Dependency) func(name string) bool {
|
||||
stable := distro.GetPackageMappingWithVariants(wm, map[string]deps.PackageVariant{})
|
||||
allGit := make(map[string]deps.PackageVariant, len(dependencies))
|
||||
for _, dep := range dependencies {
|
||||
allGit[dep.Name] = deps.VariantGit
|
||||
}
|
||||
git := distro.GetPackageMappingWithVariants(wm, allGit)
|
||||
return func(name string) bool {
|
||||
mapping, ok := git[name]
|
||||
return ok && mapping != stable[name]
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) applyGitVariants(dependencies []deps.Dependency, hasGitVariant func(name string) bool) (map[string]bool, error) {
|
||||
reinstallItems := make(map[string]bool)
|
||||
|
||||
// InstallPackages skips StatusInstalled deps unless marked for reinstall
|
||||
markGit := func(dep *deps.Dependency) {
|
||||
if dep.Variant == deps.VariantStable && dep.Status == deps.StatusInstalled {
|
||||
reinstallItems[dep.Name] = true
|
||||
}
|
||||
dep.Variant = deps.VariantGit
|
||||
}
|
||||
|
||||
for _, name := range r.cfg.GitDeps {
|
||||
name = strings.ToLower(strings.TrimSpace(name))
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
dep := findDepFold(dependencies, name)
|
||||
if dep == nil {
|
||||
return nil, fmt.Errorf("--git-deps: unknown dependency %q", name)
|
||||
}
|
||||
if !dep.CanToggle || !hasGitVariant(dep.Name) {
|
||||
return nil, fmt.Errorf("--git-deps: %q does not have a git variant on this distribution", dep.Name)
|
||||
}
|
||||
markGit(dep)
|
||||
}
|
||||
|
||||
if !r.cfg.GitAll {
|
||||
return reinstallItems, nil
|
||||
}
|
||||
|
||||
for i := range dependencies {
|
||||
if dependencies[i].CanToggle && hasGitVariant(dependencies[i].Name) {
|
||||
markGit(&dependencies[i])
|
||||
}
|
||||
}
|
||||
return reinstallItems, nil
|
||||
}
|
||||
|
||||
func findDepFold(dependencies []deps.Dependency, lowerName string) *deps.Dependency {
|
||||
if lowerName == "dms" {
|
||||
lowerName = "dms (dankmaterialshell)"
|
||||
}
|
||||
for i := range dependencies {
|
||||
if strings.ToLower(dependencies[i].Name) == lowerName {
|
||||
return &dependencies[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildDisabledItems computes the set of dependencies that should be skipped
|
||||
// during installation. Optional components are opt-in (disabled by default),
|
||||
// then re-enabled by the dedicated flags and --include-deps.
|
||||
// then re-enabled by --all-features, the dedicated flags, and --include-deps.
|
||||
func (r *Runner) buildDisabledItems(dependencies []deps.Dependency) (map[string]bool, error) {
|
||||
disabledItems := make(map[string]bool)
|
||||
|
||||
for i := range dependencies {
|
||||
if !dependencies[i].Required {
|
||||
disabledItems[dependencies[i].Name] = true
|
||||
if !r.cfg.AllFeatures {
|
||||
for i := range dependencies {
|
||||
if !dependencies[i].Required {
|
||||
disabledItems[dependencies[i].Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +403,12 @@ func (r *Runner) buildDisabledItems(dependencies []deps.Dependency) (map[string]
|
||||
}
|
||||
delete(disabledItems, "dankcalendar")
|
||||
}
|
||||
if r.cfg.DmsGreeter {
|
||||
if !r.depExists(dependencies, "dms-greeter") {
|
||||
return nil, fmt.Errorf("--dms-greeter: not available on this distribution")
|
||||
}
|
||||
delete(disabledItems, "dms-greeter")
|
||||
}
|
||||
|
||||
// Process --include-deps (enable items that are disabled by default)
|
||||
for _, name := range r.cfg.IncludeDeps {
|
||||
|
||||
@@ -357,6 +357,8 @@ func TestBuildDisabledItems(t *testing.T) {
|
||||
excludeDeps []string
|
||||
dankSearch bool
|
||||
dankCalendar bool
|
||||
dmsGreeter bool
|
||||
allFeatures bool
|
||||
deps []deps.Dependency // nil means use the shared fixture
|
||||
wantErr bool
|
||||
errContains string // substring expected in error message
|
||||
@@ -442,6 +444,31 @@ func TestBuildDisabledItems(t *testing.T) {
|
||||
wantErr: true,
|
||||
errContains: "--dankcalendar",
|
||||
},
|
||||
{
|
||||
name: "dms-greeter flag enables it",
|
||||
dmsGreeter: true,
|
||||
wantEnabled: []string{"dms-greeter"},
|
||||
wantDisabled: []string{"danksearch", "dankcalendar"},
|
||||
},
|
||||
{
|
||||
name: "dms-greeter flag when unavailable errors",
|
||||
dmsGreeter: true,
|
||||
deps: []deps.Dependency{{Name: "niri", Status: deps.StatusInstalled, Required: true}},
|
||||
wantErr: true,
|
||||
errContains: "--dms-greeter",
|
||||
},
|
||||
{
|
||||
name: "all-features enables every optional dep",
|
||||
allFeatures: true,
|
||||
wantEnabled: []string{"dms-greeter", "danksearch", "dankcalendar", "niri", "ghostty", "waybar"},
|
||||
},
|
||||
{
|
||||
name: "all-features with exclude still disables the excluded dep",
|
||||
allFeatures: true,
|
||||
excludeDeps: []string{"dankcalendar"},
|
||||
wantEnabled: []string{"dms-greeter", "danksearch"},
|
||||
wantDisabled: []string{"dankcalendar"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -451,6 +478,8 @@ func TestBuildDisabledItems(t *testing.T) {
|
||||
ExcludeDeps: tt.excludeDeps,
|
||||
DankSearch: tt.dankSearch,
|
||||
DankCalendar: tt.dankCalendar,
|
||||
DmsGreeter: tt.dmsGreeter,
|
||||
AllFeatures: tt.allFeatures,
|
||||
})
|
||||
d := tt.deps
|
||||
if d == nil {
|
||||
@@ -491,3 +520,152 @@ func TestBuildDisabledItems(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyGitVariants(t *testing.T) {
|
||||
freshDeps := func() []deps.Dependency {
|
||||
return []deps.Dependency{
|
||||
{Name: "niri", Status: deps.StatusInstalled, Variant: deps.VariantStable, CanToggle: true},
|
||||
{Name: "quickshell", Status: deps.StatusMissing, Variant: deps.VariantStable, CanToggle: true},
|
||||
{Name: "hyprland", Status: deps.StatusInstalled, Variant: deps.VariantGit, CanToggle: true},
|
||||
{Name: "dms (DankMaterialShell)", Status: deps.StatusMissing, Variant: deps.VariantStable, CanToggle: true},
|
||||
{Name: "ghostty", Status: deps.StatusInstalled, Variant: deps.VariantStable, CanToggle: false},
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
gitAll bool
|
||||
gitDeps []string
|
||||
noGitVariant []string // deps whose git and stable packages resolve identically
|
||||
wantErr bool
|
||||
errContains string
|
||||
wantGit []string
|
||||
wantStable []string
|
||||
wantReinstall []string
|
||||
}{
|
||||
{
|
||||
name: "no git flags leaves variants untouched",
|
||||
wantGit: []string{"hyprland"},
|
||||
wantStable: []string{"niri", "quickshell", "dms (DankMaterialShell)", "ghostty"},
|
||||
},
|
||||
{
|
||||
name: "git-deps flips only the named deps",
|
||||
gitDeps: []string{"niri", "quickshell"},
|
||||
wantGit: []string{"niri", "quickshell", "hyprland"},
|
||||
wantStable: []string{"dms (DankMaterialShell)", "ghostty"},
|
||||
wantReinstall: []string{"niri"},
|
||||
},
|
||||
{
|
||||
name: "git-deps matches names case-insensitively and accepts the dms alias",
|
||||
gitDeps: []string{"NIRI", "dms"},
|
||||
wantGit: []string{"niri", "dms (DankMaterialShell)", "hyprland"},
|
||||
wantStable: []string{"quickshell", "ghostty"},
|
||||
wantReinstall: []string{"niri"},
|
||||
},
|
||||
{
|
||||
name: "empty and whitespace entries are skipped",
|
||||
gitDeps: []string{"", " "},
|
||||
wantGit: []string{"hyprland"},
|
||||
wantStable: []string{"niri", "quickshell", "dms (DankMaterialShell)", "ghostty"},
|
||||
},
|
||||
{
|
||||
name: "unknown dep errors",
|
||||
gitDeps: []string{"nonexistent"},
|
||||
wantErr: true,
|
||||
errContains: "--git-deps",
|
||||
},
|
||||
{
|
||||
name: "dep without git variant errors",
|
||||
gitDeps: []string{"ghostty"},
|
||||
wantErr: true,
|
||||
errContains: "does not have a git variant",
|
||||
},
|
||||
{
|
||||
name: "git flips every toggleable dep",
|
||||
gitAll: true,
|
||||
wantGit: []string{"niri", "quickshell", "hyprland", "dms (DankMaterialShell)"},
|
||||
wantStable: []string{"ghostty"},
|
||||
wantReinstall: []string{"niri"},
|
||||
},
|
||||
{
|
||||
name: "git-deps errors when the distro has no distinct git package",
|
||||
gitDeps: []string{"niri"},
|
||||
noGitVariant: []string{"niri"},
|
||||
wantErr: true,
|
||||
errContains: "does not have a git variant",
|
||||
},
|
||||
{
|
||||
name: "git skips deps without a distinct git package",
|
||||
gitAll: true,
|
||||
noGitVariant: []string{"niri"},
|
||||
wantGit: []string{"quickshell", "hyprland", "dms (DankMaterialShell)"},
|
||||
wantStable: []string{"niri", "ghostty"},
|
||||
wantReinstall: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := NewRunner(Config{GitAll: tt.gitAll, GitDeps: tt.gitDeps})
|
||||
d := freshDeps()
|
||||
|
||||
hasGitVariant := func(name string) bool {
|
||||
for _, n := range tt.noGitVariant {
|
||||
if n == name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
reinstall, err := r.applyGitVariants(d, hasGitVariant)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("applyGitVariants() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if tt.wantErr {
|
||||
if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
|
||||
t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
variants := make(map[string]deps.PackageVariant)
|
||||
for _, dep := range d {
|
||||
variants[dep.Name] = dep.Variant
|
||||
}
|
||||
for _, name := range tt.wantGit {
|
||||
if variants[name] != deps.VariantGit {
|
||||
t.Errorf("expected %q to be VariantGit", name)
|
||||
}
|
||||
}
|
||||
for _, name := range tt.wantStable {
|
||||
if variants[name] != deps.VariantStable {
|
||||
t.Errorf("expected %q to stay VariantStable", name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(reinstall) != len(tt.wantReinstall) {
|
||||
t.Errorf("reinstall = %v, want %v", reinstall, tt.wantReinstall)
|
||||
}
|
||||
for _, name := range tt.wantReinstall {
|
||||
if !reinstall[name] {
|
||||
t.Errorf("expected %q to be marked for reinstall", name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPrivescTool(t *testing.T) {
|
||||
if err := NewRunner(Config{}).applyPrivescTool(); err != nil {
|
||||
t.Errorf("empty PrivescTool should be a no-op, got error: %v", err)
|
||||
}
|
||||
|
||||
err := NewRunner(Config{PrivescTool: "pkexec"}).applyPrivescTool()
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unsupported privesc tool, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "--privesc") {
|
||||
t.Errorf("error %q does not mention --privesc", err.Error())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user