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
6 changes: 5 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.git
.github
.cursor
.cursor/*
!.cursor/skills/
!.cursor/skills/**
**/.git
**/.DS_Store
.env
Expand All @@ -14,4 +16,6 @@ terminals
agent-transcripts
Screenshots
*.md
!SKILLS.md
!**/*_skills.md
!third_party/**
14 changes: 14 additions & 0 deletions .github/workflows/trivy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ jobs:
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

# Dockerfile prefers vendor/ when present; populate for offline-friendly builds.
- name: Vendor Go modules
run: go mod vendor

- uses: docker/setup-buildx-action@v3

- name: Build image (load into Docker)
Expand All @@ -37,3 +45,9 @@ jobs:
exit-code: "1"
format: table
vuln-type: os,library
# Debian bookworm packages often have HIGH with no fixed package yet.
ignore-unfixed: true
# Runtime needs Go + Scythe sources; skip toolchain/mod cache noise and Scythe's
# on-disk go.mod (compile-time deps). Application Go vulns are still covered via
# govulncheck and the linked binary (Gobinary).
skip-dirs: /usr/local/go,/go,/root/third_party
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@

# Local Go build artifacts (modules resolve via go.mod; vendor only if you explicitly vendor)
/vendor/
/bin/
/bin/

# k3s / OnPrem deploy local config (see deployments/README.md)
deployments/k3s/config.env
deployments/config.env
42 changes: 32 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# syntax=docker/dockerfile:1.7
# Compile inside Docker using vendored modules (run `make vendor` on the host first).
# Compile inside Docker. Prefer a host-prepared vendor/ tree (`make vendor`) for offline
# builds; if vendor/ is missing (typical CI), go mod download runs during the image build.
# On Apple Silicon, prefer `make build` (cross-compiles on the host, then Dockerfile.pack).
FROM golang:1.24-bookworm AS builder
#
# Runtime stays on golang so Scythe.embedded can `go build` at request time, but we only
# ship the binary + Scythe sources + Operator AI skill files (not the full vendor/ source tree).
FROM golang:1.25.12-bookworm AS builder

ARG TARGETOS=linux
ARG TARGETARCH
Expand All @@ -13,9 +17,11 @@ ENV CGO_ENABLED=0 \

WORKDIR /src

COPY go.mod go.sum ./
COPY vendor/ vendor/
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/*

COPY go.mod go.sum ./
COPY . .

RUN set -eux; \
Expand All @@ -28,18 +34,34 @@ RUN set -eux; \

WORKDIR /src/cmd
RUN --mount=type=cache,target=/root/.cache/go-build \
GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" \
go build -mod=vendor -trimpath -ldflags="-s -w" -o /out/ReaperC2 .
--mount=type=cache,target=/go/pkg/mod \
set -eux; \
if [ -d /src/vendor ] && [ -f /src/vendor/modules.txt ]; then \
GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" \
go build -mod=vendor -trimpath -ldflags="-s -w" -o /out/ReaperC2 .; \
else \
(cd /src && go mod download); \
GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" \
go build -mod=mod -trimpath -ldflags="-s -w" -o /out/ReaperC2 .; \
fi

FROM golang:1.24-bookworm
FROM golang:1.25.12-bookworm

COPY --from=builder /src /root
COPY --from=builder /out/ReaperC2 /root/cmd/ReaperC2
# Refresh OS packages to pick up Debian security updates in the golang base image.
RUN apt-get update \
&& apt-get upgrade -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /root

COPY --from=builder /out/ReaperC2 /root/cmd/ReaperC2
COPY --from=builder /src/third_party/Scythe /root/third_party/Scythe
COPY --from=builder /src/SKILLS.md /root/SKILLS.md
COPY --from=builder /src/.cursor/skills/reaper-red-team-operator /root/.cursor/skills/reaper-red-team-operator

ARG DEPLOY_ENV=ONPREM
ENV DEPLOY_ENV=${DEPLOY_ENV}
ENV DEPLOY_ENV=${DEPLOY_ENV} \
REAPERC2_ROOT=/root

EXPOSE 8080 8443
ENTRYPOINT ["/root/cmd/ReaperC2"]
34 changes: 26 additions & 8 deletions Dockerfile.pack
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
# syntax=docker/dockerfile:1.7
# Package a host-built ReaperC2 binary (see `make build-binaries`). No `go build` in Docker.
FROM golang:1.24-bookworm
FROM golang:1.25.12-bookworm AS pack

ARG TARGETARCH
ARG SCYTHE_GIT_REF=main
ARG SCYTHE_REPO=https://github.com/BuildAndDestroy/Scythe.git

WORKDIR /root
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& apt-get upgrade -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*

COPY . /root
COPY bin/linux-${TARGETARCH}/ReaperC2 /root/cmd/ReaperC2
WORKDIR /src
COPY . /src
COPY bin/linux-${TARGETARCH}/ReaperC2 /out/ReaperC2

RUN set -eux; \
test -f /root/cmd/ReaperC2; \
test -f third_party/Scythe/go.mod || { \
test -f /out/ReaperC2; \
if [ ! -f third_party/Scythe/go.mod ]; then \
mkdir -p third_party; \
rm -rf third_party/Scythe; \
git clone --depth 1 --branch "${SCYTHE_GIT_REF}" "${SCYTHE_REPO}" third_party/Scythe; \
}; \
fi; \
test -f third_party/Scythe/go.mod

FROM golang:1.25.12-bookworm

RUN apt-get update \
&& apt-get upgrade -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /root

COPY --from=pack /out/ReaperC2 /root/cmd/ReaperC2
COPY --from=pack /src/third_party/Scythe /root/third_party/Scythe
COPY --from=pack /src/SKILLS.md /root/SKILLS.md
COPY --from=pack /src/.cursor/skills/reaper-red-team-operator /root/.cursor/skills/reaper-red-team-operator

ARG DEPLOY_ENV=ONPREM
ENV DEPLOY_ENV=${DEPLOY_ENV}
ENV DEPLOY_ENV=${DEPLOY_ENV} \
REAPERC2_ROOT=/root

EXPOSE 8080 8443
ENTRYPOINT ["/root/cmd/ReaperC2"]
67 changes: 67 additions & 0 deletions deployments/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Deployment layouts

Two supported Kubernetes paths — pick one per cluster:

| Path | Use when | Entry |
|------|----------|--------|
| **[`k8s/reaperc2/`](k8s/reaperc2/)** | **DocumentDB** on EKS or k3s (Traefik, cert-manager, Bedrock Operator AI) | [`k8s/DEPLOY.md`](k8s/DEPLOY.md) |
| **[`k3s/`](k3s/)** | **OnPrem** homelab / Pi: NFS MongoDB, in-cluster **Ollama**, private registry | [`k3s/README.md`](k3s/README.md) |

**k3s + DocumentDB** uses the **reaperc2** path (`REAPER_CLUSTER=k3s`), not `deployments/k3s/`. See [`k8s/k3s/README.md`](k8s/k3s/README.md).

Legacy samples: [`k8s/full-deployment.yaml`](k8s/full-deployment.yaml), [`k8s/OnPrem/full-deployment.yaml`](k8s/OnPrem/full-deployment.yaml).

---

## `config.env` (OnPrem `deployments/k3s/` only)

```bash
cp deployments/k3s/config.example.env deployments/k3s/config.env
```

Or one shared file: `deployments/config.env` (scripts check `k3s/config.env` first, then `deployments/config.env`).

**Special characters:** quote passwords with `#` or `!`:

```env
REGISTRY_PASSWORD='your-password-here'
```

| Variable | Purpose |
|----------|---------|
| `MONGO_NFS_SERVER` / `MONGO_NFS_PATH` | NFS export for MongoDB PV |
| `REGISTRY_SERVER` / `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` | Private registry push/pull |
| `REGISTRY_SECRET_NAME` | Kubernetes pull secret name (default `registry-credentials`) |
| `REAPERC2_IMAGE` / `REAPERC2_IMAGE_TAG` | Container image to deploy |
| `OLLAMA_PULL_MODELS` | Comma-separated tags pulled on first Ollama start (e.g. `llama3.2:latest,gpt-oss:latest`) |
| `REAPERC2_NAMESPACE` | Default `reaperc2-ns` |
| `KUBECONFIG` | Optional kubeconfig path |

**DocumentDB / ECR builds** use [`k8s/reaperc2/build-push-image.sh`](k8s/reaperc2/build-push-image.sh) and the Makefile (`AWS_ACCOUNT_ID`, `AWS_REGION`, etc.) — not `config.env`.

---

## Mongo secrets (OnPrem `k3s/`)

Edit [`k3s/mongo-secret.yaml`](k3s/mongo-secret.yaml) before first deploy (`root_*`, `app_*`). Do not change passwords after Mongo has initialized the NFS volume without resetting data.

---

## Operator AI

[`k8s/operator-ai.yaml`](k8s/operator-ai.yaml) — ConfigMap + Secret template. Copy to `operator-ai.local.yaml` (gitignored) for real keys.

| Concern | Variable / file |
|---------|-----------------|
| Dropdown models (comma-separated per provider) | `REAPER_AI_OPENAI_MODELS`, `REAPER_AI_ANTHROPIC_MODELS`, `REAPER_AI_OLLAMA_MODELS`, … |
| In-cluster Ollama (default) | Applied via `k8s/ollama.yaml`; `REAPER_AI_OLLAMA_*` enabled in `operator-ai.yaml` |
| Pull models into Ollama | `OLLAMA_PULL_MODELS` in `ollama.yaml` ConfigMap (or `k3s/config.env`) |

Full variable list: [docs/operator-guide-ai.md](../docs/operator-guide-ai.md).

---

## See also

- [docs/kubernetes.md](../docs/kubernetes.md)
- [k8s/reaperc2/README.md](k8s/reaperc2/README.md)
59 changes: 59 additions & 0 deletions deployments/k3s/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# k3s OnPrem (NFS Mongo + Ollama)

Deploy ReaperC2 on **k3s** with **NFS MongoDB**, a **private registry** image, and in-cluster **Ollama** for Operator AI.

> **Not** the DocumentDB path. For k3s + **Amazon DocumentDB**, use [`../k8s/reaperc2/`](../k8s/reaperc2/) with `REAPER_CLUSTER=k3s` ([`../k8s/k3s/README.md`](../k8s/k3s/README.md)).

## Prerequisites

- k3s (`curl -sfL https://get.k3s.io | sh -`)
- `kubectl` or `sudo k3s kubectl`
- NFS export for MongoDB; `nfs-common` on nodes
- Go on the build machine (for `build-push-image.sh`)
- ≥ 4 GiB RAM recommended for Ollama

## Quick start

```bash
cp deployments/k3s/config.example.env deployments/k3s/config.env
# Edit config.env, mongo-secret.yaml, k8s/operator-ai.yaml (or operator-ai.local.yaml)

./deployments/k3s/scripts/build-push-image.sh # arm64 default (Pi)
./deployments/k3s/scripts/deploy.sh

kubectl port-forward -n reaperc2-ns deployment/reaperc2-deployment 8443:8443
# http://127.0.0.1:8443/ai
```

## Scripts

| Script | Purpose |
|--------|---------|
| `scripts/build-push-image.sh` | Host cross-compile + push (`--arch arm64\|amd64`, `--import-local`, `--load`) |
| `scripts/deploy.sh` | Ollama + Operator AI + Mongo + ReaperC2 |
| `scripts/seed-mongo.sh` | Seed `api_db` via port-forward |
| `scripts/reroll.sh` | Rebuild image + rollout (`--with-operator-ai`, `--fresh-mongo`) |
| `scripts/undeploy.sh` | Tear down (`--delete-pvc`) |

```bash
./deployments/k3s/scripts/deploy.sh --operator-ai-only
./deployments/k3s/scripts/reroll.sh --with-operator-ai
```

## Shared manifests

| File | Purpose |
|------|---------|
| [`../k8s/ollama.yaml`](../k8s/ollama.yaml) | In-cluster Ollama Deployment |
| [`../k8s/operator-ai.yaml`](../k8s/operator-ai.yaml) | Operator AI ConfigMap + Secret |

## Config reference

See [`../README.md`](../README.md) for `config.env` variables.

## Undeploy

```bash
./deployments/k3s/scripts/undeploy.sh
./deployments/k3s/scripts/undeploy.sh --delete-pvc # destructive
```
24 changes: 24 additions & 0 deletions deployments/k3s/config.example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copy to k3s/config.env and edit. Used by k3s/scripts/*.sh

# MongoDB NFS (static PV in mongodb-nfs-pv.yaml)
MONGO_NFS_SERVER=192.168.1.100
MONGO_NFS_PATH=/volume1/reaperc2db

# Private registry (build-push-image.sh, create-registry-secret.sh)
REGISTRY_SERVER=registry.example.com
REGISTRY_USERNAME=
REGISTRY_PASSWORD=
REGISTRY_SECRET_NAME=registry-credentials

REAPERC2_IMAGE=registry.example.com/reaperc2
REAPERC2_IMAGE_TAG=latest

# Ollama model tags pulled on first start (ConfigMap ollama-pull-models)
OLLAMA_PULL_MODELS=llama3.2:latest,gpt-oss:latest,deepseek-r1:latest

# Operator AI: set cloud keys here or patch deployments/k8s/operator-ai.yaml Secret
# REAPER_AI_OPENAI_API_KEY=
# REAPER_AI_ANTHROPIC_API_KEY=

# kubectl context (optional)
# KUBECONFIG=/path/to/k3s.yaml
Loading
Loading