diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..7b3585981 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +FROM nestybox/ubuntu-noble-systemd-docker@sha256:8b1c4409fe89bc110e1e468767074fe4403ba6bb2d1b34881fec5df8b6c2f9c3 AS fact_base + +ARG FACT_DIR=/opt/fact +COPY src $FACT_DIR +WORKDIR $FACT_DIR + +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=cache,target=/var/lib/apt \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + curl \ + python3-venv \ + postgresql-client \ + redis-tools + +RUN python3 -m venv venv +ARG VENV_DIR=$FACT_DIR/venv/bin +ENV PATH=$VENV_DIR:$PATH \ + VIRTUAL_ENV=$VENV_DIR \ + PYTHONPATH=$FACT_DIR \ + FACT_INSTALLER_SKIP_DOCKER=1 + +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=cache,target=/var/lib/apt \ + ./install/pre_install.sh -D + +FROM fact_base AS fact_frontend + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + python3 install.py -F -H + +RUN chown -R admin:admin "$FACT_DIR" + +COPY --chown=admin docker/entrypoint_frontend.sh . + +ENTRYPOINT ["./entrypoint_frontend.sh"] + +FROM fact_base AS fact_backend + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + python3 install.py -B + +RUN chown -R admin:admin "$FACT_DIR" + +COPY --chown=admin docker/entrypoint_backend.sh . + +# This file serves as a flag to indicate that the backend installation of the docker containers is completed +RUN touch DOCKER_INSTALL_INCOMPLETE +# We must still install the docker images, so we need to overwrite the flag now: +ENV FACT_INSTALLER_SKIP_DOCKER=0 + +ENTRYPOINT ["./entrypoint_backend.sh"] diff --git a/README.md b/README.md index 228ae2d4f..c0dd93afc 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,30 @@ our [tutorial](https://github.com/fkie-cad/FACT_core/blob/master/INSTALL.vagrant ### Docker -There is also a dockerized version, but it is currently unmaintained. -(see the [FACT_docker](https://github.com/fkie-cad/FACT_docker) repo for more information). +>[!IMPORTANT] +> The docker image requires Sysbox as Docker runtime. +> Sysbox installation is described [here](https://github.com/nestybox/sysbox/tree/master?tab=readme-ov-file#installation) +> Please make sure that it works before trying to run FACT with docker by running the hello-world image: +> `docker run --rm --runtime sysbox-runc hello-world` + +If you have untracked files in the directory or also installed FACT locally, create a `.dockerignore`, so you don't +copy the files into the Docker image when building it: + +```shell +# exclude untracked files: +git ls-files . --exclude-standard --others --directory > .dockerignore +# exclude ignored files: +git ls-files . --ignored --exclude-standard --others --directory >> .dockerignore +# also exclude the .git folder: +echo ".git/" >> .dockerignore +``` + +Running FACT with docker compose: + +```shell +docker compose build +docker compose up +``` ## Usage diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..d0e533493 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,77 @@ +services: + fact-frontend: + runtime: sysbox-runc + build: + context: . + target: fact_frontend + environment: + PGPASSWORD: password + ports: + - "5000:5000" + networks: + fact: + aliases: + - frontend + depends_on: + db: + condition: service_healthy + restart: true + redis: + condition: service_started + fact-backend: + runtime: sysbox-runc + build: + context: . + target: fact_backend + environment: + PGPASSWORD: password + networks: + fact: + aliases: + - backend + volumes: + - fact_files:/media/data + - fact_docker_images:/var/lib/docker + depends_on: + db: + condition: service_healthy + restart: true + redis: + condition: service_started + db: + container_name: postgres + image: postgres:17 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + PGDATA: /data/postgres + volumes: + - fact_db:/data/postgres + networks: + fact: + aliases: + - db + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d postgres"] + interval: 5s + retries: 5 + start_period: 5s + timeout: 10s + redis: + container_name: redis + image: redis:alpine + restart: always + networks: + fact: + aliases: + - redis + +networks: + fact: + driver: bridge + +volumes: + fact_db: + fact_files: + fact_docker_images: diff --git a/docker/entrypoint_backend.sh b/docker/entrypoint_backend.sh new file mode 100755 index 000000000..8ba07152a --- /dev/null +++ b/docker/entrypoint_backend.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +set -eux + +# start docker service +dockerd & + +# redis runs in a different container => replace "localhost" with "redis" +sed -i 's/host = "localhost"/host = "redis"/g' /opt/fact/config/fact-core-config.toml +# postgres also runs in a different container => replace "localhost" with "db" +sed -i 's/server = "localhost"/server = "db"/g' /opt/fact/config/fact-core-config.toml + +if [ -e DOCKER_INSTALL_INCOMPLETE ]; then + echo "Installing FACT docker images..." + python3 install.py --backend-docker-images + rm DOCKER_INSTALL_INCOMPLETE + echo "FACT docker image installation completed" +fi + +# We can't use rest/status here, because it needs the list of available plugins (which is available after the backend +# was started). +until curl -s -X GET 'http://frontend:5000/rest/statistics/general'; do + echo "Waiting for FACT frontend to start..." + sleep 2 +done +echo "FACT frontend is ready" + +python3 start_fact_backend.py diff --git a/docker/entrypoint_frontend.sh b/docker/entrypoint_frontend.sh new file mode 100755 index 000000000..5ead9de32 --- /dev/null +++ b/docker/entrypoint_frontend.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -eux + +# start docker service +dockerd & + +# redis runs in a different container => replace "localhost" with "redis" +sed -i 's/host = "localhost"/host = "redis"/g' /opt/fact/config/fact-core-config.toml +# postgres also runs in a different container => replace "localhost" with "db" +sed -i 's/server = "localhost"/server = "db"/g' /opt/fact/config/fact-core-config.toml +# replace localhost with 0.0.0.0 in uWSGI config so that the frontend can be reached from outside +sed -i 's/127.0.0.1/0.0.0.0/g' /opt/fact/config/uwsgi_config.ini + +# init the DB +python3 init_postgres.py + +python3 start_fact_frontend.py --no-radare diff --git a/src/init_postgres.py b/src/init_postgres.py index 7f8b6bf2c..337dc3a0f 100755 --- a/src/init_postgres.py +++ b/src/init_postgres.py @@ -43,9 +43,7 @@ def user_exists(user_name: str, host: str, port: str | int) -> bool: def create_admin_user(user_name: str, password: str, host: str, port: int | str): execute_psql_command( - # fmt: off - (f"CREATE USER {user_name} WITH PASSWORD '{password}' " 'LOGIN SUPERUSER INHERIT CREATEDB CREATEROLE;'), - # fmt: on + f"CREATE USER {user_name} WITH PASSWORD '{password}' LOGIN SUPERUSER INHERIT CREATEDB CREATEROLE;", host=host, port=port, ) diff --git a/src/install.py b/src/install.py index 114487ca5..d86167370 100755 --- a/src/install.py +++ b/src/install.py @@ -43,7 +43,7 @@ PROGRAM_VERSION = '1.2' PROGRAM_DESCRIPTION = 'Firmware Analysis and Comparison Tool (FACT) installation script' -FACT_INSTALLER_SKIP_DOCKER = os.getenv('FACT_INSTALLER_SKIP_DOCKER') +FACT_INSTALLER_SKIP_DOCKER = bool(int(os.getenv('FACT_INSTALLER_SKIP_DOCKER', '0'))) def _setup_argparser(): @@ -169,7 +169,7 @@ def install(): welcome() none_chosen = not (args.frontend or args.db or args.backend or args.common) # TODO maybe replace this with an cli argument - skip_docker = FACT_INSTALLER_SKIP_DOCKER is not None + skip_docker = FACT_INSTALLER_SKIP_DOCKER # Note that the skip_docker environment variable overrides the cli argument only_docker = not skip_docker and none_chosen and (args.backend_docker_images or args.frontend_docker_images)