Parcourir la source

build: add Debian 13 packaging for smartbotic-automation

Single .deb mirroring shadowman-cpp packaging style. Two-stage Docker build
(Dockerfile.base + Dockerfile.build) targeting trixie. build.sh wraps
buildx and integrates with /data/smartbotics-deb-repo for --repo / --sync.
Installs binaries to /usr/bin, configs to /etc/smartbotic-automation,
data to /var/lib/smartbotic-automation, with systemd units for webserver
and runner. Depends on upstream smartbotic-database (>= 1.7.5) and
libsmartbotic-db-client (>= 1.7.5).
fszontagh il y a 2 mois
Parent
commit
47ee8257dc

+ 1 - 0
VERSION

@@ -0,0 +1 @@
+1.0.0

+ 45 - 0
packaging/Dockerfile.base

@@ -0,0 +1,45 @@
+# smartbotic-automation Build Base Image
+# Pre-installed Debian 13 build dependencies for fast iterative builds.
+#
+# Build:
+#   docker buildx build -f packaging/Dockerfile.base \
+#     --build-arg REPO_PASS=<password> \
+#     -t smartbotic-automation-build-base:debian13 .
+
+FROM debian:trixie
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
+    && rm -rf /var/lib/apt/lists/*
+
+# SmartBotics APT repository (for libsmartbotic-db-client-dev)
+ARG REPO_USER=callerai
+ARG REPO_PASS
+COPY packaging/smartbotics-repo.gpg /usr/share/keyrings/smartbotics-repo.gpg
+RUN echo "deb [signed-by=/usr/share/keyrings/smartbotics-repo.gpg] https://repository.smartbotics.ai trixie main" \
+        > /etc/apt/sources.list.d/smartbotics.list \
+    && printf "machine repository.smartbotics.ai\nlogin %s\npassword %s\n" \
+        "$REPO_USER" "$REPO_PASS" > /etc/apt/auth.conf.d/smartbotics.conf \
+    && chmod 600 /etc/apt/auth.conf.d/smartbotics.conf \
+    && apt-get update \
+    && apt-get install -y --no-install-recommends libsmartbotic-db-client-dev \
+    && rm -rf /var/lib/apt/lists/* \
+    && rm -f /etc/apt/auth.conf.d/smartbotics.conf \
+    && rm -f /etc/apt/sources.list.d/smartbotics.list
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+        # Build tools
+        build-essential cmake ninja-build git pkg-config ccache dpkg-dev \
+        # Compression / crypto / uuid
+        libssl-dev zlib1g-dev libbrotli-dev uuid-dev \
+        # gRPC + protobuf
+        protobuf-compiler libprotobuf-dev libgrpc++-dev protobuf-compiler-grpc \
+        # JSON + logging + fmt
+        nlohmann-json3-dev libspdlog-dev libfmt-dev \
+        # curl / websockets
+        libcurl4-openssl-dev libwebsockets-dev \
+        # WebUI build (Node.js + npm)
+        nodejs npm \
+    && rm -rf /var/lib/apt/lists/* \
+    && echo "smartbotic-automation:debian13:$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /etc/smartbotic-automation-build-base

+ 75 - 0
packaging/Dockerfile.build

@@ -0,0 +1,75 @@
+# syntax=docker/dockerfile:1.4
+# smartbotic-automation Package Builder
+# Compiles the C++ binaries + WebUI bundle, then assembles a single .deb.
+#
+# Build:
+#   docker buildx build -f packaging/Dockerfile.build \
+#     --build-arg BASE_IMAGE=smartbotic-automation-build-base:debian13 \
+#     --build-arg BUILD_VERSION=1.0.0 \
+#     --target packages \
+#     --output "type=local,dest=dist/debian13/" .
+
+ARG BASE_IMAGE=debian:trixie
+FROM ${BASE_IMAGE} AS builder
+
+ARG BUILD_VERSION=0.0.0
+ARG BUILD_DEB_REVISION=1
+ARG BUILD_JOBS=8
+ARG BUILD_GIT_COMMIT=unknown
+ENV DEBIAN_FRONTEND=noninteractive
+ENV BUILD_VERSION=${BUILD_VERSION}
+ENV BUILD_DEB_REVISION=${BUILD_DEB_REVISION}
+ENV BUILD_JOBS=${BUILD_JOBS}
+
+ENV CCACHE_DIR=/ccache
+ENV CCACHE_MAXSIZE=5G
+ENV PATH="/usr/lib/ccache:${PATH}"
+
+# Fall back to fresh dep install if the base image isn't our prebuilt one
+RUN if [ ! -f /etc/smartbotic-automation-build-base ]; then \
+        apt-get update && apt-get install -y --no-install-recommends \
+            build-essential cmake ninja-build git pkg-config ccache dpkg-dev \
+            libssl-dev zlib1g-dev libbrotli-dev uuid-dev \
+            protobuf-compiler libprotobuf-dev libgrpc++-dev protobuf-compiler-grpc \
+            nlohmann-json3-dev libspdlog-dev libfmt-dev libcurl4-openssl-dev libwebsockets-dev \
+            nodejs npm libsmartbotic-db-client-dev \
+        && rm -rf /var/lib/apt/lists/*; \
+    else echo "Using pre-built base: $(cat /etc/smartbotic-automation-build-base)"; fi
+
+WORKDIR /build
+
+# WebUI build (cached separately from C++)
+COPY webui/package.json webui/package-lock.json ./webui/
+RUN --mount=type=cache,target=/npm-cache,id=smartbotic-automation-npm-cache \
+    cd webui && npm ci --cache /npm-cache
+COPY VERSION ./
+COPY webui/ ./webui/
+RUN cd webui && npm run build
+
+# C++ build
+COPY CMakeLists.txt ./
+COPY cmake/ ./cmake/
+COPY proto/ ./proto/
+COPY lib/ ./lib/
+COPY src/ ./src/
+COPY config/ ./config/
+COPY nodes/ ./nodes/
+COPY packaging/ ./packaging/
+
+RUN --mount=type=cache,target=/ccache,id=smartbotic-automation-ccache \
+    cmake -B build -G Ninja \
+        -DCMAKE_BUILD_TYPE=Release \
+        -DCMAKE_C_COMPILER_LAUNCHER=ccache \
+        -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
+    && cmake --build build --parallel ${BUILD_JOBS} \
+    && (ccache --show-stats || true)
+
+# ----- Package assembly stage -----
+FROM builder AS packager
+RUN mkdir -p /packages
+RUN chmod +x /build/packaging/scripts/create-deb.sh \
+    && OUTPUT_DIR=/packages /build/packaging/scripts/create-deb.sh
+
+# ----- Final export stage -----
+FROM scratch AS packages
+COPY --from=packager /packages/*.deb /

+ 140 - 0
packaging/build.sh

@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+# smartbotic-automation production build orchestrator.
+# Modeled on /data/shadowman-cpp/packaging/build.sh.
+#
+# Usage:
+#   packaging/build.sh                          # Build with version from VERSION
+#   packaging/build.sh 1.2.0                    # Override version
+#   packaging/build.sh --rebuild-base           # Force rebuild of base image
+#   packaging/build.sh --sync --suite trixie    # Build + publish to APT repo
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+cd "$PROJECT_DIR"
+
+BASE_IMAGE_NAME="smartbotic-automation-build-base:debian13"
+OUTPUT_DIR="${PROJECT_DIR}/dist/debian13"
+BUILD_JOBS="${BUILD_JOBS:-$(nproc)}"
+DEB_REPO_DIR="${DEB_REPO_DIR:-/data/smartbotics-deb-repo}"
+
+RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
+log_info()    { echo -e "${BLUE}[INFO]${NC} $1"; }
+log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
+log_warn()    { echo -e "${YELLOW}[WARN]${NC} $1"; }
+log_error()   { echo -e "${RED}[ERROR]${NC} $1"; }
+
+REBUILD_BASE=false
+NO_CACHE=false
+BUILD_REPO=false
+SYNC_REPO=false
+REPO_SUITE=""
+VERSION=""
+DEB_REVISION="1"
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --rebuild-base)  REBUILD_BASE=true; shift ;;
+        --no-cache)      NO_CACHE=true; shift ;;
+        --deb-revision)  DEB_REVISION="$2"; shift 2 ;;
+        --repo)          BUILD_REPO=true; shift ;;
+        --suite)         REPO_SUITE="$2"; shift 2 ;;
+        --sync)          SYNC_REPO=true; BUILD_REPO=true; shift ;;
+        --help|-h)
+            cat <<EOF
+smartbotic-automation Production Build Script
+
+Usage: $0 [OPTIONS] [VERSION]
+
+Options:
+  --rebuild-base       Force rebuild the base image
+  --no-cache           Build without using BuildKit caches
+  --deb-revision N     Debian revision suffix (default: 1)
+  --repo               Generate APT repo from built packages
+  --suite NAME         Distribution suite for repo (e.g., trixie)
+  --sync               Generate repo and sync to repository.smartbotics.ai
+
+Environment:
+  BUILD_JOBS           Parallel build jobs (default: nproc)
+  DEB_REPO_DIR         Path to smartbotics-deb-repo (default: /data/smartbotics-deb-repo)
+EOF
+            exit 0 ;;
+        *) VERSION="$1"; shift ;;
+    esac
+done
+
+if [ -z "$VERSION" ]; then
+    VERSION="$(tr -d '[:space:]' < VERSION 2>/dev/null || true)"
+    if [ -z "$VERSION" ]; then
+        log_error "Could not read version from VERSION file"; exit 1
+    fi
+fi
+
+if ! command -v docker &>/dev/null; then
+    log_error "Docker not found in PATH"; exit 1
+fi
+
+base_image_exists() { docker image inspect "$BASE_IMAGE_NAME" &>/dev/null; }
+build_base_image() {
+    log_info "Building base image: $BASE_IMAGE_NAME"
+    local repo_pass=""
+    if [ -f "${DEB_REPO_DIR}/.env" ]; then
+        repo_pass=$(grep -oP 'RepoPass:\s*\K.*' "${DEB_REPO_DIR}/.env" | tr -d '[:space:]')
+    fi
+    if [ -z "$repo_pass" ]; then
+        log_error "Could not read RepoPass from ${DEB_REPO_DIR}/.env"; exit 1
+    fi
+    docker buildx build \
+        -f packaging/Dockerfile.base \
+        --build-arg REPO_PASS="$repo_pass" \
+        -t "$BASE_IMAGE_NAME" \
+        .
+    log_success "Base image built"
+}
+
+if $REBUILD_BASE; then
+    build_base_image
+elif base_image_exists; then
+    log_success "Base image cached: $BASE_IMAGE_NAME"
+else
+    build_base_image
+fi
+
+GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
+
+rm -rf "$OUTPUT_DIR"; mkdir -p "$OUTPUT_DIR"
+
+BUILD_ARGS=(
+    -f packaging/Dockerfile.build
+    --build-arg BASE_IMAGE="$BASE_IMAGE_NAME"
+    --build-arg BUILD_VERSION="$VERSION"
+    --build-arg BUILD_DEB_REVISION="$DEB_REVISION"
+    --build-arg BUILD_GIT_COMMIT="$GIT_COMMIT"
+    --build-arg BUILD_JOBS="$BUILD_JOBS"
+    --target packages
+    --output "type=local,dest=$OUTPUT_DIR"
+)
+$NO_CACHE && BUILD_ARGS+=(--no-cache)
+
+log_info "Building smartbotic-automation ${VERSION}-${DEB_REVISION} (commit ${GIT_COMMIT})"
+docker buildx build "${BUILD_ARGS[@]}" .
+
+log_success "Built packages:"
+ls -lh "$OUTPUT_DIR"/*.deb
+
+if $BUILD_REPO; then
+    if [ -z "$REPO_SUITE" ]; then
+        log_error "--suite is required with --repo"; exit 1
+    fi
+    if [ ! -d "$DEB_REPO_DIR" ]; then
+        log_error "Repository dir not found: $DEB_REPO_DIR"; exit 1
+    fi
+    "$DEB_REPO_DIR/scripts/add-packages.sh" "$OUTPUT_DIR"
+    "$DEB_REPO_DIR/scripts/create-repo.sh" --suite "$REPO_SUITE"
+fi
+
+if $SYNC_REPO; then
+    "$DEB_REPO_DIR/scripts/sync-repo.sh"
+fi
+
+log_success "Done."

+ 7 - 0
packaging/deb/config/env

@@ -0,0 +1,7 @@
+# /etc/smartbotic-automation/env
+# Override config values with environment variables here.
+# Example:
+# DATABASE_ADDRESS=localhost:9010
+# JWT_SECRET=...
+# CREDENTIALS_MASTER_KEY=...
+# LOG_LEVEL=info

+ 25 - 0
packaging/deb/config/runner.json

@@ -0,0 +1,25 @@
+{
+  "grpc_port": 9011,
+  "runner_id": "${RUNNER_ID:runner-1}",
+  "webserver_address": "${WEBSERVER_ADDRESS:localhost:8090}",
+  "node_sync_address": "${NODE_SYNC_ADDRESS:localhost:9012}",
+  "credential_service_address": "${CREDENTIAL_SERVICE_ADDRESS:localhost:9013}",
+  "database_address": "${DATABASE_ADDRESS:localhost:9010}",
+  "max_message_size_mb": 64,
+  "node_sync": {
+    "enabled": true,
+    "reconnect_interval_ms": 5000
+  },
+  "registration": {
+    "heartbeat_interval_sec": 10,
+    "max_concurrent_executions": 10
+  },
+  "execution": {
+    "default_timeout_ms": 60000,
+    "max_memory_per_script_mb": 64
+  },
+  "logging": {
+    "level": "${LOG_LEVEL:info}",
+    "file": "${LOG_FILE:/var/log/smartbotic-automation/runner.log}"
+  }
+}

+ 29 - 0
packaging/deb/config/webserver.json

@@ -0,0 +1,29 @@
+{
+  "http_port": 8090,
+  "node_sync_port": 9012,
+  "credential_service_port": 9013,
+  "static_files_path": "${WEBUI_PATH:/usr/share/smartbotic-automation/webui}",
+  "database_address": "${DATABASE_ADDRESS:localhost:9010}",
+  "runners": {
+    "load_balancing": "least-connections",
+    "heartbeat_timeout_sec": 30,
+    "offline_removal_sec": 60
+  },
+  "auth": {
+    "jwt_secret": "${JWT_SECRET:CHANGE_ME_BEFORE_PRODUCTION}",
+    "access_token_lifetime_sec": 900,
+    "refresh_token_lifetime_sec": 604800
+  },
+  "credentials": {
+    "master_key": "${CREDENTIALS_MASTER_KEY:CHANGE_ME_BEFORE_PRODUCTION}",
+    "pbkdf2_iterations": 100000
+  },
+  "cors": {
+    "enabled": true,
+    "origins": ["*"]
+  },
+  "logging": {
+    "level": "${LOG_LEVEL:info}",
+    "file": "${LOG_FILE:/var/log/smartbotic-automation/webserver.log}"
+  }
+}

+ 58 - 0
packaging/deb/scripts/postinst

@@ -0,0 +1,58 @@
+#!/bin/bash
+# postinst for smartbotic-automation
+set -e
+
+SM_USER="smartbotic-automation"
+SM_GROUP="smartbotic-automation"
+SM_HOME="/home/smartbotic-automation"
+SERVICES="smartbotic-automation-webserver smartbotic-automation-runner"
+
+case "$1" in
+    configure)
+        # System group + user (idempotent)
+        if ! getent group "$SM_GROUP" >/dev/null 2>&1; then
+            addgroup --system "$SM_GROUP"
+        fi
+        if ! getent passwd "$SM_USER" >/dev/null 2>&1; then
+            adduser --system --ingroup "$SM_GROUP" --home "$SM_HOME" \
+                --shell /usr/sbin/nologin \
+                --gecos "SmartBotic Automation Service User" "$SM_USER"
+        fi
+
+        install -d -o "$SM_USER" -g "$SM_GROUP" -m 0750 "$SM_HOME"
+        install -d -o "$SM_USER" -g "$SM_GROUP" -m 0750 /var/lib/smartbotic-automation
+        install -d -o "$SM_USER" -g "$SM_GROUP" -m 0750 /var/log/smartbotic-automation
+        install -d -o root      -g "$SM_GROUP" -m 0750 /etc/smartbotic-automation
+
+        # systemd
+        if [ -d /run/systemd/system ]; then
+            systemctl daemon-reload
+        fi
+
+        if [ -z "$2" ]; then
+            # First install: enable but do NOT start (admin must configure
+            # database_address first; the upstream DB port is deployment-specific).
+            for svc in $SERVICES; do
+                deb-systemd-helper enable "$svc.service" >/dev/null || true
+            done
+            echo
+            echo "smartbotic-automation: services are enabled but not started."
+            echo "  WARNING: webserver.json contains placeholder values for jwt_secret and"
+            echo "           credentials.master_key. Replace 'CHANGE_ME_BEFORE_PRODUCTION'"
+            echo "           with strong unique secrets before starting the service in production."
+            echo
+            echo "  Configuration steps:"
+            echo "  1. Edit /etc/smartbotic-automation/webserver.json (set database_address, jwt_secret, credentials.master_key)"
+            echo "  2. Edit /etc/smartbotic-automation/runner.json (set database_address)"
+            echo "  3. systemctl start smartbotic-automation-webserver smartbotic-automation-runner"
+            echo
+        else
+            # Upgrade path: try-restart any active units
+            for svc in $SERVICES; do
+                deb-systemd-invoke try-restart "$svc.service" >/dev/null 2>&1 || true
+            done
+        fi
+        ;;
+esac
+
+exit 0

+ 25 - 0
packaging/deb/scripts/postrm

@@ -0,0 +1,25 @@
+#!/bin/bash
+# postrm for smartbotic-automation
+set -e
+
+SM_USER="smartbotic-automation"
+SM_GROUP="smartbotic-automation"
+
+case "$1" in
+    purge)
+        rm -rf /var/lib/smartbotic-automation /var/log/smartbotic-automation /etc/smartbotic-automation
+        if getent passwd "$SM_USER" >/dev/null 2>&1; then
+            deluser --system "$SM_USER" >/dev/null 2>&1 || true
+        fi
+        if getent group "$SM_GROUP" >/dev/null 2>&1; then
+            delgroup --system "$SM_GROUP" >/dev/null 2>&1 || true
+        fi
+        ;;
+    remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+        if [ -d /run/systemd/system ]; then
+            systemctl daemon-reload >/dev/null 2>&1 || true
+        fi
+        ;;
+esac
+
+exit 0

+ 11 - 0
packaging/deb/scripts/preinst

@@ -0,0 +1,11 @@
+#!/bin/bash
+# preinst for smartbotic-automation — minimal pre-install checks
+set -e
+
+case "$1" in
+    install|upgrade)
+        # No-op; postinst handles user/group + dirs
+        ;;
+esac
+
+exit 0

+ 18 - 0
packaging/deb/scripts/prerm

@@ -0,0 +1,18 @@
+#!/bin/bash
+# prerm for smartbotic-automation
+set -e
+
+SERVICES="smartbotic-automation-webserver smartbotic-automation-runner"
+
+case "$1" in
+    remove|deconfigure)
+        if [ -d /run/systemd/system ]; then
+            for svc in $SERVICES; do
+                deb-systemd-invoke stop "$svc.service" >/dev/null 2>&1 || true
+                deb-systemd-helper disable "$svc.service" >/dev/null 2>&1 || true
+            done
+        fi
+        ;;
+esac
+
+exit 0

+ 21 - 0
packaging/deb/systemd/smartbotic-automation-runner.service

@@ -0,0 +1,21 @@
+[Unit]
+Description=SmartBotic Automation — Runner (workflow execution)
+Documentation=https://smartbotics.ai
+After=network-online.target smartbotic-automation-webserver.service
+Wants=network-online.target smartbotic-automation-webserver.service
+
+[Service]
+Type=simple
+User=smartbotic-automation
+Group=smartbotic-automation
+ExecStart=/usr/bin/smartbotic-runner --config /etc/smartbotic-automation/runner.json
+WorkingDirectory=/var/lib/smartbotic-automation
+EnvironmentFile=-/etc/smartbotic-automation/env
+Restart=on-failure
+RestartSec=5
+TimeoutStopSec=15
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=multi-user.target

+ 21 - 0
packaging/deb/systemd/smartbotic-automation-webserver.service

@@ -0,0 +1,21 @@
+[Unit]
+Description=SmartBotic Automation — Webserver (HTTP/WS API)
+Documentation=https://smartbotics.ai
+After=network-online.target smartbotic-database.service
+Wants=network-online.target smartbotic-database.service
+
+[Service]
+Type=simple
+User=smartbotic-automation
+Group=smartbotic-automation
+ExecStart=/usr/bin/smartbotic-webserver --config /etc/smartbotic-automation/webserver.json
+WorkingDirectory=/var/lib/smartbotic-automation
+EnvironmentFile=-/etc/smartbotic-automation/env
+Restart=on-failure
+RestartSec=5
+TimeoutStopSec=15
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=multi-user.target

+ 16 - 0
packaging/deb/templates/control.smartbotic-automation

@@ -0,0 +1,16 @@
+Package: smartbotic-automation
+Version: {{VERSION}}
+Architecture: amd64
+Maintainer: Ferenc Szontagh <ferenc.szontagh@smartbotics.ai>
+Description: SmartBotic Automation — workflow execution platform
+ HTTP/WebSocket API server, workflow runner, and built-in JavaScript node
+ set for the SmartBotic automation platform. Persists workflows, executions,
+ credentials and runtime state in the upstream smartbotic-database daemon
+ (via libsmartbotic-db-client). Includes a React WebUI bundle.
+ Proprietary software by SmartBotics Kft.
+Homepage: https://smartbotics.ai
+Section: contrib/utils
+Priority: optional
+License: proprietary
+Depends: smartbotic-database (>= 1.7.5), libsmartbotic-db-client (>= 1.7.5), libc6 (>= 2.17), adduser, libssl3t64, libprotobuf32t64, libgrpc++1.51t64, libspdlog1.15, libfmt10, libuuid1, libcurl4t64, zlib1g, libbrotli1, libwebsockets19t64, nodejs
+Installed-Size: {{INSTALLED_SIZE}}

+ 100 - 0
packaging/scripts/create-deb.sh

@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+# Assemble the smartbotic-automation .deb tree and run dpkg-deb.
+# Runs INSIDE the Docker build container after `cmake --build`.
+set -euo pipefail
+
+PROJECT_DIR="${PROJECT_DIR:-/build}"
+OUTPUT_DIR="${OUTPUT_DIR:-/packages}"
+VERSION="${BUILD_VERSION:-$(cat "$PROJECT_DIR/VERSION")}"
+REVISION="${BUILD_DEB_REVISION:-1}"
+ARCH="amd64"
+NAME="smartbotic-automation"
+PKG_VERSION="${VERSION}-${REVISION}"
+
+PKG_ROOT="$(mktemp -d)"
+trap "rm -rf $PKG_ROOT" EXIT
+
+# Directory layout inside the .deb
+install -d "$PKG_ROOT/DEBIAN"
+install -d "$PKG_ROOT/usr/bin"
+install -d "$PKG_ROOT/usr/share/$NAME/webui"
+install -d "$PKG_ROOT/usr/share/$NAME/nodes"
+install -d "$PKG_ROOT/usr/share/doc/$NAME"
+install -d "$PKG_ROOT/etc/$NAME"
+install -d "$PKG_ROOT/usr/lib/systemd/system"
+install -d "$PKG_ROOT/var/lib/$NAME"
+install -d "$PKG_ROOT/var/log/$NAME"
+
+# Binaries (stripped)
+install -m 0755 "$PROJECT_DIR/build/smartbotic-webserver"     "$PKG_ROOT/usr/bin/"
+install -m 0755 "$PROJECT_DIR/build/smartbotic-runner"        "$PKG_ROOT/usr/bin/"
+install -m 0755 "$PROJECT_DIR/build/smartbotic-migrate-nodes" "$PKG_ROOT/usr/bin/"
+strip --strip-all "$PKG_ROOT/usr/bin/smartbotic-webserver" \
+                  "$PKG_ROOT/usr/bin/smartbotic-runner" \
+                  "$PKG_ROOT/usr/bin/smartbotic-migrate-nodes" || true
+
+# WebUI bundle (normalize permissions: dirs 0755, files 0644)
+cp -r "$PROJECT_DIR/webui/dist/." "$PKG_ROOT/usr/share/$NAME/webui/"
+find "$PKG_ROOT/usr/share/$NAME/webui" -type d -exec chmod 0755 {} +
+find "$PKG_ROOT/usr/share/$NAME/webui" -type f -exec chmod 0644 {} +
+
+# Default workflow nodes (normalize permissions: dirs 0755, files 0644)
+cp -r "$PROJECT_DIR/nodes/." "$PKG_ROOT/usr/share/$NAME/nodes/"
+find "$PKG_ROOT/usr/share/$NAME/nodes" -type d -exec chmod 0755 {} +
+find "$PKG_ROOT/usr/share/$NAME/nodes" -type f -exec chmod 0644 {} +
+
+# Default configs (will become conffiles via DEBIAN/conffiles)
+install -m 0644 "$PROJECT_DIR/packaging/deb/config/webserver.json" "$PKG_ROOT/etc/$NAME/webserver.json"
+install -m 0644 "$PROJECT_DIR/packaging/deb/config/runner.json"    "$PKG_ROOT/etc/$NAME/runner.json"
+install -m 0644 "$PROJECT_DIR/packaging/deb/config/env"            "$PKG_ROOT/etc/$NAME/env"
+
+# Systemd units (modern path: usr/lib, not lib)
+install -m 0644 "$PROJECT_DIR/packaging/deb/systemd/$NAME-webserver.service" \
+                "$PKG_ROOT/usr/lib/systemd/system/"
+install -m 0644 "$PROJECT_DIR/packaging/deb/systemd/$NAME-runner.service" \
+                "$PKG_ROOT/usr/lib/systemd/system/"
+
+# Minimal copyright file (required by lintian)
+cat > "$PKG_ROOT/usr/share/doc/$NAME/copyright" <<EOF
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: SmartBotic Automation
+Upstream-Contact: Ferenc Szontagh <ferenc.szontagh@smartbotics.ai>
+Source: https://smartbotics.ai
+
+Files: *
+Copyright: $(date +%Y) SmartBotics Kft.
+License: proprietary
+ Proprietary software. All rights reserved.
+ Unauthorized copying, distribution or modification is prohibited.
+EOF
+
+# Minimal changelog (required by lintian for non-native packages)
+printf "smartbotic-automation (%s) trixie; urgency=medium\n\n  * Release %s\n\n -- Ferenc Szontagh <ferenc.szontagh@smartbotics.ai>  %s\n" \
+    "${PKG_VERSION}" "${VERSION}" "$(date -R)" \
+    | gzip -9 -n > "$PKG_ROOT/usr/share/doc/$NAME/changelog.Debian.gz"
+
+# Maintainer scripts
+for script in preinst postinst prerm postrm; do
+    install -m 0755 "$PROJECT_DIR/packaging/deb/scripts/$script" "$PKG_ROOT/DEBIAN/$script"
+done
+
+# Conffiles
+cat > "$PKG_ROOT/DEBIAN/conffiles" <<EOF
+/etc/$NAME/webserver.json
+/etc/$NAME/runner.json
+/etc/$NAME/env
+EOF
+
+# Render control file from template
+INSTALLED_SIZE=$(du -sk "$PKG_ROOT" --exclude=DEBIAN | cut -f1)
+sed -e "s/{{VERSION}}/${PKG_VERSION}/g" \
+    -e "s/{{INSTALLED_SIZE}}/${INSTALLED_SIZE}/g" \
+    "$PROJECT_DIR/packaging/deb/templates/control.smartbotic-automation" \
+    > "$PKG_ROOT/DEBIAN/control"
+
+# Build the .deb
+mkdir -p "$OUTPUT_DIR"
+DEB_FILE="$OUTPUT_DIR/${NAME}_${PKG_VERSION}_${ARCH}.deb"
+dpkg-deb --build --root-owner-group "$PKG_ROOT" "$DEB_FILE"
+echo "Built: $DEB_FILE"
+ls -lh "$DEB_FILE"

BIN
packaging/smartbotics-repo.gpg