summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall
diff options
context:
space:
mode:
authorAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
committerAlexanderCurl <alexc@alexc.hu>2026-04-18 17:46:06 +0100
commit70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch)
treeab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall
parent5d94c0a7d44a2255b81815a52a7056a94a39842d (diff)
downloadRaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz
RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go219
1 files changed, 219 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go
new file mode 100644
index 0000000..042506e
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/cmd/dankinstall/main.go
@@ -0,0 +1,219 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/AvengeMedia/DankMaterialShell/core/internal/headless"
+ "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
+ "github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/spf13/cobra"
+)
+
+var Version = "dev"
+
+// Flag variables bound via pflag
+var (
+ compositor string
+ term string
+ includeDeps []string
+ excludeDeps []string
+ replaceConfigs []string
+ replaceConfigsAll bool
+ yes bool
+)
+
+var rootCmd = &cobra.Command{
+ Use: "dankinstall",
+ Short: "Install DankMaterialShell and its dependencies",
+ Long: `dankinstall sets up DankMaterialShell with your chosen compositor and terminal.
+
+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.`,
+ Args: cobra.NoArgs,
+ RunE: runDankinstall,
+ SilenceErrors: true,
+ SilenceUsage: true,
+}
+
+func init() {
+ rootCmd.Flags().StringVarP(&compositor, "compositor", "c", "", "Compositor/WM to install: niri or hyprland (enables headless mode)")
+ rootCmd.Flags().StringVarP(&term, "term", "t", "", "Terminal emulator to install: ghostty, kitty, or alacritty (enables headless mode)")
+ 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)")
+ rootCmd.Flags().BoolVar(&replaceConfigsAll, "replace-configs-all", false, "Deploy and replace all configurations")
+ rootCmd.Flags().BoolVarP(&yes, "yes", "y", false, "Auto-confirm all prompts")
+}
+
+func main() {
+ if os.Getuid() == 0 {
+ fmt.Fprintln(os.Stderr, "Error: dankinstall must not be run as root")
+ os.Exit(1)
+ }
+
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+func runDankinstall(cmd *cobra.Command, args []string) error {
+ headlessMode := compositor != "" || term != ""
+
+ if !headlessMode {
+ // Reject headless-only flags when running in TUI mode.
+ headlessOnly := []string{
+ "include-deps",
+ "exclude-deps",
+ "replace-configs",
+ "replace-configs-all",
+ "yes",
+ }
+ var set []string
+ for _, name := range headlessOnly {
+ if cmd.Flags().Changed(name) {
+ set = append(set, "--"+name)
+ }
+ }
+ if len(set) > 0 {
+ return fmt.Errorf("flags %s are only valid in headless mode (requires both --compositor and --term)", strings.Join(set, ", "))
+ }
+ }
+
+ if headlessMode {
+ return runHeadless()
+ }
+ return runTUI()
+}
+
+func runHeadless() error {
+ // Validate required flags
+ if compositor == "" {
+ return fmt.Errorf("--compositor is required for headless mode (niri or hyprland)")
+ }
+ if term == "" {
+ return fmt.Errorf("--term is required for headless mode (ghostty, kitty, or alacritty)")
+ }
+
+ cfg := headless.Config{
+ Compositor: compositor,
+ Terminal: term,
+ IncludeDeps: includeDeps,
+ ExcludeDeps: excludeDeps,
+ ReplaceConfigs: replaceConfigs,
+ ReplaceConfigsAll: replaceConfigsAll,
+ Yes: yes,
+ }
+
+ runner := headless.NewRunner(cfg)
+
+ // Set up file logging
+ fileLogger, err := log.NewFileLogger()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to create log file: %v\n", err)
+ }
+
+ if fileLogger != nil {
+ fmt.Printf("Logging to: %s\n", fileLogger.GetLogPath())
+ fileLogger.StartListening(runner.GetLogChan())
+ defer func() {
+ if err := fileLogger.Close(); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to close log file: %v\n", err)
+ }
+ }()
+ } else {
+ // Drain the log channel to prevent blocking sends from deadlocking
+ // downstream components (distros, config deployer) that write to it.
+ // Use an explicit stop signal because this code does not own the
+ // runner log channel and cannot assume it will be closed.
+ defer drainLogChan(runner.GetLogChan())()
+ }
+
+ if err := runner.Run(); err != nil {
+ if fileLogger != nil {
+ fmt.Fprintf(os.Stderr, "\nFull logs are available at: %s\n", fileLogger.GetLogPath())
+ }
+ return err
+ }
+
+ if fileLogger != nil {
+ fmt.Printf("\nFull logs are available at: %s\n", fileLogger.GetLogPath())
+ }
+ return nil
+}
+
+func runTUI() error {
+ fileLogger, err := log.NewFileLogger()
+ if err != nil {
+ fmt.Printf("Warning: Failed to create log file: %v\n", err)
+ fmt.Println("Continuing without file logging...")
+ }
+
+ logFilePath := ""
+ if fileLogger != nil {
+ logFilePath = fileLogger.GetLogPath()
+ fmt.Printf("Logging to: %s\n", logFilePath)
+ defer func() {
+ if err := fileLogger.Close(); err != nil {
+ fmt.Printf("Warning: Failed to close log file: %v\n", err)
+ }
+ }()
+ }
+
+ model := tui.NewModel(Version, logFilePath)
+
+ if fileLogger != nil {
+ fileLogger.StartListening(model.GetLogChan())
+ } else {
+ // Drain the log channel to prevent blocking sends from deadlocking
+ // downstream components (distros, config deployer) that write to it.
+ // Use an explicit stop signal because this code does not own the
+ // model log channel and cannot assume it will be closed.
+ defer drainLogChan(model.GetLogChan())()
+ }
+
+ p := tea.NewProgram(model, tea.WithAltScreen())
+ if _, err := p.Run(); err != nil {
+ if logFilePath != "" {
+ fmt.Fprintf(os.Stderr, "\nFull logs are available at: %s\n", logFilePath)
+ }
+ return fmt.Errorf("error running program: %w", err)
+ }
+
+ if logFilePath != "" {
+ fmt.Printf("\nFull logs are available at: %s\n", logFilePath)
+ }
+ return nil
+}
+
+// drainLogChan starts a goroutine that discards all messages from logCh,
+// preventing blocking sends from deadlocking downstream components. It returns
+// a cleanup function that signals the goroutine to stop and waits for it to
+// exit. Callers should defer the returned function.
+func drainLogChan(logCh <-chan string) func() {
+ drainStop := make(chan struct{})
+ drainDone := make(chan struct{})
+ go func() {
+ defer close(drainDone)
+ for {
+ select {
+ case <-drainStop:
+ return
+ case _, ok := <-logCh:
+ if !ok {
+ return
+ }
+ }
+ }
+ }()
+ return func() {
+ close(drainStop)
+ <-drainDone
+ }
+}