#!/usr/bin/env bash # SummerCMS bootstrap: create a new SummerCMS application in one command. # # curl -fsSL https://summercms.io/new | bash -s -- # # The script checks for Go 1.27 or newer, git and (optionally) 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` and runs `summer new ` next to the checkout, which # writes ./sm--app. # # Environment overrides: # SUMMERCMS_REPO framework repository to clone # (default https://git.golem15.com/golem15/summercms) # SUMMERCMS_DIR framework checkout directory (default ./summercms); the # application is created next to it # SUMMERCMS_REF tag or commit to check out instead of the newest release # # Everything below is a function definition and the last line calls main, so # a download cut short runs nothing. Only bash builtins and the git, go and # docker commands are used. say() { printf '==> %s\n' "$*" >&2 } warn() { printf 'warning: %s\n' "$*" >&2 } die() { printf 'error: %s\n' "$*" >&2 exit 1 } usage() { printf '%s\n' \ 'Usage: curl -fsSL https://summercms.io/new | bash -s -- ' \ ' bash new.sh ' \ '' \ 'Creates sm--app next to a SummerCMS framework checkout. is' \ 'lower-case letters and digits, starting with a letter.' \ '' \ 'Steps: check Go 1.27+, git and Docker; clone the framework (or move an' \ 'existing release checkout forward) at its newest release tag; install' \ 'the summer CLI with go install; run summer new .' \ '' \ 'Environment:' \ ' SUMMERCMS_REPO repository to clone (default https://git.golem15.com/golem15/summercms)' \ ' SUMMERCMS_DIR framework checkout directory (default ./summercms)' \ ' SUMMERCMS_REF tag or commit to check out instead of the newest release' } # abspath prints $1 as an absolute path against $PWD with ".", ".." and # repeated slashes removed lexically. abspath() { local p="$1" out="" seg local -a parts [[ $p == /* ]] || p="$PWD/$p" IFS=/ read -r -a parts <<<"$p" for seg in "${parts[@]}"; do case "$seg" in '' | .) ;; ..) out="${out%/*}" ;; *) out="$out/$seg" ;; esac done printf '%s\n' "${out:-/}" } # check_go requires go 1.27 or newer. check_go() { command -v go >/dev/null 2>&1 || die "Go is not installed. SummerCMS needs Go 1.27 or newer: https://go.dev/dl/" local v # Read the version from / so a go.mod in the current directory cannot # make the go command switch toolchains for the check. v="$(cd / && go env GOVERSION /dev/null)" || v="" if [[ $v =~ ^go([0-9]+)\.([0-9]+) ]]; then local major="${BASH_REMATCH[1]}" minor="${BASH_REMATCH[2]}" if ((major < 1 || (major == 1 && minor < 27))); then die "SummerCMS needs Go 1.27 or newer, found $v. Install a newer Go from https://go.dev/dl/" fi else warn "could not read the Go version (go env GOVERSION printed '${v}'); SummerCMS needs Go 1.27 or newer" fi } # check_docker only warns: the application builds without Docker, the # generated compose.yaml just needs it for the development database. check_docker() { if ! command -v docker >/dev/null 2>&1; then warn "Docker is not installed; the generated compose.yaml needs it to run PostgreSQL (https://docs.docker.com/engine/install/)" elif ! docker compose version /dev/null 2>&1; then warn "the Docker compose plugin is missing; the generated compose.yaml needs 'docker compose'" fi } # latest_tag prints the newest vX.Y.Z tag of the checkout in $1. latest_tag() { local dir="$1" tags tag # Capture the whole list first: piping into head under pipefail can # fail with SIGPIPE. tags="$(git -C "$dir" tag -l 'v*' --sort=-v:refname /dev/null)" || die "$dir is not a git repository; $hint" real="$(cd "$dir" && pwd -P)" [[ $top == "$real" ]] || die "$dir is not the root of a git repository; $hint" status="$(git -C "$dir" status --porcelain --untracked-files=no &2 exit 2 fi local name="$1" if [[ ! $name =~ ^[a-z][a-z0-9]*$ ]]; then printf 'error: invalid application name %q: use lower-case letters and digits, starting with a letter\n\n' "$name" >&2 usage >&2 exit 2 fi local repo="${SUMMERCMS_REPO:-https://git.golem15.com/golem15/summercms}" local ref="${SUMMERCMS_REF:-}" local dir parent app dir="$(abspath "${SUMMERCMS_DIR:-summercms}")" [[ $dir != / ]] || die "SUMMERCMS_DIR must not be /" parent="${dir%/*}" parent="${parent:-/}" app="${parent%/}/sm-$name-app" [[ -d $parent ]] || die "$parent does not exist" if [[ -e $app || -L $app ]]; then die "$app already exists; choose another name or remove it" fi command -v git >/dev/null 2>&1 || die "git is not installed; install git and run again" check_go check_docker checkout_framework "$repo" "$dir" "$ref" local gobin gobin="$(go_bin_dir)" || die "could not determine the Go install directory (go env GOBIN / GOPATH)" say "installing summer into $gobin" GOWORK=off go -C "$dir" install ./cmd/summer Done. Start with:\n cd %s\n' "$app" if [[ :${PATH:-}: != *":$gobin:"* ]]; then printf '\n==> summer is installed in %s, which is not on your PATH. Run this (and add it to your shell profile):\n' "$gobin" # $PATH stays literal: the user's shell expands it. # shellcheck disable=SC2016 printf ' export PATH="%s:$PATH"\n' "$gobin" fi } main "$@"