Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 0 additions & 75 deletions .github/workflows/deploy-installers.yml

This file was deleted.

4 changes: 4 additions & 0 deletions .github/workflows/test-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ jobs:
sh -n docs/install.sh
sh -n docs/install-hermes.sh
cmp website/index.html www/index.html
cmp docs/install.sh website/install.sh
cmp docs/install.sh www/install.sh
cmp docs/install-hermes.sh website/install-hermes.sh
cmp docs/install-hermes.sh www/install-hermes.sh
grep -F 'data-tab="hermes"' website/index.html
grep -F 'https://secopsai.dev/install-hermes.sh' website/index.html

Expand Down
32 changes: 17 additions & 15 deletions docs/deployment-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,30 @@ You should see HTTP 200.

### Step 3: Point secopsai.dev to the deployment

You have two simple options.
The production site publishes installer files directly from both tracked static
site roots. CI verifies that these files are byte-identical to the canonical
copies under `docs/`:

Option A (recommended): make `secopsai.dev` itself serve this Pages project.
- `website/install.sh` and `www/install.sh`
- `website/install-hermes.sh` and `www/install-hermes.sh`

This is the recommended path because the existing Cloudflare Pages Git
deployments require no separate Worker credential or manual route update.

You have two fallback options if the apex site is moved away from these Pages
projects.

Option A: make `secopsai.dev` itself serve this Pages project.

1. In Pages project settings, add custom domain `secopsai.dev`.
2. Let Cloudflare create/update DNS records automatically.
3. Wait for SSL status to become Active.

Now this should work directly:
Now both commands should work directly:

```bash
curl -fsSLI https://secopsai.dev/install.sh
curl -fsSLI https://secopsai.dev/install-hermes.sh
```

Option B: keep docs on `docs.secopsai.dev` and route only installer paths from apex domain.
Expand All @@ -91,22 +103,12 @@ If you prefer users to stay on `secopsai.dev` without redirect, use the Worker s

- `scripts/cloudflare-installer-worker.js`

Production deployment is automated by `.github/workflows/deploy-installers.yml`.
It discovers the Worker already bound to the standard installer, publishes the
reviewed script with the repository's Cloudflare secrets, manages both routes,
and verifies that each public endpoint returns a shell script.

Required GitHub repository secrets:

- `CLOUDFLARE_ACCOUNT_ID`
- `CLOUDFLARE_API_TOKEN` with Workers Scripts edit and Zone Workers Routes edit/read access

The managed routes are:
The Worker source supports both routes:

- `secopsai.dev/install.sh*`
- `secopsai.dev/install-hermes.sh*`

Manual recovery:
Worker setup or recovery:

1. Cloudflare Dashboard -> Workers & Pages -> Create Worker.
2. Paste script from `scripts/cloudflare-installer-worker.js`.
Expand Down
10 changes: 5 additions & 5 deletions tests/test_hermes_installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ def test_shell_installers_are_syntax_valid() -> None:
def test_public_hermes_installer_and_worker_route_are_wired() -> None:
installer = (ROOT / "docs" / "install-hermes.sh").read_text(encoding="utf-8")
worker = (ROOT / "scripts" / "cloudflare-installer-worker.js").read_text(encoding="utf-8")
deployment = (ROOT / ".github" / "workflows" / "deploy-installers.yml").read_text(encoding="utf-8")
assert 'MIN_VERSION="0.18.2"' in installer
assert 'PLUGIN="Techris93/secopsai/integrations/hermes"' in installer
assert "hermes plugins install" in installer
assert "hermes service install" in installer
assert '"/install-hermes.sh": "https://docs.secopsai.dev/install-hermes.sh"' in worker
assert 'wrangler@4.114.0 deploy scripts/cloudflare-installer-worker.js' in deployment
assert '--route "secopsai.dev/install.sh*"' in deployment
assert '--route "secopsai.dev/install-hermes.sh*"' in deployment
assert "CLOUDFLARE_API_TOKEN" in deployment
assert (ROOT / "website" / "install-hermes.sh").read_text(encoding="utf-8") == installer
assert (ROOT / "www" / "install-hermes.sh").read_text(encoding="utf-8") == installer
standard = (ROOT / "docs" / "install.sh").read_text(encoding="utf-8")
assert (ROOT / "website" / "install.sh").read_text(encoding="utf-8") == standard
assert (ROOT / "www" / "install.sh").read_text(encoding="utf-8") == standard


def test_tracked_website_copies_are_identical_and_contain_hermes_tab() -> None:
Expand Down
88 changes: 88 additions & 0 deletions website/install-hermes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/sh
set -eu

