Add opt-in shell lint and format targets

Introduce make lint-shell / fmt-shell with shellcheck and
shfmt (-i 2 -ci -bn), normalize first-party scripts, and
document the new targets. Keep them out of make lint / fmt.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-07-23 15:51:43 +02:00
parent 9e9272b4ac
commit 570395c4ac
13 changed files with 856 additions and 733 deletions

View File

@@ -281,12 +281,6 @@ jobs:
- uses: "./.github/actions/setup"
with:
node: "false"
- name: "Lint install.sh"
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq shellcheck
sh -n cmd/probo-agent/installer/install.sh
shellcheck cmd/probo-agent/installer/install.sh
- name: "Build probo-agent"
env:
CGO_ENABLED: "0"
@@ -396,8 +390,12 @@ jobs:
swift-version: "6.0"
- uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
- name: "Install SwiftLint"
env:
SWIFTLINT_VERSION: "0.65.0"
SWIFTLINT_SHA256: "79306a34e5c7cc55a220cd108cbb861dcad5f10138dcdf261e2624ae8b0a486b"
run: |
curl -sL "https://github.com/realm/SwiftLint/releases/download/0.65.0/swiftlint_linux_amd64.zip" -o /tmp/swiftlint.zip
curl -sL "https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/swiftlint_linux_amd64.zip" -o /tmp/swiftlint.zip
echo "${SWIFTLINT_SHA256} /tmp/swiftlint.zip" | sha256sum -c -
sudo unzip -o /tmp/swiftlint.zip -d /usr/local/bin
sudo chmod +x /usr/local/bin/swiftlint
swiftlint version
@@ -415,6 +413,35 @@ jobs:
swiftlint lint --config .swiftlint.yml --cache-path /tmp/swiftlint-cache 2>&1 | \
reviewdog -f=swiftlint -reporter=github-pr-review -filter-mode=nofilter -name="swiftlint" || true
lint-shell:
name: "lint-shell"
runs-on: "runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache"
permissions:
contents: "read"
steps:
- uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v6
with:
submodules: recursive
- uses: "runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60" # v2
- name: "Install shellcheck and shfmt"
env:
SHELLCHECK_VERSION: "v0.11.0"
SHELLCHECK_SHA256: "8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198"
SHFMT_VERSION: "v3.13.1"
SHFMT_SHA256: "fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1"
run: |
curl -sL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" -o /tmp/shellcheck.tar.xz
echo "${SHELLCHECK_SHA256} /tmp/shellcheck.tar.xz" | sha256sum -c -
tar -xJf /tmp/shellcheck.tar.xz -C /tmp
sudo install -m 755 "/tmp/shellcheck-${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/shellcheck
curl -sL "https://github.com/mvdan/sh/releases/download/${SHFMT_VERSION}/shfmt_${SHFMT_VERSION}_linux_amd64" -o /tmp/shfmt
echo "${SHFMT_SHA256} /tmp/shfmt" | sha256sum -c -
sudo install -m 755 /tmp/shfmt /usr/local/bin/shfmt
shellcheck --version
shfmt --version
- name: "Run make lint-shell"
run: make lint-shell
test:
name: "test"
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"

5
.shellcheckrc Normal file
View File

@@ -0,0 +1,5 @@
# Copyright (c) 2026 Probo Inc <hello@probo.com>.
# SPDX-License-Identifier: MIT
# Prefer shebang-detected dialect (sh vs bash).
external-sources=false

View File

