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

@@ -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,55 +15,53 @@ 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"
apt-get update -qq
apt-get install -y -qq \
build-essential \
git \
curl \
jq \
parallel \
ca-certificates \
gnupg \
lsb-release \
postgresql-client
build-essential \
git \
curl \
jq \
parallel \
ca-certificates \
gnupg \
lsb-release \
postgresql-client
if ! command -v docker &>/dev/null; then
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
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 \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
apt-get update -qq
apt-get install -y -qq \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
systemctl enable --now docker
systemctl enable --now docker
fi
usermod -aG docker "${LIMA_CIDATA_USER:-lima}" 2>/dev/null || true
if [ ! -d "/usr/local/go" ] || ! /usr/local/go/bin/go version | grep -q "go${GO_VERSION}"; then
rm -rf /usr/local/go
ARCH=$(dpkg --print-architecture)
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
| tar -C /usr/local -xzf -
rm -rf /usr/local/go
ARCH=$(dpkg --print-architecture)
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
| 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
@@ -74,14 +72,14 @@ export HOME="${HOME:-/root}"
GOBIN=/usr/local/bin /usr/local/go/bin/go install "github.com/mitranim/gow@${GOW_VERSION}"
if ! command -v node &>/dev/null || ! node --version | grep -q "v${NODE_MAJOR}"; then
curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -
apt-get install -y -qq nodejs
curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -
apt-get install -y -qq nodejs
fi
npm install -g "npm@${NPM_VERSION}"
if ! command -v mkcert &>/dev/null; then
GOBIN=/usr/local/bin /usr/local/go/bin/go install "filippo.io/mkcert@${MKCERT_VERSION}"
GOBIN=/usr/local/bin /usr/local/go/bin/go install "filippo.io/mkcert@${MKCERT_VERSION}"
fi
mkcert -install 2>/dev/null || true
@@ -102,34 +100,35 @@ mkdir -p /etc/probod
OAUTH2_SIGNING_KEY_PATH=/etc/probod/oauth2-signing-key.pem
if [ ! -f "${OAUTH2_SIGNING_KEY_PATH}" ]; then
openssl genrsa -out "${OAUTH2_SIGNING_KEY_PATH}" 2048
chmod 600 "${OAUTH2_SIGNING_KEY_PATH}"
openssl genrsa -out "${OAUTH2_SIGNING_KEY_PATH}" 2048
chmod 600 "${OAUTH2_SIGNING_KEY_PATH}"
fi
# Load developer-specific overrides (not committed to repo).
if [ -f /workspace/.sandbox.env ]; then
set -a
. /workspace/.sandbox.env
set +a
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)" \
/workspace/bin/probod-bootstrap -output /etc/probod/config.yml
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
# because it contains secrets. Transfer ownership so probod can read it.
@@ -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

