Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cmd/add/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func AddWiki(instance config.Installation, wikiID, siteName, domain, wikipath, d
}

//Checking Running status
err = orchestrators.CheckRunningStatus(instance.Path, instance.Id, instance.Orchestrator)
err = orchestrators.CheckRunningStatus(instance)
if err != nil {
return err
}
Expand Down Expand Up @@ -201,7 +201,8 @@ func AddWiki(instance config.Installation, wikiID, siteName, domain, wikipath, d
if err != nil {
return err
}
err = restart.Restart(instance)

err = restart.Restart(instance, false, false)
if err != nil {
return err
}
Expand Down
103 changes: 85 additions & 18 deletions cmd/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,32 @@ import (

"github.com/CanastaWiki/Canasta-CLI/internal/canasta"
"github.com/CanastaWiki/Canasta-CLI/internal/config"
"github.com/CanastaWiki/Canasta-CLI/internal/devmode"
"github.com/CanastaWiki/Canasta-CLI/internal/farmsettings"
"github.com/CanastaWiki/Canasta-CLI/internal/imagebuild"
"github.com/CanastaWiki/Canasta-CLI/internal/logging"
"github.com/CanastaWiki/Canasta-CLI/internal/mediawiki"
"github.com/CanastaWiki/Canasta-CLI/internal/orchestrators"
"github.com/CanastaWiki/Canasta-CLI/internal/spinner"
)

func NewCmdCreate() *cobra.Command {
var (
path string
orchestrator string
workingDir string
wikiID string
siteName string
domain string
yamlPath string
err error
keepConfig bool
canastaInfo canasta.CanastaVariables
override string
envFile string
path string
orchestrator string
workingDir string
wikiID string
siteName string
domain string
yamlPath string
err error
keepConfig bool
canastaInfo canasta.CanastaVariables
override string
envFile string
devModeFlag bool // enable dev mode
devTag string // registry image tag for dev mode
buildFromPath string // path to build Canasta from source
)
createCmd := &cobra.Command{
Use: "create",
Expand All @@ -53,15 +59,23 @@ func NewCmdCreate() *cobra.Command {
log.Fatal(fmt.Errorf("Error: Canasta instance ID should not contain spaces or non-ASCII characters, only alphanumeric characters are allowed"))
}

// Validate --dev-tag and --build-from are mutually exclusive
if devTag != "latest" && buildFromPath != "" {
log.Fatal(fmt.Errorf("Error: --dev-tag and --build-from are mutually exclusive"))
}

// Generate passwords (auto-gen if not provided via flags)
if canastaInfo, err = canasta.GeneratePasswords(workingDir, canastaInfo); err != nil {
log.Fatal(err)
}

description := "Creating Canasta installation '" + canastaInfo.Id + "'..."
if devModeFlag {
description = "Creating Canasta installation '" + canastaInfo.Id + "' with dev mode..."
}
_, done := spinner.New(description)

if err = createCanasta(canastaInfo, workingDir, path, wikiID, siteName, domain, yamlPath, orchestrator, override, envFile, done); err != nil {
if err = createCanasta(canastaInfo, workingDir, path, wikiID, siteName, domain, yamlPath, orchestrator, override, envFile, devModeFlag, devTag, buildFromPath, done); err != nil {
fmt.Print(err.Error(), "\n")
if !keepConfig {
canasta.DeleteConfigAndContainers(keepConfig, path+"/"+canastaInfo.Id, orchestrator)
Expand Down Expand Up @@ -93,6 +107,9 @@ func NewCmdCreate() *cobra.Command {
createCmd.Flags().StringVar(&canastaInfo.WikiDBUsername, "wikidbuser", "root", "The username of the wiki database user (default: \"root\")")
createCmd.Flags().StringVar(&canastaInfo.WikiDBPassword, "wikidbpass", "", "Wiki database password (if not provided, auto-generates and saves to .env). Tip: Use --wikidbpass \"$WIKI_DB_PASS\" to avoid exposing password in shell history")
createCmd.Flags().StringVarP(&envFile, "envfile", "e", "", "Path to .env file with password overrides (merged with .env.example)")
createCmd.Flags().BoolVarP(&devModeFlag, "dev", "D", false, "Enable development mode with Xdebug and code extraction")
createCmd.Flags().StringVar(&devTag, "dev-tag", "latest", "Canasta image tag to use (e.g., latest, dev-branch)")
createCmd.Flags().StringVar(&buildFromPath, "build-from", "", "Build Canasta image from local source directory (expects Canasta/, optionally CanastaBase/)")

// Mark required flags
createCmd.MarkFlagRequired("id")
Expand All @@ -102,7 +119,7 @@ func NewCmdCreate() *cobra.Command {
}

// createCanasta accepts all the keyword arguments and creates an installation of the latest Canasta.
func createCanasta(canastaInfo canasta.CanastaVariables, workingDir, path, wikiID, siteName, domain, yamlPath, orchestrator, override, envFile string, done chan struct{}) error {
func createCanasta(canastaInfo canasta.CanastaVariables, workingDir, path, wikiID, siteName, domain, yamlPath, orchestrator, override, envFile string, devModeEnabled bool, devTag, buildFromPath string, done chan struct{}) error {
// Pass a message to the "done" channel indicating the completion of createCanasta function.
// This signals the spinner to stop printing progress, regardless of success or failure.
defer func() {
Expand All @@ -111,8 +128,24 @@ func createCanasta(canastaInfo canasta.CanastaVariables, workingDir, path, wikiI
if _, err := config.GetDetails(canastaInfo.Id); err == nil {
log.Fatal(fmt.Errorf("Canasta installation with the ID already exist!"))
}

// Determine the base image to use
var baseImage string
if buildFromPath != "" {
// Build Canasta (and optionally CanastaBase) from source
logging.Print("Building Canasta from local source...\n")
builtImage, err := imagebuild.BuildFromSource(buildFromPath)
if err != nil {
return fmt.Errorf("failed to build from source: %w", err)
}
baseImage = builtImage
} else {
// Use registry image with specified tag
baseImage = canasta.GetImageWithTag(devTag)
}

// Clone the stack repository first to create the installation directory
if err := canasta.CloneStackRepo(orchestrator, canastaInfo.Id, &path); err != nil {
if err := canasta.CloneStackRepo(orchestrator, canastaInfo.Id, &path, buildFromPath); err != nil {
return err
}

Expand All @@ -132,6 +165,12 @@ func createCanasta(canastaInfo canasta.CanastaVariables, workingDir, path, wikiI
if err := canasta.CreateEnvFile(envFile, path, workingDir, canastaInfo.RootDBPassword, canastaInfo.WikiDBPassword); err != nil {
return err
}
// Set CANASTA_IMAGE in .env for local builds so docker-compose uses the locally built image
if buildFromPath != "" {
if err := canasta.SaveEnvVariable(path+"/.env", "CANASTA_IMAGE", baseImage); err != nil {
return err
}
}
if err := canasta.CopySettings(path); err != nil {
return err
}
Expand All @@ -141,18 +180,46 @@ func createCanasta(canastaInfo canasta.CanastaVariables, workingDir, path, wikiI
if err := orchestrators.CopyOverrideFile(path, orchestrator, override, workingDir); err != nil {
return err
}
if err := orchestrators.Start(path, orchestrator); err != nil {

// Dev mode: extract code and build xdebug image before starting
if devModeEnabled {
if err := devmode.SetupFullDevMode(path, orchestrator, baseImage); err != nil {
return err
}
}

// Always start without dev mode for installation to avoid xdebug interference
// (xdebug can cause hangs if a debugger is listening during install.php)
tempInstance := config.Installation{Path: path, Orchestrator: orchestrator, DevMode: false}
if err := orchestrators.Start(tempInstance); err != nil {
return err
}

if _, err := mediawiki.Install(path, yamlPath, orchestrator, canastaInfo); err != nil {
return err
}
if err := config.Add(config.Installation{Id: canastaInfo.Id, Path: path, Orchestrator: orchestrator}); err != nil {

instance := config.Installation{Id: canastaInfo.Id, Path: path, Orchestrator: orchestrator, DevMode: devModeEnabled}
if err := config.Add(instance); err != nil {
return err
}
if err := orchestrators.StopAndStart(path, orchestrator); err != nil {

// Restart to apply all settings
// Stop containers (started without dev mode)
if err := orchestrators.Stop(tempInstance); err != nil {
log.Fatal(err)
}

// Start with appropriate mode (orchestrators.Start handles dev mode automatically)
if err := orchestrators.Start(instance); err != nil {
log.Fatal(err)
}

if devModeEnabled {
fmt.Println("\033[32mDevelopment mode enabled. Edit files in mediawiki-code/ - changes appear immediately.\033[0m")
fmt.Println("\033[32mVSCode: Open the installation directory, install PHP Debug extension, and start 'Listen for Xdebug'.\033[0m")
}

fmt.Println("\033[32mIf you need email enabled for this wiki, please set $wgSMTP; email will not work otherwise. See https://mediawiki.org/wiki/Manual:$wgSMTP for options.\033[0m")
return nil
}
7 changes: 4 additions & 3 deletions cmd/import/importExisting.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func importCanasta(workingDir, canastaID, domainName, path, orchestrator, databa
if _, err := config.GetDetails(canastaID); err == nil {
log.Fatal(fmt.Errorf("Canasta installation with the ID already exist!"))
}
if err := canasta.CloneStackRepo(orchestrator, canastaID, &path); err != nil {
if err := canasta.CloneStackRepo(orchestrator, canastaID, &path, ""); err != nil {
return err
}
if err := canasta.CopyEnvFile(envPath, path, workingDir); err != nil {
Expand All @@ -85,10 +85,11 @@ func importCanasta(workingDir, canastaID, domainName, path, orchestrator, databa
if err := orchestrators.CopyOverrideFile(path, orchestrator, override, workingDir); err != nil {
return err
}
if err := orchestrators.Start(path, orchestrator); err != nil {
instance := config.Installation{Id: canastaID, Path: path, Orchestrator: orchestrator}
if err := orchestrators.Start(instance); err != nil {
return err
}
if err := config.Add(config.Installation{Id: canastaID, Path: path, Orchestrator: orchestrator}); err != nil {
if err := config.Add(instance); err != nil {
return err
}
return nil
Expand Down
6 changes: 3 additions & 3 deletions cmd/remove/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func RemoveWiki(instance config.Installation, wikiID string) error {
}

//Checking Running status
err = orchestrators.CheckRunningStatus(instance.Path, instance.Id, instance.Orchestrator)
err = orchestrators.CheckRunningStatus(instance)
if err != nil {
return err
}
Expand Down Expand Up @@ -111,8 +111,8 @@ func RemoveWiki(instance config.Installation, wikiID string) error {
return err
}

//Stop the Canasta Instance
err = restart.Restart(instance)
//Restart the Canasta Instance
err = restart.Restart(instance, false, false)
if err != nil {
return err
}
Expand Down
63 changes: 50 additions & 13 deletions cmd/restart/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import (

"github.com/CanastaWiki/Canasta-CLI/internal/canasta"
"github.com/CanastaWiki/Canasta-CLI/internal/config"
"github.com/CanastaWiki/Canasta-CLI/internal/devmode"
"github.com/CanastaWiki/Canasta-CLI/internal/logging"
"github.com/CanastaWiki/Canasta-CLI/internal/orchestrators"
)

func NewCmdCreate() *cobra.Command {
var instance config.Installation
var verbose bool
var devModeFlag bool
var noDevFlag bool
var restartCmd = &cobra.Command{
Use: "restart",
Short: "Restart the Canasta installation",
Expand All @@ -24,8 +27,12 @@ func NewCmdCreate() *cobra.Command {
if instance.Id == "" && len(args) > 0 {
instance.Id = args[0]
}
fmt.Println("Restarting Canasta installation '" + instance.Id + "'...")
err := Restart(instance)
resolvedInstance, err := canasta.CheckCanastaId(instance)
if err != nil {
log.Fatal(err)
}
fmt.Println("Restarting Canasta installation '" + resolvedInstance.Id + "'...")
err = Restart(resolvedInstance, devModeFlag, noDevFlag)
if err != nil {
log.Fatal(err)
}
Expand All @@ -41,24 +48,54 @@ func NewCmdCreate() *cobra.Command {
restartCmd.Flags().StringVarP(&instance.Id, "id", "i", "", "Canasta instance ID")
restartCmd.Flags().StringVarP(&instance.Orchestrator, "orchestrator", "o", "compose", "Orchestrator to use for installation")
restartCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose Output")
restartCmd.Flags().BoolVarP(&devModeFlag, "dev", "D", false, "Restart in development mode with Xdebug")
restartCmd.Flags().BoolVar(&noDevFlag, "no-dev", false, "Restart without development mode (disable dev mode)")
return restartCmd
}

func Restart(instance config.Installation) error {
func Restart(instance config.Installation, enableDev, disableDev bool) error {
// Handle --dev and --no-dev flags
if enableDev && disableDev {
return fmt.Errorf("cannot specify both --dev and --no-dev")
}

// Migrate to the new version Canasta
var err error
if instance.Id != "" {
instance, err = config.GetDetails(instance.Id)
if err != nil {
return err
}
if err = canasta.MigrateToNewVersion(instance.Path); err != nil {
return err
}

//Migrate to the new version Canasta
err = canasta.MigrateToNewVersion(instance.Path)
if err != nil {
// Stop containers (orchestrators.Stop handles dev mode automatically)
if err = orchestrators.Stop(instance); err != nil {
return err
}

err = orchestrators.StopAndStart(instance.Path, instance.Orchestrator)
return err
// Handle dev mode enable/disable
if enableDev {
// Enable dev mode using default registry image
baseImage := canasta.GetDefaultImage()
if err = devmode.EnableDevMode(instance.Path, instance.Orchestrator, baseImage); err != nil {
return err
}
instance.DevMode = true
if instance.Id != "" {
if err = config.Update(instance); err != nil {
logging.Print(fmt.Sprintf("Warning: could not update config: %v\n", err))
}
}
} else if disableDev {
// Disable dev mode - restore extensions/skins as real directories
if err = devmode.DisableDevMode(instance.Path); err != nil {
return err
}
instance.DevMode = false
if instance.Id != "" {
if err = config.Update(instance); err != nil {
logging.Print(fmt.Sprintf("Warning: could not update config: %v\n", err))
}
}
}

// Start containers (orchestrators.Start handles dev mode automatically)
return orchestrators.Start(instance)
}
Loading