From ef409bdbebbb8fcba4a55a936a3c19969cad3247 Mon Sep 17 00:00:00 2001 From: "ugo.bechameil" Date: Tue, 4 Aug 2026 22:40:01 +0200 Subject: [PATCH] docs(hck-cli): document /data+tmpfs layout and restricted K8s Job (HCK-17257) Update getting-started, README volume model, and license-validation notes for the two-mount read-only rootfs, and add a sample Kubernetes Job + PVC. Co-authored-by: Cursor --- Studio/README.md | 25 ++++- Studio/doc/getting-started-hck-cli.md | 149 ++++++++++++++++++-------- Studio/doc/license-validation.md | 4 + Studio/k8s/hck-cli-job.yaml | 57 ++++++++++ 4 files changed, 185 insertions(+), 50 deletions(-) create mode 100644 Studio/k8s/hck-cli-job.yaml diff --git a/Studio/README.md b/Studio/README.md index 4eb40cf..35f64df 100644 --- a/Studio/README.md +++ b/Studio/README.md @@ -80,12 +80,27 @@ The **hackolade** user pre-configured inside the image has the following UID/GID ### Required directories (inside containers) -Hackolade reads and writes data to the following folders inside containers: +#### Pre-built `hackolade/hck-cli` image (recommended) --**/home/hackolade/.config/Hackolade**: this folder (**appData**) is necessary for the proper operation of the application in containers and must be readable and writable by **hackolade** user. --**/home/hackolade/Documents/HackoladeLogs**: this folder is where Hackolade Studio writes its logging information, which is useful in case of any issue and must be readable and writable by **hackolade** user. -- **/home/hackolade/Documents/data**: we recommend this folder that will be used for generated artifacts within containers but you can define it anywhere inside container filesystem as long as you make it persistent. It may contain models, documentation, sources for reverse-engineering, artifacts out of forward-engineering, etc... Instead of a relative path to the location where the container is run, you may reference an absolute path to the location of these files. --[Optional]**/home/hackolade/.hackolade/options**: is where Hackolade reads user defined configurations like naming conventions, excel export options, custom properties, etc... +The image expects a **read-only root filesystem** with exactly two writable mounts: + +- **`/data`** (persistent volume): license/userData under `/data/app`, plus logs, models, output, settings, and options +- **`/tmp`** (tmpfs): sockets, caches, and scratch files + +See [`doc/getting-started-hck-cli.md`](./doc/getting-started-hck-cli.md) and [`compose.yml`](./compose.yml). Custom CAs use read-only PEM mounts and `NODE_EXTRA_CA_CERTS` / `SSL_CERT_FILE` — see [`doc/custom-certificates.md`](./doc/custom-certificates.md). + +**Breaking change:** do not mount `/home/hackolade/.config` for the pre-built image; that path is no longer used for license state. + +#### Custom-built `hackolade/studio` images + +Older custom builds may still use the historical layout: + +- `/home/hackolade/.config/Hackolade`: application data (**appData**) — must be readable and writable by the container user +- `/home/hackolade/Documents/HackoladeLogs`: logging information +- `/home/hackolade/Documents/data`: generated artifacts (models, documentation, RE/FE outputs, etc.) +- [Optional] `/home/hackolade/.hackolade/options`: user-defined configurations + +Prefer migrating custom images to the `/data` + `/tmp` model used by `hackolade/hck-cli`. You must create manually the folders you will bind mount prior to running hackolade studio containers because docker doesn't create them automatically anymore. diff --git a/Studio/doc/getting-started-hck-cli.md b/Studio/doc/getting-started-hck-cli.md index 4f08835..ce0f60e 100644 --- a/Studio/doc/getting-started-hck-cli.md +++ b/Studio/doc/getting-started-hck-cli.md @@ -95,20 +95,29 @@ The image uses `hck-cli` as its default entrypoint - a simple binary that execut ### Data Volume Structure -The image uses a simplified data structure with volumes mounted directly under `/data`: +The image is designed for a **read-only root filesystem**. Runtime writes go to exactly two places: +| Mount | Purpose | +| --- | --- | +| `/data` | Persistent volume: license/userData (`/data/app`), logs, models, output, settings, options | +| `/tmp` | tmpfs: sockets, caches, and scratch files (discarded when the container exits) | + +Layout under `/data`: + +- `/data/app` - Application data (license state, Electron userData). Lives on the `/data` volume via `XDG_CONFIG_HOME`. - `/data/models` - Your input model files - `/data/output` - Generated artifacts (documentation, schemas, etc.) - `/data/logs` - Application logs organized in `-command` folders (e.g., `2024-01-15-genDoc`) for per-command isolation and troubleshooting - `/data/options` - (Optional) User-defined configurations +- `/data/settings` - Optional settings -**⚠️ MANDATORY:** The application data folder (`/home/hackolade/.config`) **MUST** be mounted as a volume. This volume is absolutely required for licensing and configuration to work properly. Without this volume mounted, the CLI will not function correctly. +**⚠️ MANDATORY:** Mount a volume at `/data` **and** a writable `/tmp` (tmpfs recommended). Without `/data`, licensing and configuration will not persist. Without `/tmp`, Electron and scratch I/O will fail under `read_only: true`. -This structure reduces path length and simplifies volume management compared to the previous `/home/hackolade/Documents/...` structure. +**Breaking change:** Earlier releases required a separate volume at `/home/hackolade/.config`. That path is no longer written; migrate the named volume to `/data` (license state is under `/data/app`). -**Volume validation:** The CLI automatically validates that required volumes are properly mounted. If a required volume is missing, the CLI will display a warning message to help you identify and fix the issue before command execution fails. +**Volume validation:** The CLI automatically validates that required mounts are writable. If a required mount is missing, the CLI will display a warning (or fail in the official image) before command execution. -**Log isolation:** Logs are automatically organized per command in `/data/logs` using folders named `-command` (e.g., `2024-01-15-genDoc`, `2024-01-15-forweng`). This folder structure provides proper command isolation, making it easier to analyze logs for specific commands when troubleshooting issues. Each command execution creates its own log folder, allowing you to trace problems to specific operations by date and command type. +**Log isolation:** Logs are automatically organized per command in `/data/logs` using folders named `-command` (e.g., `2024-01-15-genDoc`, `2024-01-15-forweng`). This folder structure provides proper command isolation, making it easier to analyze logs for specific commands when troubleshooting issues. ## Quick start with Docker Compose @@ -293,45 +302,52 @@ If you prefer using Docker CLI directly instead of Docker Compose, here's how: ### Basic Command Structure ```bash -docker run --rm \ - -v hackolade-studio-app-data:/home/hackolade/.config \ - -v hackolade-studio-logs:/data/logs \ +docker run --rm --read-only \ + --cap-drop=ALL --security-opt=no-new-privileges \ + --user 1000:1001 \ + -v hackolade-studio-data:/data \ -v ${PWD}/models:/data/models \ - -v hackolade-studio-output:/data/output \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ hackolade/hck-cli:8.9.2 COMMAND [OPTIONS] ``` ### Create Required Volumes -First, create the named volumes: +First, create the named volume for persistent state: ```bash -docker volume create hackolade-studio-app-data -docker volume create hackolade-studio-logs -docker volume create hackolade-studio-output +docker volume create hackolade-studio-data ``` +`/tmp` should be a tmpfs (shown above), not a named volume. + ### Example Commands **Check version:** ```bash -docker run --rm \ - -v hackolade-studio-app-data:/home/hackolade/.config \ +docker run --rm --read-only \ + --user 1000:1001 \ + -v hackolade-studio-data:/data \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ hackolade/hck-cli:8.9.2 version ``` **Get computer ID:** ```bash -docker run --rm hackolade/hck-cli:8.9.2 getComputerId +docker run --rm --read-only \ + --user 1000:1001 \ + -v hackolade-studio-data:/data \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ + hackolade/hck-cli:8.9.2 getComputerId ``` **Generate documentation:** ```bash -docker run --rm \ - -v hackolade-studio-app-data:/home/hackolade/.config \ - -v hackolade-studio-logs:/data/logs \ +docker run --rm --read-only \ + --user 1000:1001 \ + -v hackolade-studio-data:/data \ -v ${PWD}/models:/data/models \ - -v hackolade-studio-output:/data/output \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ hackolade/hck-cli:8.9.2 genDoc \ --format=HTML \ --model /data/models/model.json \ @@ -339,12 +355,12 @@ docker run --rm \ ``` In case of offline validation: ```bash -docker run --rm \ - -v hackolade-studio-app-data:/home/hackolade/.config \ - -v hackolade-studio-logs:/data/logs \ +docker run --rm --read-only \ + --user 1000:1001 \ + -v hackolade-studio-data:/data \ -v ${PWD}/models:/data/models \ - -v hackolade-studio-output:/data/output \ - -v ${PWD}/LicenseFile.xml:/LicenseFile.xml \ + -v ${PWD}/LicenseFile.xml:/data/LicenseFile.xml:ro \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ hackolade/hck-cli:8.9.2 genDoc \ --format=HTML \ --model /data/models/model.json \ @@ -509,30 +525,28 @@ chown -R 1000:1001 ./models chown -R 1000:1001 ./output ``` -**Note:** The container runs as user `hackolade` with UID 1000 and GID 1001 (data-modelers group). +**Note:** The container runs as numeric user `1000:1001` by default (compatible with Kubernetes `runAsNonRoot`). OpenShift-style arbitrary UIDs in group 0 are also supported when `/data` is group-writable. -**Note:** Docker named volumes (like `hackolade-studio-app-data`) don't require permission changes on the host. +**Note:** Docker named volumes (like `hackolade-studio-data`) don't require permission changes on the host. Bind mounts for models should be owned by UID 1000 (or writable by group 0). ### Volume Not Found If Docker says a volume doesn't exist, create it: ```bash -docker volume create hackolade-studio-app-data -docker volume create hackolade-studio-logs -docker volume create hackolade-studio-output +docker volume create hackolade-studio-data ``` -Or let Docker Compose create them automatically on first run. +Or let Docker Compose create it automatically on first run. Ensure every run also mounts a writable `/tmp` (compose uses `tmpfs`). ### Volume Validation Warnings -The CLI automatically checks for required volumes and will warn you if they're not properly mounted. If you see warnings about missing volumes: +The CLI automatically checks for required writable mounts and will warn you if they're not properly mounted. If you see warnings about missing mounts: -1. **Check your compose.yml or docker run command** - Ensure all required volumes are defined: - - `hackolade-studio-app-data` → `/home/hackolade/.config` ⚠️ **MANDATORY** - Required for licensing and configuration - - `hackolade-studio-logs` → `/data/logs` (recommended for log isolation) - - `hackolade-studio-output` → `/data/output` (required for output operations) +1. **Check your compose.yml or docker run command** - Ensure: + - `hackolade-studio-data` → `/data` ⚠️ **MANDATORY** - license state, logs, output, settings + - tmpfs (or equivalent) → `/tmp` ⚠️ **MANDATORY** under `read_only: true` + - Optional bind: host `models` → `/data/models` 2. **Verify volumes exist:** ```bash @@ -544,9 +558,9 @@ The CLI automatically checks for required volumes and will warn you if they're n docker inspect | grep -A 10 Mounts ``` -4. **Review the warning message** - The CLI will indicate which specific volume is missing and what it's used for. +4. **Review the warning message** - The CLI will indicate which specific mount is missing and what it's used for. -**Important:** The `/home/hackolade/.config` volume is **MANDATORY** and must be mounted for the CLI to function. While the CLI will warn about missing volumes, operations will fail without the application data volume. For proper functionality and log isolation, mount all volumes as shown in the compose examples. +**Important:** A single `/data` volume plus `/tmp` tmpfs replaces the older multi-volume layout (`/home/hackolade/.config`, separate logs/output volumes). Migrate by mounting your persistent state at `/data`. ### License Validation Failed @@ -672,19 +686,64 @@ docker compose run --rm hck-cli COMMAND [OPTIONS] docker pull hackolade/hck-cli:8.9.2 # Create volumes -docker volume create hackolade-studio-app-data -docker volume create hackolade-studio-logs -docker volume create hackolade-studio-output +docker volume create hackolade-studio-data # Run command -docker run --rm \ - -v hackolade-studio-app-data:/home/hackolade/.config \ - -v hackolade-studio-logs:/data/logs \ +docker run --rm --read-only \ + --user 1000:1001 \ + -v hackolade-studio-data:/data \ -v ${PWD}/models:/data/models \ - -v hackolade-studio-output:/data/output \ + --tmpfs /tmp:rw,size=1g,mode=1777 \ hackolade/hck-cli:8.9.2 COMMAND ``` +## Kubernetes Job (restricted / read-only rootfs) + +A minimal Job that matches the compose hardening defaults (`readOnlyRootFilesystem`, non-root, dropped capabilities, `/data` PVC + `/tmp` emptyDir memory): + +```yaml +# See also: k8s/hck-cli-job.yaml in this repository +apiVersion: batch/v1 +kind: Job +metadata: + name: hck-cli-version +spec: + template: + spec: + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1001 + fsGroup: 0 + seccompProfile: + type: RuntimeDefault + containers: + - name: hck-cli + image: hackolade/hck-cli:8.9.2 + args: ["version"] + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + volumeMounts: + - name: data + mountPath: /data + - name: tmp + mountPath: /tmp + volumes: + - name: data + persistentVolumeClaim: + claimName: hck-cli-data + - name: tmp + emptyDir: + medium: Memory + sizeLimit: 1Gi +``` + +Apply the full example (including a PVC) from [`k8s/hck-cli-job.yaml`](../k8s/hck-cli-job.yaml). + ## Backward compatibility with other images For users migrating from the `hackolade/studio` image or custom-built images that use `startup.sh` as the entrypoint, this image maintains backward compatibility by including the `startup.sh` and `show-computer-id.sh` scripts. diff --git a/Studio/doc/license-validation.md b/Studio/doc/license-validation.md index 107845e..7fd7132 100644 --- a/Studio/doc/license-validation.md +++ b/Studio/doc/license-validation.md @@ -30,6 +30,8 @@ docker run --rm \ --identifier=YOUR-UUID-FROM-STEP-1 ``` +For the pre-built `hackolade/hck-cli` image, mount a single `/data` volume (license state lives under `/data/app`) and a `/tmp` tmpfs instead of `/home/hackolade/.config/Hackolade`. See [`getting-started-hck-cli.md`](./getting-started-hck-cli.md) and [`compose.yml`](../compose.yml). + Replace: - `YOUR-LICENSE-KEY` with your actual floating license key - `YOUR-UUID-FROM-STEP-1` with the UUID you copied from step 1 @@ -114,6 +116,8 @@ docker run --rm \ --file=/LicenseFile.xml ``` +With `hackolade/hck-cli`, prefer `-v hackolade-studio-data:/data` and mount the license file under `/data/...` (see [`getting-started-hck-cli.md`](./getting-started-hck-cli.md)). + Replace `YOUR-LICENSE-KEY` with your actual license key. **Important:** diff --git a/Studio/k8s/hck-cli-job.yaml b/Studio/k8s/hck-cli-job.yaml new file mode 100644 index 0000000..a2b7be3 --- /dev/null +++ b/Studio/k8s/hck-cli-job.yaml @@ -0,0 +1,57 @@ +# Example Kubernetes Job for hackolade/hck-cli with a read-only root filesystem. +# Requires a PVC (or other volume) at /data and a memory emptyDir at /tmp. +# +# kubectl apply -f hck-cli-job.yaml +# +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: hck-cli-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: hck-cli-version +spec: + backoffLimit: 1 + template: + metadata: + labels: + app: hck-cli + spec: + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1001 + fsGroup: 0 + seccompProfile: + type: RuntimeDefault + containers: + - name: hck-cli + image: hackolade/hck-cli:8.9.2 + args: ["version"] + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + volumeMounts: + - name: data + mountPath: /data + - name: tmp + mountPath: /tmp + volumes: + - name: data + persistentVolumeClaim: + claimName: hck-cli-data + - name: tmp + emptyDir: + medium: Memory + sizeLimit: 1Gi