greentic-setup 0.4.1

End-to-end bundle setup engine for the Greentic platform — pack discovery, QA-driven configuration, secrets persistence, and bundle lifecycle management
Documentation

greentic-setup

End-to-end bundle setup engine for the Greentic platform.

Provides a library and CLI for discovering, configuring, and deploying Greentic bundles — including pack resolution, QA-driven setup (via greentic-qa), secrets persistence, webhook registration, admin API types, hot reload, and adaptive card setup flows.

Dual-mode:

  • Library — core APIs for programmatic use by greentic-operator, runner, and other tools
  • CLI binarygreentic-setup bundle ... commands for bundle lifecycle management (invoked via gtc setup passthrough)

Features

  • Setup engine — plan and execute create/update/remove workflows for bundles
  • Pack discovery — scan bundle directories for .gtpack files, read CBOR/JSON manifests
  • QA-driven configuration — interactive CLI prompts and adaptive card rendering (via greentic-qa FormSpec)
  • Conditional questionsvisible_if expressions for dynamic form flows (powered by qa-spec Expr evaluation)
  • Secrets management — persist secrets and config to dev store for all pack types
  • Admin API types — mTLS-secured request/response types for runtime bundle lifecycle management
  • Hot reload — diff-based bundle change detection and reload planning
  • Adaptive card setup — session management and link generation for card-based onboarding
  • Full i18n — all user-facing strings via greentic-i18n

Architecture

