proxy

package
v0.18.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
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"
)
View Source
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
)
View Source
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

View Source
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

func CACertPath() (string, error)

CACertPath loads (or creates) the mkcert root CA and returns the path of its certificate.

func CanonicalProjectRoot

func CanonicalProjectRoot(projectRoot string) string

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

func CertHosts(domain string, extraHosts []string) []string

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

func ComposeProjectName(projectRoot string) string

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

func ConfigureResolver(ctx context.Context, baseDomain string) error

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

func ContainerCABundlePath(image string) (string, error)

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

func ContainerIsRunning(ctx context.Context) bool

ContainerIsRunning reports whether the shared Traefik container is currently running.

func EnsureContainerCABundle

func EnsureContainerCABundle(ctx context.Context, image, caPath string) (string, error)

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

func EnsureDNSContainerRunning(ctx context.Context, baseDomain string) error

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

func EnsureNetwork(ctx context.Context) error

EnsureNetwork creates the shared Docker network if it does not exist yet.

func EnsureTraefikRunning

func EnsureTraefikRunning(ctx context.Context, baseDomain string) error

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

func InstallTrust(ctx context.Context, caPath string) (string, error)

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

func IsProxyProject(cfg *shop.Config) bool

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

func IsProxyProjectForDomain(cfg *shop.Config, baseDomain string) bool

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

func LocalDomainHostname(name, baseDomain string) string

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

func ManualSetupInstructions(baseDomain, caPath string, includeTrust bool) string

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

func NoSystemdResolvedGuidance(baseDomain string) string

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

func PingHostname(baseDomain string) string

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

func ProjectHostname(projectRoot string, cfg *shop.Config, baseDomain string) (string, error)

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

func ProxyHostnames(hostname string, subdomains []string) []string

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

func ReconcileHostnames(ctx context.Context, hostnames []string) error

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

func RegisteredHostname(projectRoot string) (string, error)

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

func ResolverBlockedGuidance(baseDomain string) string

ResolverBlockedGuidance explains, in plain words, what to do when the systemd-resolved configuration cannot be written (usually because sudo is blocked).

func ResolverManualInstructions

func ResolverManualInstructions(baseDomain string) string

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

func RestartTraefik(ctx context.Context) error

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

func SaveSettings(settings Settings) error

SaveSettings persists the machine-wide proxy settings.

func ServerCertPath

func ServerCertPath(dir string) string

ServerCertPath returns the path of the server certificate used by Traefik.

func ServerKeyPath

func ServerKeyPath(dir string) string

ServerKeyPath returns the path of the server certificate key used by Traefik.

func StateDir

func StateDir() (string, error)

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

func StopDNSContainer(ctx context.Context) error

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

func StopTraefik(ctx context.Context) error

StopTraefik stops and removes the shared Traefik container. It does not remove the shared network or touch any project's own containers.

func SupportsWildcardDNS

func SupportsWildcardDNS(ctx context.Context) bool

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

func TrustBlockedGuidance(caPath string) string

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

func TrustInstructions(caPath string) string

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

func UnconfigureResolver(ctx context.Context, baseDomain string) error

UnconfigureResolver removes the systemd-resolved drop-in for the proxy domain.

func ValidateDomain

func ValidateDomain(domain string) error

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

func WSLWindowsAccessGuidance(caPath string, hostnames []string) string

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

func EnsureCertificate(dir string, hosts []string) (CertInfo, error)

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

func PrepareInfra(ctx context.Context, p InfraParams, reg Registry) (CertInfo, error)

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.

func Verify

func Verify(ctx context.Context, baseDomain string) []CheckResult

Verify runs the proxy health checks bottom-up and stops at the first failure, since later layers depend on earlier ones. It never mutates any state, so it is safe to run at any time.

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 Instance

type Instance struct {
	Container string
	Hostname  string
}

Instance is a project container reachable through the shared proxy.

func RunningInstances

func RunningInstances(ctx context.Context) ([]Instance, error)

RunningInstances lists containers currently attached to the shared proxy network, for `project proxy list`/`status`.

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

func LoadRegistry() (Registry, error)

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) Hostnames

func (r Registry) Hostnames() []string

Hostnames returns the hostname of every registered project.

func (*Registry) Remove

func (r *Registry) Remove(projectRoot string) bool

Remove deletes the entry for projectRoot, reporting whether one was removed.

func (Registry) Save

func (r Registry) Save() error

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

func LoadSettings() (Settings, error)

LoadSettings reads the machine-wide proxy settings; a missing file yields the defaults.

func (Settings) BaseDomain

func (s Settings) BaseDomain() string

BaseDomain returns the configured domain, falling back to DefaultDomain.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL