diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..90308884 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "frontend-design@claude-plugins-official": true + } +} diff --git a/.gitignore b/.gitignore index 8ff7ad07..32086046 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,8 @@ docs/*.sql config-to-validate.yaml *.bak .claude/settings.local.json + +plan/ +.stacker +done.txt +post-deploy-ran.txt diff --git a/Cargo.lock b/Cargo.lock index 07768711..3df428c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5064,6 +5064,70 @@ version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +[[package]] +name = "protoc-bin-vendored" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + [[package]] name = "pulldown-cmark" version = "0.9.6" @@ -6576,7 +6640,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stacker" -version = "0.2.8" +version = "0.2.9" dependencies = [ "actix", "actix-casbin-auth", @@ -6619,6 +6683,7 @@ dependencies = [ "prometheus", "prost", "prost-types", + "protoc-bin-vendored", "rand 0.8.5", "redis", "regex", diff --git a/Cargo.toml b/Cargo.toml index 1d65b5b2..127ac5df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ actix = "0.13.5" actix-web-actors = "4.3.1" chrono = { version = "0.4.39", features = ["serde", "clock"] } config = "0.13.4" -reqwest = { version = "0.11.23", features = ["json", "blocking", "stream"] } +reqwest = { version = "0.11.23", features = ["json", "blocking", "stream", "native-tls"] } serde = { version = "1.0.195", features = ["derive"] } tokio = { version = "1.28.1", features = ["full"] } tracing = { version = "0.1.40", features = ["log"] } @@ -118,6 +118,7 @@ indexmap = ["dep:indexmap"] explain = ["actix-casbin-auth/explain", "actix-casbin-auth/logging"] [build-dependencies] +protoc-bin-vendored = "3" tonic-build = "0.11" [dev-dependencies] diff --git a/README.md b/README.md index 3ff2d87c..e25bf70f 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,10 @@ The end-user tool. No server required for local deploys. | `stacker status` | Show running containers and health | | `stacker logs` | View container logs (`--follow`, `--service`, `--tail`) | | `stacker secrets` | Manage local `.env` secrets or remote Vault-backed `service` / `server` secrets | -| `stacker list deployments` | List deployments on the Stacker server | +| `stacker list deployments` / `stacker deployments` | List deployments on the Stacker server | +| `stacker list servers` / `stacker servers` | List saved servers | +| `stacker list clouds` / `stacker clouds` | List saved cloud credentials | +| `stacker list ssh-keys` / `stacker ssh-keys` | List per-server SSH key status | | `stacker destroy` | Tear down the deployed stack | | `stacker config validate` | Validate `stacker.yml` syntax | | `stacker config show` | Show resolved configuration | @@ -181,6 +184,8 @@ The end-user tool. No server required for local deploys. | `stacker service list` | List available service templates (20+ built-in) | | `stacker agent health` | Check Status Panel agent connectivity and health | | `stacker agent status` | Display agent snapshot — containers, versions, uptime | +| `stacker agent list apps` / `stacker agent apps` | List apps for the target deployment | +| `stacker agent list containers` / `stacker agent containers` | List containers on the target server | | `stacker agent logs ` | Retrieve container logs from the remote agent | | `stacker agent restart ` | Restart a container via the agent | | `stacker agent deploy-app` | Deploy or update an app container on the target server. `--runtime kata\|runc` selects container runtime; `--env ` selects the deploy environment/profile | @@ -202,6 +207,7 @@ The end-user tool. No server required for local deploys. | `stacker pipe replay ` | Re-run a previous pipe execution | | `stacker target [local\|cloud\|server]` | Switch deployment target mode | | `stacker env [local\|dev\|prod]` | Show or persist the active deploy environment/profile used by app-only updates | +| `stacker whoami` | Show the active login, subscription plan, and current project deployment context | | `stacker submit` | Package current stack and submit to marketplace for review | | `stacker marketplace status` | Check submission status for your marketplace templates | | `stacker marketplace logs ` | Show review comments and history for a submission | diff --git a/build.rs b/build.rs index 5577c83a..8d7bcbe8 100644 --- a/build.rs +++ b/build.rs @@ -5,8 +5,9 @@ use std::process::Command; fn main() -> Result<(), Box> { emit_git_short_hash(); + configure_protoc()?; - let proto_includes = collect_proto_include_paths(); + let proto_includes = collect_proto_include_paths()?; tonic_build::configure() .build_server(false) @@ -15,9 +16,32 @@ fn main() -> Result<(), Box> { Ok(()) } -fn collect_proto_include_paths() -> Vec { +fn configure_protoc() -> Result<(), Box> { + if env::var_os("PROTOC").is_none() { + env::set_var("PROTOC", protoc_bin_vendored::protoc_bin_path()?); + } + + if env::var_os("PROTOC_INCLUDE").is_none() { + env::set_var("PROTOC_INCLUDE", protoc_bin_vendored::include_path()?); + } + + println!("cargo:rerun-if-env-changed=PROTOC"); + println!("cargo:rerun-if-env-changed=PROTOC_INCLUDE"); + + Ok(()) +} + +fn collect_proto_include_paths() -> Result, Box> { let mut includes = vec![PathBuf::from("proto")]; + let vendored_include = PathBuf::from(protoc_bin_vendored::include_path()?); + if vendored_include + .join("google/protobuf/struct.proto") + .exists() + { + includes.push(vendored_include); + } + for candidate in [ PathBuf::from("/usr/include"), PathBuf::from("/usr/local/include"), @@ -28,7 +52,7 @@ fn collect_proto_include_paths() -> Vec { } } - includes + Ok(includes) } fn emit_git_short_hash() { diff --git a/configuration.yaml.dist b/configuration.yaml.dist index 01a237c5..57f2bd36 100644 --- a/configuration.yaml.dist +++ b/configuration.yaml.dist @@ -71,6 +71,10 @@ connectors: # USER_SERVICE_AUTH_TOKEN, PAYMENT_SERVICE_AUTH_TOKEN # STACKER_AUTH_REQUEST_TIMEOUT_SECS, STACKER_AUTH_CONNECT_TIMEOUT_SECS # DEFAULT_DEPLOY_DIR - Base directory for deployments (default: /home/trydirect) +# STACKER_PAYOUT_PROVIDER=mock|stripe_connect +# STRIPE_SECRET_KEY or PAYOUT_STRIPE_SECRET_KEY +# STRIPE_WEBHOOK_SECRET or PAYOUT_STRIPE_WEBHOOK_SECRET +# PAYOUT_ONBOARDING_RETURN_URL, PAYOUT_ONBOARDING_REFRESH_URL # Deployment settings # deployment: @@ -78,6 +82,16 @@ connectors: # # Can also be set via DEFAULT_DEPLOY_DIR environment variable # config_base_path: /home/trydirect +# Vendor payout provider. Defaults to mock for local/dev/test. +# For production Stripe Connect, set provider: stripe_connect and provide STRIPE_SECRET_KEY +# via environment variable rather than committing it here. +# payouts: +# provider: mock +# stripe_api_base_url: https://api.stripe.com +# onboarding_return_url: https://stacker.try.direct/marketplace/vendor/onboarding/return +# onboarding_refresh_url: https://stacker.try.direct/marketplace/vendor/onboarding/refresh +# timeout_secs: 15 + # Marketplace asset storage (Hetzner Object Storage / S3-compatible) # marketplace_assets: # enabled: true diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 7eef27e2..703bdb8f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -42,6 +42,9 @@ services: # Vault — must point to the real Vault service in the TryDirect network - VAULT_ADDRESS=https://vault.try.direct - VAULT_TOKEN=${STACKER_VAULT_TOKEN:-change-me} + # mTLS client cert for Vault — inline PEMs + - VAULT_CLIENT_CERT=${VAULT_CLIENT_CERT:-} + - VAULT_CLIENT_KEY=${VAULT_CLIENT_KEY:-} depends_on: stackerdb: condition: service_healthy diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index ea7ee0d7..1f9051fe 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -1,5 +1,3 @@ -version: "2.2" - volumes: stackerdb: driver: local @@ -8,30 +6,28 @@ volumes: driver: local networks: - backend: + trydirect_default: driver: bridge - name: backend external: true trydirect-network: external: true name: trydirect-network - services: - stacker: - image: trydirect/stacker:0.0.8 - build: . + image: trydirect/stacker:latest + #image: trydirect/stacker:test container_name: stacker restart: always volumes: - ./stacker/files:/app/files + - ./.env:/app/.env - ./configuration.yaml:/app/configuration.yaml - ./access_control.conf:/app/access_control.conf - ./migrations:/app/migrations - - ./.env:/app/.env + - ./stacker/ansible:/ansible/roles:ro ports: - - "8000:8000" + - "8001:8000" env_file: - ./.env environment: @@ -41,12 +37,12 @@ services: stackerdb: condition: service_healthy networks: - - backend + - trydirect_default - - stacker_queue: - image: trydirect/stacker:0.0.7 - container_name: stacker_queue + stackermq: + image: trydirect/stacker:latest + #image: trydirect/stacker:test # for testing mcp + container_name: stackermq restart: always volumes: - ./configuration.yaml:/app/configuration.yaml @@ -54,10 +50,6 @@ services: environment: - RUST_LOG=debug - RUST_BACKTRACE=1 - - AMQP_HOST=rabbitmq - - AMQP_PORT=5672 - - AMQP_USERNAME=guest - - AMQP_PASSWORD=guest env_file: - ./.env depends_on: @@ -65,9 +57,7 @@ services: condition: service_healthy entrypoint: /app/console mq listen networks: - - backend - - trydirect-network - + - trydirect_default stackerdb: container_name: stackerdb @@ -76,17 +66,17 @@ services: interval: 10s timeout: 5s retries: 5 - image: postgres:18.3 + image: postgres:16.0 restart: always ports: - - 5432 + - 5434:5432 env_file: - ./.env volumes: - stackerdb:/var/lib/postgresql/data - ./postgresql.conf:/etc/postgresql/postgresql.conf networks: - - backend + - trydirect_default stackerredis: container_name: stackerredis @@ -105,5 +95,5 @@ services: options: max-size: "10m" tag: "container_{{.Name}}" - - + networks: + - trydirect_default diff --git a/docs/MARKETPLACE_PUBLISH.md b/docs/MARKETPLACE_PUBLISH.md new file mode 100644 index 00000000..5fcc6fd3 --- /dev/null +++ b/docs/MARKETPLACE_PUBLISH.md @@ -0,0 +1,288 @@ +# Publishing a Stack to the TryDirect Marketplace + +This guide walks creators through publishing a stack to the TryDirect marketplace +so other users can deploy it in one click and you earn on every sale. + +--- + +## Table of Contents + +- [Who this is for](#who-this-is-for) +- [What you get](#what-you-get) +- [Two ways to publish](#two-ways-to-publish) +- [Path A: Publish from Stack Builder (UI)](#path-a-publish-from-stack-builder-ui) +- [Path B: Publish from CLI (stacker.yml)](#path-b-publish-from-cli-stackeryml) +- [Required metadata](#required-metadata) +- [Pricing options](#pricing-options) +- [The review process](#the-review-process) +- [After approval](#after-approval) +- [Updating a published template](#updating-a-published-template) +- [Common rejection reasons](#common-rejection-reasons) +- [FAQ](#faq) + +--- + +## Who this is for + +You should publish to the marketplace if you have a working Docker Compose stack +or `stacker.yml` that: + +- Solves a clear business problem (e.g. "Internal AI Helpdesk", "RAG Knowledge Base") +- Wires multiple services together so buyers don't have to (database + cache + app + LLM, etc.) +- Has been tested end-to-end on at least one cloud provider + +Single-container stacks are accepted but multi-service bundles consistently +outperform them in deployments and revenue. + +--- + +## What you get + +- **75% revenue share** on every paid deployment, including subscription renewals +- **Monthly Net-30 payouts** via Stripe Connect or PayPal (minimum $50) +- Automatic marketplace promotion: SEO landing page at `/applications/`, + inclusion in weekly digests, "Trending" badge if your template gains traction +- A "Deploy with TryDirect" badge you can embed in your GitHub README +- Public deploy count and creator profile + +Full payout terms: see `config/docs/MARKETPLACE_PAYOUT_TERMS.md`. + +--- + +## Two ways to publish + +| Path | Best for | Effort | +|---|---|---| +| **Stack Builder (UI)** | Stacks you built visually inside TryDirect | Click "Publish to Marketplace" | +| **stacker.yml (CLI)** | Stacks defined in your own repo | `stacker publish` | + +Both paths produce the same `StackTemplate` record and follow the same review +process. Pick whichever fits how you author your stack. + +--- + +## Path A: Publish from Stack Builder (UI) + +1. Open your stack in **Stack Builder** at `/builder`. +2. Verify it deploys cleanly to a test server. +3. Click **Publish to Marketplace** in the project sidebar. +4. Fill in the publish form: + - **Name** — a business-oriented name, not just tech ("Client AI Agent Workspace", not "n8n+Qdrant+Ollama") + - **Short description** — one sentence: what problem does it solve? + - **Long description** — markdown supported; cover use cases, requirements, customisation + - **Category** — AI Agents, Data Pipelines, SaaS Starter, Dev Tools, etc. + - **Tags** — `n8n`, `qdrant`, `ollama`, `supabase`, `postgres`, etc. + - **License / pricing** — Free, Paid (one-time), or Subscription + - **Price** (if paid) — USD + - **Support URL** — GitHub repo, docs site, or contact form + - **No-secrets confirmation** — required checkbox: confirms you removed all + embedded credentials before submitting +5. Click **Submit to marketplace**. Your dashboard will show status: + `In review` → `Approved` or `Rejected (with reason)`. + +--- + +## Path B: Publish from CLI (stacker.yml) + +Add a `marketplace` section to your `stacker.yml`, then run `stacker publish`. + +### Example `stacker.yml` marketplace block + +```yaml +name: ai-helpdesk-starter +version: 1.0.0 + +# ... your existing app, services, deploy, etc. ... + +marketplace: + publish: true + display_name: "Internal AI Helpdesk" + short_description: "Self-hosted AI helpdesk with n8n workflows, Qdrant memory, and Ollama LLM." + long_description: | + Deploy a complete internal AI helpdesk stack: n8n handles ticket routing + and workflow automation, Qdrant stores conversation memory and document + embeddings, and Ollama serves the local LLM. + + Comes pre-wired with example workflows for common helpdesk patterns. + Customise via the n8n web UI after deployment. + category: ai-agents + tags: + - n8n + - qdrant + - ollama + - helpdesk + - rag + license: paid + pricing: + plan_type: one_time # one_time | subscription | free + price: 49 + currency: USD + support_url: https://github.com/your-org/ai-helpdesk-starter + no_secrets_confirmation: true +``` + +### Submit + +```bash +# From your project root +stacker publish + +# Stacker validates stacker.yml, packages the stack definition, +# and submits it to the TryDirect marketplace for review. +``` + +### Check status + +```bash +stacker publish --status + +# Shows: in_review | approved | rejected (with reason) +``` + +--- + +## Required metadata + +| Field | Required | Notes | +|---|---|---| +| Name | Yes | 5-80 chars, business-oriented | +| Short description | Yes | 20-200 chars, one sentence | +| Long description | Yes | Markdown, 100-5000 chars | +| Category | Yes | Must match an existing category code | +| Tags | Yes | 1-10 tags | +| License | Yes | `free`, `paid`, `subscription` | +| Price | If paid/subscription | USD, > 0 | +| Support URL | Yes | Public URL where buyers can reach you | +| No-secrets confirmation | Yes | Must be `true` | + +--- + +## Pricing options + +### Free +No revenue, but full marketplace promotion (SEO page, digest inclusion, trending +badges). Good for building reputation and audience. + +### One-time +Buyer pays once, deploys as many times as they want for their own use. +You earn 75% of the sale price. Most templates start here. + +### Subscription +Buyer pays monthly or yearly. You earn 75% of every renewal cycle, not just +the first sale. Best for templates that ship updates regularly. + +You can change pricing on future versions but not retroactively. Buyers of +v1.0.0 keep their original pricing for that version's lifetime. + +--- + +## The review process + +| Step | Time | Who | +|---|---|---| +| Submission received | Instant | Auto-confirmation email | +| Initial automated checks | < 1 hour | `stacker.yml` validation, no embedded secrets, no banned services | +| Manual review | 1-3 business days | TryDirect review team | +| Decision | — | Approved → live on `/applications`; Rejected → feedback in dashboard | + +Reviewers check: +1. **Deploys cleanly** on a fresh server +2. **No embedded credentials** in env vars, configs, or volumes +3. **No insecure defaults** (e.g. `--api.insecure=true`, `0.0.0.0/0` ACLs, hardcoded passwords) +4. **Metadata accurate** — what the listing claims matches what the stack actually does +5. **Support URL reachable** — opens to a real GitHub/docs/contact page + +--- + +## After approval + +Once approved, several things happen automatically: + +- **SEO landing page** generated at `/applications/` +- **Social post** to TryDirect Twitter/X: "New on TryDirect: