diff --git a/Dockerfile.app b/Dockerfile.app new file mode 100644 index 00000000..1961b34b --- /dev/null +++ b/Dockerfile.app @@ -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 \ + && 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"] diff --git a/Makefile b/Makefile index 00c9f9f8..889df3ce 100644 --- a/Makefile +++ b/Makefile @@ -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 . 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). +# +# Targets: +# make up NAME= — set up ./, copy config, start services +# make down NAME= — stop and remove services +# make logs 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 diff --git a/core/constants.go b/core/constants.go index 0fc10a06..d90d6cb4 100644 --- a/core/constants.go +++ b/core/constants.go @@ -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 diff --git a/core/manager/pipeline.go b/core/manager/pipeline.go index f13eb40d..7a5a6880 100644 --- a/core/manager/pipeline.go +++ b/core/manager/pipeline.go @@ -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) } diff --git a/core/manager/static.go b/core/manager/static.go index a45c3ef8..c94f9cee 100644 --- a/core/manager/static.go +++ b/core/manager/static.go @@ -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) if err != nil { p := fmt.Errorf("BEAST STATIC: Authentication file does not exist for beast static container, cannot proceed deployment") diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..c2c88216 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/setup.sh b/setup.sh index ae8d4bab..76648c90 100755 --- a/setup.sh +++ b/setup.sh @@ -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" +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 @@ -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."