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"
|
- uses: "./.github/actions/setup"
|
||||||
with:
|
with:
|
||||||
node: "false"
|
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"
|
- name: "Build probo-agent"
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -396,8 +390,12 @@ jobs:
|
|||||||
swift-version: "6.0"
|
swift-version: "6.0"
|
||||||
- uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
|
- uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
|
||||||
- name: "Install SwiftLint"
|
- name: "Install SwiftLint"
|
||||||
|
env:
|
||||||
|
SWIFTLINT_VERSION: "0.65.0"
|
||||||
|
SWIFTLINT_SHA256: "79306a34e5c7cc55a220cd108cbb861dcad5f10138dcdf261e2624ae8b0a486b"
|
||||||
run: |
|
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 unzip -o /tmp/swiftlint.zip -d /usr/local/bin
|
||||||
sudo chmod +x /usr/local/bin/swiftlint
|
sudo chmod +x /usr/local/bin/swiftlint
|
||||||
swiftlint version
|
swiftlint version
|
||||||
@@ -415,6 +413,35 @@ jobs:
|
|||||||
swiftlint lint --config .swiftlint.yml --cache-path /tmp/swiftlint-cache 2>&1 | \
|
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
|
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:
|
test:
|
||||||
name: "test"
|
name: "test"
|
||||||
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"
|
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)
|
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_FLAGS?=
|
||||||
DOCKER_BUILD= DOCKER_BUILDKIT=1 $(DOCKER) build $(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; }
|
@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
|
$(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
|
.PHONY: vet
|
||||||
vet: generate embed
|
vet: generate embed
|
||||||
$(GO_VET) ./...
|
$(GO_VET) ./...
|
||||||
@@ -418,6 +445,14 @@ fmt-swift: ## Format Swift enroll-ui sources
|
|||||||
$(SWIFTLINTCMD) lint --fix --config $(SWIFTLINT_CONFIG) --cache-path .cache/swiftlint; \
|
$(SWIFTLINTCMD) lint --fix --config $(SWIFTLINT_CONFIG) --cache-path .cache/swiftlint; \
|
||||||
fi
|
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
|
.PHONY: clean
|
||||||
clean: ## Clean the project (node_modules and build artifacts)
|
clean: ## Clean the project (node_modules and build artifacts)
|
||||||
$(RM) -rf bin/*
|
$(RM) -rf bin/*
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ GITHUB_RELEASES_URL="https://github.com/getprobo/probo/releases/download"
|
|||||||
# Injected at release time by .github/workflows/release-probo-agent.yaml
|
# Injected at release time by .github/workflows/release-probo-agent.yaml
|
||||||
RELEASE_TAG="__PROBO_AGENT_RELEASE_TAG__"
|
RELEASE_TAG="__PROBO_AGENT_RELEASE_TAG__"
|
||||||
if [ -n "${PROBO_AGENT_RELEASE_TAG:-}" ]; then
|
if [ -n "${PROBO_AGENT_RELEASE_TAG:-}" ]; then
|
||||||
RELEASE_TAG="$PROBO_AGENT_RELEASE_TAG"
|
RELEASE_TAG="$PROBO_AGENT_RELEASE_TAG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
RELEASE_BASE="${PROBO_AGENT_RELEASE_BASE:-}"
|
RELEASE_BASE="${PROBO_AGENT_RELEASE_BASE:-}"
|
||||||
@@ -58,25 +58,25 @@ NO_AUTO_UPDATE="${PROBO_NO_AUTO_UPDATE:-}"
|
|||||||
SKIP_SERVICE=false
|
SKIP_SERVICE=false
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
echo "error: $*" >&2
|
echo "error: $*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
can_prompt() {
|
can_prompt() {
|
||||||
[ -t 0 ] && return 0
|
[ -t 0 ] && return 0
|
||||||
[ -r /dev/tty ] && [ -w /dev/tty ]
|
[ -r /dev/tty ] && [ -w /dev/tty ]
|
||||||
}
|
}
|
||||||
|
|
||||||
read_user() {
|
read_user() {
|
||||||
if [ -t 0 ]; then
|
if [ -t 0 ]; then
|
||||||
IFS= read -r "$1"
|
IFS= read -r "$1"
|
||||||
else
|
else
|
||||||
IFS= read -r "$1" < /dev/tty
|
IFS= read -r "$1" </dev/tty
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
probo-agent installer for Darwin, Linux, and FreeBSD.
|
probo-agent installer for Darwin, Linux, and FreeBSD.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@@ -95,271 +95,271 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
embedded_checksums() {
|
embedded_checksums() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
# __PROBO_AGENT_CHECKSUMS_BEGIN__
|
# __PROBO_AGENT_CHECKSUMS_BEGIN__
|
||||||
# __PROBO_AGENT_CHECKSUMS_END__
|
# __PROBO_AGENT_CHECKSUMS_END__
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_embedded_release() {
|
resolve_embedded_release() {
|
||||||
if [ "$RELEASE_TAG" = "__PROBO_AGENT_RELEASE_TAG__" ]; then
|
if [ "$RELEASE_TAG" = "__PROBO_AGENT_RELEASE_TAG__" ]; then
|
||||||
die "this install.sh was not published by a probo-agent release; curl install.sh from the target release"
|
die "this install.sh was not published by a probo-agent release; curl install.sh from the target release"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$RELEASE_BASE" ]; then
|
if [ -n "$RELEASE_BASE" ]; then
|
||||||
RELEASE_BASE="${RELEASE_BASE%/}"
|
RELEASE_BASE="${RELEASE_BASE%/}"
|
||||||
case "$RELEASE_BASE" in
|
case "$RELEASE_BASE" in
|
||||||
*/"$RELEASE_TAG") ;;
|
*/"$RELEASE_TAG") ;;
|
||||||
*) die "PROBO_AGENT_RELEASE_BASE must end with release tag ${RELEASE_TAG}" ;;
|
*) die "PROBO_AGENT_RELEASE_BASE must end with release tag ${RELEASE_TAG}" ;;
|
||||||
esac
|
esac
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
RELEASE_BASE="${GITHUB_RELEASES_URL}/${RELEASE_TAG}"
|
RELEASE_BASE="${GITHUB_RELEASES_URL}/${RELEASE_TAG}"
|
||||||
printf 'Using release %s\n' "$RELEASE_TAG"
|
printf 'Using release %s\n' "$RELEASE_TAG"
|
||||||
}
|
}
|
||||||
|
|
||||||
require_cmd() {
|
require_cmd() {
|
||||||
if ! command -v "$1" >/dev/null 2>&1; then
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
die "required command not found: $1"
|
die "required command not found: $1"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_platform() {
|
detect_platform() {
|
||||||
os="$(uname -s)"
|
os="$(uname -s)"
|
||||||
arch="$(uname -m)"
|
arch="$(uname -m)"
|
||||||
|
|
||||||
case "$os" in
|
case "$os" in
|
||||||
Darwin) os_label="Darwin" ;;
|
Darwin) os_label="Darwin" ;;
|
||||||
Linux) os_label="Linux" ;;
|
Linux) os_label="Linux" ;;
|
||||||
FreeBSD) os_label="Freebsd" ;;
|
FreeBSD) os_label="Freebsd" ;;
|
||||||
*) die "unsupported operating system: $os (Darwin, Linux, and FreeBSD only)" ;;
|
*) die "unsupported operating system: $os (Darwin, Linux, and FreeBSD only)" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$arch" in
|
case "$arch" in
|
||||||
x86_64 | amd64) arch_label="x86_64" ;;
|
x86_64 | amd64) arch_label="x86_64" ;;
|
||||||
arm64 | aarch64) arch_label="arm64" ;;
|
arm64 | aarch64) arch_label="arm64" ;;
|
||||||
*) die "unsupported CPU architecture: $arch" ;;
|
*) die "unsupported CPU architecture: $arch" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
archive_dir="probo-agent_${os_label}_${arch_label}"
|
archive_dir="probo-agent_${os_label}_${arch_label}"
|
||||||
archive_name="${archive_dir}.tar.gz"
|
archive_name="${archive_dir}.tar.gz"
|
||||||
}
|
}
|
||||||
|
|
||||||
sha256_file() {
|
sha256_file() {
|
||||||
file="$1"
|
file="$1"
|
||||||
if command -v sha256sum >/dev/null 2>&1; then
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
sha256sum "$file" | awk '{print $1}'
|
sha256sum "$file" | awk '{print $1}'
|
||||||
elif command -v shasum >/dev/null 2>&1; then
|
elif command -v shasum >/dev/null 2>&1; then
|
||||||
shasum -a 256 "$file" | awk '{print $1}'
|
shasum -a 256 "$file" | awk '{print $1}'
|
||||||
elif command -v sha256 >/dev/null 2>&1; then
|
elif command -v sha256 >/dev/null 2>&1; then
|
||||||
sha256 -q "$file"
|
sha256 -q "$file"
|
||||||
else
|
else
|
||||||
die "no sha256 tool found (need sha256sum, shasum, or sha256)"
|
die "no sha256 tool found (need sha256sum, shasum, or sha256)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_embedded_checksum() {
|
verify_embedded_checksum() {
|
||||||
case "${PROBO_AGENT_SKIP_CHECKSUM_VERIFY:-}" in
|
case "${PROBO_AGENT_SKIP_CHECKSUM_VERIFY:-}" in
|
||||||
1 | true | TRUE | yes | YES) return 0 ;;
|
1 | true | TRUE | yes | YES) return 0 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
archive_file="$1"
|
archive_file="$1"
|
||||||
archive_basename="$(basename "$archive_file")"
|
archive_basename="$(basename "$archive_file")"
|
||||||
|
|
||||||
expected="$(
|
expected="$(
|
||||||
embedded_checksums | awk -v name="$archive_basename" '
|
embedded_checksums | awk -v name="$archive_basename" '
|
||||||
$0 ~ /^#/ { next }
|
$0 ~ /^#/ { next }
|
||||||
$2 == name { print $1; exit }
|
$2 == name { print $1; exit }
|
||||||
'
|
'
|
||||||
)"
|
)"
|
||||||
if [ -z "$expected" ]; then
|
if [ -z "$expected" ]; then
|
||||||
die "archive ${archive_basename} not found in embedded release checksums"
|
die "archive ${archive_basename} not found in embedded release checksums"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
actual="$(sha256_file "$archive_file")"
|
actual="$(sha256_file "$archive_file")"
|
||||||
if [ "$expected" != "$actual" ]; then
|
if [ "$expected" != "$actual" ]; then
|
||||||
die "checksum mismatch for ${archive_file}"
|
die "checksum mismatch for ${archive_file}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
read_secret() {
|
read_secret() {
|
||||||
prompt_text="$1"
|
prompt_text="$1"
|
||||||
printf '%s' "$prompt_text"
|
printf '%s' "$prompt_text"
|
||||||
if command -v stty >/dev/null 2>&1; then
|
if command -v stty >/dev/null 2>&1; then
|
||||||
if [ -t 0 ]; then
|
if [ -t 0 ]; then
|
||||||
old_stty="$(stty -g 2>/dev/null || true)"
|
old_stty="$(stty -g 2>/dev/null || true)"
|
||||||
stty -echo 2>/dev/null || true
|
stty -echo 2>/dev/null || true
|
||||||
IFS= read -r REPLY || REPLY=""
|
IFS= read -r REPLY || REPLY=""
|
||||||
printf '\n'
|
printf '\n'
|
||||||
if [ -n "${old_stty:-}" ]; then
|
if [ -n "${old_stty:-}" ]; then
|
||||||
stty "$old_stty" 2>/dev/null || stty echo 2>/dev/null || true
|
stty "$old_stty" 2>/dev/null || stty echo 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
old_stty="$(stty -g < /dev/tty 2>/dev/null || true)"
|
old_stty="$(stty -g </dev/tty 2>/dev/null || true)"
|
||||||
stty -echo < /dev/tty 2>/dev/null || true
|
stty -echo </dev/tty 2>/dev/null || true
|
||||||
IFS= read -r REPLY < /dev/tty || REPLY=""
|
IFS= read -r REPLY </dev/tty || REPLY=""
|
||||||
printf '\n' >/dev/tty
|
printf '\n' >/dev/tty
|
||||||
if [ -n "${old_stty:-}" ]; then
|
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
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
read_user REPLY
|
read_user REPLY
|
||||||
fi
|
fi
|
||||||
ENROLLMENT_TOKEN="$REPLY"
|
ENROLLMENT_TOKEN="$REPLY"
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_server_url() {
|
prompt_server_url() {
|
||||||
if [ -n "$SERVER_URL" ]; then
|
if [ -n "$SERVER_URL" ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if ! can_prompt; then
|
if ! can_prompt; then
|
||||||
die "PROBO_SERVER_URL is required in non-interactive mode"
|
die "PROBO_SERVER_URL is required in non-interactive mode"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '\nProbo server URL:\n'
|
printf '\nProbo server URL:\n'
|
||||||
printf ' 1) https://us.probo.com (United States)\n'
|
printf ' 1) https://us.probo.com (United States)\n'
|
||||||
printf ' 2) https://eu.probo.com (European Union)\n'
|
printf ' 2) https://eu.probo.com (European Union)\n'
|
||||||
printf ' 3) Enter a custom URL\n'
|
printf ' 3) Enter a custom URL\n'
|
||||||
printf 'Choice [1]: '
|
printf 'Choice [1]: '
|
||||||
read_user choice
|
read_user choice
|
||||||
|
|
||||||
case "${choice:-1}" in
|
case "${choice:-1}" in
|
||||||
1 | "") SERVER_URL="https://us.probo.com" ;;
|
1 | "") SERVER_URL="https://us.probo.com" ;;
|
||||||
2) SERVER_URL="https://eu.probo.com" ;;
|
2) SERVER_URL="https://eu.probo.com" ;;
|
||||||
3)
|
3)
|
||||||
printf 'Server URL: '
|
printf 'Server URL: '
|
||||||
read_user SERVER_URL
|
read_user SERVER_URL
|
||||||
;;
|
;;
|
||||||
*) SERVER_URL="$choice" ;;
|
*) SERVER_URL="$choice" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [ -z "$SERVER_URL" ]; then
|
if [ -z "$SERVER_URL" ]; then
|
||||||
die "server URL is required"
|
die "server URL is required"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_enrollment_token() {
|
prompt_enrollment_token() {
|
||||||
if [ -n "$ENROLLMENT_TOKEN" ]; then
|
if [ -n "$ENROLLMENT_TOKEN" ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if ! can_prompt; then
|
if ! can_prompt; then
|
||||||
die "PROBO_ENROLLMENT_TOKEN is required in non-interactive mode"
|
die "PROBO_ENROLLMENT_TOKEN is required in non-interactive mode"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
read_secret "Enrollment token: "
|
read_secret "Enrollment token: "
|
||||||
if [ -z "$ENROLLMENT_TOKEN" ]; then
|
if [ -z "$ENROLLMENT_TOKEN" ]; then
|
||||||
die "enrollment token is required"
|
die "enrollment token is required"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_args() {
|
parse_args() {
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--server)
|
--server)
|
||||||
[ $# -ge 2 ] || die "--server requires a value"
|
[ $# -ge 2 ] || die "--server requires a value"
|
||||||
SERVER_URL="$2"
|
SERVER_URL="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--enrollment-token)
|
--enrollment-token)
|
||||||
[ $# -ge 2 ] || die "--enrollment-token requires a value"
|
[ $# -ge 2 ] || die "--enrollment-token requires a value"
|
||||||
ENROLLMENT_TOKEN="$2"
|
ENROLLMENT_TOKEN="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--no-auto-update)
|
--no-auto-update)
|
||||||
NO_AUTO_UPDATE=true
|
NO_AUTO_UPDATE=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--skip-service)
|
--skip-service)
|
||||||
SKIP_SERVICE=true
|
SKIP_SERVICE=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--dir)
|
--dir)
|
||||||
[ $# -ge 2 ] || die "--dir requires a value"
|
[ $# -ge 2 ] || die "--dir requires a value"
|
||||||
STATE_DIR="$2"
|
STATE_DIR="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
die "unknown option: $1 (try --help)"
|
die "unknown option: $1 (try --help)"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
run_agent_install() {
|
run_agent_install() {
|
||||||
set -- --server "$SERVER_URL" --enrollment-token "$ENROLLMENT_TOKEN"
|
set -- --server "$SERVER_URL" --enrollment-token "$ENROLLMENT_TOKEN"
|
||||||
if [ -n "$STATE_DIR" ]; then
|
if [ -n "$STATE_DIR" ]; then
|
||||||
set -- "$@" --dir "$STATE_DIR"
|
set -- "$@" --dir "$STATE_DIR"
|
||||||
fi
|
fi
|
||||||
case "$NO_AUTO_UPDATE" in
|
case "$NO_AUTO_UPDATE" in
|
||||||
1 | true | TRUE | yes | YES) set -- "$@" --no-auto-update ;;
|
1 | true | TRUE | yes | YES) set -- "$@" --no-auto-update ;;
|
||||||
esac
|
esac
|
||||||
case "$SKIP_SERVICE" in
|
case "$SKIP_SERVICE" in
|
||||||
1 | true | TRUE | yes | YES) set -- "$@" --skip-service ;;
|
1 | true | TRUE | yes | YES) set -- "$@" --skip-service ;;
|
||||||
esac
|
esac
|
||||||
"$BINARY_PATH" install "$@"
|
"$BINARY_PATH" install "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
parse_args "$@"
|
parse_args "$@"
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
die "this installer must run as root; re-run with: curl -fsSL \"…/install.sh\" | sudo sh"
|
die "this installer must run as root; re-run with: curl -fsSL \"…/install.sh\" | sudo sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
require_cmd curl
|
require_cmd curl
|
||||||
require_cmd tar
|
require_cmd tar
|
||||||
require_cmd install
|
require_cmd install
|
||||||
|
|
||||||
detect_platform
|
detect_platform
|
||||||
resolve_embedded_release
|
resolve_embedded_release
|
||||||
|
|
||||||
workdir="$(mktemp -d "${TMPDIR:-/tmp}/probo-agent-install.XXXXXX")"
|
workdir="$(mktemp -d "${TMPDIR:-/tmp}/probo-agent-install.XXXXXX")"
|
||||||
trap 'rm -rf "$workdir"' EXIT INT HUP TERM
|
trap 'rm -rf "$workdir"' EXIT INT HUP TERM
|
||||||
|
|
||||||
printf 'Downloading probo-agent %s …\n' "$archive_name"
|
printf 'Downloading probo-agent %s …\n' "$archive_name"
|
||||||
|
|
||||||
curl -fsSL "${RELEASE_BASE}/${archive_name}" -o "${workdir}/${archive_name}"
|
curl -fsSL "${RELEASE_BASE}/${archive_name}" -o "${workdir}/${archive_name}"
|
||||||
|
|
||||||
verify_embedded_checksum "${workdir}/${archive_name}"
|
verify_embedded_checksum "${workdir}/${archive_name}"
|
||||||
|
|
||||||
tar -xzf "${workdir}/${archive_name}" -C "$workdir"
|
tar -xzf "${workdir}/${archive_name}" -C "$workdir"
|
||||||
if [ ! -f "${workdir}/${archive_dir}/probo-agent" ]; then
|
if [ ! -f "${workdir}/${archive_dir}/probo-agent" ]; then
|
||||||
die "archive did not contain probo-agent binary"
|
die "archive did not contain probo-agent binary"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install -m 0755 "${workdir}/${archive_dir}/probo-agent" "$BINARY_PATH"
|
install -m 0755 "${workdir}/${archive_dir}/probo-agent" "$BINARY_PATH"
|
||||||
printf 'Installed %s\n' "$BINARY_PATH"
|
printf 'Installed %s\n' "$BINARY_PATH"
|
||||||
|
|
||||||
prompt_server_url
|
prompt_server_url
|
||||||
prompt_enrollment_token
|
prompt_enrollment_token
|
||||||
|
|
||||||
printf 'Enrolling device …\n'
|
printf 'Enrolling device …\n'
|
||||||
if run_agent_install; then
|
if run_agent_install; then
|
||||||
enroll_ok=true
|
enroll_ok=true
|
||||||
else
|
else
|
||||||
enroll_ok=false
|
enroll_ok=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$enroll_ok" = true ]; then
|
if [ "$enroll_ok" = true ]; then
|
||||||
case "$SKIP_SERVICE" in
|
case "$SKIP_SERVICE" in
|
||||||
1 | true | TRUE | yes | YES)
|
1 | true | TRUE | yes | YES)
|
||||||
printf 'Device enrolled (service installation skipped).\n'
|
printf 'Device enrolled (service installation skipped).\n'
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
printf 'Device enrolled and service installed.\n'
|
printf 'Device enrolled and service installed.\n'
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
printf 'warning: probo-agent install failed; binary is at %s\n' "$BINARY_PATH" >&2
|
printf 'warning: probo-agent install failed; binary is at %s\n' "$BINARY_PATH" >&2
|
||||||
printf 'Re-run: %s install --server … --enrollment-token …\n' "$BINARY_PATH" >&2
|
printf 'Re-run: %s install --server … --enrollment-token …\n' "$BINARY_PATH" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -56,97 +56,116 @@ APPLE_TEAM_ID="${APPLE_TEAM_ID:-}"
|
|||||||
NOTARYTOOL_KEYCHAIN_PROFILE="${NOTARYTOOL_KEYCHAIN_PROFILE:-probo-agent-notary}"
|
NOTARYTOOL_KEYCHAIN_PROFILE="${NOTARYTOOL_KEYCHAIN_PROFILE:-probo-agent-notary}"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
sed -ne '/^#/!q; s/^# \{0,1\}//; 2,$ p' < "$0"
|
sed -ne '/^#/!q; s/^# \{0,1\}//; 2,$ p' <"$0"
|
||||||
}
|
}
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--binary) BINARY="$2"; shift 2 ;;
|
--binary)
|
||||||
--version) VERSION="$2"; shift 2 ;;
|
BINARY="$2"
|
||||||
--output) OUTPUT="$2"; shift 2 ;;
|
shift 2
|
||||||
--identifier) IDENTIFIER="$2"; shift 2 ;;
|
;;
|
||||||
-h|--help) usage; exit 0 ;;
|
--version)
|
||||||
*) echo "unknown flag: $1" >&2; usage >&2; exit 2 ;;
|
VERSION="$2"
|
||||||
esac
|
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
|
done
|
||||||
|
|
||||||
if [ -z "${BINARY}" ] || [ ! -x "${BINARY}" ]; then
|
if [ -z "${BINARY}" ] || [ ! -x "${BINARY}" ]; then
|
||||||
echo "error: --binary <path-to-probo-agent> is required and must be executable" >&2
|
echo "error: --binary <path-to-probo-agent> is required and must be executable" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Distribution.xml advertises hostArchitectures=arm64,x86_64. Refuse a
|
# Distribution.xml advertises hostArchitectures=arm64,x86_64. Refuse a
|
||||||
# binary that lacks either slice so Installer cannot install on a CPU
|
# binary that lacks either slice so Installer cannot install on a CPU
|
||||||
# the agent cannot run on.
|
# the agent cannot run on.
|
||||||
if ! command -v lipo >/dev/null 2>&1; then
|
if ! command -v lipo >/dev/null 2>&1; then
|
||||||
echo "error: lipo is required to validate --binary architecture (run on macOS)" >&2
|
echo "error: lipo is required to validate --binary architecture (run on macOS)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
BINARY_ARCHS="$(lipo -archs "${BINARY}")"
|
BINARY_ARCHS="$(lipo -archs "${BINARY}")"
|
||||||
has_arm64=false
|
has_arm64=false
|
||||||
has_x86_64=false
|
has_x86_64=false
|
||||||
for arch_slice in ${BINARY_ARCHS}; do
|
for arch_slice in ${BINARY_ARCHS}; do
|
||||||
case "${arch_slice}" in
|
case "${arch_slice}" in
|
||||||
arm64) has_arm64=true ;;
|
arm64) has_arm64=true ;;
|
||||||
x86_64) has_x86_64=true ;;
|
x86_64) has_x86_64=true ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
if [ "${has_arm64}" != true ] || [ "${has_x86_64}" != true ]; then
|
if [ "${has_arm64}" != true ] || [ "${has_x86_64}" != true ]; then
|
||||||
echo "error: --binary must be a fat binary with arm64 and x86_64 slices (got: ${BINARY_ARCHS}); use lipo -create" >&2
|
echo "error: --binary must be a fat binary with arm64 and x86_64 slices (got: ${BINARY_ARCHS}); use lipo -create" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${VERSION}" ]; then
|
if [ -z "${VERSION}" ]; then
|
||||||
VERSION="$(cat "${REPO_ROOT}/cmd/probo-agent/VERSION")"
|
VERSION="$(cat "${REPO_ROOT}/cmd/probo-agent/VERSION")"
|
||||||
fi
|
fi
|
||||||
if [ -z "${OUTPUT}" ]; then
|
if [ -z "${OUTPUT}" ]; then
|
||||||
mkdir -p "${REPO_ROOT}/dist"
|
mkdir -p "${REPO_ROOT}/dist"
|
||||||
OUTPUT="${REPO_ROOT}/dist/probo-agent_${VERSION}_darwin.pkg"
|
OUTPUT="${REPO_ROOT}/dist/probo-agent_${VERSION}_darwin.pkg"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! command -v pkgbuild >/dev/null 2>&1 || ! command -v productbuild >/dev/null 2>&1; then
|
if ! command -v pkgbuild >/dev/null 2>&1 || ! command -v productbuild >/dev/null 2>&1; then
|
||||||
echo "error: pkgbuild and productbuild are required (run on macOS)" >&2
|
echo "error: pkgbuild and productbuild are required (run on macOS)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! command -v swift >/dev/null 2>&1; then
|
if ! command -v swift >/dev/null 2>&1; then
|
||||||
echo "error: swift is required to build Probo Agent.app (run on macOS)" >&2
|
echo "error: swift is required to build Probo Agent.app (run on macOS)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -z "${CODESIGN_IDENTITY}" ]; then
|
if [ -z "${CODESIGN_IDENTITY}" ]; then
|
||||||
echo "error: CODESIGN_IDENTITY is required (privileged helper must be signed)" >&2
|
echo "error: CODESIGN_IDENTITY is required (privileged helper must be signed)" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
if [ -z "${APPLE_TEAM_ID}" ]; then
|
if [ -z "${APPLE_TEAM_ID}" ]; then
|
||||||
echo "error: APPLE_TEAM_ID is required (SMAuthorizedClients team requirement)" >&2
|
echo "error: APPLE_TEAM_ID is required (SMAuthorizedClients team requirement)" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
notarize_enabled=false
|
notarize_enabled=false
|
||||||
if [ -n "${APPLE_ID}" ] && [ -n "${APPLE_ID_PASSWORD}" ]; then
|
if [ -n "${APPLE_ID}" ] && [ -n "${APPLE_ID_PASSWORD}" ]; then
|
||||||
notarize_enabled=true
|
notarize_enabled=true
|
||||||
fi
|
fi
|
||||||
if [ "${notarize_enabled}" = true ] && [ -z "${INSTALLER_IDENTITY}" ]; then
|
if [ "${notarize_enabled}" = true ] && [ -z "${INSTALLER_IDENTITY}" ]; then
|
||||||
echo "error: notarization requires INSTALLER_IDENTITY" >&2
|
echo "error: notarization requires INSTALLER_IDENTITY" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
codesign_runtime() {
|
codesign_runtime() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
codesign \
|
codesign \
|
||||||
--force \
|
--force \
|
||||||
--options runtime \
|
--options runtime \
|
||||||
--timestamp \
|
--timestamp \
|
||||||
--sign "${CODESIGN_IDENTITY}" \
|
--sign "${CODESIGN_IDENTITY}" \
|
||||||
"${path}"
|
"${path}"
|
||||||
codesign --verify --verbose=2 "${path}"
|
codesign --verify --verbose=2 "${path}"
|
||||||
}
|
}
|
||||||
|
|
||||||
client_requirement() {
|
client_requirement() {
|
||||||
printf 'anchor apple generic and identifier "com.probo.agent.url-handler" and certificate leaf[subject.OU] = "%s"' "${APPLE_TEAM_ID}"
|
printf 'anchor apple generic and identifier "com.probo.agent.url-handler" and certificate leaf[subject.OU] = "%s"' "${APPLE_TEAM_ID}"
|
||||||
}
|
}
|
||||||
|
|
||||||
team_id_option() {
|
team_id_option() {
|
||||||
printf '"%s"' "${APPLE_TEAM_ID}"
|
printf '"%s"' "${APPLE_TEAM_ID}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build AppIcon.icns from the single committed master PNG
|
# Build AppIcon.icns from the single committed master PNG
|
||||||
@@ -154,213 +173,213 @@ team_id_option() {
|
|||||||
# pad-then-resize pipeline. Writes under STAGE; does not touch the
|
# pad-then-resize pipeline. Writes under STAGE; does not touch the
|
||||||
# source tree.
|
# source tree.
|
||||||
generate_app_icon_icns() {
|
generate_app_icon_icns() {
|
||||||
local icon_original="$1"
|
local icon_original="$1"
|
||||||
local icns_out="$2"
|
local icns_out="$2"
|
||||||
local icon_dir padded tmp iconset
|
local icon_dir padded tmp iconset
|
||||||
|
|
||||||
if [ ! -f "${icon_original}" ]; then
|
if [ ! -f "${icon_original}" ]; then
|
||||||
echo "error: missing app icon source ${icon_original}" >&2
|
echo "error: missing app icon source ${icon_original}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
icon_dir="${STAGE}/app-icon"
|
icon_dir="${STAGE}/app-icon"
|
||||||
padded="${icon_dir}/icon-padded.png"
|
padded="${icon_dir}/icon-padded.png"
|
||||||
tmp="${icon_dir}/icon-padded.tmp.png"
|
tmp="${icon_dir}/icon-padded.tmp.png"
|
||||||
iconset="${icon_dir}/AppIcon.iconset"
|
iconset="${icon_dir}/AppIcon.iconset"
|
||||||
rm -rf "${icon_dir}"
|
rm -rf "${icon_dir}"
|
||||||
mkdir -p "${iconset}"
|
mkdir -p "${iconset}"
|
||||||
|
|
||||||
# 10% padding on all sides (960 content inside 1200 canvas).
|
# 10% padding on all sides (960 content inside 1200 canvas).
|
||||||
sips -z 960 960 "${icon_original}" --out "${tmp}" >/dev/null
|
sips -z 960 960 "${icon_original}" --out "${tmp}" >/dev/null
|
||||||
sips --padToHeightWidth 1200 1200 "${tmp}" --out "${padded}" >/dev/null
|
sips --padToHeightWidth 1200 1200 "${tmp}" --out "${padded}" >/dev/null
|
||||||
rm -f "${tmp}"
|
rm -f "${tmp}"
|
||||||
|
|
||||||
# Write through temp names: sips mishandles @2x suffixes in --out paths.
|
# Write through temp names: sips mishandles @2x suffixes in --out paths.
|
||||||
sips -z 16 16 "${padded}" --out "${icon_dir}/16.png" >/dev/null
|
sips -z 16 16 "${padded}" --out "${icon_dir}/16.png" >/dev/null
|
||||||
sips -z 32 32 "${padded}" --out "${icon_dir}/32.png" >/dev/null
|
sips -z 32 32 "${padded}" --out "${icon_dir}/32.png" >/dev/null
|
||||||
sips -z 64 64 "${padded}" --out "${icon_dir}/64.png" >/dev/null
|
sips -z 64 64 "${padded}" --out "${icon_dir}/64.png" >/dev/null
|
||||||
sips -z 128 128 "${padded}" --out "${icon_dir}/128.png" >/dev/null
|
sips -z 128 128 "${padded}" --out "${icon_dir}/128.png" >/dev/null
|
||||||
sips -z 256 256 "${padded}" --out "${icon_dir}/256.png" >/dev/null
|
sips -z 256 256 "${padded}" --out "${icon_dir}/256.png" >/dev/null
|
||||||
sips -z 512 512 "${padded}" --out "${icon_dir}/512.png" >/dev/null
|
sips -z 512 512 "${padded}" --out "${icon_dir}/512.png" >/dev/null
|
||||||
sips -z 1024 1024 "${padded}" --out "${icon_dir}/1024.png" >/dev/null
|
sips -z 1024 1024 "${padded}" --out "${icon_dir}/1024.png" >/dev/null
|
||||||
|
|
||||||
# Build @2x names via concatenation so the shell never treats @ as a
|
# Build @2x names via concatenation so the shell never treats @ as a
|
||||||
# glob qualifier (and sips is never asked to write those paths).
|
# glob qualifier (and sips is never asked to write those paths).
|
||||||
local at2x
|
local at2x
|
||||||
at2x='@2x.png'
|
at2x='@2x.png'
|
||||||
cp "${icon_dir}/16.png" "${iconset}/icon_16x16.png"
|
cp "${icon_dir}/16.png" "${iconset}/icon_16x16.png"
|
||||||
cp "${icon_dir}/32.png" "${iconset}/icon_16x16${at2x}"
|
cp "${icon_dir}/32.png" "${iconset}/icon_16x16${at2x}"
|
||||||
cp "${icon_dir}/32.png" "${iconset}/icon_32x32.png"
|
cp "${icon_dir}/32.png" "${iconset}/icon_32x32.png"
|
||||||
cp "${icon_dir}/64.png" "${iconset}/icon_32x32${at2x}"
|
cp "${icon_dir}/64.png" "${iconset}/icon_32x32${at2x}"
|
||||||
cp "${icon_dir}/128.png" "${iconset}/icon_128x128.png"
|
cp "${icon_dir}/128.png" "${iconset}/icon_128x128.png"
|
||||||
cp "${icon_dir}/256.png" "${iconset}/icon_128x128${at2x}"
|
cp "${icon_dir}/256.png" "${iconset}/icon_128x128${at2x}"
|
||||||
cp "${icon_dir}/256.png" "${iconset}/icon_256x256.png"
|
cp "${icon_dir}/256.png" "${iconset}/icon_256x256.png"
|
||||||
cp "${icon_dir}/512.png" "${iconset}/icon_256x256${at2x}"
|
cp "${icon_dir}/512.png" "${iconset}/icon_256x256${at2x}"
|
||||||
cp "${icon_dir}/512.png" "${iconset}/icon_512x512.png"
|
cp "${icon_dir}/512.png" "${iconset}/icon_512x512.png"
|
||||||
cp "${icon_dir}/1024.png" "${iconset}/icon_512x512${at2x}"
|
cp "${icon_dir}/1024.png" "${iconset}/icon_512x512${at2x}"
|
||||||
|
|
||||||
iconutil -c icns "${iconset}" -o "${icns_out}"
|
iconutil -c icns "${iconset}" -o "${icns_out}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build Probo Agent.app (URL handler + embedded privileged helper) into
|
# Build Probo Agent.app (URL handler + embedded privileged helper) into
|
||||||
# parent_dir. Signs nested Mach-Os then the .app bundle (bottom-up).
|
# parent_dir. Signs nested Mach-Os then the .app bundle (bottom-up).
|
||||||
build_probo_agent_app() {
|
build_probo_agent_app() {
|
||||||
local parent_dir="$1"
|
local parent_dir="$1"
|
||||||
local build_dir render_dir
|
local build_dir render_dir
|
||||||
local helper_info_plist helper_launchd_plist
|
local helper_info_plist helper_launchd_plist
|
||||||
local helper_binary url_handler_binary bin_dir
|
local helper_binary url_handler_binary bin_dir
|
||||||
local app_root contents macos resources launch_services launch_daemons
|
local app_root contents macos resources launch_services launch_daemons
|
||||||
local plist embedded_helper embedded_launchd
|
local plist embedded_helper embedded_launchd
|
||||||
local helper_requirement app_icon_original app_icon_icns
|
local helper_requirement app_icon_original app_icon_icns
|
||||||
local -a helper_linker_flags swift_arch_args
|
local -a helper_linker_flags swift_arch_args
|
||||||
|
|
||||||
build_dir="${STAGE}/enroll-ui-build"
|
build_dir="${STAGE}/enroll-ui-build"
|
||||||
render_dir="${build_dir}/rendered"
|
render_dir="${build_dir}/rendered"
|
||||||
mkdir -p "${render_dir}"
|
mkdir -p "${render_dir}"
|
||||||
|
|
||||||
swift_arch_args=(--arch arm64 --arch x86_64)
|
swift_arch_args=(--arch arm64 --arch x86_64)
|
||||||
|
|
||||||
sed \
|
sed \
|
||||||
-e "s|@@VERSION@@|${VERSION}|g" \
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
||||||
"${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift.tmpl" \
|
"${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift.tmpl" \
|
||||||
> "${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift"
|
>"${ENROLL_UI_DIR}/Shared/HelperVersion.generated.swift"
|
||||||
|
|
||||||
sed \
|
sed \
|
||||||
-e "s|@@TEAM_ID_OPTION@@|$(team_id_option)|g" \
|
-e "s|@@TEAM_ID_OPTION@@|$(team_id_option)|g" \
|
||||||
"${ENROLL_UI_DIR}/Shared/SigningConstants.generated.swift.tmpl" \
|
"${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_info_plist="${render_dir}/helper-info.plist"
|
||||||
helper_launchd_plist="${render_dir}/helper-launchd.plist"
|
helper_launchd_plist="${render_dir}/helper-launchd.plist"
|
||||||
|
|
||||||
sed \
|
sed \
|
||||||
-e "s|@@VERSION@@|${VERSION}|g" \
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
||||||
-e "s|@@CLIENT_DESIGNATED_REQUIREMENT@@|$(client_requirement)|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}"
|
cp "${ENROLL_UI_DIR}/HelperTool/Launchd.plist.tmpl" "${helper_launchd_plist}"
|
||||||
|
|
||||||
helper_linker_flags=(
|
helper_linker_flags=(
|
||||||
-Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist
|
-Xlinker -sectcreate -Xlinker __TEXT -Xlinker __info_plist
|
||||||
-Xlinker "${helper_info_plist}"
|
-Xlinker "${helper_info_plist}"
|
||||||
-Xlinker -sectcreate -Xlinker __TEXT -Xlinker __launchd_plist
|
-Xlinker -sectcreate -Xlinker __TEXT -Xlinker __launchd_plist
|
||||||
-Xlinker "${helper_launchd_plist}"
|
-Xlinker "${helper_launchd_plist}"
|
||||||
)
|
)
|
||||||
|
|
||||||
pushd "${ENROLL_UI_DIR}" >/dev/null
|
pushd "${ENROLL_UI_DIR}" >/dev/null
|
||||||
swift build -c release "${swift_arch_args[@]}" \
|
swift build -c release "${swift_arch_args[@]}" \
|
||||||
--scratch-path "${build_dir}/swift" \
|
--scratch-path "${build_dir}/swift" \
|
||||||
--product "${HELPER_LABEL}" \
|
--product "${HELPER_LABEL}" \
|
||||||
"${helper_linker_flags[@]}"
|
"${helper_linker_flags[@]}"
|
||||||
|
|
||||||
swift build -c release "${swift_arch_args[@]}" \
|
swift build -c release "${swift_arch_args[@]}" \
|
||||||
--scratch-path "${build_dir}/swift" \
|
--scratch-path "${build_dir}/swift" \
|
||||||
--product "${URL_HANDLER_NAME}"
|
--product "${URL_HANDLER_NAME}"
|
||||||
|
|
||||||
bin_dir="$(swift build -c release "${swift_arch_args[@]}" \
|
bin_dir="$(swift build -c release "${swift_arch_args[@]}" \
|
||||||
--scratch-path "${build_dir}/swift" --show-bin-path)"
|
--scratch-path "${build_dir}/swift" --show-bin-path)"
|
||||||
helper_binary="${bin_dir}/${HELPER_LABEL}"
|
helper_binary="${bin_dir}/${HELPER_LABEL}"
|
||||||
url_handler_binary="${bin_dir}/${URL_HANDLER_NAME}"
|
url_handler_binary="${bin_dir}/${URL_HANDLER_NAME}"
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
if [ ! -x "${helper_binary}" ] || [ ! -x "${url_handler_binary}" ]; then
|
if [ ! -x "${helper_binary}" ] || [ ! -x "${url_handler_binary}" ]; then
|
||||||
echo "error: expected release binaries were not produced" >&2
|
echo "error: expected release binaries were not produced" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
app_root="${parent_dir}/${APP_NAME}"
|
app_root="${parent_dir}/${APP_NAME}"
|
||||||
contents="${app_root}/Contents"
|
contents="${app_root}/Contents"
|
||||||
macos="${contents}/MacOS"
|
macos="${contents}/MacOS"
|
||||||
resources="${contents}/Resources"
|
resources="${contents}/Resources"
|
||||||
launch_services="${contents}/Library/LaunchServices"
|
launch_services="${contents}/Library/LaunchServices"
|
||||||
launch_daemons="${contents}/Library/LaunchDaemons"
|
launch_daemons="${contents}/Library/LaunchDaemons"
|
||||||
plist="${contents}/Info.plist"
|
plist="${contents}/Info.plist"
|
||||||
embedded_helper="${launch_services}/${HELPER_LABEL}"
|
embedded_helper="${launch_services}/${HELPER_LABEL}"
|
||||||
embedded_launchd="${launch_daemons}/${HELPER_LABEL}.plist"
|
embedded_launchd="${launch_daemons}/${HELPER_LABEL}.plist"
|
||||||
app_icon_original="${ENROLL_UI_DIR}/Resources/icon-original.png"
|
app_icon_original="${ENROLL_UI_DIR}/Resources/icon-original.png"
|
||||||
app_icon_icns="${STAGE}/AppIcon.icns"
|
app_icon_icns="${STAGE}/AppIcon.icns"
|
||||||
|
|
||||||
rm -rf "${app_root}"
|
rm -rf "${app_root}"
|
||||||
mkdir -p "${macos}" "${resources}" "${launch_services}" "${launch_daemons}"
|
mkdir -p "${macos}" "${resources}" "${launch_services}" "${launch_daemons}"
|
||||||
|
|
||||||
generate_app_icon_icns "${app_icon_original}" "${app_icon_icns}"
|
generate_app_icon_icns "${app_icon_original}" "${app_icon_icns}"
|
||||||
|
|
||||||
install -m 0755 "${url_handler_binary}" "${macos}/${URL_HANDLER_NAME}"
|
install -m 0755 "${url_handler_binary}" "${macos}/${URL_HANDLER_NAME}"
|
||||||
install -m 0755 "${helper_binary}" "${embedded_helper}"
|
install -m 0755 "${helper_binary}" "${embedded_helper}"
|
||||||
install -m 0644 "${helper_launchd_plist}" "${embedded_launchd}"
|
install -m 0644 "${helper_launchd_plist}" "${embedded_launchd}"
|
||||||
ditto --norsrc --noextattr "${app_icon_icns}" "${resources}/AppIcon.icns"
|
ditto --norsrc --noextattr "${app_icon_icns}" "${resources}/AppIcon.icns"
|
||||||
|
|
||||||
# Sign helper before writing Info.plist so SMPrivilegedExecutables
|
# Sign helper before writing Info.plist so SMPrivilegedExecutables
|
||||||
# can embed the helper's designated requirement.
|
# can embed the helper's designated requirement.
|
||||||
codesign_runtime "${embedded_helper}"
|
codesign_runtime "${embedded_helper}"
|
||||||
# codesign prints "Executable=…" on stderr and either
|
# codesign prints "Executable=…" on stderr and either
|
||||||
# "# designated => …" (modern) or "designated => …" (older) on stdout.
|
# "# designated => …" (modern) or "designated => …" (older) on stdout.
|
||||||
helper_requirement="$(
|
helper_requirement="$(
|
||||||
codesign -d -r- "${embedded_helper}" 2>&1 \
|
codesign -d -r- "${embedded_helper}" 2>&1 \
|
||||||
| sed -n -e 's/^# designated => //p' -e 's/^designated => //p'
|
| sed -n -e 's/^# designated => //p' -e 's/^designated => //p'
|
||||||
)"
|
)"
|
||||||
if [ -z "${helper_requirement}" ]; then
|
if [ -z "${helper_requirement}" ]; then
|
||||||
echo "error: cannot extract designated requirement from signed helper" >&2
|
echo "error: cannot extract designated requirement from signed helper" >&2
|
||||||
codesign -d -r- "${embedded_helper}" 2>&1 >&2 || true
|
codesign -d -r- "${embedded_helper}" 2>&1 >&2 || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Helper designated requirement: ${helper_requirement}"
|
echo "Helper designated requirement: ${helper_requirement}"
|
||||||
|
|
||||||
sed \
|
sed \
|
||||||
-e "s|@@VERSION@@|${VERSION}|g" \
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
||||||
-e "s|@@HELPER_DESIGNATED_REQUIREMENT@@|${helper_requirement}|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
|
if ! plutil -lint "${plist}" >/dev/null; then
|
||||||
echo "error: rendered Info.plist failed plutil -lint" >&2
|
echo "error: rendered Info.plist failed plutil -lint" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if ! grep -q '<string>probo</string>' "${plist}"; then
|
if ! grep -q '<string>probo</string>' "${plist}"; then
|
||||||
echo "error: Info.plist is missing probo URL scheme" >&2
|
echo "error: Info.plist is missing probo URL scheme" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
codesign_runtime "${macos}/${URL_HANDLER_NAME}"
|
codesign_runtime "${macos}/${URL_HANDLER_NAME}"
|
||||||
codesign_runtime "${app_root}"
|
codesign_runtime "${app_root}"
|
||||||
|
|
||||||
echo "Built ${app_root}"
|
echo "Built ${app_root}"
|
||||||
}
|
}
|
||||||
|
|
||||||
notarytool_submit() {
|
notarytool_submit() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
xcrun notarytool submit "${path}" \
|
xcrun notarytool submit "${path}" \
|
||||||
--keychain-profile "${NOTARYTOOL_KEYCHAIN_PROFILE}" \
|
--keychain-profile "${NOTARYTOOL_KEYCHAIN_PROFILE}" \
|
||||||
--wait
|
--wait
|
||||||
}
|
}
|
||||||
|
|
||||||
# pkgbuild records protected com.apple.provenance xattrs as empty
|
# pkgbuild records protected com.apple.provenance xattrs as empty
|
||||||
# AppleDouble (._*) Bom entries. Rewrite the Bom with mkbom so the
|
# AppleDouble (._*) Bom entries. Rewrite the Bom with mkbom so the
|
||||||
# installer does not lay down those stubs next to real files.
|
# installer does not lay down those stubs next to real files.
|
||||||
rewrite_component_bom() {
|
rewrite_component_bom() {
|
||||||
local pkg="$1"
|
local pkg="$1"
|
||||||
local expand_dir root_dir flat_pkg
|
local expand_dir root_dir flat_pkg
|
||||||
|
|
||||||
expand_dir="${STAGE}/component-expand"
|
expand_dir="${STAGE}/component-expand"
|
||||||
root_dir="${STAGE}/component-root"
|
root_dir="${STAGE}/component-root"
|
||||||
flat_pkg="${STAGE}/probo-agent-component-clean.pkg"
|
flat_pkg="${STAGE}/probo-agent-component-clean.pkg"
|
||||||
rm -rf "${expand_dir}" "${root_dir}" "${flat_pkg}"
|
rm -rf "${expand_dir}" "${root_dir}" "${flat_pkg}"
|
||||||
# pkgutil --expand creates the destination directory itself.
|
# pkgutil --expand creates the destination directory itself.
|
||||||
pkgutil --expand "${pkg}" "${expand_dir}"
|
pkgutil --expand "${pkg}" "${expand_dir}"
|
||||||
find "${expand_dir}/Scripts" -name '._*' -delete 2>/dev/null || true
|
find "${expand_dir}/Scripts" -name '._*' -delete 2>/dev/null || true
|
||||||
|
|
||||||
mkdir -p "${root_dir}"
|
mkdir -p "${root_dir}"
|
||||||
(
|
(
|
||||||
cd "${root_dir}"
|
cd "${root_dir}"
|
||||||
gzip -dc "${expand_dir}/Payload" | cpio -idmu 2>/dev/null
|
gzip -dc "${expand_dir}/Payload" | cpio -idmu 2>/dev/null
|
||||||
)
|
)
|
||||||
find "${root_dir}" -name '._*' -delete 2>/dev/null || true
|
find "${root_dir}" -name '._*' -delete 2>/dev/null || true
|
||||||
mkbom "${root_dir}" "${expand_dir}/Bom"
|
mkbom "${root_dir}" "${expand_dir}/Bom"
|
||||||
if lsbom "${expand_dir}/Bom" | grep -q '/\._'; then
|
if lsbom "${expand_dir}/Bom" | grep -q '/\._'; then
|
||||||
echo "error: rewritten Bom still contains AppleDouble entries" >&2
|
echo "error: rewritten Bom still contains AppleDouble entries" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
pkgutil --flatten "${expand_dir}" "${flat_pkg}"
|
pkgutil --flatten "${expand_dir}" "${flat_pkg}"
|
||||||
mv "${flat_pkg}" "${pkg}"
|
mv "${flat_pkg}" "${pkg}"
|
||||||
}
|
}
|
||||||
|
|
||||||
STAGE="$(mktemp -d -t probo-agent-pkg)"
|
STAGE="$(mktemp -d -t probo-agent-pkg)"
|
||||||
@@ -379,18 +398,18 @@ build_probo_agent_app "${PAYLOAD}/Applications"
|
|||||||
APP_PATH="${PAYLOAD}/Applications/${APP_NAME}"
|
APP_PATH="${PAYLOAD}/Applications/${APP_NAME}"
|
||||||
|
|
||||||
if [ "${notarize_enabled}" = true ]; then
|
if [ "${notarize_enabled}" = true ]; then
|
||||||
# Password appears on argv only for this short-lived store. Submits
|
# Password appears on argv only for this short-lived store. Submits
|
||||||
# use --keychain-profile so concurrent processes cannot read it.
|
# use --keychain-profile so concurrent processes cannot read it.
|
||||||
xcrun notarytool store-credentials "${NOTARYTOOL_KEYCHAIN_PROFILE}" \
|
xcrun notarytool store-credentials "${NOTARYTOOL_KEYCHAIN_PROFILE}" \
|
||||||
--apple-id "${APPLE_ID}" \
|
--apple-id "${APPLE_ID}" \
|
||||||
--password "${APPLE_ID_PASSWORD}" \
|
--password "${APPLE_ID_PASSWORD}" \
|
||||||
--team-id "${APPLE_TEAM_ID}"
|
--team-id "${APPLE_TEAM_ID}"
|
||||||
echo "Notarizing Probo Agent.app before packaging..."
|
echo "Notarizing Probo Agent.app before packaging..."
|
||||||
zip_path="${STAGE}/probo-agent-app.zip"
|
zip_path="${STAGE}/probo-agent-app.zip"
|
||||||
ditto -c -k --keepParent "${APP_PATH}" "${zip_path}"
|
ditto -c -k --keepParent "${APP_PATH}" "${zip_path}"
|
||||||
notarytool_submit "${zip_path}"
|
notarytool_submit "${zip_path}"
|
||||||
rm -f "${zip_path}"
|
rm -f "${zip_path}"
|
||||||
xcrun stapler staple "${APP_PATH}"
|
xcrun stapler staple "${APP_PATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# AppleDouble / xattr hygiene: COPYFILE_DISABLE + ditto --norsrc/--noextattr
|
# AppleDouble / xattr hygiene: COPYFILE_DISABLE + ditto --norsrc/--noextattr
|
||||||
@@ -401,53 +420,53 @@ export COPYFILE_DISABLE=1
|
|||||||
ditto --norsrc --noextattr "${SCRIPT_DIR}/scripts/preinstall" "${SCRIPTS}/preinstall"
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/scripts/preinstall" "${SCRIPTS}/preinstall"
|
||||||
ditto --norsrc --noextattr "${SCRIPT_DIR}/scripts/postinstall" "${SCRIPTS}/postinstall"
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/scripts/postinstall" "${SCRIPTS}/postinstall"
|
||||||
ditto --norsrc --noextattr \
|
ditto --norsrc --noextattr \
|
||||||
"${REPO_ROOT}/pkg/deviceagent/tray/launchagent.plist.tmpl" \
|
"${REPO_ROOT}/pkg/deviceagent/tray/launchagent.plist.tmpl" \
|
||||||
"${SCRIPTS}/launchagent.plist.tmpl"
|
"${SCRIPTS}/launchagent.plist.tmpl"
|
||||||
chmod 0755 "${SCRIPTS}/preinstall" "${SCRIPTS}/postinstall"
|
chmod 0755 "${SCRIPTS}/preinstall" "${SCRIPTS}/postinstall"
|
||||||
chmod 0644 "${SCRIPTS}/launchagent.plist.tmpl"
|
chmod 0644 "${SCRIPTS}/launchagent.plist.tmpl"
|
||||||
|
|
||||||
ditto --norsrc --noextattr "${SCRIPT_DIR}/Resources/welcome.html" "${RESOURCES}/welcome.html"
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/Resources/welcome.html" "${RESOURCES}/welcome.html"
|
||||||
ditto --norsrc --noextattr "${SCRIPT_DIR}/Resources/conclusion.html" "${RESOURCES}/conclusion.html"
|
ditto --norsrc --noextattr "${SCRIPT_DIR}/Resources/conclusion.html" "${RESOURCES}/conclusion.html"
|
||||||
ditto --norsrc --noextattr "${REPO_ROOT}/LICENSE" "${RESOURCES}/license.txt"
|
ditto --norsrc --noextattr "${REPO_ROOT}/LICENSE" "${RESOURCES}/license.txt"
|
||||||
|
|
||||||
xattr -cr "${PAYLOAD}" "${SCRIPTS}" "${RESOURCES}" 2>/dev/null || true
|
xattr -cr "${PAYLOAD}" "${SCRIPTS}" "${RESOURCES}" 2>/dev/null || true
|
||||||
find "${PAYLOAD}" "${SCRIPTS}" "${RESOURCES}" -name '._*' -delete 2>/dev/null || true
|
find "${PAYLOAD}" "${SCRIPTS}" "${RESOURCES}" -name '._*' -delete 2>/dev/null || true
|
||||||
|
|
||||||
COMPONENT_PKG="${STAGE}/probo-agent-component.pkg"
|
COMPONENT_PKG="${STAGE}/probo-agent-component.pkg"
|
||||||
pkgbuild \
|
pkgbuild \
|
||||||
--root "${PAYLOAD}" \
|
--root "${PAYLOAD}" \
|
||||||
--scripts "${SCRIPTS}" \
|
--scripts "${SCRIPTS}" \
|
||||||
--identifier "${IDENTIFIER}" \
|
--identifier "${IDENTIFIER}" \
|
||||||
--version "${VERSION}" \
|
--version "${VERSION}" \
|
||||||
--install-location "/" \
|
--install-location "/" \
|
||||||
"${COMPONENT_PKG}"
|
"${COMPONENT_PKG}"
|
||||||
|
|
||||||
rewrite_component_bom "${COMPONENT_PKG}"
|
rewrite_component_bom "${COMPONENT_PKG}"
|
||||||
|
|
||||||
DISTRIBUTION="${STAGE}/Distribution.xml"
|
DISTRIBUTION="${STAGE}/Distribution.xml"
|
||||||
sed \
|
sed \
|
||||||
-e "s|@@VERSION@@|${VERSION}|g" \
|
-e "s|@@VERSION@@|${VERSION}|g" \
|
||||||
-e "s|@@IDENTIFIER@@|${IDENTIFIER}|g" \
|
-e "s|@@IDENTIFIER@@|${IDENTIFIER}|g" \
|
||||||
"${SCRIPT_DIR}/Distribution.xml.tmpl" > "${DISTRIBUTION}"
|
"${SCRIPT_DIR}/Distribution.xml.tmpl" >"${DISTRIBUTION}"
|
||||||
|
|
||||||
mkdir -p "$(dirname "${OUTPUT}")"
|
mkdir -p "$(dirname "${OUTPUT}")"
|
||||||
|
|
||||||
PRODUCTBUILD_ARGS=(
|
PRODUCTBUILD_ARGS=(
|
||||||
--distribution "${DISTRIBUTION}"
|
--distribution "${DISTRIBUTION}"
|
||||||
--package-path "${STAGE}"
|
--package-path "${STAGE}"
|
||||||
--resources "${RESOURCES}"
|
--resources "${RESOURCES}"
|
||||||
)
|
)
|
||||||
if [ -n "${INSTALLER_IDENTITY}" ]; then
|
if [ -n "${INSTALLER_IDENTITY}" ]; then
|
||||||
PRODUCTBUILD_ARGS+=(--sign "${INSTALLER_IDENTITY}")
|
PRODUCTBUILD_ARGS+=(--sign "${INSTALLER_IDENTITY}")
|
||||||
fi
|
fi
|
||||||
PRODUCTBUILD_ARGS+=("${OUTPUT}")
|
PRODUCTBUILD_ARGS+=("${OUTPUT}")
|
||||||
|
|
||||||
productbuild "${PRODUCTBUILD_ARGS[@]}"
|
productbuild "${PRODUCTBUILD_ARGS[@]}"
|
||||||
|
|
||||||
if [ "${notarize_enabled}" = true ]; then
|
if [ "${notarize_enabled}" = true ]; then
|
||||||
echo "Notarizing ${OUTPUT}..."
|
echo "Notarizing ${OUTPUT}..."
|
||||||
notarytool_submit "${OUTPUT}"
|
notarytool_submit "${OUTPUT}"
|
||||||
xcrun stapler staple "${OUTPUT}"
|
xcrun stapler staple "${OUTPUT}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Built ${OUTPUT}"
|
echo "Built ${OUTPUT}"
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
PKG="${1:-}"
|
PKG="${1:-}"
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "error: must run as root (try: sudo make -C cmd/probo-agent install)" >&2
|
echo "error: must run as root (try: sudo make -C cmd/probo-agent install)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${PKG}" ] || [ ! -f "${PKG}" ]; then
|
if [ -z "${PKG}" ] || [ ! -f "${PKG}" ]; then
|
||||||
echo "error: usage: $0 /path/to/probo-agent_*.pkg" >&2
|
echo "error: usage: $0 /path/to/probo-agent_*.pkg" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
"${SCRIPT_DIR}/uninstall.sh"
|
"${SCRIPT_DIR}/uninstall.sh"
|
||||||
|
|||||||
@@ -26,94 +26,93 @@ PKG_ID="com.probo.agent"
|
|||||||
LSREGISTER="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
|
LSREGISTER="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister"
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
printf '%s\n' "$*"
|
printf '%s\n' "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
printf 'error: %s\n' "$*" >&2
|
printf 'error: %s\n' "$*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
require_root() {
|
require_root() {
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
die "must run as root (try: sudo make -C cmd/probo-agent uninstall)"
|
die "must run as root (try: sudo make -C cmd/probo-agent uninstall)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
bootout_system_plist() {
|
bootout_system_plist() {
|
||||||
local plist="$1"
|
local plist="$1"
|
||||||
if [ -f "${plist}" ]; then
|
if [ -f "${plist}" ]; then
|
||||||
launchctl bootout system "${plist}" 2>/dev/null || true
|
launchctl bootout system "${plist}" 2>/dev/null || true
|
||||||
log "Booted out ${plist}"
|
log "Booted out ${plist}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
bootout_tray_for_user() {
|
bootout_tray_for_user() {
|
||||||
local username="$1"
|
local username="$1"
|
||||||
local user_uid
|
local user_uid
|
||||||
|
|
||||||
if [ -z "${username}" ] || \
|
if [ -z "${username}" ] \
|
||||||
[ "${username}" = "root" ] || \
|
|| [ "${username}" = "root" ] \
|
||||||
[ "${username}" = "loginwindow" ]; then
|
|| [ "${username}" = "loginwindow" ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
user_uid="$(id -u "${username}" 2>/dev/null || true)"
|
user_uid="$(id -u "${username}" 2>/dev/null || true)"
|
||||||
if [ -z "${user_uid}" ]; then
|
if [ -z "${user_uid}" ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
launchctl bootout "gui/${user_uid}/${TRAY_LABEL}" 2>/dev/null || true
|
launchctl bootout "gui/${user_uid}/${TRAY_LABEL}" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
unregister_apps() {
|
unregister_apps() {
|
||||||
local path
|
local path
|
||||||
for path in \
|
for path in \
|
||||||
"/Applications/Probo Agent.app" \
|
"/Applications/Probo Agent.app" \
|
||||||
"/Applications/Probo Agent.localized/Probo Agent.app"
|
"/Applications/Probo Agent.localized/Probo Agent.app"; do
|
||||||
do
|
if [ -d "${path}" ] && [ -x "${LSREGISTER}" ]; then
|
||||||
if [ -d "${path}" ] && [ -x "${LSREGISTER}" ]; then
|
"${LSREGISTER}" -u "${path}" 2>/dev/null || true
|
||||||
"${LSREGISTER}" -u "${path}" 2>/dev/null || true
|
log "Unregistered Launch Services entry for ${path}"
|
||||||
log "Unregistered Launch Services entry for ${path}"
|
fi
|
||||||
fi
|
done
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kill_leftovers() {
|
kill_leftovers() {
|
||||||
# Best-effort; deleted-but-running binaries otherwise keep claiming probo://.
|
# Best-effort; deleted-but-running binaries otherwise keep claiming probo://.
|
||||||
pkill -x probo-agent-url-handler 2>/dev/null || true
|
pkill -x probo-agent-url-handler 2>/dev/null || true
|
||||||
pkill -f '/usr/local/bin/probo-agent tray' 2>/dev/null || true
|
pkill -f '/usr/local/bin/probo-agent tray' 2>/dev/null || true
|
||||||
pkill -f '/Library/PrivilegedHelperTools/com.probo.agent.helper' 2>/dev/null || true
|
pkill -f '/Library/PrivilegedHelperTools/com.probo.agent.helper' 2>/dev/null || true
|
||||||
# Agent daemon may still be running after plist bootout races.
|
# Agent daemon may still be running after plist bootout races.
|
||||||
pkill -x probo-agent 2>/dev/null || true
|
pkill -x probo-agent 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
require_root
|
require_root
|
||||||
|
|
||||||
if [ "$(uname -s)" != "Darwin" ]; then
|
if [ "$(uname -s)" != "Darwin" ]; then
|
||||||
die "this uninstall script is macOS-only"
|
die "this uninstall script is macOS-only"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "=== probo-agent macOS uninstall $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
|
log "=== probo-agent macOS uninstall $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
|
||||||
|
|
||||||
# Prefer the agent's own uninstall for service/tray/state when present.
|
# Prefer the agent's own uninstall for service/tray/state when present.
|
||||||
if [ -x "${BINARY}" ]; then
|
if [ -x "${BINARY}" ]; then
|
||||||
if "${BINARY}" uninstall; then
|
if "${BINARY}" uninstall; then
|
||||||
log "Ran: ${BINARY} uninstall"
|
log "Ran: ${BINARY} uninstall"
|
||||||
else
|
else
|
||||||
log "warning: ${BINARY} uninstall failed; continuing with manual cleanup"
|
log "warning: ${BINARY} uninstall failed; continuing with manual cleanup"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "Binary not found at ${BINARY}; skipping probo-agent uninstall"
|
log "Binary not found at ${BINARY}; skipping probo-agent uninstall"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
seen_users=" "
|
seen_users=" "
|
||||||
for username in $(users 2>/dev/null || true); do
|
for username in $(users 2>/dev/null || true); do
|
||||||
case "${seen_users}" in
|
case "${seen_users}" in
|
||||||
*" ${username} "*) continue ;;
|
*" ${username} "*) continue ;;
|
||||||
esac
|
esac
|
||||||
seen_users="${seen_users}${username} "
|
seen_users="${seen_users}${username} "
|
||||||
bootout_tray_for_user "${username}"
|
bootout_tray_for_user "${username}"
|
||||||
done
|
done
|
||||||
bootout_tray_for_user "$(stat -f "%Su" /dev/console 2>/dev/null || true)"
|
bootout_tray_for_user "$(stat -f "%Su" /dev/console 2>/dev/null || true)"
|
||||||
|
|
||||||
@@ -127,23 +126,23 @@ log "Removed LaunchDaemon / LaunchAgent / helper files (if present)"
|
|||||||
|
|
||||||
unregister_apps
|
unregister_apps
|
||||||
rm -rf \
|
rm -rf \
|
||||||
"/Applications/Probo Agent.app" \
|
"/Applications/Probo Agent.app" \
|
||||||
"/Applications/Probo Agent.localized"
|
"/Applications/Probo Agent.localized"
|
||||||
log "Removed Probo Agent.app (if present)"
|
log "Removed Probo Agent.app (if present)"
|
||||||
|
|
||||||
rm -f "${BINARY}"
|
rm -f "${BINARY}"
|
||||||
rm -rf "${STATE_DIR}" "${RUN_DIR}"
|
rm -rf "${STATE_DIR}" "${RUN_DIR}"
|
||||||
rm -f \
|
rm -f \
|
||||||
/var/log/probo-agent.log \
|
/var/log/probo-agent.log \
|
||||||
/var/log/probo-agent-install.log \
|
/var/log/probo-agent-install.log \
|
||||||
/tmp/probo-agent.conf
|
/tmp/probo-agent.conf
|
||||||
log "Removed binary, state, run dir, logs, and staged conf (if present)"
|
log "Removed binary, state, run dir, logs, and staged conf (if present)"
|
||||||
|
|
||||||
if pkgutil --pkg-info "${PKG_ID}" >/dev/null 2>&1; then
|
if pkgutil --pkg-info "${PKG_ID}" >/dev/null 2>&1; then
|
||||||
if ! pkgutil --forget "${PKG_ID}" >/dev/null; then
|
if ! pkgutil --forget "${PKG_ID}" >/dev/null; then
|
||||||
die "failed to forget PKG receipt ${PKG_ID}"
|
die "failed to forget PKG receipt ${PKG_ID}"
|
||||||
fi
|
fi
|
||||||
log "Forgot PKG receipt ${PKG_ID}"
|
log "Forgot PKG receipt ${PKG_ID}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "=== uninstall done ==="
|
log "=== uninstall done ==="
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -eu
|
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;
|
CREATE USER probod;
|
||||||
ALTER USER probod WITH SUPERUSER;
|
ALTER USER probod WITH SUPERUSER;
|
||||||
ALTER USER probod PASSWORD 'probod';
|
ALTER USER probod PASSWORD 'probod';
|
||||||
@@ -12,13 +12,13 @@ CREATE DATABASE probod_test;
|
|||||||
GRANT ALL PRIVILEGES ON DATABASE probod_test TO probod;
|
GRANT ALL PRIVILEGES ON DATABASE probod_test TO probod;
|
||||||
EOF
|
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;
|
ALTER SCHEMA public OWNER TO probod;
|
||||||
GRANT ALL ON SCHEMA public TO probod;
|
GRANT ALL ON SCHEMA public TO probod;
|
||||||
ALTER DATABASE probod SET probo.trust_center_base_domain TO 'probopage.localhost';
|
ALTER DATABASE probod SET probo.trust_center_base_domain TO 'probopage.localhost';
|
||||||
EOF
|
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;
|
ALTER SCHEMA public OWNER TO probod;
|
||||||
GRANT ALL ON SCHEMA public TO probod;
|
GRANT ALL ON SCHEMA public TO probod;
|
||||||
ALTER DATABASE probod_test SET probo.trust_center_base_domain TO 'probopage.localhost';
|
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 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` | 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-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` | Format Go code |
|
||||||
| `make fmt-swift` | Opt-in: format Swift enroll-ui (`swift format` + SwiftLint `--fix`; needs Swift) |
|
| `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 clean` | Remove all build artifacts, `node_modules`, generated files, and coverage |
|
||||||
| `make help` | List targets with `##` doc comments |
|
| `make help` | List targets with `##` doc comments |
|
||||||
|
|
||||||
@@ -86,3 +88,10 @@ Individual codegen is driven by `go generate`:
|
|||||||
| `SWIFTLINTCMD` | `swiftlint` | SwiftLint binary |
|
| `SWIFTLINTCMD` | `swiftlint` | SwiftLint binary |
|
||||||
| `SWIFTCMD` | `swift` | Swift toolchain binary (`swift format`) |
|
| `SWIFTCMD` | `swift` | Swift toolchain binary (`swift format`) |
|
||||||
| `SWIFT_ENROLL_UI` | `cmd/probo-agent/installer/macos/enroll-ui` | Path to the Swift SPM package |
|
| `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,55 +15,53 @@ GO_VERSION="1.26.5"
|
|||||||
NODE_MAJOR=24
|
NODE_MAJOR=24
|
||||||
NPM_VERSION="11.8.0"
|
NPM_VERSION="11.8.0"
|
||||||
|
|
||||||
GOTESTSUM_VERSION="v1.13.0"
|
|
||||||
GOLANGCI_LINT_VERSION="v2.11.3"
|
|
||||||
GOW_VERSION="v0.0.0-20260225145757-ff0f6779ab4c"
|
GOW_VERSION="v0.0.0-20260225145757-ff0f6779ab4c"
|
||||||
MKCERT_VERSION="v1.4.4"
|
MKCERT_VERSION="v1.4.4"
|
||||||
|
|
||||||
apt-get update -qq
|
apt-get update -qq
|
||||||
apt-get install -y -qq \
|
apt-get install -y -qq \
|
||||||
build-essential \
|
build-essential \
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
jq \
|
jq \
|
||||||
parallel \
|
parallel \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
gnupg \
|
gnupg \
|
||||||
lsb-release \
|
lsb-release \
|
||||||
postgresql-client
|
postgresql-client
|
||||||
|
|
||||||
if ! command -v docker &>/dev/null; then
|
if ! command -v docker &>/dev/null; then
|
||||||
install -m 0755 -d /etc/apt/keyrings
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
|
||||||
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||||
|
|
||||||
echo \
|
echo \
|
||||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
||||||
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \
|
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 update -qq
|
||||||
apt-get install -y -qq \
|
apt-get install -y -qq \
|
||||||
docker-ce \
|
docker-ce \
|
||||||
docker-ce-cli \
|
docker-ce-cli \
|
||||||
containerd.io \
|
containerd.io \
|
||||||
docker-buildx-plugin \
|
docker-buildx-plugin \
|
||||||
docker-compose-plugin
|
docker-compose-plugin
|
||||||
|
|
||||||
systemctl enable --now docker
|
systemctl enable --now docker
|
||||||
fi
|
fi
|
||||||
|
|
||||||
usermod -aG docker "${LIMA_CIDATA_USER:-lima}" 2>/dev/null || true
|
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
|
if [ ! -d "/usr/local/go" ] || ! /usr/local/go/bin/go version | grep -q "go${GO_VERSION}"; then
|
||||||
rm -rf /usr/local/go
|
rm -rf /usr/local/go
|
||||||
ARCH=$(dpkg --print-architecture)
|
ARCH=$(dpkg --print-architecture)
|
||||||
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
|
curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
|
||||||
| tar -C /usr/local -xzf -
|
| tar -C /usr/local -xzf -
|
||||||
fi
|
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"
|
export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"
|
||||||
GOEOF
|
GOEOF
|
||||||
chmod +x /etc/profile.d/go.sh
|
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}"
|
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
|
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 -
|
curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -
|
||||||
apt-get install -y -qq nodejs
|
apt-get install -y -qq nodejs
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm install -g "npm@${NPM_VERSION}"
|
npm install -g "npm@${NPM_VERSION}"
|
||||||
|
|
||||||
if ! command -v mkcert &>/dev/null; then
|
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
|
fi
|
||||||
mkcert -install 2>/dev/null || true
|
mkcert -install 2>/dev/null || true
|
||||||
|
|
||||||
@@ -102,34 +100,35 @@ mkdir -p /etc/probod
|
|||||||
|
|
||||||
OAUTH2_SIGNING_KEY_PATH=/etc/probod/oauth2-signing-key.pem
|
OAUTH2_SIGNING_KEY_PATH=/etc/probod/oauth2-signing-key.pem
|
||||||
if [ ! -f "${OAUTH2_SIGNING_KEY_PATH}" ]; then
|
if [ ! -f "${OAUTH2_SIGNING_KEY_PATH}" ]; then
|
||||||
openssl genrsa -out "${OAUTH2_SIGNING_KEY_PATH}" 2048
|
openssl genrsa -out "${OAUTH2_SIGNING_KEY_PATH}" 2048
|
||||||
chmod 600 "${OAUTH2_SIGNING_KEY_PATH}"
|
chmod 600 "${OAUTH2_SIGNING_KEY_PATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Load developer-specific overrides (not committed to repo).
|
# Load developer-specific overrides (not committed to repo).
|
||||||
if [ -f /workspace/.sandbox.env ]; then
|
if [ -f /workspace/.sandbox.env ]; then
|
||||||
set -a
|
set -a
|
||||||
. /workspace/.sandbox.env
|
# shellcheck source=/dev/null
|
||||||
set +a
|
. /workspace/.sandbox.env
|
||||||
|
set +a
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PROBOD_BASE_URL="http://${VM_IP}:8080" \
|
PROBOD_BASE_URL="http://${VM_IP}:8080" \
|
||||||
PROBOD_AUTH_COOKIE_DOMAIN="${VM_IP}" \
|
PROBOD_AUTH_COOKIE_DOMAIN="${VM_IP}" \
|
||||||
PROBOD_AUTH_COOKIE_SECURE=false \
|
PROBOD_AUTH_COOKIE_SECURE=false \
|
||||||
PROBOD_AUTH_COOKIE_SECRET="this-is-a-secure-secret-for-cookie-signing-at-least-32-bytes" \
|
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_AUTH_PASSWORD_PEPPER="this-is-a-secure-pepper-for-password-hashing-at-least-32-bytes" \
|
||||||
PROBOD_ENCRYPTION_KEY="thisisnotasecretAAAAAAAAAAAAAAAAAAAAAAAAAAA=" \
|
PROBOD_ENCRYPTION_KEY="thisisnotasecretAAAAAAAAAAAAAAAAAAAAAAAAAAA=" \
|
||||||
PROBOD_OAUTH2_SERVER_SIGNING_KEY="$(cat "${OAUTH2_SIGNING_KEY_PATH}")" \
|
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_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_ENDPOINT="http://127.0.0.1:8333" \
|
||||||
PROBOD_AWS_ACCESS_KEY_ID="probod" \
|
PROBOD_AWS_ACCESS_KEY_ID="probod" \
|
||||||
PROBOD_AWS_SECRET_ACCESS_KEY="thisisnotasecret" \
|
PROBOD_AWS_SECRET_ACCESS_KEY="thisisnotasecret" \
|
||||||
PROBOD_AWS_USE_PATH_STYLE=true \
|
PROBOD_AWS_USE_PATH_STYLE=true \
|
||||||
PROBOD_ACME_DIRECTORY="https://127.0.0.1:9000/acme/acme/directory" \
|
PROBOD_ACME_DIRECTORY="https://127.0.0.1:9000/acme/acme/directory" \
|
||||||
PROBOD_ACME_EMAIL="admin@probo.com" \
|
PROBOD_ACME_EMAIL="admin@probo.com" \
|
||||||
PROBOD_ACME_KEY_TYPE="EC256" \
|
PROBOD_ACME_KEY_TYPE="EC256" \
|
||||||
PROBOD_ACME_ROOT_CA="$(cat /workspace/compose/step-ca/certs/root_ca.crt)" \
|
PROBOD_ACME_ROOT_CA="$(cat /workspace/compose/step-ca/certs/root_ca.crt)" \
|
||||||
/workspace/bin/probod-bootstrap -output /etc/probod/config.yml
|
/workspace/bin/probod-bootstrap -output /etc/probod/config.yml
|
||||||
|
|
||||||
# probod runs as ${LIMA_USER} but bootstrap writes config.yml as root with 0600
|
# 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.
|
# 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
|
# Bind-mount VM-local node_modules over the shared workspace to avoid
|
||||||
# platform conflicts between macOS host and Linux VM native binaries.
|
# 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]
|
[Unit]
|
||||||
Description=Bind-mount VM-local node_modules over workspace
|
Description=Bind-mount VM-local node_modules over workspace
|
||||||
DefaultDependencies=no
|
DefaultDependencies=no
|
||||||
@@ -167,11 +166,11 @@ su - "${LIMA_USER}" -c "cd /workspace && npm ci"
|
|||||||
make -C /workspace generate WITH_APPS=1
|
make -C /workspace generate WITH_APPS=1
|
||||||
make -C /workspace embed
|
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/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/compliance-portal/.env
|
||||||
|
|
||||||
# Install systemd services for the sandbox
|
# Install systemd services for the sandbox
|
||||||
cat > /etc/systemd/system/probo-stack.service << EOF
|
cat >/etc/systemd/system/probo-stack.service <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Probo Docker Compose Stack
|
Description=Probo Docker Compose Stack
|
||||||
Requires=docker.service
|
Requires=docker.service
|
||||||
@@ -191,7 +190,7 @@ RestartSec=5s
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > /etc/systemd/system/probod.service << EOF
|
cat >/etc/systemd/system/probod.service <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Probo API Server
|
Description=Probo API Server
|
||||||
Requires=probo-stack.service
|
Requires=probo-stack.service
|
||||||
@@ -211,7 +210,7 @@ Environment=PATH=/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > /etc/systemd/system/probo-console.service << EOF
|
cat >/etc/systemd/system/probo-console.service <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Probo Console Dev Server
|
Description=Probo Console Dev Server
|
||||||
Requires=probo-node-modules.service
|
Requires=probo-node-modules.service
|
||||||
@@ -229,7 +228,7 @@ RestartSec=3s
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat > /etc/systemd/system/probo-compliance-portal.service << EOF
|
cat >/etc/systemd/system/probo-compliance-portal.service <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Probo Compliance Portal Dev Server
|
Description=Probo Compliance Portal Dev Server
|
||||||
Requires=probo-node-modules.service
|
Requires=probo-node-modules.service
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ WORKTREE_NAME="$(basename "${REPO_ROOT}")"
|
|||||||
VM_NAME="probo-${WORKTREE_NAME}"
|
VM_NAME="probo-${WORKTREE_NAME}"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") <command> [options]
|
Usage: $(basename "$0") <command> [options]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
@@ -29,151 +29,166 @@ Commands:
|
|||||||
|
|
||||||
VM name: ${VM_NAME} (derived from worktree directory)
|
VM name: ${VM_NAME} (derived from worktree directory)
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
get_vm_ip() {
|
get_vm_ip() {
|
||||||
limactl shell "${VM_NAME}" ip -4 -j addr show dev lima0 2>/dev/null \
|
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
|
| jq -r '.[0].addr_info[0].local // empty' 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
get_vm_status() {
|
get_vm_status() {
|
||||||
local status
|
local status
|
||||||
status=$(limactl list --json 2>/dev/null \
|
status=$(limactl list --json 2>/dev/null \
|
||||||
| jq -r "select(.name == \"${VM_NAME}\") | .status" 2>/dev/null) || true
|
| jq -r "select(.name == \"${VM_NAME}\") | .status" 2>/dev/null) || true
|
||||||
echo "${status:-NotFound}"
|
echo "${status:-NotFound}"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_create() {
|
cmd_create() {
|
||||||
local cpus="" memory="" disk=""
|
local cpus="" memory="" disk=""
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--cpus) cpus="$2"; shift 2 ;;
|
--cpus)
|
||||||
--memory) memory="$2"; shift 2 ;;
|
cpus="$2"
|
||||||
--disk) disk="$2"; shift 2 ;;
|
shift 2
|
||||||
*) echo "Unknown option: $1"; usage ;;
|
;;
|
||||||
esac
|
--memory)
|
||||||
done
|
memory="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--disk)
|
||||||
|
disk="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
echo "Creating sandbox: ${VM_NAME}"
|
echo "Creating sandbox: ${VM_NAME}"
|
||||||
echo "Worktree: ${REPO_ROOT}"
|
echo "Worktree: ${REPO_ROOT}"
|
||||||
|
|
||||||
local -a create_args=(
|
local -a create_args=(
|
||||||
--name "${VM_NAME}"
|
--name "${VM_NAME}"
|
||||||
--tty=false
|
--tty=false
|
||||||
--set ".mounts = [{\"location\": \"${REPO_ROOT}\", \"mountPoint\": \"/workspace\", \"writable\": true},{\"location\": \"${HOME}/go\", \"mountPoint\": \"/home/${USER}.guest/go\", \"writable\": true}]"
|
--set ".mounts = [{\"location\": \"${REPO_ROOT}\", \"mountPoint\": \"/workspace\", \"writable\": true},{\"location\": \"${HOME}/go\", \"mountPoint\": \"/home/${USER}.guest/go\", \"writable\": true}]"
|
||||||
--mount-type virtiofs
|
--mount-type virtiofs
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ -n "${cpus}" ]]; then
|
if [[ -n "${cpus}" ]]; then
|
||||||
create_args+=(--cpus "${cpus}")
|
create_args+=(--cpus "${cpus}")
|
||||||
fi
|
fi
|
||||||
if [[ -n "${memory}" ]]; then
|
if [[ -n "${memory}" ]]; then
|
||||||
create_args+=(--memory "${memory}")
|
create_args+=(--memory "${memory}")
|
||||||
fi
|
fi
|
||||||
if [[ -n "${disk}" ]]; then
|
if [[ -n "${disk}" ]]; then
|
||||||
create_args+=(--disk "${disk}")
|
create_args+=(--disk "${disk}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
limactl create "${create_args[@]}" "${TEMPLATE}"
|
limactl create "${create_args[@]}" "${TEMPLATE}"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_start() {
|
cmd_start() {
|
||||||
echo "Starting sandbox: ${VM_NAME}"
|
echo "Starting sandbox: ${VM_NAME}"
|
||||||
limactl start "${VM_NAME}"
|
limactl start "${VM_NAME}"
|
||||||
echo ""
|
echo ""
|
||||||
cmd_status
|
cmd_status
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_boot_logs() {
|
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() {
|
cmd_stop() {
|
||||||
echo "Stopping sandbox: ${VM_NAME}"
|
echo "Stopping sandbox: ${VM_NAME}"
|
||||||
limactl stop "${VM_NAME}"
|
limactl stop "${VM_NAME}"
|
||||||
echo "Sandbox stopped."
|
echo "Sandbox stopped."
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_restart() {
|
cmd_restart() {
|
||||||
cmd_stop
|
cmd_stop
|
||||||
echo ""
|
echo ""
|
||||||
cmd_start
|
cmd_start
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_delete() {
|
cmd_delete() {
|
||||||
echo "Deleting sandbox: ${VM_NAME}"
|
echo "Deleting sandbox: ${VM_NAME}"
|
||||||
limactl delete --force "${VM_NAME}"
|
limactl delete --force "${VM_NAME}"
|
||||||
echo "Sandbox deleted."
|
echo "Sandbox deleted."
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_ssh() {
|
cmd_ssh() {
|
||||||
exec limactl shell --workdir /workspace "${VM_NAME}"
|
exec limactl shell --workdir /workspace "${VM_NAME}"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_exec() {
|
cmd_exec() {
|
||||||
limactl shell --workdir /workspace "${VM_NAME}" "$@"
|
limactl shell --workdir /workspace "${VM_NAME}" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_status() {
|
cmd_status() {
|
||||||
local status ip
|
local status ip
|
||||||
status="$(get_vm_status)"
|
status="$(get_vm_status)"
|
||||||
ip="$(get_vm_ip)"
|
ip="$(get_vm_ip)"
|
||||||
|
|
||||||
echo "Sandbox: ${VM_NAME}"
|
echo "Sandbox: ${VM_NAME}"
|
||||||
echo "State: ${status}"
|
echo "State: ${status}"
|
||||||
echo "IP: ${ip:-"-"}"
|
echo "IP: ${ip:-"-"}"
|
||||||
|
|
||||||
if [[ "${status}" == "Running" && -n "${ip}" ]]; then
|
if [[ "${status}" == "Running" && -n "${ip}" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Services (use VM IP to access from host):"
|
echo "Services (use VM IP to access from host):"
|
||||||
echo " Console: http://${ip}:5173"
|
echo " Console: http://${ip}:5173"
|
||||||
echo " Compliance Portal: http://${ip}:5174"
|
echo " Compliance Portal: http://${ip}:5174"
|
||||||
echo " API: http://${ip}:8080"
|
echo " API: http://${ip}:8080"
|
||||||
echo " Grafana: http://${ip}:3001"
|
echo " Grafana: http://${ip}:3001"
|
||||||
echo " Mailpit: http://${ip}:8025"
|
echo " Mailpit: http://${ip}:8025"
|
||||||
echo " Keycloak: http://${ip}:8082"
|
echo " Keycloak: http://${ip}:8082"
|
||||||
echo " PostgreSQL: psql -h ${ip} -U probod"
|
echo " PostgreSQL: psql -h ${ip} -U probod"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_list() {
|
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-")) |
|
select(.name | startswith("probo-")) |
|
||||||
[.name, .status] | @tsv
|
[.name, .status] | @tsv
|
||||||
' | while IFS=$'\t' read -r name status; do
|
' | while IFS=$'\t' read -r name status; do
|
||||||
local ip="-"
|
local ip="-"
|
||||||
if [[ "${status}" == "Running" ]]; then
|
if [[ "${status}" == "Running" ]]; then
|
||||||
ip=$(limactl shell "${name}" ip -4 -j addr show dev lima0 2>/dev/null \
|
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 "-")
|
| jq -r '.[0].addr_info[0].local // "-"' 2>/dev/null || echo "-")
|
||||||
fi
|
fi
|
||||||
printf "%-25s %-12s %s\n" "${name}" "${status}" "${ip}"
|
printf "%-25s %-12s %s\n" "${name}" "${status}" "${ip}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
usage
|
usage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
command="$1"
|
command="$1"
|
||||||
shift
|
shift
|
||||||
|
|
||||||
case "${command}" in
|
case "${command}" in
|
||||||
create) cmd_create "$@" ;;
|
create) cmd_create "$@" ;;
|
||||||
start) cmd_start ;;
|
start) cmd_start ;;
|
||||||
boot-logs) cmd_boot_logs ;;
|
boot-logs) cmd_boot_logs ;;
|
||||||
stop) cmd_stop ;;
|
stop) cmd_stop ;;
|
||||||
restart) cmd_restart ;;
|
restart) cmd_restart ;;
|
||||||
delete) cmd_delete ;;
|
delete) cmd_delete ;;
|
||||||
ssh) cmd_ssh ;;
|
ssh) cmd_ssh ;;
|
||||||
exec)
|
exec)
|
||||||
if [[ "${1:-}" == "--" ]]; then shift; fi
|
if [[ "${1:-}" == "--" ]]; then shift; fi
|
||||||
cmd_exec "$@"
|
cmd_exec "$@"
|
||||||
;;
|
;;
|
||||||
status) cmd_status ;;
|
status) cmd_status ;;
|
||||||
list) cmd_list ;;
|
list) cmd_list ;;
|
||||||
*) echo "Unknown command: ${command}"; usage ;;
|
*)
|
||||||
|
echo "Unknown command: ${command}"
|
||||||
|
usage
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ schema_body=$(mktemp)
|
|||||||
trap 'rm -f "$mutation_fields" "$schema_body"' EXIT
|
trap 'rm -f "$mutation_fields" "$schema_body"' EXIT
|
||||||
|
|
||||||
process_file() {
|
process_file() {
|
||||||
awk -v mf="$mutation_fields" '
|
awk -v mf="$mutation_fields" '
|
||||||
/^type Mutation$/ { next }
|
/^type Mutation$/ { next }
|
||||||
/^(extend )?type Mutation \{/ { skip=1; depth=1; next }
|
/^(extend )?type Mutation \{/ { skip=1; depth=1; next }
|
||||||
skip {
|
skip {
|
||||||
@@ -29,16 +29,16 @@ process_file() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
process_file "$base"
|
process_file "$base"
|
||||||
for f in "$graphql_dir"/*.graphql; do
|
for f in "$graphql_dir"/*.graphql; do
|
||||||
[ "$f" = "$base" ] && continue
|
[ "$f" = "$base" ] && continue
|
||||||
process_file "$f"
|
process_file "$f"
|
||||||
done
|
done
|
||||||
} > "$schema_body"
|
} >"$schema_body"
|
||||||
|
|
||||||
{
|
{
|
||||||
cat "$schema_body"
|
cat "$schema_body"
|
||||||
printf '\ntype Mutation {\n'
|
printf '\ntype Mutation {\n'
|
||||||
cat "$mutation_fields"
|
cat "$mutation_fields"
|
||||||
printf '}\n'
|
printf '}\n'
|
||||||
} > "$output"
|
} >"$output"
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
|
# GraphQL documents are intentional single-quoted literals (no expansion).
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BASE_URL="${PROBO_SEED_URL:-http://localhost:8080}"
|
BASE_URL="${PROBO_SEED_URL:-http://localhost:8080}"
|
||||||
@@ -58,7 +61,8 @@ check_error() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prb_api() {
|
prb_api() {
|
||||||
local context="$1"; shift
|
local context="$1"
|
||||||
|
shift
|
||||||
local resp
|
local resp
|
||||||
resp=$($PRB api "$@")
|
resp=$($PRB api "$@")
|
||||||
check_error "$resp" "$context"
|
check_error "$resp" "$context"
|
||||||
@@ -66,13 +70,17 @@ prb_api() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
curl -sf -o /dev/null "$BASE_URL/healthz" \
|
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..."
|
echo "==> Bootstrapping user and organization..."
|
||||||
vars=$(jo input="$(jo \
|
vars=$(jo input="$(
|
||||||
email="$EMAIL" \
|
jo \
|
||||||
password="$PASSWORD" \
|
email="$EMAIL" \
|
||||||
fullName="$FULL_NAME" \
|
password="$PASSWORD" \
|
||||||
|
fullName="$FULL_NAME"
|
||||||
)")
|
)")
|
||||||
resp=$(gql_connect '
|
resp=$(gql_connect '
|
||||||
mutation($input: SignUpInput!) {
|
mutation($input: SignUpInput!) {
|
||||||
@@ -96,9 +104,10 @@ check_error "$resp" "createOrganization"
|
|||||||
ORG_ID=$(echo "$resp" | jq -r '.data.createOrganization.organization.id')
|
ORG_ID=$(echo "$resp" | jq -r '.data.createOrganization.organization.id')
|
||||||
echo " Created organization $ORG_NAME ($ORG_ID)"
|
echo " Created organization $ORG_NAME ($ORG_ID)"
|
||||||
|
|
||||||
vars=$(jo input="$(jo \
|
vars=$(jo input="$(
|
||||||
organizationId="$ORG_ID" \
|
jo \
|
||||||
continue="$BASE_URL" \
|
organizationId="$ORG_ID" \
|
||||||
|
continue="$BASE_URL"
|
||||||
)")
|
)")
|
||||||
resp=$(gql_connect '
|
resp=$(gql_connect '
|
||||||
mutation($input: AssumeOrganizationSessionInput!) {
|
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 \
|
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")
|
|| date -u -d "+1 year" +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
vars=$(jo input="$(jo \
|
vars=$(jo input="$(
|
||||||
name=seed \
|
jo \
|
||||||
expiresAt="$EXPIRES_AT" \
|
name=seed \
|
||||||
|
expiresAt="$EXPIRES_AT"
|
||||||
)")
|
)")
|
||||||
resp=$(gql_connect '
|
resp=$(gql_connect '
|
||||||
mutation($input: CreatePersonalAPIKeyInput!) {
|
mutation($input: CreatePersonalAPIKeyInput!) {
|
||||||
@@ -148,14 +158,15 @@ create_person() {
|
|||||||
local email="$3"
|
local email="$3"
|
||||||
|
|
||||||
local vars
|
local vars
|
||||||
vars=$(jo input="$(jo \
|
vars=$(jo input="$(
|
||||||
organizationId="$ORG_ID" \
|
jo \
|
||||||
emailAddress="$email" \
|
organizationId="$ORG_ID" \
|
||||||
fullName="$full_name" \
|
emailAddress="$email" \
|
||||||
role=EMPLOYEE \
|
fullName="$full_name" \
|
||||||
kind=EMPLOYEE \
|
role=EMPLOYEE \
|
||||||
additionalEmailAddresses="$(jo -a < /dev/null)" \
|
kind=EMPLOYEE \
|
||||||
position="$position" \
|
additionalEmailAddresses="$(jo -a </dev/null)" \
|
||||||
|
position="$position"
|
||||||
)")
|
)")
|
||||||
resp=$(gql_connect '
|
resp=$(gql_connect '
|
||||||
mutation($input: CreateUserInput!) {
|
mutation($input: CreateUserInput!) {
|
||||||
@@ -219,10 +230,11 @@ create_framework() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
' -f input="$(jo \
|
' -f input="$(
|
||||||
organizationId="$ORG_ID" \
|
jo \
|
||||||
name="$name" \
|
organizationId="$ORG_ID" \
|
||||||
description="$desc" \
|
name="$name" \
|
||||||
|
description="$desc"
|
||||||
)")
|
)")
|
||||||
local id
|
local id
|
||||||
id=$(echo "$resp" | jq -r '.data.createFramework.frameworkEdge.node.id // empty')
|
id=$(echo "$resp" | jq -r '.data.createFramework.frameworkEdge.node.id // empty')
|
||||||
@@ -243,7 +255,7 @@ create_control() {
|
|||||||
--framework "$framework_id" \
|
--framework "$framework_id" \
|
||||||
--section-title "$section" \
|
--section-title "$section" \
|
||||||
--name "$name" \
|
--name "$name" \
|
||||||
--description "$desc" > /dev/null
|
--description "$desc" >/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# ISO 27001:2022
|
# ISO 27001:2022
|
||||||
@@ -457,7 +469,7 @@ create_risk() {
|
|||||||
--category "$category" \
|
--category "$category" \
|
||||||
--treatment "$treatment" \
|
--treatment "$treatment" \
|
||||||
--inherent-likelihood "$likelihood" \
|
--inherent-likelihood "$likelihood" \
|
||||||
--inherent-impact "$impact" > /dev/null
|
--inherent-impact "$impact" >/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
create_risk \
|
create_risk \
|
||||||
@@ -586,11 +598,12 @@ create_third_party() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
' -f input="$(jo \
|
' -f input="$(
|
||||||
|
jo \
|
||||||
organizationId="$ORG_ID" \
|
organizationId="$ORG_ID" \
|
||||||
name="$name" \
|
name="$name" \
|
||||||
description="$description" \
|
description="$description"
|
||||||
)")
|
)")
|
||||||
local id
|
local id
|
||||||
id=$(echo "$resp" | jq -r '.data.createThirdParty.thirdPartyEdge.node.id // empty')
|
id=$(echo "$resp" | jq -r '.data.createThirdParty.thirdPartyEdge.node.id // empty')
|
||||||
if [ -z "$id" ]; then
|
if [ -z "$id" ]; then
|
||||||
@@ -657,11 +670,12 @@ create_measure() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
' -f input="$(jo \
|
' -f input="$(
|
||||||
|
jo \
|
||||||
organizationId="$ORG_ID" \
|
organizationId="$ORG_ID" \
|
||||||
name="$name" \
|
name="$name" \
|
||||||
category="$category" \
|
category="$category"
|
||||||
)")
|
)")
|
||||||
local id
|
local id
|
||||||
id=$(echo "$resp" | jq -r '.data.createMeasure.measureEdge.node.id // empty')
|
id=$(echo "$resp" | jq -r '.data.createMeasure.measureEdge.node.id // empty')
|
||||||
if [ -z "$id" ]; then
|
if [ -z "$id" ]; then
|
||||||
@@ -796,7 +810,8 @@ agent_heartbeat() {
|
|||||||
|
|
||||||
# agent_postures <api_key> <CHECK_KEY:STATUS>...
|
# agent_postures <api_key> <CHECK_KEY:STATUS>...
|
||||||
agent_postures() {
|
agent_postures() {
|
||||||
local api_key="$1"; shift
|
local api_key="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
local now
|
local now
|
||||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
@@ -827,7 +842,7 @@ revoke_device() {
|
|||||||
device { id }
|
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>...
|
# 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" \
|
seed_device "${PROFILE_IDS[0]}" "jane-macbook-pro" "DARWIN" "14.5" "C02XY1Z2JGH7" \
|
||||||
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
|
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
|
||||||
OS_VERSION:PASS AUTO_UPDATE:PASS PASSWORD_POLICY:PASS REMOTE_LOGIN: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" \
|
seed_device "${PROFILE_IDS[1]}" "marcus-thinkpad" "LINUX" "Ubuntu 24.04" "PF3ABCDE" \
|
||||||
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:FAIL TIME_SYNC:PASS \
|
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:FAIL TIME_SYNC:PASS \
|
||||||
OS_VERSION:PASS AUTO_UPDATE:UNKNOWN PASSWORD_POLICY:PASS REMOTE_LOGIN:FAIL \
|
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" \
|
seed_device "${PROFILE_IDS[4]}" "emily-macbook-air" "DARWIN" "14.4" "C02AB3C4JGH8" \
|
||||||
DISK_ENCRYPTION:PASS SCREEN_LOCK:FAIL FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
|
DISK_ENCRYPTION:PASS SCREEN_LOCK:FAIL FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
|
||||||
OS_VERSION:PASS AUTO_UPDATE:PASS PASSWORD_POLICY:FAIL REMOTE_LOGIN: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" \
|
seed_device "${PROFILE_IDS[7]}" "alex-devbox" "LINUX" "Debian 12" "PF9ZYXWV" \
|
||||||
DISK_ENCRYPTION:FAIL SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
|
DISK_ENCRYPTION:FAIL SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:PASS \
|
||||||
OS_VERSION:UNKNOWN AUTO_UPDATE:PASS PASSWORD_POLICY:PASS REMOTE_LOGIN: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" \
|
seed_device "${PROFILE_IDS[3]}" "david-surface" "WINDOWS" "Windows 11 23H2" "5CD1234ABC" \
|
||||||
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:FAIL \
|
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:PASS TIME_SYNC:FAIL \
|
||||||
OS_VERSION:PASS AUTO_UPDATE:PASS PASSWORD_POLICY:PASS REMOTE_LOGIN: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[2]}" "sofia-latitude" "WINDOWS" "Windows 11 22H2" "5CD9876ZYX" \
|
seed_device "${PROFILE_IDS[2]}" "sofia-latitude" "WINDOWS" "Windows 11 22H2" "5CD9876ZYX" \
|
||||||
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:FAIL TIME_SYNC:PASS \
|
DISK_ENCRYPTION:PASS SCREEN_LOCK:PASS FIREWALL_ENABLED:FAIL TIME_SYNC:PASS \
|
||||||
OS_VERSION:FAIL AUTO_UPDATE:FAIL PASSWORD_POLICY:PASS REMOTE_LOGIN: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.
|
# 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.
|
# 1 revoked device: fully activated, then revoked.
|
||||||
revoked_id=$(seed_device "${PROFILE_IDS[5]}" "james-old-macbook" "DARWIN" "12.7" "C02OLD1JGH9" \
|
revoked_id=$(seed_device "${PROFILE_IDS[5]}" "james-old-macbook" "DARWIN" "12.7" "C02OLD1JGH9" \
|
||||||
|
|||||||
Reference in New Issue
Block a user