{"allowed_envs":["PAY_TO","FACILITATOR_URL","PROTOCOL_FEE_BPS","DEFAULT_MODEL","FACILITATOR_PRIVATE_KEY","ANTHROPIC_API_KEY","PHALA_API_KEY"],"docker_compose_file":"# Umbra gateway inside a Phala Cloud confidential VM (Intel TDX, dstack).\n# This file is what the attestation measures (compose_hash). The release tarball is pinned here, in the clear,\n# by URL and sha256, so the pin is part of the measurement: change the release and the compose_hash changes.\n# Secrets (keys, wallet) come from the sealed environment and are not part of the measurement.\nservices:\n  gateway:\n    image: node:20-alpine\n    working_dir: /app\n    command: >\n      sh -c \"apk add --no-cache curl >/dev/null\n      && curl -fsSL -o /tmp/gateway.tgz 'https://umbra-inference.app/releases/umbra-gateway-0.16.0.tgz'\n      && echo '7771b26009e2ef77c2aae2b77ef1076268b058279f10be09bb4789fe1eb24851  /tmp/gateway.tgz' | sha256sum -c -\n      && tar xzf /tmp/gateway.tgz -C /app\n      && npm ci --omit=dev --no-audit --no-fund\n      && node server.js\"\n    ports:\n      - \"8402:8402\"\n    volumes:\n      - /var/run/dstack.sock:/var/run/dstack.sock\n    environment:\n      - PORT=8402\n      - RELEASE_URL=https://umbra-inference.app/releases/umbra-gateway-0.16.0.tgz\n      - RELEASE_SHA256=7771b26009e2ef77c2aae2b77ef1076268b058279f10be09bb4789fe1eb24851\n      - PAY_TO=${PAY_TO}\n      # Networks and the Solana receiving address are public: literal here so they are part of the measurement.\n      - X402_NETWORKS=eip155:4663,eip155:8453,eip155:84532,solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp,solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1\n      - SOLANA_PAY_TO=2RQ5UCotYBwPt2DA81vH7ySSwvSTBFVhxBHDdmNpMmED\n      - FACILITATOR_URL=${FACILITATOR_URL}\n      - FACILITATOR_PRIVATE_KEY=${FACILITATOR_PRIVATE_KEY}\n      - PROTOCOL_FEE_BPS=${PROTOCOL_FEE_BPS}\n      - DEFAULT_MODEL=${DEFAULT_MODEL}\n      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}\n      - PHALA_API_KEY=${PHALA_API_KEY}\n      # Revenue policy executor. Literal on purpose: these values are part of the measured compose.\n      - BUYBACK_ENABLED=1\n      - BUYBACK_DRY_RUN=0\n      - BUYBACK_THRESHOLD_USDG=100\n      - BUYBACK_SHARE_BPS=5000\n      - BUYBACK_MAX_USDG=2000\n      - BUYBACK_SLIPPAGE_BPS=300\n      - BUYBACK_MIN_INTERVAL_DAYS=28\n      # Holder discount tiers (UMBRA balance of the paying wallet on eip155:4663). Literal: measured.\n      - HOLDER_DISCOUNT=1\n      - HOLDER_TIER1_UMBRA=100000\n      - HOLDER_TIER1_FEE_BPS=200\n      - HOLDER_TIER2_UMBRA=1000000\n      - HOLDER_TIER2_FEE_BPS=0\n    restart: unless-stopped\n","features":["kms","tproxy-net"],"gateway_enabled":true,"kms_enabled":true,"local_key_provider_enabled":false,"manifest_version":2,"name":"","no_instance_id":false,"pre_launch_script":"#!/bin/bash\necho \"----------------------------------------------\"\necho \"Running Phala Cloud Pre-Launch Script v0.0.20\"\necho \"----------------------------------------------\"\nset -e\n\n# Function: notify host\n\nnotify_host() {\n    if command -v dstack-util >/dev/null 2>&1; then\n        dstack-util notify-host -e \"$1\" -d \"$2\"\n    else\n        tdxctl notify-host -e \"$1\" -d \"$2\"\n    fi\n}\n\nnotify_host_hoot_info() {\n    notify_host \"boot.progress\" \"$1\"\n}\n\nnotify_host_hoot_error() {\n    notify_host \"boot.error\" \"$1\"\n}\n\n# Function: Perform Docker cleanup\nperform_cleanup() {\n    echo \"Pruning unused images\"\n    docker image prune -af\n    echo \"Pruning unused volumes\"\n    docker volume prune -f\n    notify_host_hoot_info \"docker cleanup completed\"\n}\n\n# Function: Check Docker login status without exposing credentials\ncheck_docker_login() {\n    local registry=\"${1-}\"\n\n    # When registry is specified, check auth entry for that registry in Docker config\n    if [[ -n \"$registry\" ]]; then\n        local docker_config_path=\"${DOCKER_CONFIG:-${HOME:-}/.docker}/config.json\"\n        if [[ -f \"$docker_config_path\" ]] && grep -q \"$registry\" \"$docker_config_path\"; then\n            return 0\n        else\n            return 1\n        fi\n    fi\n\n    # Fallback check when no explicit registry is provided\n    if docker info 2>/dev/null | grep -q \"Username\"; then\n        return 0\n    else\n        return 1\n    fi\n}\n\n# Function: print the SHA256 fingerprint of every authorized key\n#\n# Output matches `ssh-keygen -lf` and the fingerprints GitHub and Phala Cloud\n# display, so an operator can match the boot log against the account's key list.\n# The guest image ships neither ssh-keygen nor a base64 applet (BusyBox has\n# base32 only), so openssl does both the decode and the digest.\nprint_authorized_key_fingerprints() {\n    local file=\"$1\"\n    if ! command -v openssl >/dev/null 2>&1; then\n        echo \"openssl not available; skipping SSH key fingerprints\"\n        return 0\n    fi\n\n    # Locals start unset under bash; initialize so set -u (inherited from\n    # dstack app-compose.sh source) does not trip the read loop guard.\n    local key_type=\"\" blob=\"\" comment=\"\" decoded_bytes=\"\" fingerprint=\"\"\n    # `|| [[ -n \"$key_type\" ]]` keeps the final line when the file does not end\n    # with a newline; read returns non-zero there but still fills the variables.\n    while read -r key_type blob comment || [[ -n \"$key_type\" ]]; do\n        if [[ -z \"$blob\" ]]; then\n            continue\n        fi\n        # openssl exits 0 on undecodable input and emits nothing, which would\n        # otherwise print the SHA256 of the empty string as a real fingerprint.\n        decoded_bytes=$(printf '%s' \"$blob\" | openssl base64 -d -A 2>/dev/null | wc -c || true)\n        if [[ \"$decoded_bytes\" -eq 0 ]]; then\n            echo \"  $key_type <unreadable key>\"\n            continue\n        fi\n        fingerprint=$(printf '%s' \"$blob\" | openssl base64 -d -A 2>/dev/null | openssl dgst -sha256 -binary 2>/dev/null | openssl base64 -A 2>/dev/null | tr -d '=' || true)\n        echo \"  $key_type SHA256:$fingerprint\"\n    done < \"$file\"\n}\n\n# Main logic starts here\necho \"Starting login process...\"\n\n# Optional env vars below use ${VAR-} / ${VAR:-} so this script stays safe when\n# app-compose.sh sources it under `set -euo pipefail` without registry/SSH/root\n# credentials set. Bare \"$VAR\" throws \"unbound variable\" under nounset.\nDOCKER_REGISTRY_TARGET=\"${DSTACK_DOCKER_REGISTRY:-docker.io}\"\n\n# Check if Docker credentials exist\nif [[ -n \"${DSTACK_DOCKER_USERNAME-}\" && -n \"${DSTACK_DOCKER_PASSWORD-}\" ]]; then\n    echo \"Docker credentials found\"\n    echo \"Target Docker registry: $DOCKER_REGISTRY_TARGET\"\n\n    # Check if already logged in\n    if check_docker_login \"${DSTACK_DOCKER_REGISTRY-}\"; then\n        echo \"Already logged in to Docker registry: $DOCKER_REGISTRY_TARGET\"\n    else\n        echo \"Logging in to Docker registry: $DOCKER_REGISTRY_TARGET\"\n        # Login without exposing password in process list\n        if [[ -n \"${DSTACK_DOCKER_REGISTRY-}\" ]]; then\n            if ! echo \"$DSTACK_DOCKER_PASSWORD\" | docker login -u \"$DSTACK_DOCKER_USERNAME\" --password-stdin \"$DSTACK_DOCKER_REGISTRY\"; then\n                echo \"Docker login failed: $DOCKER_REGISTRY_TARGET\"\n                notify_host_hoot_error \"docker login failed\"\n                exit 1\n            fi\n        elif ! echo \"$DSTACK_DOCKER_PASSWORD\" | docker login -u \"$DSTACK_DOCKER_USERNAME\" --password-stdin; then\n            echo \"Docker login failed: $DOCKER_REGISTRY_TARGET\"\n            notify_host_hoot_error \"docker login failed\"\n            exit 1\n        fi\n        echo \"Docker login successful: $DOCKER_REGISTRY_TARGET\"\n    fi\n# Check if AWS ECR credentials exist\nelif [[ -n \"${DSTACK_AWS_ACCESS_KEY_ID-}\" && -n \"${DSTACK_AWS_SECRET_ACCESS_KEY-}\" && -n \"${DSTACK_AWS_REGION-}\" && -n \"${DSTACK_AWS_ECR_REGISTRY-}\" ]]; then\n    echo \"AWS ECR credentials found\"\n\n    # Check if AWS CLI is installed\n    if [ ! -f \"./aws/dist/aws\" ]; then\n        notify_host_hoot_info \"awscli not installed, installing...\"\n        echo \"AWS CLI not installed, installing...\"\n        if ! curl \"https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.24.14.zip\" -o \"awscliv2.zip\"; then\n            echo \"AWS CLI download failed\"\n            notify_host_hoot_error \"awscli install failed\"\n            exit 1\n        fi\n        if ! echo \"6ff031a26df7daebbfa3ccddc9af1450 awscliv2.zip\" | md5sum -c; then\n            echo \"MD5 checksum failed\"\n            notify_host_hoot_error \"awscli install failed\"\n            exit 1\n        fi\n        unzip awscliv2.zip &> /dev/null\n    else\n        echo \"AWS CLI is already installed: ./aws/dist/aws\"\n    fi\n\n    # Set AWS credentials as environment variables\n    export AWS_ACCESS_KEY_ID=\"$DSTACK_AWS_ACCESS_KEY_ID\"\n    export AWS_SECRET_ACCESS_KEY=\"$DSTACK_AWS_SECRET_ACCESS_KEY\"\n    export AWS_DEFAULT_REGION=\"$DSTACK_AWS_REGION\"\n\n    # Set session token if provided (for temporary credentials)\n    if [[ -n \"${DSTACK_AWS_SESSION_TOKEN-}\" ]]; then\n        echo \"AWS session token found, using temporary credentials\"\n        export AWS_SESSION_TOKEN=\"$DSTACK_AWS_SESSION_TOKEN\"\n    fi\n\n    # Test AWS credentials before attempting ECR login\n    echo \"Testing AWS credentials...\"\n    if ! ./aws/dist/aws sts get-caller-identity &> /dev/null; then\n        echo \"AWS credentials test failed\"\n        # For session token credentials, this might be expected if they're expired\n        # Log warning but don't fail startup\n        if [[ -n \"${DSTACK_AWS_SESSION_TOKEN-}\" ]]; then\n            echo \"Warning: AWS temporary credentials may have expired, continuing startup\"\n            notify_host_hoot_info \"AWS temporary credentials may have expired\"\n        else\n            echo \"AWS credentials test failed\"\n            notify_host_hoot_error \"Invalid AWS credentials\"\n            exit 1\n        fi\n    else\n        echo \"Logging in to AWS ECR...\"\n        if ./aws/dist/aws ecr get-login-password --region \"$DSTACK_AWS_REGION\" | docker login --username AWS --password-stdin \"$DSTACK_AWS_ECR_REGISTRY\"; then\n            echo \"AWS ECR login successful\"\n            notify_host_hoot_info \"AWS ECR login successful\"\n        else\n            echo \"AWS ECR login failed\"\n            # For session token credentials, don't fail startup if login fails\n            if [[ -n \"${DSTACK_AWS_SESSION_TOKEN-}\" ]]; then\n                echo \"Warning: AWS ECR login failed with temporary credentials, continuing startup\"\n                notify_host_hoot_info \"AWS ECR login failed with temporary credentials\"\n            else\n                notify_host_hoot_error \"AWS ECR login failed\"\n                exit 1\n            fi\n        fi\n    fi\nfi\n\nperform_cleanup\n\n#\n# GHCR image pull access verification (pure HTTP, no docker daemon)\n#\nif [[ \"$DOCKER_REGISTRY_TARGET\" == \"ghcr.io\" && -n \"${DSTACK_DOCKER_USERNAME-}\" && -n \"${DSTACK_DOCKER_PASSWORD-}\" ]]; then\n    # Strip surrounding quotes from compose image refs. Two tr calls keep the\n    # intent obvious inside this Python string (no quote-concatenation idiom).\n    COMPOSE_IMAGES=$(grep 'image:' /dstack/docker-compose.yaml 2>/dev/null | awk '{print $2}' | tr -d '\"' | tr -d \"'\" || true)\n    HEADER=\"Accept: application/vnd.oci.image.index.v1+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json\"\n    for img in $COMPOSE_IMAGES; do\n        [[ \"$img\" != ghcr.io/* ]] && continue\n        ref=\"${img#ghcr.io/}\"\n        # Digest-pinned refs (repo@sha256:...) use the digest as the manifest\n        # reference; tag refs (repo:tag) use the tag, defaulting to latest.\n        if [[ \"$ref\" == *@* ]]; then\n            repo=\"${ref%%@*}\"\n            reference=\"${ref#*@}\"\n        else\n            repo=\"${ref%%:*}\"\n            reference=\"${ref##*:}\"\n            [[ \"$reference\" == \"$ref\" ]] && reference=\"latest\"\n        fi\n        echo \"Verifying GHCR pull access: $img\"\n        token=$(curl -sf -u \"$DSTACK_DOCKER_USERNAME:$DSTACK_DOCKER_PASSWORD\" \"https://ghcr.io/token?service=ghcr.io&scope=repository:${repo}:pull\" | jq -r '.token // empty' || true)\n        if [[ -z \"${token-}\" ]]; then\n            echo \"ERROR: GHCR token exchange failed for $img\"\n            notify_host_hoot_error \"GHCR token exchange failed: $img\"\n            exit 1\n        fi\n        http_code=$(curl -s -o /dev/null -w \"%{http_code}\" -H \"Authorization: Bearer $token\" -H \"$HEADER\" \"https://ghcr.io/v2/${repo}/manifests/${reference}\")\n        if [[ \"$http_code\" != \"200\" ]]; then\n            echo \"ERROR: GHCR pull access denied for $img (HTTP $http_code)\"\n            notify_host_hoot_error \"GHCR pull access denied: $img (HTTP $http_code)\"\n            exit 1\n        fi\n        echo \"GHCR pull access OK: $img\"\n    done\nfi\n\n#\n# Pull latest images from docker-compose.yaml so existing CVMs pick up new tags.\n# Pull is fail-soft: short-lived tokens may expire between login and pull, but\n# falling back to already-cached images is preferable to blocking boot.\n#\necho \"Images before pull:\"\ndocker images --format '{{.Repository}}:{{.Tag}} {{.ID}} ({{.CreatedSince}})'\n\necho \"Pulling latest images from /dstack/docker-compose.yaml...\"\nif docker compose -f /dstack/docker-compose.yaml pull; then\n    echo \"docker compose pull completed\"\n    notify_host_hoot_info \"docker compose pull completed\"\nelse\n    echo \"WARNING: docker compose pull failed; continuing with existing images\"\n    notify_host_hoot_info \"docker compose pull failed; using existing images\"\nfi\n\necho \"Images after pull:\"\ndocker images --format '{{.Repository}}:{{.Tag}} {{.ID}} ({{.CreatedSince}})'\n\n#\n# Set root password.\n#\necho \"Setting root password..\"\n\n# Check if password files are writable\nPASSWD_WRITABLE=true\nif [ ! -w /etc/passwd ]; then\n    echo \"Warning: /etc/passwd is read-only\"\n    PASSWD_WRITABLE=false\nfi\nif [ ! -w /etc/shadow ]; then\n    echo \"Warning: /etc/shadow is read-only\"\n    PASSWD_WRITABLE=false\nfi\n\nif [ \"$PASSWD_WRITABLE\" = \"false\" ]; then\n    echo \"Skipping password setup due to read-only file system\"\nelse\n    # Check if chpasswd is available\n    if command -v chpasswd >/dev/null 2>&1; then\n        echo \"Using chpasswd method\"\n\n        if [ -n \"${DSTACK_ROOT_PASSWORD-}\" ]; then\n            echo \"Setting root password from user..\"\n            echo \"root:$DSTACK_ROOT_PASSWORD\" | chpasswd\n            unset DSTACK_ROOT_PASSWORD\n            echo \"Root password set/updated from DSTACK_ROOT_PASSWORD\"\n        elif [ -z \"$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2)\" ]; then\n            echo \"Setting random root password..\"\n            # dstack guest busybox lacks head -c (FANCY_HEAD off); dd is the\n            # last pipeline command, so its 0 status masks tr's SIGPIPE exit.\n            DSTACK_ROOT_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null)\n            echo \"root:$DSTACK_ROOT_PASSWORD\" | chpasswd\n            unset DSTACK_ROOT_PASSWORD\n            echo \"Root password set (random auto-init)\"\n        else\n            echo \"Root password already set; no changes.\"\n        fi\n    else\n        # No chpasswd: do not pipe into interactive passwd(1). It often reads\n        # /dev/tty and fails with \"password for root is unchanged\" during boot.\n        echo \"Using passwd method\"\n\n        if [ -n \"${DSTACK_ROOT_PASSWORD-}\" ]; then\n            echo \"Setting root password from user..\"\n            if command -v passwd >/dev/null 2>&1 && passwd --help 2>&1 | grep -q -- '--stdin'; then\n                echo \"$DSTACK_ROOT_PASSWORD\" | passwd --stdin root\n            elif command -v openssl >/dev/null 2>&1; then\n                # busybox passwd has no --stdin; write the SHA-512 crypt hash\n                # into /etc/shadow directly instead of driving interactive passwd.\n                HASHED_ROOT_PASSWORD=$(openssl passwd -6 \"$DSTACK_ROOT_PASSWORD\")\n                sed -i \"s|^root:[^:]*:|root:${HASHED_ROOT_PASSWORD}:|\" /etc/shadow\n                unset HASHED_ROOT_PASSWORD\n            else\n                echo \"Error: cannot set DSTACK_ROOT_PASSWORD non-interactively\"\n                echo \"Need chpasswd, passwd --stdin, or openssl\"\n                exit 1\n            fi\n            unset DSTACK_ROOT_PASSWORD\n            echo \"Root password set/updated from DSTACK_ROOT_PASSWORD\"\n        elif [ -z \"$(grep '^root:' /etc/shadow 2>/dev/null | cut -d: -f2)\" ]; then\n            # Never lock the account here: sshd built without PAM (dstack dev\n            # images) treats a locked account as \"deny all authentication\",\n            # which breaks public-key SSH too. Set a discarded random password\n            # by writing its crypt hash into /etc/shadow directly.\n            if command -v openssl >/dev/null 2>&1; then\n                echo \"Setting random root password..\"\n                DSTACK_ROOT_PASSWORD=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null)\n                HASHED_ROOT_PASSWORD=$(openssl passwd -6 \"$DSTACK_ROOT_PASSWORD\")\n                sed -i \"s|^root:[^:]*:|root:${HASHED_ROOT_PASSWORD}:|\" /etc/shadow\n                unset DSTACK_ROOT_PASSWORD HASHED_ROOT_PASSWORD\n                echo \"Root password set (random auto-init)\"\n            else\n                echo \"Warning: no openssl to hash a random password; leaving root password empty\"\n            fi\n        else\n            echo \"Root password already set; no changes.\"\n        fi\n    fi\nfi\n\n#\n# Set SSH authorized keys\n#\nif mkdir -p /home/root/.ssh 2>/dev/null; then\n    if [[ -n \"${DSTACK_ROOT_PUBLIC_KEY-}\" ]]; then\n        echo \"$DSTACK_ROOT_PUBLIC_KEY\" > /home/root/.ssh/authorized_keys\n        unset DSTACK_ROOT_PUBLIC_KEY\n        echo \"Root public key set\"\n    fi\n    if [[ -n \"${DSTACK_AUTHORIZED_KEYS-}\" ]]; then\n        echo \"$DSTACK_AUTHORIZED_KEYS\" > /home/root/.ssh/authorized_keys\n        unset DSTACK_AUTHORIZED_KEYS\n        echo \"Root authorized_keys set\"\n    fi\n\n    if [[ -f /dstack/user_config ]] && jq empty /dstack/user_config 2>/dev/null; then\n        if [[ $(jq 'has(\"ssh_authorized_keys\")' /dstack/user_config 2>/dev/null) == \"true\" ]]; then\n            # jq -r terminates the value with a newline; jq -j does not, which\n            # both concatenated the next appended key onto the same line and\n            # made line-based counting report one key fewer than the file holds.\n            jq -r '.ssh_authorized_keys' /dstack/user_config >> /home/root/.ssh/authorized_keys\n            # Drop duplicates and the blank line an empty key list produces.\n            sort -u /home/root/.ssh/authorized_keys > /home/root/.ssh/authorized_keys.tmp\n            mv /home/root/.ssh/authorized_keys.tmp /home/root/.ssh/authorized_keys\n            KEY_COUNT=$(grep -c '^[^[:space:]]' /home/root/.ssh/authorized_keys || true)\n            echo \"Set root authorized_keys from user preferences, total $KEY_COUNT keys\"\n            print_authorized_key_fingerprints /home/root/.ssh/authorized_keys\n        fi\n    fi\nelse\n    echo \"Warning: Cannot create /home/root/.ssh directory (read-only file system?)\"\n    echo \"Skipping SSH key setup\"\nfi\n\nif [[ -S /var/run/dstack.sock ]]; then\n    export DSTACK_APP_ID=$(curl -s --unix-socket /var/run/dstack.sock http://dstack/Info | jq -j .app_id)\nelif [[ -S /var/run/tappd.sock ]]; then\n    export DSTACK_APP_ID=$(curl -s --unix-socket /var/run/tappd.sock http://dstack/prpc/Tappd.Info | jq -j .app_id)\nfi\n# Check if DSTACK_GATEWAY_DOMAIN is not set, try to get it from user_config or app-compose.json\n# Priority: user_config > app-compose.json\nif [[ -z \"${DSTACK_GATEWAY_DOMAIN-}\" ]]; then\n    # First try to get from /dstack/user_config if it exists and is valid JSON\n    if [[ -f /dstack/user_config ]] && jq empty /dstack/user_config 2>/dev/null; then\n        if [[ $(jq 'has(\"default_gateway_domain\")' /dstack/user_config 2>/dev/null) == \"true\" ]]; then\n            export DSTACK_GATEWAY_DOMAIN=$(jq -j '.default_gateway_domain' /dstack/user_config)\n        fi\n    fi\n\n    # If still not set, try to get from app-compose.json\n    if [[ -z \"${DSTACK_GATEWAY_DOMAIN-}\" ]] && [[ -f app-compose.json ]] && [[ $(jq 'has(\"default_gateway_domain\")' app-compose.json) == \"true\" ]]; then\n        export DSTACK_GATEWAY_DOMAIN=$(jq -j '.default_gateway_domain' app-compose.json)\n    fi\nfi\nif [[ -n \"${DSTACK_GATEWAY_DOMAIN-}\" && -n \"${DSTACK_APP_ID-}\" ]]; then\n    export DSTACK_APP_DOMAIN=$DSTACK_APP_ID\".\"$DSTACK_GATEWAY_DOMAIN\nfi\n\necho \"----------------------------------------------\"\necho \"Script execution completed\"\necho \"----------------------------------------------\"\n","public_logs":true,"public_sysinfo":true,"public_tcbinfo":true,"runner":"docker-compose","secure_time":false,"storage_fs":"zfs","tproxy_enabled":true}