feat(airgap): multi-source import (server path, URL, file upload) with overlay UX

Import now supports three sources: server-side path (USB/NFS volumes),
backend URL download, and browser file upload. Export/import workflows
refactored from routed pages to overlay dialogs. Docs updated with
volume mount instructions and source comparison table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-07 15:33:21 +03:00
parent 6e72ad844e
commit 3a95f315bd
11 changed files with 1641 additions and 882 deletions

View File

@@ -50,6 +50,24 @@ Rollback resistance is enforced via:
- A monotonicity checker (`IVersionMonotonicityChecker`) that compares incoming bundle versions to the active version.
- Optional force-activate path requiring a human reason, stored alongside the activation record.
## Import sources
The importer accepts bundles from three sources:
| Source | Transport | Use case |
|---|---|---|
| **Server path** | Container reads from the import staging volume at `/var/lib/concelier/import/`. Host-side location controlled by `STELLAOPS_AIRGAP_IMPORT_DIR` (default `./airgap-import`). | USB drives, NFS mounts, large bundles (GB+). Zero browser transfer. |
| **URL** | Backend fetches the bundle from an internal URL directly. | Internal mirrors, S3, artifact registries. |
| **File upload** | Browser uploads via multipart/form-data. | Small bundles only; limited by browser memory. |
For Docker Compose deployments, the import volume is mounted read-only:
```yaml
# In docker-compose.stella-ops.yml (concelier service):
- ${STELLAOPS_AIRGAP_IMPORT_DIR:-./airgap-import}:/var/lib/concelier/import:ro
```
For Kubernetes deployments, mount an emptyDir, hostPath, or PVC at `/var/lib/concelier/import` and pre-stage bundles via init containers or sidecar pods.
## Storage model
The importer writes deterministic metadata that other components can query:

View File

@@ -13,9 +13,38 @@ Scope: deploy sealed-mode Concelier evidence bundles using deterministic NDJSON
- Concelier WebService running with `concelier:features:airgap` enabled.
- No external egress; only local file system allowed for bundle path.
- PostgreSQL indexes applied (`advisory_observations`, `advisory_linksets` tables).
- **Import volume mounted**: The Concelier container must have the import staging directory mounted. In Docker Compose this is configured via `STELLAOPS_AIRGAP_IMPORT_DIR` (defaults to `./airgap-import` on the host, mounted read-only at `/var/lib/concelier/import` inside the container).
## Import Volume Setup (Docker Compose)
The Concelier service mounts an import staging volume for air-gapped bundle ingestion.
Bundles placed on the host at `$STELLAOPS_AIRGAP_IMPORT_DIR` are visible inside the container at `/var/lib/concelier/import/`.
```bash
# Default: ./airgap-import relative to the compose directory
mkdir -p devops/compose/airgap-import
# Override: point to USB, NFS mount, or any host directory
export STELLAOPS_AIRGAP_IMPORT_DIR=/media/usb/stellaops-bundles
docker compose -f docker-compose.stella-ops.yml up -d concelier
```
The volume is mounted **read-only** — the Concelier service reads and validates bundles but never modifies the staging directory. The environment variable `CONCELIER_IMPORT__STAGINGROOT` tells the service where to find staged bundles inside the container.
### UI Console Import
The Feeds & Airgap console (Ops → Operations → Feeds & Airgap → Airgap Bundles → Import) supports three import sources:
| Source | Description | Volume needed? |
|---|---|---|
| **Server Path** | Path inside the container (e.g. `/var/lib/concelier/import/bundle.tar.gz`). Zero browser transfer. | Yes |
| **URL** | Internal URL the backend downloads directly. | No |
| **File Upload** | Browser drag-and-drop for small bundles. | No |
For large bundles (GB+), use **Server Path** or **URL** — never browser upload.
## Steps
1) Transfer bundle directory to offline controller host.
1) Stage the bundle onto the import volume (or transfer to the offline controller host).
2) Verify hashes:
```bash
sha256sum concelier-airgap.ndjson | diff - <(jq -r .bundleSha256 bundle.manifest.json)