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:
@@ -0,0 +1,118 @@
|
||||
package wizard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Profile represents a configuration profile
|
||||
type Profile struct {
|
||||
Name string
|
||||
Description string
|
||||
ConfigDir string
|
||||
DataDir string
|
||||
IsServer bool
|
||||
}
|
||||
|
||||
// GetProfiles returns available setup profiles
|
||||
func GetProfiles() []Profile {
|
||||
home, _ := os.UserHomeDir()
|
||||
|
||||
return []Profile{
|
||||
{
|
||||
Name: "Personal",
|
||||
Description: "Single user, default locations",
|
||||
ConfigDir: filepath.Join(home, ".config", "opal"),
|
||||
DataDir: filepath.Join(home, ".local", "share", "opal"),
|
||||
IsServer: false,
|
||||
},
|
||||
{
|
||||
Name: "Server",
|
||||
Description: "Production deployment, system paths",
|
||||
ConfigDir: "/etc/opal",
|
||||
DataDir: "/var/lib/opal",
|
||||
IsServer: true,
|
||||
},
|
||||
{
|
||||
Name: "Custom",
|
||||
Description: "Configure all options manually",
|
||||
ConfigDir: "",
|
||||
DataDir: "",
|
||||
IsServer: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfileChoices returns profile names for display
|
||||
func GetProfileChoices() []string {
|
||||
profiles := GetProfiles()
|
||||
choices := make([]string, len(profiles))
|
||||
for i, p := range profiles {
|
||||
choices[i] = fmt.Sprintf("%s - %s", p.Name, p.Description)
|
||||
}
|
||||
return choices
|
||||
}
|
||||
|
||||
// SystemdServiceTemplate returns a systemd service file template
|
||||
func SystemdServiceTemplate(configDir, dataDir string) string {
|
||||
return fmt.Sprintf(`[Unit]
|
||||
Description=Opal Task API Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=opal
|
||||
Group=opal
|
||||
WorkingDirectory=%s
|
||||
EnvironmentFile=%s/opal.env
|
||||
ExecStart=/usr/local/bin/opal server start --addr :8080
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=%s
|
||||
ReadOnlyPaths=%s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`, dataDir, configDir, dataDir, configDir)
|
||||
}
|
||||
|
||||
// EnvFileTemplate returns an environment file template
|
||||
func EnvFileTemplate(oauthIssuer, oauthClientID, oauthClientSecret, oauthRedirectURI, jwtSecret string) string {
|
||||
return fmt.Sprintf(`# Opal Server Configuration
|
||||
# Generated by opal setup wizard
|
||||
|
||||
# Server
|
||||
SERVER_ADDR=:8080
|
||||
|
||||
# Directory Configuration (optional - defaults to /etc/opal and /var/lib/opal)
|
||||
# OPAL_CONFIG_DIR=/etc/opal
|
||||
# OPAL_DATA_DIR=/var/lib/opal
|
||||
|
||||
# OAuth Configuration
|
||||
OAUTH_ENABLED=true
|
||||
OAUTH_ISSUER=%s
|
||||
OAUTH_CLIENT_ID=%s
|
||||
OAUTH_CLIENT_SECRET=%s
|
||||
OAUTH_REDIRECT_URI=%s
|
||||
|
||||
# JWT Configuration
|
||||
JWT_SECRET=%s
|
||||
JWT_EXPIRY=3600
|
||||
|
||||
# Refresh Token Configuration
|
||||
REFRESH_TOKEN_EXPIRY=604800
|
||||
`,
|
||||
oauthIssuer,
|
||||
oauthClientID,
|
||||
oauthClientSecret,
|
||||
oauthRedirectURI,
|
||||
jwtSecret,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user