@@ -12,7 +12,7 @@ WORKTREE_NAME="$(basename "${REPO_ROOT}")"
VM_NAME="probo-${WORKTREE_NAME}"
usage() {
cat <<EOF
cat <<EOF
Usage: $(basename "$0") <command> [options]
Commands:
@@ -29,151 +29,166 @@ Commands:
VM name: ${VM_NAME} (derived from worktree directory)
EOF
exit 1
exit 1
}
get_vm_ip() {
limactl shell "${VM_NAME}" ip -4 -j addr show dev lima0 2>/dev/null \
| jq -r '.[0].addr_info[0].local // empty' 2>/dev/null || true
limactl shell "${VM_NAME}" ip -4 -j addr show dev lima0 2>/dev/null \
| jq -r '.[0].addr_info[0].local // empty' 2>/dev/null || true
}
get_vm_status() {
local status
status=$(limactl list --json 2>/dev/null \
| jq -r "select(.name == \"${VM_NAME}\") | .status" 2>/dev/null) || true
echo "${status:-NotFound}"
local status
status=$(limactl list --json 2>/dev/null \
| jq -r "select(.name == \"${VM_NAME}\") | .status" 2>/dev/null) || true
echo "${status:-NotFound}"
}
cmd_create() {
local cpus="" memory="" disk=""
local cpus="" memory="" disk=""
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 ;;
esac
done
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
;;
esac
done
echo "Creating sandbox: ${VM_NAME}"
echo "Worktree: ${REPO_ROOT}"
echo "Creating sandbox: ${VM_NAME}"
echo "Worktree: ${REPO_ROOT}"
local -a create_args=(
--name "${VM_NAME}"
--tty=false
--set ".mounts = [{\"location\": \"${REPO_ROOT}\", \"mountPoint\": \"/workspace\", \"writable\": true},{\"location\": \"${HOME}/go\", \"mountPoint\": \"/home/${USER}.guest/go\", \"writable\": true}]"
--mount-type virtiofs
)
local -a create_args=(
--name "${VM_NAME}"
--tty=false
--set ".mounts = [{\"location\": \"${REPO_ROOT}\", \"mountPoint\": \"/workspace\", \"writable\": true},{\"location\": \"${HOME}/go\", \"mountPoint\": \"/home/${USER}.guest/go\", \"writable\": true}]"
--mount-type virtiofs
)
if [[ -n "${cpus}" ]]; then
create_args+=(--cpus "${cpus}")
fi
if [[ -n "${memory}" ]]; then
create_args+=(--memory "${memory}")
fi
if [[ -n "${disk}" ]]; then
create_args+=(--disk "${disk}")
fi
if [[ -n "${cpus}" ]]; then
create_args+=(--cpus "${cpus}")
fi
if [[ -n "${memory}" ]]; then
create_args+=(--memory "${memory}")
fi
if [[ -n "${disk}" ]]; then
create_args+=(--disk "${disk}")
fi
limactl create "${create_args[@]}" "${TEMPLATE}"
limactl create "${create_args[@]}" "${TEMPLATE}"
}
cmd_start() {
echo "Starting sandbox: ${VM_NAME}"
limactl start "${VM_NAME}"
echo ""
cmd_status
echo "Starting sandbox: ${VM_NAME}"
limactl start "${VM_NAME}"
echo ""
cmd_status
}
cmd_boot_logs() {
limactl shell "${VM_NAME}" -- sudo tail -f /var/log/cloud-init-output.log
limactl shell "${VM_NAME}" -- sudo tail -f /var/log/cloud-init-output.log
}
cmd_stop() {
echo "Stopping sandbox: ${VM_NAME}"
limactl stop "${VM_NAME}"
echo "Sandbox stopped."
echo "Stopping sandbox: ${VM_NAME}"
limactl stop "${VM_NAME}"
echo "Sandbox stopped."
}
cmd_restart() {
cmd_stop
echo ""
cmd_start
cmd_stop
echo ""
cmd_start
}
cmd_delete() {
echo "Deleting sandbox: ${VM_NAME}"
limactl delete --force "${VM_NAME}"
echo "Sandbox deleted."
echo "Deleting sandbox: ${VM_NAME}"
limactl delete --force "${VM_NAME}"
echo "Sandbox deleted."
}
cmd_ssh() {
exec limactl shell --workdir /workspace "${VM_NAME}"
exec limactl shell --workdir /workspace "${VM_NAME}"
}
cmd_exec() {
limactl shell --workdir /workspace "${VM_NAME}" "$@"
limactl shell --workdir /workspace "${VM_NAME}" "$@"
}
cmd_status() {
local status ip
status="$(get_vm_status)"
ip="$(get_vm_ip)"
local status ip
status="$(get_vm_status)"
ip="$(get_vm_ip)"
echo "Sandbox: ${VM_NAME}"
echo "State: ${status}"
echo "IP: ${ip:-"-"}"
echo "Sandbox: ${VM_NAME}"
echo "State: ${status}"
echo "IP: ${ip:-"-"}"
if [[ "${status}" == "Running" && -n "${ip}" ]]; then
echo ""
echo "Services (use VM IP to access from host):"
echo " Console: http://${ip}:5173"
echo " Compliance Portal: http://${ip}:5174"
echo " API: http://${ip}:8080"
echo " Grafana: http://${ip}:3001"
echo " Mailpit: http://${ip}:8025"
echo " Keycloak: http://${ip}:8082"
echo " PostgreSQL: psql -h ${ip} -U probod"
fi
if [[ "${status}" == "Running" && -n "${ip}" ]]; then
echo ""
echo "Services (use VM IP to access from host):"
echo " Console: http://${ip}:5173"
echo " Compliance Portal: http://${ip}:5174"
echo " API: http://${ip}:8080"
echo " Grafana: http://${ip}:3001"
echo " Mailpit: http://${ip}:8025"
echo " Keycloak: http://${ip}:8082"
echo " PostgreSQL: psql -h ${ip} -U probod"
fi
}
cmd_list() {
printf "%-25s %-12s %s\n" "NAME" "STATE" "IP"
printf "%-25s %-12s %s\n" "NAME" "STATE" "IP"
limactl list --json 2>/dev/null | jq -r '
limactl list --json 2>/dev/null | jq -r '
select(.name | startswith("probo-")) |
[.name, .status] | @tsv
' | while IFS=$'\t' read -r name status; do
local ip="-"
if [[ "${status}" == "Running" ]]; then
ip=$(limactl shell "${name}" ip -4 -j addr show dev lima0 2>/dev/null \
| jq -r '.[0].addr_info[0].local // "-"' 2>/dev/null || echo "-")
fi
printf "%-25s %-12s %s\n" "${name}" "${status}" "${ip}"
done
local ip="-"
if [[ "${status}" == "Running" ]]; then
ip=$(limactl shell "${name}" ip -4 -j addr show dev lima0 2>/dev/null \
| jq -r '.[0].addr_info[0].local // "-"' 2>/dev/null || echo "-")
fi
printf "%-25s %-12s %s\n" "${name}" "${status}" "${ip}"
done
}
if [[ $# -lt 1 ]]; then
usage
usage
fi
command="$1"
shift
case "${command}" in
create) cmd_create "$@" ;;
start) cmd_start ;;
boot-logs) cmd_boot_logs ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
delete) cmd_delete ;;
ssh) cmd_ssh ;;
exec)
if [[ "${1:-}" == "--" ]]; then shift; fi
cmd_exec "$@"
;;
status) cmd_status ;;
list) cmd_list ;;
*) echo "Unknown command: ${command}"; usage ;;
create) cmd_create "$@" ;;
start) cmd_start ;;
boot-logs) cmd_boot_logs ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
delete) cmd_delete ;;
ssh) cmd_ssh ;;
exec)
if [[ "${1:-}" == "--" ]]; then shift; fi
cmd_exec "$@"
;;
status) cmd_status ;;
list) cmd_list ;;
*)
echo "Unknown command: ${command}"
usage
;;
esac