MIN_VERSION="0.18.2"
CORE_HOME="${SECOPSAI_HOME:-"$HOME/secopsai"}"
HERMES_DATA_HOME="${HERMES_HOME:-"$HOME/.hermes"}"
INTERVAL="${SECOPSAI_HERMES_REFRESH_SECONDS:-300}"
PLUGIN="Techris93/secopsai/integrations/hermes"

fail() {
echo "Error: $*" >&2
exit 1
}

for command in hermes git bash python3 curl; do
command -v "$command" >/dev/null 2>&1 || fail "$command is required"
done

python3 -m pip --version >/dev/null 2>&1 || fail "python3 -m pip is required"

version_output=$(hermes --version 2>&1 | sed -n '1p')
version=$(printf '%s\n' "$version_output" | sed -n 's/.*Hermes Agent v\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')
[ -n "$version" ] || fail "could not determine the Hermes Agent version"

if ! python3 - "$version" "$MIN_VERSION" <<'PY'
import sys

def version(value):
return tuple(int(part) for part in value.split("."))

raise SystemExit(0 if version(sys.argv[1]) >= version(sys.argv[2]) else 1)
PY
then
fail "Hermes Agent $MIN_VERSION or later is required (found $version)"
fi

[ -d "$HERMES_DATA_HOME" ] || fail "Hermes home not found: $HERMES_DATA_HOME"

temporary=$(mktemp "${TMPDIR:-/tmp}/secopsai-hermes.XXXXXX")
trap 'rm -f "$temporary"' EXIT HUP INT TERM

echo "Installing SecOpsAI Core for Hermes Agent..."
curl -fsSL https://secopsai.dev/install.sh -o "$temporary"
SECOPSAI_HOME="$CORE_HOME" SECOPSAI_SETUP_PROFILE=hermes bash "$temporary"

PYTHON="$CORE_HOME/.venv/bin/python"
[ -x "$PYTHON" ] || fail "SecOpsAI virtual environment was not created: $PYTHON"

echo "Installing the read-only SecOpsAI Hermes plugin..."
hermes plugins install "$PLUGIN" --force --enable

echo "Running the first bounded Hermes telemetry refresh..."
HERMES_HOME="$HERMES_DATA_HOME" "$PYTHON" -m secopsai.cli --json hermes refresh --hermes-home "$HERMES_DATA_HOME"

echo "Installing persistent monitoring..."
HERMES_HOME="$HERMES_DATA_HOME" "$PYTHON" -m secopsai.cli --json hermes service install \
--hermes-home "$HERMES_DATA_HOME" \
--interval "$INTERVAL"

echo "Verifying the complete integration..."
HERMES_HOME="$HERMES_DATA_HOME" "$PYTHON" -m secopsai.cli --json hermes doctor --hermes-home "$HERMES_DATA_HOME"

cat <<EOF

SecOpsAI for Hermes Agent is installed.

Status:
$PYTHON -m secopsai.cli hermes doctor

Hermes findings:
$PYTHON -m secopsai.cli list --platform hermes --no-refresh

Service controls:
$PYTHON -m secopsai.cli hermes service status
$PYTHON -m secopsai.cli hermes service logs
$PYTHON -m secopsai.cli hermes service run-now

Updates:
hermes plugins update secopsai
Re-run: curl -fsSL https://secopsai.dev/install-hermes.sh | bash

Uninstall integration components while retaining findings and logs:
$PYTHON -m secopsai.cli hermes service uninstall
hermes plugins remove secopsai

Documentation:
https://docs.secopsai.dev/Hermes-Integration/
EOF
50 changes: 50 additions & 0 deletions website/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/sh
set -eu

# Bootstrap installer for secopsai.dev.
# Optional controls:
# SECOPSAI_INSTALL_REF=<git ref/commit> (default: pinned immutable commit)
# SECOPSAI_HOME=<install dir> (default: $HOME/secopsai)
#
# This script:
# 1. Clones or updates the secopsai repo at SECOPSAI_HOME
# 2. Checks out the requested ref
# 3. Runs setup.sh from the repo root (non-interactive-safe by default)

INSTALL_REF="${SECOPSAI_INSTALL_REF:-v1.0.0}"
REPO_URL="https://github.com/Techris93/secopsai.git"
REPO_DIR="${SECOPSAI_HOME:-"$HOME/secopsai"}"
SETUP_PROFILE="${SECOPSAI_SETUP_PROFILE:-default}"

if ! command -v git >/dev/null 2>&1; then
echo "Error: git is required to install secopsai." >&2
exit 1
fi

