- new.sh gains --update, run inside one application directory (go.mod and summer.yaml required); it changes only that application - the framework checkout is read from the app's go.mod replace (single-line or block form); module-version replaces, missing targets and foreign or dirty checkouts are refused through the shared check_framework_dir - a detached checkout moves fast-forward only to the newest release (or SUMMERCMS_REF) through the shared move_detached; a branch checkout is pulled with git pull --no-rebase --ff-only, refusing a missing upstream or a diverged branch with the checkout unchanged - other sm-*-app directories on the same checkout are named on one "not rebuilt" line and never built or written - summer is reinstalled, a missing SUMMER_ADMIN__JWT__SECRET is appended to this app's .env (openssl, else head and base64; mode kept, never printed), then summer build runs and the script ends with the migrate and restart reminder and the shared path_hint - sunrise.UpdateScript() serves the same script with its last line changed to main "$@" --update, so truncated downloads still run nothing - check-phase14.3.sh --forbidden now allows only the literal SUMMER_ADMIN__JWT__SECRET in new.sh and still fails on any other SUMMER_ name or on a read or export of one; TestUpdateScript and TestUpdateScriptRoute are required tests
58 lines
2.2 KiB
Go
58 lines
2.2 KiB
Go
// Package sunrise embeds the SummerCMS bootstrap script, new.sh, so the
|
|
// script is versioned with the framework release that ships it.
|
|
//
|
|
// The script is what `curl -fsSL https://summercms.io/new | bash -s -- <name>`
|
|
// runs: it checks for Go 1.27 or newer, git and Docker, clones the framework
|
|
// at its newest release tag (or moves an existing release checkout forward),
|
|
// installs the summer CLI and runs `summer new <name>` next to the checkout.
|
|
// A web server hands out Script() as text/plain.
|
|
//
|
|
// The same script has an update mode, which
|
|
// `curl -fsSL https://summercms.io/update | bash` runs inside one
|
|
// application: it moves the framework checkout named by the application's
|
|
// go.mod forward, reinstalls summer and rebuilds that application.
|
|
// UpdateScript() is the script with update mode preset, for the /update URL.
|
|
package sunrise
|
|
|
|
import (
|
|
"bytes"
|
|
_ "embed"
|
|
)
|
|
|
|
//go:embed new.sh
|
|
var script []byte
|
|
|
|
// Script returns the bootstrap script. Each call returns a fresh copy, so a
|
|
// caller may modify the slice without affecting later calls.
|
|
func Script() []byte {
|
|
return bytes.Clone(script)
|
|
}
|
|
|
|
const (
|
|
mainCall = "main \"$@\"\n"
|
|
updateCall = "main \"$@\" --update\n"
|
|
)
|
|
|
|
var updateScript = deriveUpdateScript(script)
|
|
|
|
// deriveUpdateScript replaces the final `main "$@"` line of s with
|
|
// `main "$@" --update`. It panics when s does not end with that line, so a
|
|
// changed script fails every test instead of serving create mode at /update.
|
|
func deriveUpdateScript(s []byte) []byte {
|
|
if !bytes.HasSuffix(s, []byte(mainCall)) {
|
|
panic(`sunrise: new.sh must end with the line main "$@"`)
|
|
}
|
|
out := bytes.Clone(s[:len(s)-len(mainCall)])
|
|
return append(out, updateCall...)
|
|
}
|
|
|
|
// UpdateScript returns the bootstrap script with update mode preset: Script()
|
|
// with its final line `main "$@"` replaced by `main "$@" --update`, so both
|
|
// URLs serve the same logic. The flag follows "$@" so that every proper
|
|
// prefix of the last line, as a cut-short download delivers it, calls main
|
|
// without a valid --update or with no arguments, and main exits with the
|
|
// usage instead of running anything. Each call returns a fresh copy.
|
|
func UpdateScript() []byte {
|
|
return bytes.Clone(updateScript)
|
|
}
|