Skip to content

Commit 46ba634

Browse files
committed
feat: added a Dockerfile and docker-compose.yml for fact
* also includes entrypoint scripts for the frontend and backend
1 parent c93cb37 commit 46ba634

File tree

12 files changed

+260
-10
lines changed

12 files changed

+260
-10
lines changed

Dockerfile

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
FROM nestybox/ubuntu-noble-systemd-docker@sha256:8b1c4409fe89bc110e1e468767074fe4403ba6bb2d1b34881fec5df8b6c2f9c3 AS fact_base
2+
3+
ARG FACT_DIR=/opt/fact
4+
COPY src $FACT_DIR
5+
WORKDIR $FACT_DIR
6+
7+
RUN --mount=type=cache,target=/var/cache/apt \
8+
--mount=type=cache,target=/var/lib/apt \
9+
apt-get update && \
10+
apt-get install -y --no-install-recommends \
11+
curl \
12+
python3-venv \
13+
postgresql-client \
14+
redis-tools
15+
16+
RUN python3 -m venv venv
17+
ARG VENV_DIR=$FACT_DIR/venv/bin
18+
ENV PATH=$VENV_DIR:$PATH \
19+
VIRTUAL_ENV=$VENV_DIR \
20+
PYTHONPATH=$FACT_DIR \
21+
FACT_INSTALLER_SKIP_DOCKER=1
22+
23+
RUN --mount=type=cache,target=/var/cache/apt \
24+
--mount=type=cache,target=/var/lib/apt \
25+
./install/pre_install.sh -D
26+
27+
FROM fact_base AS fact_frontend
28+
29+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
30+
--mount=type=cache,target=/var/lib/apt,sharing=locked \
31+
python3 install.py -F -H
32+
33+
RUN chown -R admin:admin "$FACT_DIR"
34+
35+
COPY --chown=admin docker/entrypoint_frontend.sh .
36+
37+
ENTRYPOINT ["./entrypoint_frontend.sh"]
38+
39+
FROM fact_base AS fact_backend
40+
41+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
42+
--mount=type=cache,target=/var/lib/apt,sharing=locked \
43+
python3 install.py -B
44+
45+
RUN chown -R admin:admin "$FACT_DIR"
46+
47+
COPY --chown=admin docker/entrypoint_backend.sh .
48+
49+
# This file serves as a flag to indicate that the backend installation of the docker containers is completed
50+
RUN touch DOCKER_INSTALL_INCOMPLETE
51+
# We must still install the docker images, so we need to overwrite the flag now:
52+
ENV FACT_INSTALLER_SKIP_DOCKER=0
53+
54+
ENTRYPOINT ["./entrypoint_backend.sh"]

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,30 @@ our [tutorial](https://github.com/fkie-cad/FACT_core/blob/master/INSTALL.vagrant
114114

115115
### Docker
116116

117-
There is also a dockerized version, but it is currently unmaintained.
118-
(see the [FACT_docker](https://github.com/fkie-cad/FACT_docker) repo for more information).
117+
>[!IMPORTANT]
118+
> The docker image requires Sysbox as Docker runtime.
119+
> Sysbox installation is described [here](https://github.com/nestybox/sysbox/tree/master?tab=readme-ov-file#installation)
120+
> Please make sure that it works before trying to run FACT with docker by running the hello-world image:
121+
> `docker run --rm --runtime sysbox-runc hello-world`
122+
123+
If you have untracked files in the directory or also installed FACT locally, create a `.dockerignore`, so you don't
124+
copy the files into the Docker image when building it:
125+
126+
```shell
127+
# exclude untracked files:
128+
git ls-files . --exclude-standard --others --directory > .dockerignore
129+
# exclude ignored files:
130+
git ls-files . --ignored --exclude-standard --others --directory >> .dockerignore
131+
# also exclude the .git folder:
132+
echo ".git/" >> .dockerignore
133+
```
134+
135+
Running FACT with docker compose:
136+
137+
```shell
138+
docker compose build
139+
docker compose up
140+
```
119141

120142
## Usage
121143

docker-compose.yml

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
services:
2+
fact-frontend:
3+
runtime: sysbox-runc
4+
build:
5+
context: .
6+
target: fact_frontend
7+
environment:
8+
PGPASSWORD: password
9+
HASURA_HOST: hasura
10+
HASURA_PORT: 8080
11+
ports:
12+
- "5000:5000"
13+
networks:
14+
fact:
15+
aliases:
16+
- frontend
17+
depends_on:
18+
db:
19+
condition: service_healthy
20+
restart: true
21+
redis:
22+
condition: service_started
23+
fact-backend:
24+
runtime: sysbox-runc
25+
build:
26+
context: .
27+
target: fact_backend
28+
environment:
29+
PGPASSWORD: password
30+
networks:
31+
fact:
32+
aliases:
33+
- backend
34+
volumes:
35+
- fact_files:/media/data
36+
- fact_docker_images:/var/lib/docker
37+
depends_on:
38+
db:
39+
condition: service_healthy
40+
restart: true
41+
redis:
42+
condition: service_started
43+
db:
44+
container_name: postgres
45+
image: postgres:17
46+
environment:
47+
POSTGRES_USER: postgres
48+
POSTGRES_PASSWORD: password
49+
PGDATA: /data/postgres
50+
volumes:
51+
- fact_db:/data/postgres
52+
networks:
53+
fact:
54+
aliases:
55+
- db
56+
restart: unless-stopped
57+
healthcheck:
58+
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d postgres"]
59+
interval: 5s
60+
retries: 5
61+
start_period: 5s
62+
timeout: 10s
63+
redis:
64+
container_name: redis
65+
image: redis:alpine
66+
restart: always
67+
networks:
68+
fact:
69+
aliases:
70+
- redis
71+
hasura:
72+
image: hasura/graphql-engine:v2.38.0
73+
ports:
74+
- "18080:8080"
75+
restart: always
76+
extra_hosts:
77+
- "host.docker.internal:host-gateway"
78+
environment:
79+
HASURA_GRAPHQL_METADATA_DATABASE_URL: postgresql://postgres:password@db:5432/postgres
80+
PG_DATABASE_URL: postgresql://postgres:password@db:5432/postgres
81+
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
82+
HASURA_GRAPHQL_CONSOLE_ASSETS_DIR: /srv/console-assets
83+
FACT_DB_URL: "postgresql://postgres:password@db:5432/fact_db"
84+
HASURA_GRAPHQL_ADMIN_SECRET: "${HASURA_ADMIN_SECRET:-4dM1n_S3cR3T_changemeplz}"
85+
HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "ro_user"
86+
depends_on:
87+
hasura-connector:
88+
condition: service_healthy
89+
networks:
90+
fact:
91+
aliases:
92+
- hasura
93+
hasura-connector:
94+
image: hasura/graphql-data-connector:v2.38.0
95+
restart: always
96+
ports:
97+
- "18081:8081"
98+
environment:
99+
QUARKUS_LOG_LEVEL: ERROR
100+
QUARKUS_OPENTELEMETRY_ENABLED: "false"
101+
healthcheck:
102+
test: ["CMD", "curl", "-f", "http://localhost:8081/api/v1/athena/health"]
103+
interval: 5s
104+
timeout: 10s
105+
retries: 5
106+
start_period: 5s
107+
networks:
108+
fact:
109+
aliases:
110+
- hasura-connector
111+
112+
networks:
113+
fact:
114+
driver: bridge
115+
116+
volumes:
117+
fact_db:
118+
fact_files:
119+
fact_docker_images:

docker/entrypoint_backend.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
3+
set -eux
4+
5+
# start docker service
6+
dockerd &
7+
8+
# redis runs in a different container => replace "localhost" with "redis"
9+
sed -i 's/host = "localhost"/host = "redis"/g' /opt/fact/config/fact-core-config.toml
10+
# postgres also runs in a different container => replace "localhost" with "db"
11+
sed -i 's/server = "localhost"/server = "db"/g' /opt/fact/config/fact-core-config.toml
12+
13+
if [ -e DOCKER_INSTALL_INCOMPLETE ]; then
14+
echo "Installing FACT docker images..."
15+
python3 install.py --backend-docker-images
16+
rm DOCKER_INSTALL_INCOMPLETE
17+
echo "FACT docker image installation completed"
18+
fi
19+
20+
# We can't use rest/status here, because it needs the list of available plugins (which is available after the backend
21+
# was started).
22+
until curl -s -X GET 'http://frontend:5000/rest/statistics/general'; do
23+
echo "Waiting for FACT frontend to start..."
24+
sleep 2
25+
done
26+
echo "FACT frontend is ready"
27+
28+
python3 start_fact_backend.py

docker/entrypoint_frontend.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
3+
set -eux
4+
5+
# start docker service
6+
dockerd &
7+
8+
# redis runs in a different container => replace "localhost" with "redis"
9+
sed -i '/^\[common\.redis\]/,/^\[/{ /host = "localhost"/s//host = "redis"/ }' /opt/fact/config/fact-core-config.toml
10+
# postgres also runs in a different container => replace "localhost" with "db"
11+
sed -i 's/server = "localhost"/server = "db"/g' /opt/fact/config/fact-core-config.toml
12+
# switch to correct hasura host/port
13+
sed -i '/^\[frontend\.hasura\]/,/^\[/{ /host = "localhost"/s//host = "hasura"/ }' /opt/fact/config/fact-core-config.toml
14+
sed -i '/^\[frontend\.hasura\]/,/^\[/{ /port = 33333/s//port = 8080/ }' /opt/fact/config/fact-core-config.toml
15+
# replace localhost with 0.0.0.0 in uWSGI config so that the frontend can be reached from outside
16+
sed -i 's/127.0.0.1/0.0.0.0/g' /opt/fact/config/uwsgi_config.ini
17+
18+
# init the DB
19+
python3 init_postgres.py
20+
21+
# init hasura
22+
python3 storage/graphql/hasura/init_hasura.py
23+
24+
python3 start_fact_frontend.py --no-radare

src/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class Authentication(BaseModel):
113113
class Hasura(BaseModel):
114114
model_config = ConfigDict(extra='forbid')
115115
admin_secret: str
116+
host: str = 'localhost'
116117
port: int = 33_333
117118

118119

src/config/fact-core-config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,6 @@ password-salt = "5up3r5tr0n6_p455w0rd_5417"
183183

184184

185185
[frontend.hasura]
186+
host = "localhost"
187+
port = 33333
186188
admin-secret = "4dM1n_S3cR3T_changemeplz"

src/init_postgres.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ def user_exists(user_name: str, host: str, port: str | int) -> bool:
4343

4444
def create_admin_user(user_name: str, password: str, host: str, port: int | str):
4545
execute_psql_command(
46-
# fmt: off
47-
(f"CREATE USER {user_name} WITH PASSWORD '{password}' " 'LOGIN SUPERUSER INHERIT CREATEDB CREATEROLE;'),
48-
# fmt: on
46+
f"CREATE USER {user_name} WITH PASSWORD '{password}' LOGIN SUPERUSER INHERIT CREATEDB CREATEROLE;",
4947
host=host,
5048
port=port,
5149
)

src/install.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
PROGRAM_VERSION = '1.2'
4444
PROGRAM_DESCRIPTION = 'Firmware Analysis and Comparison Tool (FACT) installation script'
4545

46-
FACT_INSTALLER_SKIP_DOCKER = os.getenv('FACT_INSTALLER_SKIP_DOCKER')
46+
FACT_INSTALLER_SKIP_DOCKER = bool(int(os.getenv('FACT_INSTALLER_SKIP_DOCKER', '0')))
4747

4848

4949
def _setup_argparser():
@@ -169,7 +169,7 @@ def install():
169169
welcome()
170170
none_chosen = not (args.frontend or args.db or args.backend or args.common)
171171
# TODO maybe replace this with an cli argument
172-
skip_docker = FACT_INSTALLER_SKIP_DOCKER is not None
172+
skip_docker = FACT_INSTALLER_SKIP_DOCKER
173173
# Note that the skip_docker environment variable overrides the cli argument
174174
only_docker = not skip_docker and none_chosen and (args.backend_docker_images or args.frontend_docker_images)
175175

src/storage/graphql/hasura/init_hasura.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class HasuraInitError(Exception):
4848
class HasuraSetup:
4949
def __init__(self, db_name: str | None = None, testing: bool = False):
5050
self.db_name = db_name or config.common.postgres.database
51-
self.url = f'http://localhost:{config.frontend.hasura.port}/v1/metadata'
51+
host = config.frontend.hasura.host
52+
port = config.frontend.hasura.port
53+
self.url = f'http://{host}:{port}/v1/metadata'
5254
self.headers = {
5355
'Content-Type': 'application/json',
5456
'X-Hasura-Role': 'admin',

0 commit comments

Comments
 (0)