Skip to content
Open
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
32 changes: 32 additions & 0 deletions Dockerfile.app
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM golang:1.23 AS builder

RUN apt-get update && apt-get install -y \
make git gcc util-linux \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN cp scripts/docker-enter /usr/bin/docker-enter && \
cp scripts/docker_enter /usr/bin/docker_enter && \
chmod u+s /usr/bin/docker_enter && \
gcc -o /usr/bin/importenv scripts/importenv.c

RUN make build

FROM ubuntu:22.04

RUN apt-get update && apt-get install -y ca-certificates \
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime image doesn’t install the docker CLI / Compose plugin, but the app calls exec.Command("docker", "compose", ...) (see pkg/cr/containers.go). In the container this will fail with docker: not found for Docker Compose-based challenge deployments; install a docker CLI + compose plugin in the final stage (or refactor to avoid shelling out).

Suggested change
RUN apt-get update && apt-get install -y ca-certificates \
RUN apt-get update && apt-get install -y ca-certificates docker.io docker-compose-plugin \

Copilot uses AI. Check for mistakes.
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /go/bin/beast /usr/local/bin/beast
COPY setup.sh /usr/local/bin/setup.sh
RUN chmod +x /usr/local/bin/setup.sh

EXPOSE 5005

ENTRYPOINT ["setup.sh"]
63 changes: 63 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,66 @@ installenv:
@./scripts/installenv.sh

.PHONY: build format test check_format tools docs installenv

# ── Docker Compose Targets ────────────────────────────────────────────────────
# Usage: make up NAME=myctf
#
# NAME (required, no spaces) — used to create a .<NAME> folder on the host which
# is mounted as /root/.beast inside the beast container. This keeps each
# deployment isolated and named.
#
# Prerequisites:
# - config.toml must exist alongside this Makefile.
# - In config.toml set psql_config.host = "postgres" (the compose service name).
#
Comment on lines +92 to +95
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Docker Compose help text says to set psql_config.host = "postgres" “for the compose network”, but the provided docker-compose.yml uses network_mode: host and doesn’t define a postgres service/network. Either add the referenced service/network to compose, or update these prerequisites to match the actual Docker networking model.

Copilot uses AI. Check for mistakes.
# Targets:
# make up NAME=<name> — set up .<name>/, copy config, start services
# make down NAME=<name> — stop and remove services
# make logs NAME=<name> — tail beast service logs

check-name:
@if [ -z "$(NAME)" ]; then \
echo "Error: NAME is required. Usage: make up NAME=myctf"; \
exit 1; \
fi
@if echo "$(NAME)" | grep -q "[[:space:]]"; then \
echo "Error: NAME must not contain spaces"; \
exit 1; \
fi

check-config:
@if [ ! -f "config.toml" ]; then \
echo "Error: config.toml not found in current directory."; \
echo "Place your config.toml here (see _examples/example.config.toml)."; \
echo "Ensure psql_config.host = \"postgres\" for the compose network."; \
exit 1; \
fi

BEAST_DIR = $(HOME)/.$(NAME)

setup-beast-dir: check-name check-config
@echo "[*] Setting up $(BEAST_DIR)..."
@mkdir -p $(BEAST_DIR)/assets/logo
@mkdir -p $(BEAST_DIR)/assets/mailTemplates
@mkdir -p $(BEAST_DIR)/remote
@mkdir -p $(BEAST_DIR)/uploads
@mkdir -p $(BEAST_DIR)/secrets
@mkdir -p $(BEAST_DIR)/scripts
@mkdir -p $(BEAST_DIR)/staging
@mkdir -p $(BEAST_DIR)/cache
@mkdir -p $(BEAST_DIR)/logs
@cp config.toml $(BEAST_DIR)/config.toml
@echo "[*] $(BEAST_DIR) ready (mounted as /root/.beast in container)"

up: setup-beast-dir
@echo "[*] Starting beast services (project: $(NAME))..."
@BEAST_DIR=$(BEAST_DIR) docker compose --project-name $(NAME) up -d --build
@echo "[*] Beast API running at http://localhost:5005"

down: check-name
@docker compose --project-name $(NAME) down

logs: check-name
@docker compose --project-name $(NAME) logs -f beast

.PHONY: check-name check-config setup-beast-dir up down logs
6 changes: 6 additions & 0 deletions core/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ var (
BEAST_GLOBAL_DIR = filepath.Join(os.Getenv("HOME"), ".beast")
AUTHORIZED_KEYS_FILE = filepath.Join(os.Getenv("HOME"), ".ssh", "authorized_keys")
BEAST_TEMP_DIR = filepath.Join(os.TempDir(), "beast")
BEAST_MOUNT_DIR = func() string {
if hostDir := os.Getenv("BEAST_HOST_DIR"); hostDir != "" {
return hostDir
}
return BEAST_GLOBAL_DIR
}()
)

