Skip to content

Commit 75910e3

Browse files
akoclaude
andcommitted
Fix docker run failing on fresh projects (issue #13)
Auto-link PAD runtime files from runtime cache into mxbuild directory and create a default demo user when none exist, so `mxcli docker run` works out of the box on blank projects created by `mx create-project`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e602cac commit 75910e3

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

cmd/mxcli/docker/build.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ func Build(opts BuildOptions) error {
7373
}
7474
fmt.Fprintf(w, " MxBuild: %s\n", mxbuildPath)
7575

76+
// Step 2b: Ensure PAD runtime files are linked
77+
if err := ensurePADFiles(pv.ProductVersion, w); err != nil {
78+
fmt.Fprintf(w, " Warning: %v\n", err)
79+
}
80+
7681
// Step 3: Resolve JDK 21
7782
fmt.Fprintln(w, "Resolving JDK 21...")
7883
javaHome, err := resolveJDK21()
@@ -273,6 +278,21 @@ func Run(opts RunOptions) error {
273278
return fmt.Errorf("setting up runtime: %w", err)
274279
}
275280

281+
// Step 3b: Link PAD runtime files into mxbuild directory
282+
// MxBuild's PAD builder expects template files at mxbuild/{ver}/runtime/pad/,
283+
// but they live in the separately downloaded runtime at runtime/{ver}/runtime/pad/.
284+
if err := ensurePADFiles(pv.ProductVersion, w); err != nil {
285+
return fmt.Errorf("linking PAD files: %w", err)
286+
}
287+
288+
// Step 3c: Ensure demo users exist
289+
// Blank projects created by mx create-project have no demo users, which means
290+
// the app starts but login fails. Create a default admin if none exist.
291+
if err := ensureDemoUsers(opts.ProjectPath, w); err != nil {
292+
// Non-fatal: warn but continue — the app will start, just login won't work.
293+
fmt.Fprintf(w, " Warning: could not ensure demo users: %v\n", err)
294+
}
295+
276296
// Step 4: Initialize Docker stack (idempotent)
277297
dockerDir := filepath.Join(filepath.Dir(opts.ProjectPath), ".docker")
278298
composePath := filepath.Join(dockerDir, "docker-compose.yml")
@@ -582,6 +602,112 @@ func extractZip(zipPath, targetDir string) error {
582602
return nil
583603
}
584604

605+
// ensurePADFiles ensures that PAD runtime template files are available in the mxbuild
606+
// directory. MxBuild's PAD builder expects files at ~/.mxcli/mxbuild/{ver}/runtime/pad/
607+
// but the runtime is downloaded separately to ~/.mxcli/runtime/{ver}/runtime/pad/.
608+
// This creates a symlink from the mxbuild location to the runtime location.
609+
func ensurePADFiles(productVersion string, w io.Writer) error {
610+
mxbuildDir, err := MxBuildCacheDir(productVersion)
611+
if err != nil {
612+
return err
613+
}
614+
runtimeDir, err := RuntimeCacheDir(productVersion)
615+
if err != nil {
616+
return err
617+
}
618+
619+
mxbuildPAD := filepath.Join(mxbuildDir, "runtime", "pad")
620+
runtimePAD := filepath.Join(runtimeDir, "runtime", "pad")
621+
622+
// Already exists (previous run, or bundled with mxbuild)
623+
if _, err := os.Stat(mxbuildPAD); err == nil {
624+
fmt.Fprintln(w, " PAD runtime files already present.")
625+
return nil
626+
}
627+
628+
// Check that the runtime PAD source exists
629+
if _, err := os.Stat(runtimePAD); err != nil {
630+
return fmt.Errorf("runtime PAD files not found at %s", runtimePAD)
631+
}
632+
633+
// Create parent directory if needed
634+
if err := os.MkdirAll(filepath.Dir(mxbuildPAD), 0755); err != nil {
635+
return fmt.Errorf("creating runtime directory in mxbuild: %w", err)
636+
}
637+
638+
// Symlink runtime/pad into mxbuild
639+
if err := os.Symlink(runtimePAD, mxbuildPAD); err != nil {
640+
return fmt.Errorf("symlinking PAD files: %w", err)
641+
}
642+
fmt.Fprintf(w, " Linked PAD runtime files: %s -> %s\n", mxbuildPAD, runtimePAD)
643+
return nil
644+
}
645+
646+
// ensureDemoUsers checks whether the project has demo users configured.
647+
// If not, it enables demo users and creates a default admin user so the
648+
// application is accessible after startup.
649+
func ensureDemoUsers(projectPath string, w io.Writer) error {
650+
fmt.Fprintln(w, "Checking demo users...")
651+
652+
reader, err := mpr.Open(projectPath)
653+
if err != nil {
654+
return fmt.Errorf("opening project: %w", err)
655+
}
656+
657+
ps, err := reader.GetProjectSecurity()
658+
reader.Close()
659+
if err != nil {
660+
return fmt.Errorf("reading project security: %w", err)
661+
}
662+
663+
// If demo users already exist, nothing to do
664+
if len(ps.DemoUsers) > 0 {
665+
fmt.Fprintf(w, " Found %d demo user(s), skipping.\n", len(ps.DemoUsers))
666+
return nil
667+
}
668+
669+
fmt.Fprintln(w, " No demo users found, creating default admin...")
670+
671+
writer, err := mpr.NewWriter(projectPath)
672+
if err != nil {
673+
return fmt.Errorf("opening project for writing: %w", err)
674+
}
675+
defer writer.Close()
676+
677+
// Re-read security through writer's reader
678+
ps, err = writer.Reader().GetProjectSecurity()
679+
if err != nil {
680+
return fmt.Errorf("reading project security: %w", err)
681+
}
682+
683+
// Enable demo users if not already enabled
684+
if !ps.EnableDemoUsers {
685+
if err := writer.SetProjectDemoUsersEnabled(ps.ID, true); err != nil {
686+
return fmt.Errorf("enabling demo users: %w", err)
687+
}
688+
fmt.Fprintln(w, " Enabled demo users.")
689+
}
690+
691+
// Pick the first user role that looks like an admin, or fall back to the first role
692+
roleName := "Administrator"
693+
if len(ps.UserRoles) > 0 {
694+
roleName = ps.UserRoles[0].Name
695+
for _, ur := range ps.UserRoles {
696+
if ur.Name == "Administrator" || ur.Name == "Admin" {
697+
roleName = ur.Name
698+
break
699+
}
700+
}
701+
}
702+
703+
if err := writer.AddDemoUser(ps.ID, "admin", "Admin123!", []string{roleName}); err != nil {
704+
return fmt.Errorf("creating demo user: %w", err)
705+
}
706+
707+
fmt.Fprintf(w, " Created demo user: admin / Admin123! (role: %s)\n", roleName)
708+
return nil
709+
}
710+
585711
// DescribePatches returns the list of patches that would be applied for a given version.
586712
func DescribePatches(pv *version.ProjectVersion) []string {
587713
var patches []string

0 commit comments

Comments
 (0)