View File

@@ -16,7 +16,7 @@ schema_body=$(mktemp)
trap 'rm -f "$mutation_fields" "$schema_body"' EXIT
process_file() {
awk -v mf="$mutation_fields" '
awk -v mf="$mutation_fields" '
/^type Mutation$/ { next }
/^(extend )?type Mutation \{/ { skip=1; depth=1; next }
skip {
@@ -29,16 +29,16 @@ process_file() {
}
{
process_file "$base"
for f in "$graphql_dir"/*.graphql; do
[ "$f" = "$base" ] && continue
process_file "$f"
done
} > "$schema_body"
process_file "$base"
for f in "$graphql_dir"/*.graphql; do
[ "$f" = "$base" ] && continue
process_file "$f"
done
} >"$schema_body"
{
cat "$schema_body"
printf '\ntype Mutation {\n'
cat "$mutation_fields"
printf '}\n'
} > "$output"
cat "$schema_body"
printf '\ntype Mutation {\n'
cat "$mutation_fields"
printf '}\n'
} >"$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 \
email="$EMAIL" \
password="$PASSWORD" \
fullName="$FULL_NAME" \
vars=$(jo input="$(
jo \
email="$EMAIL" \
password="$PASSWORD" \
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 \
organizationId="$ORG_ID" \
continue="$BASE_URL" \
vars=$(jo input="$(
jo \
organizationId="$ORG_ID" \
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 \
name=seed \
expiresAt="$EXPIRES_AT" \
vars=$(jo input="$(
jo \
name=seed \
expiresAt="$EXPIRES_AT"
)")
resp=$(gql_connect '
mutation($input: CreatePersonalAPIKeyInput!) {
@@ -148,14 +158,15 @@ create_person() {
local email="$3"
local vars
vars=$(jo input="$(jo \
organizationId="$ORG_ID" \
emailAddress="$email" \
fullName="$full_name" \
role=EMPLOYEE \
kind=EMPLOYEE \
additionalEmailAddresses="$(jo -a < /dev/null)" \
position="$position" \
vars=$(jo input="$(
jo \
organizationId="$ORG_ID" \
emailAddress="$email" \
fullName="$full_name" \
role=EMPLOYEE \
kind=EMPLOYEE \
additionalEmailAddresses="$(jo -a </dev/null)" \
position="$position"
)")
resp=$(gql_connect '
mutation($input: CreateUserInput!) {
@@ -219,10 +230,11 @@ create_framework() {
}
}
}
' -f input="$(jo \
organizationId="$ORG_ID" \
name="$name" \
description="$desc" \
' -f input="$(
jo \
organizationId="$ORG_ID" \
name="$name" \
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,11 +598,12 @@ 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')
if [ -z "$id" ]; then
@@ -657,11 +670,12 @@ 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')
if [ -z "$id" ]; then
@@ -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" \