@@ -91,6 +91,28 @@ func Install(rc *eos_io.RuntimeContext, config *Config) (*InstallResult, error)
9191 }
9292 }
9393
94+ // INTERVENE - Set up session backups
95+ if config .SetupSessionBackups && ! config .SkipSessionBackups {
96+ logger .Info ("Setting up coding session backups" )
97+ backupConfig := & SessionBackupConfig {
98+ User : config .User ,
99+ CronInterval : config .SessionBackupInterval ,
100+ DryRun : config .DryRun ,
101+ }
102+ backupResult , err := SetupSessionBackups (rc , backupConfig )
103+ if err != nil {
104+ result .Warnings = append (result .Warnings ,
105+ fmt .Sprintf ("Session backup setup had issues: %v" , err ))
106+ logger .Warn ("Session backup setup had issues" , zap .Error (err ))
107+ } else {
108+ result .SessionBackupsConfigured = true
109+ result .SessionBackupResult = backupResult
110+ logger .Info ("Session backups configured" ,
111+ zap .Bool ("cron_configured" , backupResult .CronConfigured ),
112+ zap .Int ("scripts_installed" , len (backupResult .ScriptsInstalled )))
113+ }
114+ }
115+
94116 // INTERVENE - Cleanup old IDE servers if requested
95117 if config .CleanupIDEServers {
96118 logger .Info ("Cleaning up old IDE server versions" )
@@ -367,6 +389,26 @@ func GenerateAccessInstructions(rc *eos_io.RuntimeContext, config *Config, resul
367389 sb .WriteString ("\n " )
368390 }
369391
392+ // Session backups configured
393+ if result .SessionBackupsConfigured && result .SessionBackupResult != nil {
394+ sb .WriteString ("Session Backups:\n " )
395+ sb .WriteString (strings .Repeat ("-" , 30 ) + "\n " )
396+ if result .SessionBackupResult .CronConfigured {
397+ sb .WriteString (fmt .Sprintf (" Schedule: %s\n " , result .SessionBackupResult .CronInterval ))
398+ }
399+ sb .WriteString (fmt .Sprintf (" Backup dir: %s\n " , result .SessionBackupResult .BackupDir ))
400+ if result .SessionBackupResult .ClaudeDataFound {
401+ sb .WriteString (" Claude Code data: found\n " )
402+ }
403+ if result .SessionBackupResult .CodexDataFound {
404+ sb .WriteString (" Codex data: found\n " )
405+ }
406+ sb .WriteString ("\n " )
407+ sb .WriteString (" Manage backups: ~/bin/setup-coding-session-backups.sh\n " )
408+ sb .WriteString (" Export to Markdown: ~/bin/export-coding-sessions.sh --today\n " )
409+ sb .WriteString ("\n " )
410+ }
411+
370412 // Supported IDEs
371413 sb .WriteString ("Supported IDEs:\n " )
372414 sb .WriteString (strings .Repeat ("-" , 30 ) + "\n " )
@@ -423,12 +465,13 @@ func formatBytes(bytes int64) string {
423465 }
424466}
425467
426- // InstallAITools installs AI coding assistants (Claude Code, OpenAI Codex CLI)
468+ // InstallAITools installs AI coding assistants (Claude Code, OpenAI Codex CLI, Windsurf )
427469func InstallAITools (rc * eos_io.RuntimeContext , config * Config , result * InstallResult ) error {
428470 logger := otelzap .Ctx (rc .Ctx )
429471 logger .Info ("Installing AI coding tools" ,
430472 zap .Bool ("skip_claude" , config .SkipClaudeCode ),
431473 zap .Bool ("skip_codex" , config .SkipCodex ),
474+ zap .Bool ("skip_windsurf" , config .SkipWindsurf ),
432475 zap .Bool ("dry_run" , config .DryRun ))
433476
434477 result .AIToolsInstalled = []string {}
@@ -450,6 +493,14 @@ func InstallAITools(rc *eos_io.RuntimeContext, config *Config, result *InstallRe
450493 }
451494 }
452495
496+ // Install Windsurf (x86_64 only)
497+ if ! config .SkipWindsurf {
498+ if err := installWindsurf (rc , config , result ); err != nil {
499+ logger .Warn ("Windsurf installation skipped or failed" , zap .Error (err ))
500+ // Don't set lastErr - Windsurf skip is informational on non-x86
501+ }
502+ }
503+
453504 return lastErr
454505}
455506
@@ -552,6 +603,113 @@ func installCodexCLI(rc *eos_io.RuntimeContext, config *Config, result *InstallR
552603 return nil
553604}
554605
606+ // installWindsurf installs Windsurf IDE (x86_64 only)
607+ // NOTE: Windsurf is a desktop IDE that runs on the CLIENT machine, not the server.
608+ // This function installs it on the server for local development or if the server
609+ // has a desktop environment. For remote SSH use, Windsurf should be installed on
610+ // the local machine and will connect via SSH.
611+ func installWindsurf (rc * eos_io.RuntimeContext , config * Config , result * InstallResult ) error {
612+ logger := otelzap .Ctx (rc .Ctx )
613+ logger .Info ("Checking Windsurf installation requirements" )
614+
615+ // Check architecture - Windsurf only supports x86_64
616+ arch := runtime .GOARCH
617+ if arch != "amd64" {
618+ logger .Info ("Windsurf not available on this architecture" ,
619+ zap .String ("arch" , arch ),
620+ zap .String ("required" , "amd64" ))
621+ result .AIToolsInstalled = append (result .AIToolsInstalled ,
622+ fmt .Sprintf ("Windsurf (skipped - requires x86_64, got %s)" , arch ))
623+ return fmt .Errorf ("windsurf requires x86_64 architecture, this system is %s" , arch )
624+ }
625+
626+ // Check if windsurf is already installed
627+ if _ , err := exec .LookPath ("windsurf" ); err == nil {
628+ logger .Info ("Windsurf already installed" )
629+ result .AIToolsInstalled = append (result .AIToolsInstalled , "Windsurf (already installed)" )
630+ result .WindsurfInstalled = true
631+ return nil
632+ }
633+
634+ // Check if the .deb package is available in standard locations
635+ windsurfDebPath := "/tmp/windsurf.deb"
636+
637+ if config .DryRun {
638+ logger .Info ("DRY RUN: Would install Windsurf IDE" ,
639+ zap .String ("arch" , arch ))
640+ result .AIToolsInstalled = append (result .AIToolsInstalled , "Windsurf (would install)" )
641+ return nil
642+ }
643+
644+ // Try to download and install Windsurf
645+ // Windsurf provides a .deb package for Ubuntu/Debian
646+ windsurfDownloadURL := "https://windsurf-stable.codeiumdata.com/linux-x64/stable/latest/Windsurf.deb"
647+
648+ logger .Info ("Downloading Windsurf" , zap .String ("url" , windsurfDownloadURL ))
649+
650+ // Download the .deb file
651+ resp , err := http .Get (windsurfDownloadURL )
652+ if err != nil {
653+ result .Warnings = append (result .Warnings ,
654+ fmt .Sprintf ("Failed to download Windsurf: %v. Install manually from https://codeium.com/windsurf" , err ))
655+ return fmt .Errorf ("failed to download windsurf: %w" , err )
656+ }
657+ defer resp .Body .Close ()
658+
659+ if resp .StatusCode != http .StatusOK {
660+ result .Warnings = append (result .Warnings ,
661+ fmt .Sprintf ("Failed to download Windsurf: HTTP %d. Install manually from https://codeium.com/windsurf" , resp .StatusCode ))
662+ return fmt .Errorf ("failed to download windsurf: HTTP %d" , resp .StatusCode )
663+ }
664+
665+ // Save to temp file
666+ debFile , err := os .Create (windsurfDebPath )
667+ if err != nil {
668+ return fmt .Errorf ("failed to create temp file for windsurf: %w" , err )
669+ }
670+ defer os .Remove (windsurfDebPath )
671+
672+ if _ , err := io .Copy (debFile , resp .Body ); err != nil {
673+ debFile .Close ()
674+ return fmt .Errorf ("failed to save windsurf package: %w" , err )
675+ }
676+ debFile .Close ()
677+
678+ logger .Info ("Installing Windsurf package" )
679+
680+ // Install using dpkg
681+ installCmd := exec .Command ("dpkg" , "-i" , windsurfDebPath )
682+ output , err := installCmd .CombinedOutput ()
683+ if err != nil {
684+ // Try to fix dependencies
685+ logger .Warn ("dpkg install had issues, attempting to fix dependencies" ,
686+ zap .String ("output" , string (output )))
687+
688+ fixCmd := exec .Command ("apt-get" , "install" , "-f" , "-y" )
689+ fixOutput , fixErr := fixCmd .CombinedOutput ()
690+ if fixErr != nil {
691+ logger .Error ("Failed to fix Windsurf dependencies" ,
692+ zap .Error (fixErr ),
693+ zap .String ("output" , string (fixOutput )))
694+ result .Warnings = append (result .Warnings ,
695+ fmt .Sprintf ("Windsurf installation had dependency issues. Run: sudo apt-get install -f" ))
696+ return fmt .Errorf ("windsurf dependency fix failed: %w" , fixErr )
697+ }
698+ }
699+
700+ // Verify installation
701+ if _ , err := exec .LookPath ("windsurf" ); err != nil {
702+ result .Warnings = append (result .Warnings ,
703+ "Windsurf package installed but 'windsurf' command not found in PATH" )
704+ return fmt .Errorf ("windsurf installed but not in PATH" )
705+ }
706+
707+ logger .Info ("Windsurf installed successfully" )
708+ result .AIToolsInstalled = append (result .AIToolsInstalled , "Windsurf" )
709+ result .WindsurfInstalled = true
710+ return nil
711+ }
712+
555713func downloadInstallerWithChecksum (url , expectedChecksum string ) (string , error ) {
556714 resp , err := http .Get (url )
557715 if err != nil {
0 commit comments