const ( //names
Expand Down
2 changes: 1 addition & 1 deletion core/manager/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ func deployChallenge(challenge *database.Challenge, config cfg.BeastChallengeCon
staticMount := make(map[string]string)
var staticMountDir string
if challenge.ServerDeployed == core.LOCALHOST || challenge.ServerDeployed == "" {
staticMountDir = filepath.Join(core.BEAST_GLOBAL_DIR, core.BEAST_STAGING_DIR, config.Challenge.Metadata.Name, core.BEAST_STATIC_FOLDER)
staticMountDir = filepath.Join(core.BEAST_MOUNT_DIR, core.BEAST_STAGING_DIR, config.Challenge.Metadata.Name, core.BEAST_STATIC_FOLDER)
} else {
staticMountDir = filepath.Join("$HOME/.beast", core.BEAST_STAGING_DIR, config.Challenge.Metadata.Name, core.BEAST_STATIC_FOLDER)
}
Expand Down
4 changes: 2 additions & 2 deletions core/manager/static.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ func DeployStaticContentContainer() error {

// Remove the prefix sha256:
imageId := images[0].ID[7:]
stagingDirPath := filepath.Join(core.BEAST_GLOBAL_DIR, core.BEAST_STAGING_DIR)
stagingDirPath := filepath.Join(core.BEAST_MOUNT_DIR, core.BEAST_STAGING_DIR)
err = utils.CreateIfNotExistDir(stagingDirPath)
if err != nil {
log.Errorf("Error in validating staging mount point : %s", err)
return errors.New("INVALID_STAGING_AREA")
}

beastStaticAuthFile := filepath.Join(core.BEAST_GLOBAL_DIR, core.BEAST_STATIC_AUTH_FILE)
beastStaticAuthFile := filepath.Join(core.BEAST_MOUNT_DIR, core.BEAST_STATIC_AUTH_FILE)
err = utils.ValidateFileExists(beastStaticAuthFile)
Comment on lines +53 to 61
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stagingDirPath/beastStaticAuthFile are now based on core.BEAST_MOUNT_DIR, and then used with CreateIfNotExistDir / ValidateFileExists. When running beast inside Docker (with BEAST_HOST_DIR pointing to a host-only path), these checks run inside the beast container filesystem and will fail even if the host path exists. Suggest separating “local path inside beast” (likely core.BEAST_GLOBAL_DIR) for filesystem operations from “host path” for Docker bind mount sources (core.BEAST_MOUNT_DIR).

Copilot uses AI. Check for mistakes.
if err != nil {
p := fmt.Errorf("BEAST STATIC: Authentication file does not exist for beast static container, cannot proceed deployment")
Expand Down
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
beast:
build:
context: .
dockerfile: Dockerfile.app
network_mode: host
volumes:
- ${BEAST_DIR}:/root/.beast
- /var/run/docker.sock:/var/run/docker.sock
environment:
- HOME=/root
- BEAST_FLAGS=${BEAST_FLAGS:--v}
- BEAST_HOST_DIR=${BEAST_DIR}
restart: unless-stopped
Comment on lines +7 to +14
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BEAST_HOST_DIR is set to ${BEAST_DIR} (a host path), but only ${BEAST_DIR} is mounted at /root/.beast. Code paths updated to use core.BEAST_MOUNT_DIR now try to create/validate files/dirs at the host path (inside the beast container), which will fail unless that same absolute path is also mounted into the container. Consider either mounting ${BEAST_DIR} to the same path inside the container (bind ${BEAST_DIR}:${BEAST_DIR}), or limiting BEAST_HOST_DIR usage to Docker bind-mount sources while keeping on-container file checks under /root/.beast.

Copilot uses AI. Check for mistakes.
62 changes: 52 additions & 10 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -1,35 +1,77 @@
#!/bin/bash

# Detect if running inside a Docker container
IN_CONTAINER=false
[ -f /.dockerenv ] && IN_CONTAINER=true

echo -e "Setting up sample environment for beast..."

# In container, HOME=/root; locally, use /home/$USER
if [ "$IN_CONTAINER" = true ]; then
BEAST_HOME="$HOME"
else
BEAST_HOME="/home/$USER"
Comment on lines +9 to +13
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the non-container case, BEAST_HOME is hard-coded to /home/$USER. This will break on systems where $HOME is not /home/$USER or where $USER is unset (common in non-interactive shells/CI). Prefer deriving from $HOME (and/or allow an override) instead of assuming /home/$USER.

Suggested change
# In container, HOME=/root; locally, use /home/$USER
if [ "$IN_CONTAINER" = true ]; then
BEAST_HOME="$HOME"
else
BEAST_HOME="/home/$USER"
# Determine BEAST_HOME: allow override, prefer HOME, fall back to /home/$USER
if [ -n "$BEAST_HOME" ]; then
:
elif [ "$IN_CONTAINER" = true ]; then
BEAST_HOME="${HOME:-/root}"
else
if [ -n "$HOME" ]; then
BEAST_HOME="$HOME"
elif [ -n "$USER" ]; then
BEAST_HOME="/home/$USER"
else
echo "Unable to determine BEAST_HOME; set BEAST_HOME or HOME." >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
fi

# Creating required directories
mkdir -p "/home/$USER/.beast" "/home/$USER/.beast/assets/logo" "/home/$USER/.beast/assets/mailTemplates" "/home/$USER/.beast/remote" "/home/$USER/.beast/uploads" "/home/$USER/.beast/secrets" "/home/$USER/.beast/scripts" "/home/$USER/.beast/staging"
mkdir -p \
"${BEAST_HOME}/.beast" \
"${BEAST_HOME}/.beast/assets/logo" \
"${BEAST_HOME}/.beast/assets/mailTemplates" \
"${BEAST_HOME}/.beast/remote" \
"${BEAST_HOME}/.beast/uploads" \
"${BEAST_HOME}/.beast/secrets" \
"${BEAST_HOME}/.beast/scripts" \
"${BEAST_HOME}/.beast/staging" \
"${BEAST_HOME}/.beast/cache" \
"${BEAST_HOME}/.beast/logs"

# Creating random authorized_keys and secret.key files
echo -e "auth_keys" >/home/$USER/.beast/authorized_keys
echo -e "auth_keys" >/home/$USER/.beast/secret.key
# Creating placeholder authorized_keys and secret.key files if absent
[ ! -f "${BEAST_HOME}/.beast/beast_authorized_keys" ] && touch "${BEAST_HOME}/.beast/beast_authorized_keys"
[ ! -f "${BEAST_HOME}/.beast/secret.key" ] && touch "${BEAST_HOME}/.beast/secret.key"

BEAST_GLOBAL_CONFIG=~/.beast/config.toml
EXAMPLE_CONFIG_FILE=./_examples/example.config.toml
BEAST_GLOBAL_CONFIG="${BEAST_HOME}/.beast/config.toml"
EXAMPLE_CONFIG_FILE="./_examples/example.config.toml"

if [ -f "$BEAST_GLOBAL_CONFIG" ]; then
echo -e "Found $BEAST_GLOBAL_CONFIG"
else
if [ "$IN_CONTAINER" = true ]; then
echo -e "\e[31mconfig.toml not found at ${BEAST_GLOBAL_CONFIG}"
echo -e "\e[31mMount your config.toml to ${BEAST_GLOBAL_CONFIG} and retry."
exit 1
fi

if [ -f "$EXAMPLE_CONFIG_FILE" ]; then
echo -e "Copying example config file"
cp ./_examples/example.config.toml $BEAST_GLOBAL_CONFIG
cp ./_examples/example.config.toml "$BEAST_GLOBAL_CONFIG"
else
echo -e '\e[93mCould not find example.config.toml'
echo -e 'Downloading example.config.toml'
wget https://raw.githubusercontent.com/sdslabs/beast/master/_examples/example.config.toml
cp ./example.config.toml $BEAST_GLOBAL_CONFIG
cp ./example.config.toml "$BEAST_GLOBAL_CONFIG"
exit
fi
sed -i "s/vsts/$USER/g" $BEAST_GLOBAL_CONFIG
sed -i "s/vsts/$USER/g" "$BEAST_GLOBAL_CONFIG"
fi

echo -e "Created .beast folder..."

# ── Container path: binary already built, just start beast ───────────────────
if [ "$IN_CONTAINER" = true ]; then
echo -e "Checking Docker socket..."
if [ ! -S /var/run/docker.sock ]; then
echo -e "\e[31mDocker socket not found at /var/run/docker.sock"
echo -e "\e[31mMount the host Docker socket and retry."
exit 1
fi
echo -e "Docker socket available. Starting beast..."
BEAST_FLAGS="${BEAST_FLAGS:--v}"
echo -e "Running: beast run ${BEAST_FLAGS}"
exec beast run ${BEAST_FLAGS}
fi

# ── Local path: build then advise the user to run beast ──────────────────────
echo -e "Building beast..."

export GO111MODULES=on
Expand All @@ -42,7 +84,7 @@ if [ -z "$GOPATH" ]; then
fi

echo -e 'checking if docker is running...'
# Checking if docker deamon is running or not by checking its PID
# Checking if docker daemon is running or not by checking its PID
DOCKER_PID_FILE=/var/run/docker.pid
if [ -f "$DOCKER_PID_FILE" ]; then
echo -e "Docker is running."
Expand Down
Loading