@@ -25,6 +25,24 @@ SWIFTLINT_CONFIG ?= .swiftlint.yml
swift_sources = $(shell find $(SWIFT_ENROLL_UI) \( -name '*.swift' ! -name '*.generated.swift' ! -path '*/.build/*' \) | sort)
SHELLCHECKCMD ?= shellcheck
SHFMTCMD ?= shfmt
SHFMTFLAGS ?= -i 2 -ci -bn
# First-party shell scripts linted by lint-shell / fmt-shell (CI).
# Add every new first-party *.sh here; do not include vendored/submodule scripts.
SHELL_SCRIPTS := \
cmd/probo-agent/installer/install.sh \
cmd/probo-agent/installer/macos/build.sh \
cmd/probo-agent/installer/macos/reinstall.sh \
cmd/probo-agent/installer/macos/uninstall.sh \
compose/postgres/01_probod.sh \
contrib/lima/provision.sh \
contrib/lima/sandbox.sh \
contrib/merge-graphql-schema.sh \
contrib/seed.sh \
entrypoint.sh
DOCKER_BUILD_FLAGS?=
DOCKER_BUILD= DOCKER_BUILDKIT=1 $(DOCKER) build $(DOCKER_BUILD_FLAGS)
@@ -137,6 +155,15 @@ swift-lint: ## Lint Swift with SwiftLint
@command -v $(SWIFTLINTCMD) >/dev/null 2>&1 || { echo "error: '$(SWIFTLINTCMD)' not found; install SwiftLint (e.g. brew install swiftlint)"; exit 1; }
$(SWIFTLINTCMD) lint --strict --config $(SWIFTLINT_CONFIG) --cache-path .cache/swiftlint
.PHONY: lint-shell
lint-shell: ## Lint first-party shell scripts (shfmt + shellcheck)
@if [ -z "$(SHELL_SCRIPTS)" ]; then \
echo "error: no shell scripts found"; \
exit 1; \
fi
$(SHFMTCMD) -d $(SHFMTFLAGS) $(SHELL_SCRIPTS)
$(SHELLCHECKCMD) $(SHELL_SCRIPTS)
.PHONY: vet
vet: generate embed
$(GO_VET) ./...
@@ -418,6 +445,14 @@ fmt-swift: ## Format Swift enroll-ui sources
$(SWIFTLINTCMD) lint --fix --config $(SWIFTLINT_CONFIG) --cache-path .cache/swiftlint; \
fi
.PHONY: fmt-shell
fmt-shell: ## Format first-party shell scripts with shfmt
@if [ -z "$(SHELL_SCRIPTS)" ]; then \
echo "error: no shell scripts found"; \
exit 1; \
fi
$(SHFMTCMD) -w $(SHFMTFLAGS) $(SHELL_SCRIPTS)
.PHONY: clean
clean: ## Clean the project (node_modules and build artifacts)
$(RM) -rf bin/*

View File

@@ -71,7 +71,7 @@ read_user() {
if [ -t 0 ]; then
IFS= read -r "$1"
else
IFS= read -r "$1" < /dev/tty
IFS= read -r "$1" </dev/tty
fi
}
@@ -196,12 +196,12 @@ read_secret() {
stty "$old_stty" 2>/dev/null || stty echo 2>/dev/null || true
fi
else
old_stty="$(stty -g < /dev/tty 2>/dev/null || true)"
stty -echo < /dev/tty 2>/dev/null || true
IFS= read -r REPLY < /dev/tty || REPLY=""
old_stty="$(stty -g </dev/tty 2>/dev/null || true)"
stty -echo </dev/tty 2>/dev/null || true
IFS= read -r REPLY </dev/tty || REPLY=""
printf '\n' >/dev/tty
if [ -n "${old_stty:-}" ]; then
stty "$old_stty" < /dev/tty 2>/dev/null || stty echo < /dev/tty 2>/dev/null || true
stty "$old_stty" </dev/tty 2>/dev/null || stty echo </dev/tty 2>/dev/null || true
fi
fi
else

View File

@@ -56,17 +56,36 @@ APPLE_TEAM_ID="${APPLE_TEAM_ID:-}"
NOTARYTOOL_KEYCHAIN_PROFILE="${NOTARYTOOL_KEYCHAIN_PROFILE:-probo-agent-notary}"
usage() {
sed -ne '/^#/!q; s/^# \{0,1\}//; 2,$ p' < "$0"
sed -ne '/^#/!q; s/^# \{0,1\}//; 2,$ p' <"$0"
}
while [ $# -gt 0 ]; do
case "$1" in
--binary) BINARY="$2"; shift 2 ;;
--version) VERSION="$2"; shift 2 ;;
--output) OUTPUT="$2"; shift 2 ;;
--identifier) IDENTIFIER="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown flag: $1" >&2; usage >&2; exit 2 ;;
--binary)
BINARY="$2"
shift 2
;;
--version)
VERSION="$2"
shift 2
;;
--output)
OUTPUT="$2"
shift 2
;;
--identifier)
IDENTIFIER="$2"
shift 2
;;
-h | --help)
usage
exit 0
;;
*)
echo "unknown flag: $1" >&2
usage >&2
exit 2
;;
esac
done
@@ -223,12 +242,12 @@ build_probo_agent_app() {
sed \
-e "s|@@VERSION@@|${VERSION}|g" \
"${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift.tmpl" \
> "${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift"
>"${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift"
sed \
-e "s|@@TEAM_ID_OPTION@@|$(team_id_option)|g" \
"${ENROLL_UI_DIR}/Shared/SigningConstants.generated.swift.tmpl" \
> "${ENROLL_UI_DIR}/Shared/SigningConstants.generated.swift"
>"${ENROLL_UI_DIR}/Shared/SigningConstants.generated.swift"
helper_info_plist="${render_dir}/helper-info.plist"
helper_launchd_plist="${render_dir}/helper-launchd.plist"
@@ -236,7 +255,7 @@ build_probo_agent_app() {
sed \
-e "s|@@VERSION@@|${VERSION}|g" \
-e "s|@@CLIENT_DESIGNATED_REQUIREMENT@@|$(client_requirement)|g" \
"${ENROLL_UI_DIR}/HelperTool/Info.plist.tmpl" > "${helper_info_plist}"
"${ENROLL_UI_DIR}/HelperTool/Info.plist.tmpl" >"${helper_info_plist}"
cp "${ENROLL_UI_DIR}/HelperTool/Launchd.plist.tmpl" "${helper_launchd_plist}"
@@ -309,7 +328,7 @@ build_probo_agent_app() {
sed \
-e "s|@@VERSION@@|${VERSION}|g" \
-e "s|@@HELPER_DESIGNATED_REQUIREMENT@@|${helper_requirement}|g" \
"${ENROLL_UI_DIR}/Info.plist.tmpl" > "${plist}"
"${ENROLL_UI_DIR}/Info.plist.tmpl" >"${plist}"
if ! plutil -lint "${plist}" >/dev/null; then
echo "error: rendered Info.plist failed plutil -lint" >&2
@@ -428,7 +447,7 @@ DISTRIBUTION="${STAGE}/Distribution.xml"
sed \
-e "s|@@VERSION@@|${VERSION}|g" \
-e "s|@@IDENTIFIER@@|${IDENTIFIER}|g" \
"${SCRIPT_DIR}/Distribution.xml.tmpl" > "${DISTRIBUTION}"
"${SCRIPT_DIR}/Distribution.xml.tmpl" >"${DISTRIBUTION}"
mkdir -p "$(dirname "${OUTPUT}")"

View File

@@ -52,9 +52,9 @@ bootout_tray_for_user() {
local username="$1"
local user_uid
if [ -z "${username}" ] || \
[ "${username}" = "root" ] || \
[ "${username}" = "loginwindow" ]; then
if [ -z "${username}" ] \
|| [ "${username}" = "root" ] \
|| [ "${username}" = "loginwindow" ]; then
return 0
fi
@@ -70,8 +70,7 @@ unregister_apps() {
local path
for path in \
"/Applications/Probo Agent.app" \
"/Applications/Probo Agent.localized/Probo Agent.app"
do
"/Applications/Probo Agent.localized/Probo Agent.app"; do
if [ -d "${path}" ] && [ -x "${LSREGISTER}" ]; then
"${LSREGISTER}" -u "${path}" 2>/dev/null || true
log "Unregistered Launch Services entry for ${path}"

View File

@@ -2,7 +2,7 @@
set -eu
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER <<-EOF
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" <<-EOF
CREATE USER probod;
ALTER USER probod WITH SUPERUSER;
ALTER USER probod PASSWORD 'probod';
@@ -12,13 +12,13 @@ CREATE DATABASE probod_test;
GRANT ALL PRIVILEGES ON DATABASE probod_test TO probod;
EOF
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod <<-EOF
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d probod <<-EOF
ALTER SCHEMA public OWNER TO probod;
GRANT ALL ON SCHEMA public TO probod;
ALTER DATABASE probod SET probo.trust_center_base_domain TO 'probopage.localhost';
EOF
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod_test <<-EOF
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d probod_test <<-EOF
ALTER SCHEMA public OWNER TO probod;
GRANT ALL ON SCHEMA public TO probod;
ALTER DATABASE probod_test SET probo.trust_center_base_domain TO 'probopage.localhost';

View File

@@ -16,8 +16,10 @@ The project uses a `GNUmakefile` at the root. Builds run with `--jobs=$(nproc)`
| `make test-e2e` | Run console end-to-end tests (requires `bin/probod`) |
| `make lint` | Run Go + JS linters: `vet` + `go-fmt` + `go-fix` + `go-lint` + `lint-js` |
| `make lint-swift` | Opt-in: lint Swift enroll-ui (`swift-fmt` + `swift-lint`; needs Swift + SwiftLint; CI runs this on Linux) |
| `make lint-shell` | Opt-in: lint `SHELL_SCRIPTS` (`shfmt -d` + `shellcheck`; CI runs this) |
| `make fmt` | Format Go code |
| `make fmt-swift` | Opt-in: format Swift enroll-ui (`swift format` + SwiftLint `--fix`; needs Swift) |
| `make fmt-shell` | Opt-in: format `SHELL_SCRIPTS` with `shfmt` |
| `make clean` | Remove all build artifacts, `node_modules`, generated files, and coverage |
| `make help` | List targets with `##` doc comments |
@@ -86,3 +88,10 @@ Individual codegen is driven by `go generate`:
| `SWIFTLINTCMD` | `swiftlint` | SwiftLint binary |
| `SWIFTCMD` | `swift` | Swift toolchain binary (`swift format`) |
| `SWIFT_ENROLL_UI` | `cmd/probo-agent/installer/macos/enroll-ui` | Path to the Swift SPM package |
| `SHELLCHECKCMD` | `shellcheck` | ShellCheck binary |
| `SHFMTCMD` | `shfmt` | shfmt binary |
| `SHFMTFLAGS` | `-i 2 -ci -bn` | Flags passed to `shfmt` |
## Shell scripts
`make lint-shell` / `make fmt-shell` only touch the static `SHELL_SCRIPTS` list in the root `GNUmakefile` (not a recursive `find`). When you add a new first-party `*.sh` file, append it to that list so CI formats and lint it. Do not add vendored or git-submodule scripts (for example under `pkg/validator/data/disposable-email-domains`).

View File

@@ -15,8 +15,6 @@ GO_VERSION="1.26.5"
NODE_MAJOR=24
NPM_VERSION="11.8.0"
GOTESTSUM_VERSION="v1.13.0"
GOLANGCI_LINT_VERSION="v2.11.3"
GOW_VERSION="v0.0.0-20260225145757-ff0f6779ab4c"
MKCERT_VERSION="v1.4.4"
@@ -41,7 +39,7 @@ if ! command -v docker &>/dev/null; then
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
| tee /etc/apt/sources.list.d/docker.list >/dev/null
apt-get update -qq
apt-get install -y -qq \
@@ -63,7 +61,7 @@ if [ ! -d "/usr/local/go" ] || ! /usr/local/go/bin/go version | grep -q "go${GO_
| tar -C /usr/local -xzf -
fi
cat > /etc/profile.d/go.sh << 'GOEOF'
cat >/etc/profile.d/go.sh <<'GOEOF'
export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"
GOEOF
chmod +x /etc/profile.d/go.sh
@@ -109,26 +107,27 @@ fi
# Load developer-specific overrides (not committed to repo).
if [ -f /workspace/.sandbox.env ]; then
set -a
# shellcheck source=/dev/null
. /workspace/.sandbox.env
set +a
fi
PROBOD_BASE_URL="http://${VM_IP}:8080" \
PROBOD_AUTH_COOKIE_DOMAIN="${VM_IP}" \
PROBOD_AUTH_COOKIE_SECURE=false \
PROBOD_AUTH_COOKIE_SECRET="this-is-a-secure-secret-for-cookie-signing-at-least-32-bytes" \
PROBOD_AUTH_PASSWORD_PEPPER="this-is-a-secure-pepper-for-password-hashing-at-least-32-bytes" \
PROBOD_ENCRYPTION_KEY="thisisnotasecretAAAAAAAAAAAAAAAAAAAAAAAAAAA=" \
PROBOD_OAUTH2_SERVER_SIGNING_KEY="$(cat "${OAUTH2_SIGNING_KEY_PATH}")" \
PROBOD_API_CORS_ALLOWED_ORIGINS="http://${VM_IP}:8080,http://${VM_IP}:5173,http://${VM_IP}:5174" \
PROBOD_AWS_ENDPOINT="http://127.0.0.1:8333" \
PROBOD_AWS_ACCESS_KEY_ID="probod" \
PROBOD_AWS_SECRET_ACCESS_KEY="thisisnotasecret" \
PROBOD_AWS_USE_PATH_STYLE=true \
PROBOD_ACME_DIRECTORY="https://127.0.0.1:9000/acme/acme/directory" \
PROBOD_ACME_EMAIL="admin@probo.com" \
PROBOD_ACME_KEY_TYPE="EC256" \
PROBOD_ACME_ROOT_CA="$(cat /workspace/compose/step-ca/certs/root_ca.crt)" \
PROBOD_AUTH_COOKIE_DOMAIN="${VM_IP}" \
PROBOD_AUTH_COOKIE_SECURE=false \
PROBOD_AUTH_COOKIE_SECRET="this-is-a-secure-secret-for-cookie-signing-at-least-32-bytes" \
PROBOD_AUTH_PASSWORD_PEPPER="this-is-a-secure-pepper-for-password-hashing-at-least-32-bytes" \
PROBOD_ENCRYPTION_KEY="thisisnotasecretAAAAAAAAAAAAAAAAAAAAAAAAAAA=" \
PROBOD_OAUTH2_SERVER_SIGNING_KEY="$(cat "${OAUTH2_SIGNING_KEY_PATH}")" \
PROBOD_API_CORS_ALLOWED_ORIGINS="http://${VM_IP}:8080,http://${VM_IP}:5173,http://${VM_IP}:5174" \
PROBOD_AWS_ENDPOINT="http://127.0.0.1:8333" \
PROBOD_AWS_ACCESS_KEY_ID="probod" \
PROBOD_AWS_SECRET_ACCESS_KEY="thisisnotasecret" \
PROBOD_AWS_USE_PATH_STYLE=true \
PROBOD_ACME_DIRECTORY="https://127.0.0.1:9000/acme/acme/directory" \
PROBOD_ACME_EMAIL="admin@probo.com" \
PROBOD_ACME_KEY_TYPE="EC256" \
PROBOD_ACME_ROOT_CA="$(cat /workspace/compose/step-ca/certs/root_ca.crt)" \
/workspace/bin/probod-bootstrap -output /etc/probod/config.yml
# probod runs as ${LIMA_USER} but bootstrap writes config.yml as root with 0600
@@ -137,7 +136,7 @@ chown "${LIMA_USER}:${LIMA_USER}" /etc/probod/config.yml "${OAUTH2_SIGNING_KEY_P
# Bind-mount VM-local node_modules over the shared workspace to avoid
# platform conflicts between macOS host and Linux VM native binaries.
cat > /etc/systemd/system/probo-node-modules.service << EOF
cat >/etc/systemd/system/probo-node-modules.service <<EOF
[Unit]
Description=Bind-mount VM-local node_modules over workspace
DefaultDependencies=no
@@ -167,11 +166,11 @@ su - "${LIMA_USER}" -c "cd /workspace && npm ci"
make -C /workspace generate WITH_APPS=1
make -C /workspace embed
echo "VITE_API_URL=http://${VM_IP}:8080" > /workspace/apps/console/.env
echo "VITE_API_URL=http://${VM_IP}:8080" > /workspace/apps/compliance-portal/.env
echo "VITE_API_URL=http://${VM_IP}:8080" >/workspace/apps/console/.env
echo "VITE_API_URL=http://${VM_IP}:8080" >/workspace/apps/compliance-portal/.env
# Install systemd services for the sandbox
cat > /etc/systemd/system/probo-stack.service << EOF
cat >/etc/systemd/system/probo-stack.service <<EOF
[Unit]
Description=Probo Docker Compose Stack
Requires=docker.service
@@ -191,7 +190,7 @@ RestartSec=5s
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/probod.service << EOF
cat >/etc/systemd/system/probod.service <<EOF
[Unit]
Description=Probo API Server
Requires=probo-stack.service
@@ -211,7 +210,7 @@ Environment=PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/probo-console.service << EOF
cat >/etc/systemd/system/probo-console.service <<EOF
[Unit]
Description=Probo Console Dev Server
Requires=probo-node-modules.service
@@ -229,7 +228,7 @@ RestartSec=3s
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/probo-compliance-portal.service << EOF
cat >/etc/systemd/system/probo-compliance-portal.service <<EOF
[Unit]
Description=Probo Compliance Portal Dev Server
Requires=probo-node-modules.service

View File

@@ -49,10 +49,22 @@ cmd_create() {
while [[ $# -gt 0 ]]; do
case "$1" in
--cpus) cpus="$2"; shift 2 ;;
--memory) memory="$2"; shift 2 ;;
--disk) disk="$2"; shift 2 ;;
*) echo "Unknown option: $1"; usage ;;
--cpus)
cpus="$2"
shift 2
;;
--memory)
memory="$2"
shift 2
;;
--disk)
disk="$2"
shift 2
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
@@ -175,5 +187,8 @@ case "${command}" in
;;
status) cmd_status ;;
list) cmd_list ;;
*) echo "Unknown command: ${command}"; usage ;;
*)
echo "Unknown command: ${command}"
usage
;;
esac

View File

@@ -34,11 +34,11 @@ process_file() {
[ "$f" = "$base" ] && continue
process_file "$f"
done
} > "$schema_body"
} >"$schema_body"
{
cat "$schema_body"
printf '\ntype Mutation {\n'
cat "$mutation_fields"
printf '}\n'
} > "$output"
} >"$output"

View File

@@ -19,6 +19,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# GraphQL documents are intentional single-quoted literals (no expansion).
# shellcheck disable=SC2016
set -euo pipefail
BASE_URL="${PROBO_SEED_URL:-http://localhost:8080}"
@@ -58,7 +61,8 @@ check_error() {
}
prb_api() {
local context="$1"; shift
local context="$1"
shift
local resp
resp=$($PRB api "$@")
check_error "$resp" "$context"
@@ -66,13 +70,17 @@ prb_api() {
}
curl -sf -o /dev/null "$BASE_URL/healthz" \
|| { echo "ERROR: API at $BASE_URL is not available" >&2; exit 1; }
|| {
echo "ERROR: API at $BASE_URL is not available" >&2
exit 1
}
echo "==> Bootstrapping user and organization..."
vars=$(jo input="$(jo \
vars=$(jo input="$(
jo \
email="$EMAIL" \
password="$PASSWORD" \
fullName="$FULL_NAME" \
fullName="$FULL_NAME"
)")
resp=$(gql_connect '
mutation($input: SignUpInput!) {
@@ -96,9 +104,10 @@ check_error "$resp" "createOrganization"
ORG_ID=$(echo "$resp" | jq -r '.data.createOrganization.organization.id')
echo " Created organization $ORG_NAME ($ORG_ID)"
vars=$(jo input="$(jo \
vars=$(jo input="$(
jo \
organizationId="$ORG_ID" \
continue="$BASE_URL" \
continue="$BASE_URL"
)")
resp=$(gql_connect '
mutation($input: AssumeOrganizationSessionInput!) {
@@ -116,9 +125,10 @@ echo " Assumed organization session"
EXPIRES_AT=$(date -u -v+1y +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
|| date -u -d "+1 year" +"%Y-%m-%dT%H:%M:%SZ")
vars=$(jo input="$(jo \
vars=$(jo input="$(
jo \
name=seed \
expiresAt="$EXPIRES_AT" \
expiresAt="$EXPIRES_AT"
)")
resp=$(gql_connect '
mutation($input: CreatePersonalAPIKeyInput!) {
@@ -148,14 +158,15 @@ create_person() {
local email="$3"
local vars
vars=$(jo input="$(jo \
vars=$(jo input="$(
jo \
organizationId="$ORG_ID" \
emailAddress="$email" \
fullName="$full_name" \
role=EMPLOYEE \
kind=EMPLOYEE \
additionalEmailAddresses="$(jo -a < /dev/null)" \
position="$position" \
additionalEmailAddresses="$(jo -a </dev/null)" \
position="$position"
)")
resp=$(gql_connect '
mutation($input: CreateUserInput!) {
@@ -219,10 +230,11 @@ create_framework() {
}
}
}
' -f input="$(jo \
' -f input="$(
jo \
organizationId="$ORG_ID" \
name="$name" \
description="$desc" \
description="$desc"
)")
local id
id=$(echo "$resp" | jq -r '.data.createFramework.frameworkEdge.node.id // empty')
@@ -243,7 +255,7 @@ create_control() {
--framework "$framework_id" \
--section-title "$section" \
--name "$name" \
--description "$desc" > /dev/null
--description "$desc" >/dev/null
}
# ISO 27001:2022
@@ -457,7 +469,7 @@ create_risk() {
--category "$category" \
--treatment "$treatment" \
--inherent-likelihood "$likelihood" \
--inherent-impact "$impact" > /dev/null
--inherent-impact "$impact" >/dev/null
}
create_risk \
@@ -586,10 +598,11 @@ create_third_party() {
}
}
}
' -f input="$(jo \
' -f input="$(
jo \
organizationId="$ORG_ID" \
name="$name" \
description="$description" \
description="$description"
)")
local id
id=$(echo "$resp" | jq -r '.data.createThirdParty.thirdPartyEdge.node.id // empty')
@@ -657,10 +670,11 @@ create_measure() {
}
}
}
' -f input="$(jo \
' -f input="$(
jo \
organizationId="$ORG_ID" \
name="$name" \
category="$category" \
category="$category"
)")
local id
id=$(echo "$resp" | jq -r '.data.createMeasure.measureEdge.node.id // empty')
@@ -796,7 +810,8 @@ agent_heartbeat() {
# agent_postures <api_key> <CHECK_KEY:STATUS>...
agent_postures() {
local api_key="$1"; shift
local api_key="$1"
shift
local now
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
@@ -827,7 +842,7 @@ revoke_device() {
device { id }
}
}
' -f input="$(jo deviceId="$device_id")" > /dev/null
' -f input="$(jo deviceId="$device_id")" >/dev/null
}
# seed_device <owner_id> <hostname> <platform> <os_version> <serial> <CHECK_KEY:STATUS>...
@@ -863,35 +878,35 @@ seed_device() {
seed_device "${PROFILE_IDS[0]}" "jane-macbook-pro" "DARWIN" "14.5" "C02XY1Z2JGH7" \
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
OS_VERSION:PASS AUTO_UPDATE:PASS PASSWORD_POLICY:PASS REMOTE_LOGIN:PASS \
MALWARE_PROTECTION:PASS > /dev/null
MALWARE_PROTECTION:PASS >/dev/null
seed_device "${PROFILE_IDS[1]}" "marcus-thinkpad" "LINUX" "Ubuntu 24.04" "PF3ABCDE" \
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:FAIL TIME_SYNC:PASS \
OS_VERSION:PASS AUTO_UPDATE:UNKNOWN PASSWORD_POLICY:PASS REMOTE_LOGIN:FAIL \
MALWARE_PROTECTION:NOT_APPLICABLE > /dev/null
MALWARE_PROTECTION:NOT_APPLICABLE >/dev/null
seed_device "${PROFILE_IDS[4]}" "emily-macbook-air" "DARWIN" "14.4" "C02AB3C4JGH8" \
DISK_ENCRYPTION:PASS SCREEN_LOCK:FAIL FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
OS_VERSION:PASS AUTO_UPDATE:PASS PASSWORD_POLICY:FAIL REMOTE_LOGIN:PASS \
MALWARE_PROTECTION:PASS > /dev/null
MALWARE_PROTECTION:PASS >/dev/null
seed_device "${PROFILE_IDS[7]}" "alex-devbox" "LINUX" "Debian 12" "PF9ZYXWV" \
DISK_ENCRYPTION:FAIL SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
OS_VERSION:UNKNOWN AUTO_UPDATE:PASS PASSWORD_POLICY:PASS REMOTE_LOGIN:PASS \
MALWARE_PROTECTION:NOT_APPLICABLE > /dev/null
MALWARE_PROTECTION:NOT_APPLICABLE >/dev/null
seed_device "${PROFILE_IDS[3]}" "david-surface" "WINDOWS" "Windows 11 23H2" "5CD1234ABC" \
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:FAIL \
OS_VERSION:PASS AUTO_UPDATE:PASS PASSWORD_POLICY:PASS REMOTE_LOGIN:PASS \
MALWARE_PROTECTION:PASS > /dev/null
MALWARE_PROTECTION:PASS >/dev/null
seed_device "${PROFILE_IDS[2]}" "sofia-latitude" "WINDOWS" "Windows 11 22H2" "5CD9876ZYX" \
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:FAIL TIME_SYNC:PASS \
OS_VERSION:FAIL AUTO_UPDATE:FAIL PASSWORD_POLICY:PASS REMOTE_LOGIN:PASS \
MALWARE_PROTECTION:UNKNOWN > /dev/null
MALWARE_PROTECTION:UNKNOWN >/dev/null
# 1 pending device: created and assigned, but never enrolled/activated.
create_device "${PROFILE_IDS[6]}" > /dev/null
create_device "${PROFILE_IDS[6]}" >/dev/null
# 1 revoked device: fully activated, then revoked.
revoked_id=$(seed_device "${PROFILE_IDS[5]}" "james-old-macbook" "DARWIN" "12.7" "C02OLD1JGH9" \