// Package installscript renders the credential-helper install scripts // (install.sh, install.ps1) from the running deployment's configuration. // // The scripts used to be static files under pkg/appview/public/static/, which // meant every rebranded deployment served atcr.io's script: it installed // docker-credential-atcr and told the user to point Docker's credHelpers at // atcr.io no matter which registry they had actually been browsing. Rendering // them from a Brand keeps the helper binary name, the credHelpers value, the // config directory and the registry host in exactly one place, shared with the // install/settings UI templates. package installscript import ( "embed" "fmt" "io" "regexp" "strings" "text/template" ) //go:embed templates/*.tmpl var templatesFS embed.FS var tmpl = template.Must(template.ParseFS(templatesFS, "templates/*.tmpl")) // DefaultName is the credential helper brand used when nothing is configured. const DefaultName = "atcr" // DefaultReleasesBaseURL is where the helper release archives are published. // The DID-based Tangled URL is used rather than the handle-based one so the // link survives a handle rename; pkg/credhelper's self-updater uses the same. const DefaultReleasesBaseURL = "https://tangled.org/did:plc:e3kzdezk5gsirzh7eoqplc64" // nameRE constrains Brand.Name. The value is interpolated into a shell script // and a PowerShell script, and Docker additionally requires that it be usable // as a filename suffix (docker-credential-), so keep it boring. var nameRE = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) // Brand is the credential-helper identity of one deployment. Docker resolves a // credHelpers value "x" by executing "docker-credential-x", so the helper's // binary name, the credHelpers value and (by convention) the helper's config // directory are all the same word. Keeping them as one field is what stops // them drifting apart across the scripts and the UI. type Brand struct { // Name is the credHelpers value, e.g. "atcr" or "seamark". Name string // DisplayName is the human brand shown in script output, e.g. "Seamark". DisplayName string // ReleasesBaseURL is the Tangled repo the release archives hang off. ReleasesBaseURL string } // NewBrand normalizes a configured brand, filling in defaults. func NewBrand(name, displayName, releasesBaseURL string) (Brand, error) { b := Brand{ Name: strings.TrimSpace(name), DisplayName: strings.TrimSpace(displayName), ReleasesBaseURL: strings.TrimRight(strings.TrimSpace(releasesBaseURL), "/"), } if b.Name == "" { b.Name = DefaultName } if !nameRE.MatchString(b.Name) { return Brand{}, fmt.Errorf("credential helper name %q must match %s", b.Name, nameRE) } if b.DisplayName == "" { b.DisplayName = strings.ToUpper(b.Name) } if b.ReleasesBaseURL == "" { b.ReleasesBaseURL = DefaultReleasesBaseURL } return b, nil } // BinaryName is the executable Docker looks for: docker-credential-. func (b Brand) BinaryName() string { return "docker-credential-" + b.Name } // ConfigDir is the helper's home-relative config directory, e.g. "~/.atcr". func (b Brand) ConfigDir() string { return "~/." + b.Name } // DeviceFile is where the helper stores its device credential. func (b Brand) DeviceFile() string { return b.ConfigDir() + "/device.json" } // EnvPrefix is the prefix for the install scripts' override variables, e.g. // ATCR_VERSION / SEAMARK_VERSION. func (b Brand) EnvPrefix() string { return strings.ToUpper(strings.ReplaceAll(b.Name, "-", "_")) } // Params is everything a rendered install script needs. type Params struct { Brand // RegistryHost is the credHelpers key: the registry host Docker // authenticates against, e.g. "atcr.io" or "seamark.cr". This is NOT // necessarily the site the script was downloaded from. RegistryHost string // SiteHost is the web UI host the script is served from, used only for // the usage comment at the top of the script. SiteHost string } // RenderShell writes install.sh for the given params. func RenderShell(w io.Writer, p Params) error { return tmpl.ExecuteTemplate(w, "install.sh.tmpl", p) } // RenderPowerShell writes install.ps1 for the given params. func RenderPowerShell(w io.Writer, p Params) error { return tmpl.ExecuteTemplate(w, "install.ps1.tmpl", p) }