Files
summercms/modules/sunrise
Jakub Zych ddf1178c51 feat(sunrise): print the PATH hint for the user's login shell
- new.sh reads the basename of SHELL (via ${SHELL:-} under set -u) and
  prints fish_add_path "<dir>" for fish, which keeps it for new sessions,
  export PATH="<dir>:$PATH" naming ~/.zshrc or ~/.bashrc for zsh and
  bash, and today's generic export line with "your shell profile" for
  any other, empty or unset shell
- dq_escape escapes the install directory for the target shell's double
  quotes: backslash, double quote and dollar always, plus backtick and a
  single-quoted ! for POSIX shells; a per-character loop, so bash 3.2
  and 5.2 behave the same
- the behaviour harness defaults SHELL to empty so cases do not depend on
  the developer's login shell, adds an unset-SHELL mode and covers fish,
  zsh, bash, empty, unset, unknown and two metacharacter directories
- TestBootstrapEndToEnd pins SHELL=/bin/bash
- sunrise README and docs/setup/installation.md describe the per-shell
  hint; the manual PATH section gains fish_add_path (go env GOPATH)/bin
2026-10-08 15:34:42 +02:00
..

sunrise

Bootstrap script that installs the summer CLI and creates a new SummerCMS application in one command, embedded so it is versioned with the framework.

import "git.golem15.com/golem15/summercms/modules/sunrise"

Overview

sunrise holds new.sh, the script behind the one-liner served at https://summercms.io/new:

curl -fsSL https://summercms.io/new | bash -s -- blog

The script checks for Go 1.27 or newer, git and Docker, clones the framework into ./summercms at its newest release tag (or moves an existing release checkout forward to it), installs the summer CLI with go install ./cmd/summer and runs summer new blog --framework <checkout> next to the checkout, which writes ./sm-blog-app. summer new prints the next steps; the script adds the cd line and, when the Go install directory is not on PATH, a hint for the login shell named by SHELL: fish_add_path <dir> for fish (which keeps it for new sessions), an export PATH=... line naming ~/.zshrc or ~/.bashrc for zsh and bash, and the generic export PATH=... line with "your shell profile" for any other shell.

The script lives inside this package so //go:embed can reach it, and a web server serves the bytes of the framework release it was built against. sunrise.Script returns them.

Features

  • Truncation-safe: everything is a function definition and the last line is main "$@", so a download cut short runs nothing.
  • Prerequisites first: Go older than 1.27 or missing, or a missing git, stops the script with an install hint before anything is cloned; a missing Docker or compose plugin is only a warning.
  • Release tags: a fresh clone is checked out detached at the newest vX.Y.Z tag, or at SUMMERCMS_REF when set.
  • Fast-forward-only updates: an existing checkout is fetched (tags are never force-updated) and moved only when its HEAD is an ancestor of the target.
  • Refusals that protect local work: an existing directory that is not a git repository, whose go.mod is not the framework module, that has changes to tracked files, or that is on a branch is left untouched and the script stops with a message.
  • Early name check: a name that is not lower-case letters and digits starting with a letter, or an existing sm-<name>-app, stops the script before any clone or install.
  • Login-shell-aware PATH hint: the closing hint is one correct line for fish, zsh or bash, with a generic export PATH=... line for any other shell; the directory is escaped for the target shell's double quotes and $PATH is printed literally.
  • No privilege escalation and no dynamic code; only bash builtins plus git, go and docker are run, with stdin closed and GIT_TERMINAL_PROMPT=0 so nothing waits for input.

Usage

Run the script from the directory that should hold the framework checkout and the application:

curl -fsSL https://summercms.io/new | bash -s -- blog

bash -s -- <name> passes the application name to the piped script; -h or --help prints the usage. A local copy runs the same way with bash new.sh blog.

Serving the script from a Go handler:

package acme

import (
	"net/http"

	"git.golem15.com/golem15/summercms/modules/sunrise"
)

func newScript(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Header().Set("X-Content-Type-Options", "nosniff")
	_, _ = w.Write(sunrise.Script())
}

API reference

Identifier Description
sunrise.Script Returns a fresh copy of the embedded new.sh bytes on every call.

Configuration

The script reads three environment variables, plus SHELL, which only picks the form of the PATH hint. It never reads or sets SUMMER_-prefixed variables, which the framework reads as configuration.

Variable Default Purpose
SUMMERCMS_REPO https://git.golem15.com/golem15/summercms Repository to clone.
SUMMERCMS_DIR summercms (relative to the current directory) Framework checkout directory; the application is created next to it.
SUMMERCMS_REF newest vX.Y.Z tag Tag or commit to check out instead of the newest release.

Dependencies

  • SummerCMS modules: none.
  • Third-party: none.
  • Standard library: bytes, embed.

Testing

go test ./modules/sunrise/...

The default run checks the embedded bytes, the script's shape and syntax (bash -n), and runs every line-boundary prefix and every byte prefix of the final line against stub commands to prove a truncated download invokes nothing.

SUMMERCMS_BOOTSTRAP_CHECK=1 go test ./modules/sunrise/ -run TestBootstrapEndToEnd -timeout 20m

The gated run serves the script from a local HTTP server, pipes it through curl | bash with SUMMERCMS_REPO set to this repository and SUMMERCMS_REF to its HEAD, and builds the generated application. It needs bash, curl, git, Go and access to the Go module proxy.