feat: add interactive setup wizard for first-run configuration

Implement a comprehensive setup wizard to improve onboarding and
configuration experience for both personal and server deployments.

Key features:
- Interactive wizard with profile selection (personal/server/custom)
- Quick setup mode with sensible defaults
- First-run detection with helpful welcome message
- Directory configuration with validation
- Server OAuth/JWT configuration with auto-generation
- Environment file creation for server deployments
- Template generators for systemd service and env files

New commands:
- opal setup              # Interactive wizard
- opal setup --quick      # Quick setup with defaults
- opal setup --profile    # Use specific profile
- opal setup --show-systemd  # Show systemd template
- opal setup --show-env   # Show environment file template

Implementation:
- internal/wizard/prompts.go: Reusable prompt utilities
- internal/wizard/profiles.go: Profile definitions and templates
- cmd/setup.go: Main setup command implementation
- cmd/root.go: First-run detection and welcome message
- internal/engine/config.go: ConfigExists() and IsFirstRun() helpers

User experience:
- On first run, shows welcome message suggesting 'opal setup'
- Non-intrusive - creates defaults automatically if skipped
- Wizard guides through all configuration options
- Server setup includes OAuth/JWT configuration
- Environment file created with proper permissions (0600)
- Clear next steps displayed after completion
This commit is contained in:
2026-01-06 21:49:13 +01:00
parent 5d01c9f564
commit 140d9f7f25
6 changed files with 721 additions and 2 deletions
+15
View File
@@ -219,6 +219,21 @@ func GetSyncConflictLogPath() (string, error) {
return filepath.Join(dataDir, "sync_conflicts.log"), nil
}
// ConfigExists checks if a configuration file exists
func ConfigExists() bool {
configPath, err := GetConfigPath()
if err != nil {
return false
}
_, err = os.Stat(configPath)
return err == nil
}
// IsFirstRun checks if this is the first time opal is being run
func IsFirstRun() bool {
return !ConfigExists()
}
// LoadConfig loads the configuration from file or creates default
func LoadConfig() (*Config, error) {
if globalConfig != nil {