greentic-setup
├── bin/
│   └── greentic_setup  CLI binary (simple mode + bundle subcommands)
├── cli_i18n        CLI localization helper (CliI18n wrapper)
├── engine          SetupEngine: plan → execute orchestration
├── plan            SetupPlan, SetupStep, SetupMode, metadata types
├── bundle          Bundle directory creation, gmap paths, provider registry
├── bundle_source   Pack source resolution (file://, oci://, path)
├── discovery       Pack discovery from .gtpack files (CBOR + JSON manifests)
├── gtbundle        Portable .gtbundle archive format (zip/squashfs)
├── qa/
│   ├── bridge      Provider QA JSON → FormSpec conversion (+ visible_if)
│   ├── wizard      Interactive wizard, validation, card rendering
│   └── persist     Secrets + config persistence to dev store
├── secrets         Dev store path resolution, SecretsSetup
├── setup_input     Setup answers loading from JSON/YAML files
├── setup_to_formspec  Legacy setup.yaml → FormSpec conversion
├── secret_name     Canonical secret name/URI normalization
├── admin/
│   ├── tls         AdminTlsConfig for mTLS endpoint
│   └── routes      Admin API request/response types
├── reload          BundleDiff, ReloadPlan, ReloadAction
├── card_setup      CardSetupSession, SetupLinkConfig
└── webhook         Webhook URL validation helpers

Previously embedded in greentic-operator (~5,000 lines). Extracted as a standalone library so it can be reused by the operator, runner, CLI tools, and admin APIs.

Usage

Simple Mode (recommended)

The simplest way to use greentic-setup — just point it at a bundle:

# Interactive wizard - prompts for all configuration
greentic-setup ./my-bundle

# Preview what will happen (dry-run)
greentic-setup --dry-run ./my-bundle

# Generate answers template file
greentic-setup --dry-run --emit-answers answers.yaml ./my-bundle

# Apply answers from file (non-interactive)
greentic-setup --answers answers.yaml ./my-bundle

# Works with .gtbundle archives too
greentic-setup ./my-bundle.gtbundle

Options:

Flag Description
--dry-run Preview setup plan without executing
--emit-answers <FILE> Generate answers template to file
-a, --answers <FILE> Apply answers from file
-t, --tenant <TENANT> Tenant identifier (default: demo)
--team <TEAM> Team identifier
-e, --env <ENV> Environment: dev/staging/prod (default: dev)

Via gtc passthrough

gtc setup ./my-bundle
gtc setup --answers answers.yaml ./my-bundle
gtc setup bundle status --bundle ./my-bundle

Advanced: Bundle Subcommands

For fine-grained control over bundle lifecycle:

# Initialize a new bundle
greentic-setup bundle init ./my-bundle --name "My Bundle"

# Add a pack to the bundle
greentic-setup bundle add telegram-pack.gtpack --bundle ./my-bundle

# Interactive setup (wizard mode)
greentic-setup bundle setup --bundle ./my-bundle

# Setup with answers file
greentic-setup bundle setup --bundle ./my-bundle --answers answers.yaml

# Generate answers template
greentic-setup bundle setup --bundle ./my-bundle --emit-answers answers.yaml

# Setup specific provider
greentic-setup bundle setup messaging-telegram --bundle ./my-bundle --answers telegram.yaml

# Update provider configuration
greentic-setup bundle update messaging-telegram --bundle ./my-bundle --answers telegram.yaml

# Remove a provider
greentic-setup bundle remove messaging-telegram --bundle ./my-bundle --force

# Build portable bundle (.gtbundle archive)
greentic-setup bundle build --bundle ./my-bundle --out ./dist

# List packs in bundle
greentic-setup bundle list --bundle ./my-bundle --domain messaging

# Show bundle status
greentic-setup bundle status --bundle ./my-bundle --format json

As a library

use greentic_setup::{SetupEngine, SetupMode};
use greentic_setup::engine::{SetupRequest, SetupConfig};
use greentic_setup::plan::TenantSelection;
use std::path::PathBuf;

let config = SetupConfig {
    tenant: "demo".to_string(),
    team: Some("default".to_string()),
    env: "dev".to_string(),
    offline: false,
    verbose: true,
};

let engine = SetupEngine::new(config);

let request = SetupRequest {
    bundle: PathBuf::from("./demo-bundle"),
    bundle_name: Some("My Bundle".to_string()),
    pack_refs: vec!["oci://ghcr.io/greentic-ai/telegram:latest".to_string()],
    tenants: vec![TenantSelection {
        tenant: "demo".to_string(),
        team: Some("default".to_string()),
        allow_paths: vec!["packs/messaging-telegram".to_string()],
    }],
    // ... other fields default
    ..Default::default()
};

let plan = engine.plan(SetupMode::Create, &request, false).unwrap();
engine.print_plan(&plan);

QA-driven setup

use greentic_setup::qa::wizard::run_qa_setup;
use std::path::Path;

let (answers, form_spec) = run_qa_setup(
    Path::new("providers/messaging/messaging-telegram.gtpack"),
    "messaging-telegram",
    None,       // no pre-loaded answers
    true,       // interactive
    None,       // no pre-built FormSpec
).unwrap();

Bundle-level static hosting policy

Answers files can now carry bundle/platform static hosting policy separately from provider answers:

bundle_source: ./my-bundle
env: dev
tenant: demo
platform_setup:
  static_routes:
    public_web_enabled: false
    public_surface_policy: disabled
    default_route_prefix_policy: pack_declared
    tenant_path_policy: pack_declared
setup_answers:
  messaging-telegram:
    bot_token: "your-bot-token"

When setup executes, it persists the normalized bundle-level artifact to state/config/platform/static-routes.json.

Pack discovery

use greentic_setup::discovery;
use std::path::Path;

let result = discovery::discover(Path::new("./demo-bundle")).unwrap();
println!("Found {} providers", result.providers.len());
for p in &result.providers {
    println!("  {} ({}) @ {}", p.provider_id, p.domain, p.pack_path.display());
}

Hot reload diffing

use greentic_setup::reload::{diff_discoveries, plan_reload};
use std::path::Path;

let prev = discovery::discover(Path::new("./bundle-v1")).unwrap();
let curr = discovery::discover(Path::new("./bundle-v2")).unwrap();
let diff = diff_discoveries(&prev, &curr);

if !diff.is_empty() {
    let plan = plan_reload(Path::new("./bundle-v2"), &diff);
    println!("{} reload actions needed", plan.actions.len());
}

Admin API types

use greentic_setup::admin::{AdminTlsConfig, BundleDeployRequest, AdminResponse};

let tls = AdminTlsConfig {
    server_cert: "certs/server.crt".into(),
    server_key: "certs/server.key".into(),
    client_ca: "certs/ca.crt".into(),
    allowed_clients: vec!["CN=greentic-admin".into()],
    port: 8443,
};
tls.validate().unwrap();

From CLI (via greentic-operator)

# Interactive setup
gtc op demo wizard --execute

# Automated setup from answers file
gtc op demo start --setup-input answers.json

# QA setup wizard
gtc op demo setup-wizard

Modules

Module Description
engine SetupEngine — orchestrates plan building for create/update/remove
plan Plan types: SetupPlan, SetupStep, SetupMode, metadata
bundle Bundle directory structure creation and management
bundle_source Pack source resolution (file://, oci://, path)
discovery Pack discovery from .gtpack files (CBOR + JSON manifests)
gtbundle Portable .gtbundle archive format (zip/squashfs)
cli_i18n CLI localization helper for user-facing messages
qa::bridge Provider QA JSON → FormSpec conversion with visible_if support
qa::wizard Interactive wizard, validation, card rendering, visibility evaluation
qa::persist Secrets + config persistence (visibility-aware)
secrets Dev store path resolution, SecretsSetup
setup_input Setup answers loading from JSON/YAML
setup_to_formspec Legacy setup.yaml → FormSpec conversion
secret_name Canonical secret name normalization
admin::tls AdminTlsConfig for mTLS endpoints
admin::routes Admin API request/response types
reload BundleDiff, ReloadPlan, ReloadAction for hot reload
card_setup CardSetupSession, SetupLinkConfig for adaptive card flows
webhook Webhook URL validation helpers

Secret URI Format

secrets://{env}/{tenant}/{team}/{provider_id}/{key}
          dev    demo      _    messaging-telegram  bot_token
  • Team: None / "default" / empty → "_" (wildcard)
  • Key: normalized via canonical_secret_name() (lowercase, underscores, collapsed)

Conditional Questions (visible_if)

Provider QA specs can include visible_if expressions:

{
  "id": "redis_password",
  "label": "Redis Password",
  "required": true,
  "secret": true,
  "visible_if": {"field": "redis_auth_enabled", "eq": "true"}
}

Supported formats:

  • {"field": "q1", "eq": "value"} — equality check
  • {"field": "q1"} — truthy check
  • Full qa-spec Expr JSON (And/Or/Not/Eq/Ne/Lt/Gt/IsSet)

Invisible questions are:

  • Skipped during interactive prompts
  • Skipped during validation (required check)
  • Not persisted as secrets

CI and Releases

Local checks

bash ci/local_check.sh

Runs: fmt → clippy → test → build → doc → package dry-run.

Cutting a release

  1. Bump version in Cargo.toml
  2. Commit and tag: git tag v0.4.x
  3. Push tag: git push origin v0.4.x
  4. publish.yml workflow triggers → publishes to crates.io

Required GitHub secrets: CARGO_REGISTRY_TOKEN

i18n

tools/i18n.sh all        # translate + validate + status
tools/i18n.sh translate  # generate translations (200 per batch)

Requires greentic-i18n repo at ../greentic-i18n/.

Documentation

Document Description
Demo Guide Complete guide for creating and running bundles
Admin API Reference Full admin endpoint documentation with examples
Adaptive Cards Guide Card-based setup flow with security details
mTLS Setup Guide Certificate generation and configuration
Demo Features Advanced features demo (conditional QA, secrets, hot reload)
Manual Testing Step-by-step testing guide for all features

API Reference (rustdoc)

cargo doc --open

License

MIT