if ! command -v bash >/dev/null 2>&1; then
echo "Error: bash is required to install secopsai." >&2
exit 1
fi

# Clone or update repo
if [ ! -d "$REPO_DIR/.git" ]; then
echo "Cloning secopsai into $REPO_DIR..."
git clone "$REPO_URL" "$REPO_DIR"
else
echo "Using existing secopsai checkout at $REPO_DIR..."
fi

cd "$REPO_DIR"

if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
echo "Error: the existing SecOpsAI checkout has local tracked changes." >&2
echo "Commit or preserve those changes, or set SECOPSAI_HOME to a dedicated install directory." >&2
exit 1
fi

echo "Checking out $INSTALL_REF..."
git fetch --tags origin >/dev/null 2>&1
git checkout --detach "$INSTALL_REF" >/dev/null 2>&1

echo "Running setup.sh..."
exec bash setup.sh --non-interactive --profile "$SETUP_PROFILE"
88 changes: 88 additions & 0 deletions www/install-hermes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/sh
set -eu

MIN_VERSION="0.18.2"
CORE_HOME="${SECOPSAI_HOME:-"$HOME/secopsai"}"
HERMES_DATA_HOME="${HERMES_HOME:-"$HOME/.hermes"}"
INTERVAL="${SECOPSAI_HERMES_REFRESH_SECONDS:-300}"
PLUGIN="Techris93/secopsai/integrations/hermes"

fail() {
echo "Error: $*" >&2
exit 1
}

for command in hermes git bash python3 curl; do
command -v "$command" >/dev/null 2>&1 || fail "$command is required"
done

python3 -m pip --version >/dev/null 2>&1 || fail "python3 -m pip is required"

version_output=$(hermes --version 2>&1 | sed -n '1p')
version=$(printf '%s\n' "$version_output" | sed -n 's/.*Hermes Agent v\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p')
[ -n "$version" ] || fail "could not determine the Hermes Agent version"

if ! python3 - "$version" "$MIN_VERSION" <<'PY'
import sys

def version(value):
return tuple(int(part) for part in value.split("."))

raise SystemExit(0 if version(sys.argv[1]) >= version(sys.argv[2]) else 1)
PY
then
fail "Hermes Agent $MIN_VERSION or later is required (found $version)"
fi

[ -d "$HERMES_DATA_HOME" ] || fail "Hermes home not found: $HERMES_DATA_HOME"

temporary=$(mktemp "${TMPDIR:-/tmp}/secopsai-hermes.XXXXXX")
trap 'rm -f "$temporary"' EXIT HUP INT TERM

echo "Installing SecOpsAI Core for Hermes Agent..."
curl -fsSL https://secopsai.dev/install.sh -o "$temporary"
SECOPSAI_HOME="$CORE_HOME" SECOPSAI_SETUP_PROFILE=hermes bash "$temporary"

PYTHON="$CORE_HOME/.venv/bin/python"
[ -x "$PYTHON" ] || fail "SecOpsAI virtual environment was not created: $PYTHON"

echo "Installing the read-only SecOpsAI Hermes plugin..."
hermes plugins install "$PLUGIN" --force --enable

echo "Running the first bounded Hermes telemetry refresh..."
HERMES_HOME="$HERMES_DATA_HOME" "$PYTHON" -m secopsai.cli --json hermes refresh --hermes-home "$HERMES_DATA_HOME"

echo "Installing persistent monitoring..."
HERMES_HOME="$HERMES_DATA_HOME" "$PYTHON" -m secopsai.cli --json hermes service install \
--hermes-home "$HERMES_DATA_HOME" \
--interval "$INTERVAL"

echo "Verifying the complete integration..."
HERMES_HOME="$HERMES_DATA_HOME" "$PYTHON" -m secopsai.cli --json hermes doctor --hermes-home "$HERMES_DATA_HOME"

cat <<EOF

SecOpsAI for Hermes Agent is installed.

Status:
$PYTHON -m secopsai.cli hermes doctor

Hermes findings:
$PYTHON -m secopsai.cli list --platform hermes --no-refresh

Service controls:
$PYTHON -m secopsai.cli hermes service status
$PYTHON -m secopsai.cli hermes service logs
$PYTHON -m secopsai.cli hermes service run-now

Updates:
hermes plugins update secopsai
Re-run: curl -fsSL https://secopsai.dev/install-hermes.sh | bash

Uninstall integration components while retaining findings and logs:
$PYTHON -m secopsai.cli hermes service uninstall
hermes plugins remove secopsai

Documentation:
https://docs.secopsai.dev/Hermes-Integration/
EOF
Loading
Loading