#!/usr/bin/env bash # # Stamp SPDX license headers on hand-written source files. # # member-console is dual-licensed: AGPL-3.0-only for everyone, or a separate # commercial license from the copyright holder (see COMMERCIAL.md). Every # hand-written source file carries the dual identifier so the offer is legible # from the file itself, not only from the repository root. # # Covered: Go, SQL migrations and seeds, the Go HTML templates under # internal/, and the first-party JavaScript and CSS under internal/. Not # covered: sqlc query files (sqlc copies every comment preceding the first # "-- name:" line into the generated Go as that query's doc comment, so a # header there pollutes the generated code; the generated .sql.go beside each # query file carries the header instead), the vendored browser assets listed # below (their own licenses, see NOTICE), configuration, shell, documentation, # images. Repository-wide coverage of those, and a check that fails on a bare # new file, is what REUSE tooling provides; adopting it is deferred until CI # exists (status/issues.md, "LicenseRef-Commercial has no resolvable text"). # # sqlc rewrites its output on every run and has no option for emitting a # header, so `make sqlc-generate` invokes this script afterwards to restore the # headers it drops. Run `make spdx-headers` to stamp the whole tree. # # Usage: # scripts/spdx-headers.sh # every tracked eligible file # scripts/spdx-headers.sh DIR [DIR..] # every eligible file on disk under # # DIR, including files not yet tracked # # Idempotent: a file whose first lines already carry an SPDX-License-Identifier # is left untouched, so a changed identifier must be rewritten deliberately. # # Templates get the {{- /* */ -}} form: an HTML comment would be sent to every # browser, and the trim markers keep the rendered output byte-identical. set -euo pipefail IDENTIFIER="AGPL-3.0-only OR LicenseRef-Commercial" COPYRIGHT="2025-2026 Christian Galo" # Third-party assets redistributed under their own licenses; see NOTICE. VENDORED=( internal/embeds/static/bootstrap.bundle.js internal/embeds/static/bootstrap.css internal/embeds/static/htmx.min.js internal/embeds/static/sortable.min.js ) cd "$(git rev-parse --show-toplevel)" # Go and SQL are eligible anywhere; templates and assets only under internal/, # which keeps archived review pages and exploration dossiers out. eligible() { local f="$1" for v in "${VENDORED[@]}"; do [ "$f" = "$v" ] && return 1 done case "$f" in */queries/*.sql) return 1 ;; *.go | *.sql) return 0 ;; internal/*.html | internal/*.js | internal/*.css) return 0 ;; *) return 1 ;; esac } header() { case "$1" in *.go | *.js) printf '// SPDX-License-Identifier: %s\n// SPDX-FileCopyrightText: %s\n\n' "$IDENTIFIER" "$COPYRIGHT" ;; *.sql) printf -- '-- SPDX-License-Identifier: %s\n-- SPDX-FileCopyrightText: %s\n\n' "$IDENTIFIER" "$COPYRIGHT" ;; *.css) printf '/* SPDX-License-Identifier: %s */\n/* SPDX-FileCopyrightText: %s */\n\n' "$IDENTIFIER" "$COPYRIGHT" ;; *.html) printf '{{- /* SPDX-License-Identifier: %s */ -}}\n{{- /* SPDX-FileCopyrightText: %s */ -}}\n\n' "$IDENTIFIER" "$COPYRIGHT" ;; esac } if [ "$#" -gt 0 ]; then mapfile -t candidates < <(find "$@" -type f \( -name '*.go' -o -name '*.sql' -o -name '*.html' -o -name '*.js' -o -name '*.css' \) | sed 's#^\./##' | sort) else mapfile -t candidates < <(git ls-files -- '*.go' '*.sql' 'internal/*.html' 'internal/*.js' 'internal/*.css') fi stamped=0 eligible_count=0 for f in "${candidates[@]}"; do [ -n "$f" ] || continue eligible "$f" || continue eligible_count=$((eligible_count + 1)) if head -n 3 "$f" | grep -q 'SPDX-License-Identifier:'; then continue fi tmp="$(mktemp)" { header "$f" cat "$f" } >"$tmp" # Copy through rather than mv so the file keeps its original mode. cat "$tmp" >"$f" rm -f "$tmp" stamped=$((stamped + 1)) done printf 'spdx-headers: stamped %d of %d eligible files\n' "$stamped" "$eligible_count"