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:
41
.github/workflows/make.yaml
vendored
41
.github/workflows/make.yaml
vendored
@@ -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
5
.shellcheckrc
Normal 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
|
||||
35
GNUmakefile
35
GNUmakefile
@@ -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/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}")"
|
||||
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" \
|
||||
|
||||
Reference in New Issue
Block a user