diff --git a/compose.yaml b/compose.yaml index 416a76f4..ee60e00f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -55,6 +55,60 @@ services: networks: - thothii + workspace-maintenance: + image: thothii-core:local + profiles: [workspace-maintenance] + pull_policy: never + entrypoint: ["/usr/bin/tini", "--", "/app/docker/workspace-maintenance-entrypoint.sh"] + environment: + THT_HARNESS_DIR: /app/harness + THT_BIN: /opt/venv/bin/tht + THT_DATA_ROOT: /data + THT_WORKSPACE_REGISTRY_ROOT: /data/workspace-registry + THT_WORKSPACE_GIT_REMOTE: ${THT_WORKSPACE_GIT_REMOTE:?set THT_WORKSPACE_GIT_REMOTE} + THT_WORKSPACE_GIT_BRANCH: ${THT_WORKSPACE_GIT_BRANCH:-main} + THT_WORKSPACE_INSTALLATION_ID: ${THT_WORKSPACE_INSTALLATION_ID:-local} + THT_WORKSPACE_SECRET_ROOTS: /run/secrets + THT_SECRETS_FILE: /run/secrets/thothii.secrets + THT_DB_NAME: ${THT_DB_NAME:-} + THT_DWH_REST_URL: ${THT_DWH_REST_URL:-} + THT_LLM_URL: ${THT_LLM_URL:-} + THT_INTERNAL_QDRANT_URL: http://qdrant:6333 + THT_INTERNAL_EMBEDDING_URL: http://embedding:11434 + THT_INTERNAL_EMBEDDING_MODEL: qwen3-embedding:0.6b + THT_INTERNAL_EMBEDDING_DIMENSIONS: "1024" + HOME: /tmp/thoth + AWS_ACCESS_KEY_ID: "" + AWS_SECRET_ACCESS_KEY: "" + AWS_SESSION_TOKEN: "" + AWS_PROFILE: "" + AWS_DEFAULT_PROFILE: "" + AWS_CONFIG_FILE: /dev/null + AWS_SHARED_CREDENTIALS_FILE: /dev/null + volumes: + - type: volume + source: workspace-registry + target: /data/workspace-registry + read_only: true + - type: volume + source: sessions + target: /data/sessions + secrets: + - source: thothii_secrets + target: thothii.secrets + user: "10001:10001" + read_only: true + tmpfs: + - /tmp:rw,noexec,nosuid,nodev,size=64m,mode=1777 + - /var/tmp:rw,noexec,nosuid,nodev,size=32m,mode=1777 + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + restart: "no" + networks: + - thothii + frontend: build: context: . diff --git a/deploy/compose.git-https.yaml b/deploy/compose.git-https.yaml index 7438eede..7e4fc6d5 100644 --- a/deploy/compose.git-https.yaml +++ b/deploy/compose.git-https.yaml @@ -1,5 +1,6 @@ # Select this override only for an HTTPS Git remote. The separate CA mount keeps TLS validation # explicit; neither host-only source file nor its contents belongs in the base Compose contract. +# Active-snapshot workspace-maintenance operations intentionally receive no Git credential mounts. x-thoth-git-transport: https services: diff --git a/deploy/compose.git-ssh.yaml b/deploy/compose.git-ssh.yaml index 67c1e91b..c3c7a7a9 100644 --- a/deploy/compose.git-ssh.yaml +++ b/deploy/compose.git-ssh.yaml @@ -1,5 +1,6 @@ # Select this override only for an SSH Git remote. The host-only source files must be absolute, # normalized paths; strict host-key checking is mandatory for registry pull and publish. +# Active-snapshot workspace-maintenance operations intentionally receive no Git credential mounts. x-thoth-git-transport: ssh services: diff --git a/deploy/compose.local.yaml b/deploy/compose.local.yaml index 4b9bf1b2..a5f83478 100644 --- a/deploy/compose.local.yaml +++ b/deploy/compose.local.yaml @@ -11,3 +11,8 @@ services: ports: - "127.0.0.1:${THOTH_HTTP_PORT:-8080}:8080" restart: "no" + + workspace-maintenance: + environment: + THT_WORKSPACE_INSTALLATION_ID: local + restart: "no" diff --git a/deploy/compose.preprocess.yaml b/deploy/compose.preprocess.yaml index 07b8c0c1..4873ba99 100644 --- a/deploy/compose.preprocess.yaml +++ b/deploy/compose.preprocess.yaml @@ -1,3 +1,5 @@ +# Retired for operator use: this profile remains only as a non-public engine-fixture path. +# It exercises the legacy preprocessing fixtures and must not become a second operator interface. services: preprocess-evidence: image: thothii-core:local diff --git a/deploy/compose.server.yaml b/deploy/compose.server.yaml index a3a9fdf6..9c37d091 100644 --- a/deploy/compose.server.yaml +++ b/deploy/compose.server.yaml @@ -35,3 +35,18 @@ services: ports: - "${THOTH_SERVER_BIND:-127.0.0.1}:${THOTH_HTTP_PORT:-8080}:8080" restart: unless-stopped + + + workspace-maintenance: + environment: + THT_DATA_ROOT: /data + THT_WORKSPACE_INSTALLATION_ID: server + volumes: !override + - type: bind + source: ${THT_DATA_ROOT:?set THT_DATA_ROOT}/sessions + target: /data/sessions + - type: bind + source: ${THT_WORKSPACE_REGISTRY_ROOT:?set THT_WORKSPACE_REGISTRY_ROOT} + target: /data/workspace-registry + read_only: true + restart: "no" diff --git a/docker/core.Dockerfile b/docker/core.Dockerfile index a891eb1d..c6cfb521 100644 --- a/docker/core.Dockerfile +++ b/docker/core.Dockerfile @@ -33,10 +33,16 @@ LABEL org.opencontainers.image.title="thothii-core" \ io.thothii.pi.version="${PI_VERSION}" # Runtime tools -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl ca-certificates ripgrep fd-find tini git openssh-client \ - && rm -rf /var/lib/apt/lists/* \ - && ln -s /usr/bin/fdfind /usr/local/bin/fd +RUN set -eux; \ + runtime_packages="curl ca-certificates ripgrep fd-find tini git openssh-client"; \ + if ! command -v flock >/dev/null 2>&1; then \ + runtime_packages="$runtime_packages util-linux"; \ + fi; \ + apt-get update; \ + apt-get install -y --no-install-recommends $runtime_packages; \ + rm -rf /var/lib/apt/lists/*; \ + command -v flock >/dev/null 2>&1; \ + ln -s /usr/bin/fdfind /usr/local/bin/fd # Node 22 + npm copiati dall'immagine ufficiale (stesso Debian bookworm → binario compatibile) COPY --from=node-runtime /usr/local/bin/node /usr/local/bin/node @@ -91,10 +97,10 @@ ENV PATH="/opt/venv/bin:/usr/local/bin:$PATH" \ HOME=/home/thoth COPY scripts/verify-line-endings.sh /usr/local/bin/verify-line-endings -COPY docker/core-entrypoint.sh docker/session-migrate.sh docker/ensure-pi-trust.mjs docker/embedding-model-init.sh /app/docker/ +COPY docker/core-entrypoint.sh docker/workspace-maintenance-entrypoint.sh docker/session-migrate.sh docker/ensure-pi-trust.mjs docker/embedding-model-init.sh /app/docker/ COPY docker/smoke/core-smoke.sh /app/docker/smoke/core-smoke.sh RUN /usr/local/bin/verify-line-endings /app/docker \ - && chmod +x /app/docker/core-entrypoint.sh /app/docker/session-migrate.sh /app/docker/embedding-model-init.sh /app/docker/smoke/core-smoke.sh + && chmod +x /app/docker/core-entrypoint.sh /app/docker/workspace-maintenance-entrypoint.sh /app/docker/session-migrate.sh /app/docker/embedding-model-init.sh /app/docker/smoke/core-smoke.sh WORKDIR /app/backend USER thoth diff --git a/docker/workspace-maintenance-entrypoint.sh b/docker/workspace-maintenance-entrypoint.sh new file mode 100644 index 00000000..aecbbcf6 --- /dev/null +++ b/docker/workspace-maintenance-entrypoint.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +exec node /app/backend/dist/workspace-maintenance.js "$@" diff --git a/scripts/generate-connector-secrets-override.sh b/scripts/generate-connector-secrets-override.sh index 5b553572..c06e146d 100755 --- a/scripts/generate-connector-secrets-override.sh +++ b/scripts/generate-connector-secrets-override.sh @@ -3,7 +3,10 @@ set -euo pipefail usage() { - echo "usage: $0 --bindings-env --operator-env --output " >&2 + cat >&2 <<'EOF' +usage: $0 --bindings-env --operator-env --output \ + [--service ]... [--role ]... +EOF exit 2 } @@ -40,20 +43,59 @@ read_env_value() { printf '%s' "$result" } +binding_matches_roles() { + local name="$1" role + for role in "${roles[@]}"; do + case "$role" in + all) return 0 ;; + dwh) + [[ "$name" == *"_DWH_"* ]] && return 0 + ;; + evidence) + [[ "$name" == *"_EVIDENCE_"* ]] && return 0 + ;; + *) + echo "unsupported role filter: $role" >&2 + exit 2 + ;; + esac + done + return 1 +} + bindings_env="" operator_env="" output="" +services=() +roles=() while (($#)); do case "$1" in --bindings-env) bindings_env="${2:-}"; shift 2 ;; --operator-env) operator_env="${2:-}"; shift 2 ;; --output) output="${2:-}"; shift 2 ;; + --service) services+=("${2:-}"); shift 2 ;; + --role) + roles+=("$(printf '%s' "${2:-}" | tr '[:upper:]' '[:lower:]')") + shift 2 + ;; *) usage ;; esac done [[ -f "$bindings_env" && -f "$operator_env" && -n "$output" ]] || usage [[ ! -e "$output" ]] || { echo "refusing to overwrite connector override: $output" >&2; exit 2; } +((${#services[@]})) || services=(core) +((${#roles[@]})) || roles=(all) + +for service in "${services[@]}"; do + case "$service" in + core|workspace-maintenance) ;; + *) + echo "unsupported service target: $service" >&2 + exit 2 + ;; + esac +done names=() targets=() @@ -64,6 +106,7 @@ while IFS=$'\t' read -r name target; do echo "retired semantic secret binding is not supported: ${name%_FILE}_SOURCE" >&2 exit 2 fi + binding_matches_roles "$name" || continue [[ "$target" =~ ^/run/secrets/[A-Za-z0-9][A-Za-z0-9_.-]*$ && "$target" != *..* ]] || { echo "invalid connector secret target for $name: $target" >&2 exit 2 @@ -101,20 +144,25 @@ done < <( ' "$bindings_env" ) -((${#names[@]})) || { echo "no THT_WS_*_FILE connector bindings found in $bindings_env" >&2; exit 2; } +((${#names[@]})) || { + echo "no THT_WS_*_FILE connector bindings matched the selected roles in $bindings_env" >&2 + exit 2 +} { printf '%s\n' '# Generated by scripts/generate-connector-secrets-override.sh; keep this file untracked.' - printf '%s\n' \ - 'services:' \ - ' core:' \ - ' env_file:' \ - ' - path: ${THT_WORKSPACE_BINDINGS_ENV_FILE:?set THT_WORKSPACE_BINDINGS_ENV_FILE}' \ - ' required: true' \ - ' secrets:' - for ((index = 0; index < ${#names[@]}; index += 1)); do - printf ' - source: connector_secret_%d\n' "$((index + 1))" - printf ' target: %s\n' "${targets[index]}" + printf '%s\n' 'services:' + for service in "${services[@]}"; do + printf ' %s:\n' "$service" + printf '%s\n' \ + ' env_file:' \ + ' - path: ${THT_WORKSPACE_BINDINGS_ENV_FILE:?set THT_WORKSPACE_BINDINGS_ENV_FILE}' \ + ' required: true' \ + ' secrets:' + for ((index = 0; index < ${#names[@]}; index += 1)); do + printf ' - source: connector_secret_%d\n' "$((index + 1))" + printf ' target: %s\n' "${targets[index]}" + done done printf '%s\n' '' 'secrets:' for ((index = 0; index < ${#names[@]}; index += 1)); do @@ -123,4 +171,5 @@ done < <( done } >"$output" -printf 'generated %s connector secret mount(s) at %s\n' "${#names[@]}" "$output" +printf 'generated %s connector secret mount(s) for %s at %s\n' \ + "${#names[@]}" "$(IFS=,; printf '%s' "${services[*]}")" "$output"