Files
homarr/tools/homarr-dev/internal/run/run.go
T

120 lines
2.7 KiB
Go

package run
import (
"context"
"fmt"
"os"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/docker"
"github.com/homarr-labs/homarr/tools/homarr-dev/internal/gh"
)
type Options struct {
Context context.Context
PR int
Tag string
Demo bool
Env []string
FetchTitle bool
FindPort func(int) int
}
type Plan struct {
Image string
Name string
Volume string
Label string
HostPort int
Env []string
Platform string
Pull bool
PRNumber int
}
func BuildPlan(opts Options) (*Plan, error) {
p := &Plan{}
ctx := opts.Context
if ctx == nil {
ctx = context.Background()
}
findPort := opts.FindPort
if findPort == nil {
findPort = docker.FindFreePort
}
if opts.PR > 0 {
p.PRNumber = opts.PR
p.Image = fmt.Sprintf("ghcr.io/homarr-labs/homarr-test:pr-%d", opts.PR)
p.Name = fmt.Sprintf("homarr_pr_%d", opts.PR)
p.Volume = fmt.Sprintf("homarr_pr_%d_data", opts.PR)
p.Label = fmt.Sprintf("PR #%d", opts.PR)
p.Pull = true
p.Platform = "linux/amd64"
if opts.FetchTitle {
if pr, err := gh.GetPR(ctx, opts.PR); err == nil && pr.Title != "" {
p.Label = fmt.Sprintf("PR #%d — %s", opts.PR, pr.Title)
}
}
} else if opts.Tag != "" {
p.Image = "homarr:" + opts.Tag
p.Name = "homarr_" + opts.Tag
p.Volume = "homarr_" + opts.Tag + "_data"
p.Label = "tag: " + opts.Tag
} else {
return nil, fmt.Errorf("need a tag or --pr")
}
p.HostPort = findPort(7575)
if p.HostPort == 0 {
return nil, fmt.Errorf("no free host port is available")
}
p.Env = append(p.Env, opts.Env...)
if opts.Demo {
p.Env = append(p.Env, "DEMO_MODE=true", "UNSAFE_ENABLE_MOCK_INTEGRATION=true")
p.Label += " (demo)"
}
return p, nil
}
func StartDetached(p *Plan) error {
return start(p, true)
}
func StartForeground(p *Plan) error {
return start(p, false)
}
func start(p *Plan, daemon bool) error {
const attempts = 5
for attempt := 0; attempt < attempts; attempt++ {
err := docker.Start(docker.StartOptions{
Name: p.Name,
Image: p.Image,
Volume: p.Volume,
HostPort: p.HostPort,
Env: p.Env,
Platform: p.Platform,
PullAlways: p.Pull && attempt == 0,
Daemon: daemon,
})
if err == nil {
return nil
}
if !docker.IsPortConflict(err) || attempt == attempts-1 {
if docker.IsPortConflict(err) {
return fmt.Errorf("unable to bind a host port after %d attempts; restart Docker or OrbStack: %w", attempts, err)
}
return err
}
previousPort := p.HostPort
p.HostPort = docker.FindFreePort(p.HostPort + 1)
if p.HostPort == 0 {
return fmt.Errorf("no free host port is available: %w", err)
}
if !daemon {
fmt.Fprintf(os.Stderr, "Port %d unavailable; retrying on %d.\n", previousPort, p.HostPort)
}
}
return nil
}