Documentation
¶
Index ¶
- Constants
- Variables
- func ApplyRandomPorts(ctx context.Context, projectRoot, configPath string, cfg *shop.Config, ...) (*shop.Config, []docker.PortOverride, error)
- func BaseDomain() string
- func CACertPath() (string, error)
- func CanonicalProjectRoot(projectRoot string) string
- func CertHosts(domain string, extraHosts []string) []string
- func ComposeProjectName(projectRoot string) string
- func ConfigureResolver(ctx context.Context, baseDomain string) error
- func ContainerCABundlePath(image string) (string, error)
- func ContainerIsRunning(ctx context.Context) bool
- func EnsureContainerCABundle(ctx context.Context, image, caPath string) (string, error)
- func EnsureDNSContainerRunning(ctx context.Context, baseDomain string) error
- func EnsureNetwork(ctx context.Context) error
- func EnsureTraefikRunning(ctx context.Context, baseDomain string) error
- func InstallTrust(ctx context.Context, caPath string) (string, error)
- func IsProxyProject(cfg *shop.Config) bool
- func IsProxyProjectForDomain(cfg *shop.Config, baseDomain string) bool
- func LocalDomainHostname(name, baseDomain string) string
- func ManualSetupInstructions(baseDomain, caPath string, includeTrust bool) string
- func NewEnvironment(projectRoot string, cfg *shop.Config, proxyFallback bool) (*docker.Environment, error)
- func NewProxiedEnvironment(projectRoot string, cfg *shop.Config, hostname string) (*docker.Environment, error)
- func NoSystemdResolvedGuidance(baseDomain string) string
- func PingHostname(baseDomain string) string
- func ProjectHostname(projectRoot string, cfg *shop.Config, baseDomain string) (string, error)
- func ProxyHostnames(hostname string, subdomains []string) []string
- func ReconcileHostnames(ctx context.Context, hostnames []string) error
- func RegisteredHostname(projectRoot string) (string, error)
- func ResolverBlockedGuidance(baseDomain string) string
- func ResolverManualInstructions(baseDomain string) string
- func RestartTraefik(ctx context.Context) error
- func SaveSettings(settings Settings) error
- func ServerCertPath(dir string) string
- func ServerKeyPath(dir string) string
- func StateDir() (string, error)
- func StopDNSContainer(ctx context.Context) error
- func StopTraefik(ctx context.Context) error
- func SupportsWildcardDNS(ctx context.Context) bool
- func TrustBlockedGuidance(caPath string) string
- func TrustInstructions(caPath string) string
- func UnconfigureResolver(ctx context.Context, baseDomain string) error
- func ValidateDomain(domain string) error
- func WSLResolverGuidance() string
- func WSLWindowsAccessGuidance(caPath string, hostnames []string) string
- type CertInfo
- type CheckResult
- type InfraParams
- type Instance
- type InstanceInfo
- type ProjectEntry
- type Registry
- func (r Registry) Find(projectRoot string) (ProjectEntry, bool)
- func (r Registry) FindByHostname(hostname, exceptProjectRoot string) (ProjectEntry, bool)
- func (r Registry) Hostnames() []string
- func (r *Registry) Remove(projectRoot string) bool
- func (r Registry) Save() error
- func (r *Registry) Upsert(entry ProjectEntry)
- type ResolverStatus
- type Settings
Constants ¶
const ( // DNSContainerName is the fixed name of the shared DNS container that // answers *.<domain> with 127.0.0.1 for the OS resolver. DNSContainerName = "shopware-cli-proxy-dns" // DNSImage is the CoreDNS image used as the DNS responder. CoreDNS is a // CNCF project, so there is no DNS-server code for shopware-cli to // maintain — only the one-zone Corefile below. DNSImage = "coredns/coredns:1.14.6" )
const ( // DefaultDomain is the shared domain every project is routed under, // e.g. "my-shop.shopware.local". DefaultDomain = "shopware.local" // ContainerName is the fixed name of the shared Traefik container. ContainerName = "shopware-cli-proxy" // NetworkName is the shared external Docker network Traefik and every // proxied project's services are attached to. NetworkName = "shopware-cli-proxy" // TraefikImage is the image used for the shared reverse proxy. TraefikImage = "traefik:v3" // StorefrontAssetsPort is the extra host port and Traefik entrypoint // ("sfassets") exposed for the deprecated storefront webpack watcher's // asset+HMR server. It is routed by hostname, so every shop shares this one // port; the per-shop route lives in internal/docker/compose_override.go. StorefrontAssetsPort = 8443 )
const DNSPort = 53535
DNSPort is the fixed local port the shared DNS container publishes on 127.0.0.1. A high port is used so nothing needs elevated privileges; the OS resolver configuration points at it explicitly.
Variables ¶
var ErrNoSystemdResolved = errors.New("systemd-resolved is not available")
ErrNoSystemdResolved is returned by ConfigureResolver on Linux systems without systemd-resolved, where wildcard DNS routing is not available.
Functions ¶
func ApplyRandomPorts ¶
func ApplyRandomPorts(ctx context.Context, projectRoot, configPath string, cfg *shop.Config, proxyFallback bool, conflicts []docker.PortConflict) (*shop.Config, []docker.PortOverride, error)
ApplyRandomPorts remaps every conflicting host port to a random free one: it regenerates compose.yaml with the overrides and persists them to the local config override so future runs reuse them. The overrides come back in conflict order. The returned config carries them; cfg itself is left untouched so callers can adopt the result only once every write succeeded.
func BaseDomain ¶
func BaseDomain() string
BaseDomain returns the machine-wide proxy base domain from settings, falling back to the default when no settings are stored yet.
func CACertPath ¶
CACertPath loads (or creates) the mkcert root CA and returns the path of its certificate.
func CanonicalProjectRoot ¶
CanonicalProjectRoot resolves symlinks in projectRoot so the registry is keyed consistently regardless of how a project directory was reached. If symlinks cannot be resolved, projectRoot is returned unchanged.
func CertHosts ¶
CertHosts declares the SANs needed for a base domain plus explicitly registered hosts. The wildcard covers one subdomain level, which matches <project>.<domain>.
func ComposeProjectName ¶
ComposeProjectName resolves the Docker Compose project name for a project. Docker projects created by `project create` carry a unique COMPOSE_PROJECT_NAME (sw-<name>-<hash>) in .env; older projects fall back to Compose's default of the sanitized directory basename.
func ConfigureResolver ¶
ConfigureResolver wires systemd-resolved split-DNS routing for the proxy domain to the shared DNS container via sudo. On systems without systemd-resolved it returns ErrNoSystemdResolved; callers fall back to per-project /etc/hosts entries.
func ContainerCABundlePath ¶
ContainerCABundlePath is the deterministic host path of the combined CA bundle for image (its system CAs plus the proxy root CA). It is keyed by the image tag so different PHP/Node images keep separate bundles. The path is pure: EnsureContainerCABundle writes the file, while the compose builders only reference the path — the file is guaranteed to exist by PrepareInfra before the containers start.
func ContainerIsRunning ¶
ContainerIsRunning reports whether the shared Traefik container is currently running.
func EnsureContainerCABundle ¶
EnsureContainerCABundle writes (or refreshes) the combined CA bundle for image: the image's own system trust store with the proxy root CA appended. Mounting it over /etc/ssl/certs/ca-certificates.crt keeps the container trusting the public internet AND the proxy's HTTPS certificates. Just mounting the bare CA under /usr/local/share/ca-certificates does nothing — the image runs as www-data and never runs update-ca-certificates. It rebuilds when the bundle is missing or older than the CA, so a regenerated CA propagates (an image gaining new public CAs is not detected; that is a rare, benign staleness callers refresh by deleting the state dir).
func EnsureDNSContainerRunning ¶
EnsureDNSContainerRunning idempotently starts the shared DNS container: it writes the Corefile, recreates a container left over from an older config or a different domain, and (re-)starts one that publishes the DNS port on 127.0.0.1 only. It mirrors EnsureTraefikRunning and is safe to call from any project at any time.
func EnsureNetwork ¶
EnsureNetwork creates the shared Docker network if it does not exist yet.
func EnsureTraefikRunning ¶
EnsureTraefikRunning idempotently starts the shared Traefik container: it creates the shared network if needed, writes the TLS configuration, restarts an existing up-to-date container, or (re-)creates one bound to host ports 80 and 443. It is safe to call from any project at any time.
func InstallTrust ¶
InstallTrust installs the mkcert root CA into the system and browser trust stores. When the mkcert binary is installed, "mkcert -install" is tried first (it shares the same CAROOT and additionally covers Java trust stores); on failure the truststore library — the same code mkcert is built on — is used directly. It returns a human readable summary.
func IsProxyProject ¶
IsProxyProject reports whether the project is configured to be served at a stable hostname under the shared proxy's base domain (the signal `project create --local-domain` and `proxy up` write into .config/shopware-project.yml), as opposed to a fixed localhost port.
func IsProxyProjectForDomain ¶
IsProxyProjectForDomain is the pure core of IsProxyProject with the base domain passed in, so it can be tested without depending on stored settings.
func LocalDomainHostname ¶
LocalDomainHostname returns the stable proxy hostname for a project name, e.g. "my-shop.shopware.local". The current directory ("" or ".") resolves to the working directory's name so it never yields a malformed "..<domain>" hostname. Underscores (valid in a project name but not a DNS label) become dashes and the label is lowercased, matching Docker/DNS.
func ManualSetupInstructions ¶
ManualSetupInstructions returns the system-level steps a user can perform themselves — pointing the OS resolver at the DNS server and (unless includeTrust is false) trusting the local HTTPS certificate — for users who decline the automatic sudo setup. The DNS server, Traefik and the certificate are still run by the CLI in containers; only these two system-touching steps are left to the user.
func NewEnvironment ¶
func NewEnvironment(projectRoot string, cfg *shop.Config, proxyFallback bool) (*docker.Environment, error)
NewEnvironment resolves the project's Docker dev environment for its effective run mode: proxied when the config names a hostname under the proxy base domain, plain fixed-port otherwise. proxyFallback forces plain mode for a project whose config still names the proxy hostname (a failed proxy bootstrap, or `proxy down` reverting it). It is the single mode-aware entry point every generic caller (project dev, the dev TUI) uses, so regenerating the compose file can never silently drop a project out of proxy mode.
func NewProxiedEnvironment ¶
func NewProxiedEnvironment(projectRoot string, cfg *shop.Config, hostname string) (*docker.Environment, error)
NewProxiedEnvironment builds the project's environment in proxy mode at an explicit hostname. `proxy up` needs it because it regenerates the compose file before the config records the hostname.
func NoSystemdResolvedGuidance ¶
NoSystemdResolvedGuidance explains, in plain words, why automatic DNS cannot work without systemd-resolved, how to enable it, and the manual /etc/hosts entry as the last resort.
func PingHostname ¶
PingHostname is the hostname of the proxy's own health endpoint under baseDomain, served by Traefik itself. `proxy verify` uses it to prove the whole chain (DNS, routing, TLS, trust) end to end.
func ProjectHostname ¶
ProjectHostname derives the hostname a project should be reachable at through the shared proxy. If a url is explicitly set in .config/shopware-project.yml, its host is used as an override — unless it is an IP address or localhost, which is what freshly created projects point at (http://127.0.0.1:8000) and never a usable proxy hostname. Otherwise the hostname is derived from the project directory name.
func ProxyHostnames ¶
ProxyHostnames returns the browser-facing hostnames for a shop served through the proxy: the root hostname plus every routed subdomain (an empty entry denotes the root), as reported by the environment's RoutedSubdomains. It is used to build the Windows hosts file line under WSL, where wildcards are not available.
func ReconcileHostnames ¶
ReconcileHostnames makes shop hostnames resolve to the shared proxy from inside containers, by registering them as network aliases of the Traefik container. Its purpose is self-reachability: a shop whose APP_URL is https://shop1.shopware.local must be able to reach that URL from its own containers (app registration callbacks, sitemap generation, self API calls) and have it routed back to itself over TLS. Because all registered hostnames are aliased on the one shared Traefik, shops can also reach each other by hostname — a useful side effect, not the goal. Docker only accepts aliases at connect time, so the container is re-attached; this is skipped when the alias set is already correct, avoiding a needless network flap.
func RegisteredHostname ¶
RegisteredHostname returns the hostname the project is registered under with the shared proxy, or "" when it is not registered. The error reports a registry that could not be read, which callers must not mistake for "not registered".
func ResolverBlockedGuidance ¶
ResolverBlockedGuidance explains, in plain words, what to do when the systemd-resolved configuration cannot be written (usually because sudo is blocked).
func ResolverManualInstructions ¶
ResolverManualInstructions returns the exact steps to route the proxy domain to the DNS server by hand, for users who choose to do the resolver setup themselves instead of letting the CLI run sudo. Without systemd-resolved there is no machine-wide resolver to configure, so it explains that instead.
func RestartTraefik ¶
RestartTraefik restarts the shared proxy container so it serves a regenerated server certificate. Traefik's file provider watches the dynamic configuration file, but not the certificate files it references — without a restart it would keep the previous certificate in memory.
func SaveSettings ¶
SaveSettings persists the machine-wide proxy settings.
func ServerCertPath ¶
ServerCertPath returns the path of the server certificate used by Traefik.
func ServerKeyPath ¶
ServerKeyPath returns the path of the server certificate key used by Traefik.
func StateDir ¶
StateDir returns the directory shopware-cli uses to keep proxy state: the project registry, settings, the CoreDNS Corefile and the Traefik config. It is created if it does not exist yet.
func StopDNSContainer ¶
StopDNSContainer stops and removes the shared DNS container. It leaves the OS resolver configuration in place (removed only by a domain change or a manual resolver reset).
func StopTraefik ¶
StopTraefik stops and removes the shared Traefik container. It does not remove the shared network or touch any project's own containers.
func SupportsWildcardDNS ¶
SupportsWildcardDNS reports whether this system can resolve the whole proxy domain via the shared DNS container, which requires systemd-resolved on Linux. Without it, per-project /etc/hosts entries are the fallback.
func TrustBlockedGuidance ¶
TrustBlockedGuidance explains, in plain words, what to do when the trust store cannot be written (usually because sudo/administrator rights are blocked on the machine).
func TrustInstructions ¶
TrustInstructions returns the manual command that trusts the root CA on the current operating system, e.g. for handing over to an IT team.
func UnconfigureResolver ¶
UnconfigureResolver removes the systemd-resolved drop-in for the proxy domain.
func ValidateDomain ¶
ValidateDomain checks that domain is a plain lowercase DNS name usable as the proxy's base domain (e.g. "shopware.local" or "dev.internal").
func WSLResolverGuidance ¶
func WSLResolverGuidance() string
WSLResolverGuidance explains why OS resolution fails under WSL even though systemd-resolved answers — getent does not consult resolved — and how to fix it: add resolved to nsswitch as a fallback *after* dns. It must be last, because on WSL resolved only knows the proxy domain (no internet upstream), so putting it first would break internet DNS.
func WSLWindowsAccessGuidance ¶
WSLWindowsAccessGuidance describes the one-time manual steps needed to reach proxy-served shops from a browser running on Windows when shopware-cli runs under WSL: the setup only touches the Linux side of the subsystem, so Windows still needs the CA imported into its trust store and the shop hostnames added to its hosts file. hostnames is the ready-to-paste host list for step 3; when it is empty (e.g. `proxy setup` run outside a project) that line is replaced by a pointer to run `proxy up` in a shop folder to obtain it.
Types ¶
type CertInfo ¶
type CertInfo struct {
// CAPath is the mkcert root CA certificate the server certificate chains to.
CAPath string
// Changed is true when the server certificate was (re-)generated.
Changed bool
// CACreated is true when a new root CA was created (it is not trusted yet).
CACreated bool
}
CertInfo describes the certificate the proxy serves.
func EnsureCertificate ¶
EnsureCertificate makes sure a server certificate covering all hosts exists. Certificates are issued by the mkcert root CA in mkcert's CAROOT (created when missing), so they are trusted as soon as the user ran "mkcert -install" or "shopware-cli project proxy trust" once.
func PrepareInfra ¶
PrepareInfra brings up the shared infrastructure a proxy project needs before its containers start: it checks the hostname is free, ensures the server certificate, the shared Traefik container and the DNS container. It does not write the project's compose file (WriteComposeFile does, in proxy mode) nor register the project — the caller layers that on top. It returns the certificate info so callers can react to a freshly created CA. Safe to call repeatedly.
type CheckResult ¶
type CheckResult struct {
// Name describes what was checked, e.g. "OS resolves *.shopware.local".
Name string
// Err is nil when the check passed.
Err error
// Hint tells the user how to fix a failed check.
Hint string
// Pending marks a failure that only means "this layer is not running yet"
// (the shared DNS or Traefik container), as opposed to something being
// broken. Callers render it as a calm "not started yet" rather than an error,
// so a torn-down machine does not look faulty.
Pending bool
}
CheckResult is the outcome of one verification step.
type InfraParams ¶
type InfraParams struct {
CanonicalRoot string
Hostname string
BaseDomain string
// ConfigPath names the project config file; used only in the hostname
// collision hint.
ConfigPath string
// Image is the docker-dev image the shop's containers run. PrepareInfra
// reads its system CA bundle to build the combined trust bundle.
Image string
}
InfraParams carries the project-specific inputs PrepareInfra needs. The caller resolves them from the project config and machine settings.
type InstanceInfo ¶
type InstanceInfo struct {
Name string // friendly project name (the project directory basename)
URL string // https://<hostname>
Running bool
MemBytes int64 // 0 when not running
Uptime time.Duration // 0 when not running
}
InstanceInfo describes one project registered with the shared proxy: whether it is currently running and, for running instances, its approximate memory use and uptime.
func InstanceStats ¶
func InstanceStats(ctx context.Context) (instances []InstanceInfo, combinedMem int64, err error)
InstanceStats reports every project registered with the shared proxy. Running instances come first (then stopped ones, in registry order), each carrying an approximate memory total and uptime while running. combinedMem is the summed memory of the running instances. Memory and uptime are best-effort — a docker stats/inspect error yields a partial result rather than failing the overview.
type ProjectEntry ¶
type ProjectEntry struct {
// ProjectRoot is the canonical (symlink-resolved) absolute path to the
// project directory. It is the registry's key.
ProjectRoot string `json:"project_root"`
Hostname string `json:"hostname"`
RegisteredAt time.Time `json:"registered_at"`
// PreviousAppURL is the APP_URL the project had before registration, so
// "proxy down" can restore it (empty means the 127.0.0.1:8000 default).
PreviousAppURL string `json:"previous_app_url,omitempty"`
// PreviousConfig captures the url keys of the loaded project config before
// registration rewrote them; nil means the file was not touched.
PreviousConfig *shop.ConfigURLState `json:"previous_config,omitempty"`
}
ProjectEntry is one project registered with the shared proxy.
type Registry ¶
type Registry struct {
Projects []ProjectEntry `json:"projects"`
}
Registry is the local record of projects registered with the shared proxy.
func LoadRegistry ¶
LoadRegistry reads the project registry from the shared state directory. A missing file is not an error; it returns an empty registry.
func (Registry) Find ¶
func (r Registry) Find(projectRoot string) (ProjectEntry, bool)
Find returns the entry for projectRoot, if any.
func (Registry) FindByHostname ¶
func (r Registry) FindByHostname(hostname, exceptProjectRoot string) (ProjectEntry, bool)
FindByHostname returns the entry using hostname, if any, ignoring the entry (if any) belonging to exceptProjectRoot. It is used to detect hostname collisions between two different projects.
func (*Registry) Remove ¶
Remove deletes the entry for projectRoot, reporting whether one was removed.
func (Registry) Save ¶
Save writes the registry back to the shared state directory atomically: it writes a temp file in the same directory and renames it over the target, so a crash or a concurrent reader never sees a half-written registry.
func (*Registry) Upsert ¶
func (r *Registry) Upsert(entry ProjectEntry)
Upsert adds entry, replacing any existing entry with the same ProjectRoot.
type ResolverStatus ¶
type ResolverStatus struct {
Configured bool
// Detail is a short human-readable explanation of the current state.
Detail string
}
ResolverStatus describes whether the OS is configured to resolve the proxy domain via the shared DNS container.
func CheckResolverConfigured ¶
func CheckResolverConfigured(baseDomain string) ResolverStatus
CheckResolverConfigured reports whether systemd-resolved routes the proxy domain to the shared DNS container.
type Settings ¶
type Settings struct {
// Domain is the base domain projects are routed under. Empty means
// DefaultDomain.
Domain string `json:"domain,omitempty"`
}
Settings are the machine-wide proxy settings, chosen once via "project proxy setup" and read by every other proxy command.
func LoadSettings ¶
LoadSettings reads the machine-wide proxy settings; a missing file yields the defaults.
func (Settings) BaseDomain ¶
BaseDomain returns the configured domain, falling back to DefaultDomain.