saas-controller
npx skills add https://github.com/afterthought/saas-controller --skill saas-controller
Agent 安装分布
Skill 文档
SaaS Controller
Multi-cloud service orchestration for devenv. Declarative service definitions with pluggable providers. Manages local dev (sc up with Tailscale HTTPS) and cloud deployment (sc deploy).
Architecture
Providers own the full service lifecycle. Each provider generates its own docker-compose stack with a Tailscale sidecar for HTTPS on the tailnet.
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â saas-controller â
â â
â ââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â Providers (WHAT) â â
â â Each provider owns up() + deploy() â â
â âââââââââââââââââââââââââââââââââââââââââââââââââ⤠â
â â zuplo â API gateway + docs portal â â
â â docker-compose â Generic compose stacks â â
â â [your own] â via externalProviders â â
â ââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â
â sc up topology (per service): â
â ââââââââââââââââââââââââââââââââââââââââââââ â
â â docker-compose stack â â
â â âââââââââââââ ââââââââââââââââââââââââ â â
â â â tailscale â â app container(s) â â â
â â â sidecar ââââ network_mode: â â â
â â â â â service:tailscale â â â
â â â HTTPS:443 â â PORT=3000 â â â
â â âââââââââââââ ââââââââââââââââââââââââ â â
â â URL: https://sc-<slug>-<service>.<tailnet> â â
â ââââââââââââââââââââââââââââââââââââââââââââ â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Import
# devenv.yaml
imports:
- github:afterthought/saas-controller
Or as a flake input:
# flake.nix
inputs.saas-controller.url = "github:afterthought/saas-controller";
# In your devenv module:
imports = [ inputs.saas-controller.outPath ];
Example A: Minimal Service
A hello-world service running locally with Tailscale HTTPS:
# devenv.nix
{ pkgs, lib, config, ... }:
{
imports = [ /* saas-controller module */ ];
saas-controller.services.hello-world = {
enable = true;
provider = "hello-world";
providerConfig = {
path = "examples/hello-world"; # dir with server.mjs + Dockerfile
};
environments = {
local.enable = true;
};
};
}
sc up # Starts compose stack with tailscale sidecar
# Prints: https://sc-<slug>-hello-world.<tailnet>:443
Example B: Zuplo Gateway with Secrets
A Zuplo API gateway with secretspec profiles, multiple environments, and secret management:
{ pkgs, lib, config, ... }:
{
imports = [ /* saas-controller module */ ];
saas-controller.services.my-gateway = {
enable = true;
displayName = "My API Gateway";
provider = "zuplo";
providerConfig = {
project = "my-gateway"; # Zuplo project name
account = "my-account"; # Zuplo account
path = "services/my-gateway"; # Path to zuplo project in repo
};
environments = {
local.enable = true;
production.enable = true;
preview.enable = true;
};
# Secret management
secretspec = {
auth.provider = "client-myorg"; # SecretSpec provider alias
auth.saToken = "client-myorg"; # 1Password SA token alias
environments = {
local = {
serviceProfiles = [ "tailscale" ];
# â validates TS_CLIENT_SECRET, TS_CLIENT_ID
};
production = {
serviceProfiles = [ "zuplo-backend" ];
# â validates zuplo secrets for production
};
};
tags = [ "tailscale" "zuplo" ]; # For filtered checking
};
};
}
sc up # Start locally with tailscale HTTPS
sc deploy my-gateway -e production # Deploy to production
sc check-secrets --tag tailscale # Validate tailscale secrets
Secret Profiles
Secrets are managed in two layers: controller-level profile definitions and per-service composition.
Controller Level: Define profiles
saas-controller.secretProfiles = {
tailscale = {
TS_CLIENT_SECRET = {
description = "Tailscale OAuth client secret";
required = true;
providers = [ "saas-controller" ];
};
TS_CLIENT_ID = {
description = "Tailscale OAuth client ID";
required = false;
providers = [ "saas-controller" ];
};
};
my-api-keys = {
API_KEY = {
description = "External API key";
providers = [ "saas-controller" ];
};
};
};
Service Level: Compose profiles per environment
services.my-service.secretspec = {
auth.provider = "client-myorg"; # SecretSpec provider alias
auth.saToken = "client-myorg"; # 1Password SA token alias
environments = {
local = {
serviceProfiles = [ "tailscale" ];
# Only tailscale secrets needed locally
};
production = {
serviceProfiles = [ "my-api-keys" ];
# API keys needed for production deployment
};
};
tags = [ "tailscale" ]; # For sc check-secrets --tag
};
Provider Auto-Export
Providers can declare secretProfiles in their implementation. These are automatically merged into saas-controller.secretProfiles. When a service uses a provider, that provider’s profiles are auto-included â no manual wiring needed.
For example, the zuplo provider exports zuplo and zudoku profiles. Any service with provider = "zuplo" automatically gets those profiles available.
Checking Secrets
sc check-secrets # Check all services
sc check-secrets --tag tailscale # Only tailscale-tagged services
sc check-secrets --service my-gateway # Specific service
sc secret-status # Show secret-to-service mapping table
CLI Reference
# Local development
sc up # Start all local services
sc up my-gateway # Start specific service
# Deployment
sc deploy # Deploy all to production (default)
sc deploy --environment production # Deploy all to production
sc deploy my-gateway -e preview # Deploy specific service to preview
sc undeploy my-gateway # Remove persistent service
# Secret management
sc check-secrets # Validate all service secrets
sc check-secrets --tag tailscale # Filter by tag
sc check-secrets --service my-gateway # Filter by service
sc secret-status # Secret-to-service mapping table
# Secret reconciliation
sc setup-env production # Check all secrets for production
sc diff-secrets local production # Compare secrets between environments
sc reconcile-secrets # Show all secrets across all environments
sc reconcile-secrets -e production # Show secrets for one environment
# Other
sc help # Show help
provision-projects # One-time project setup
Task Integration
# sc up is also available as a devenv task
devenv tasks run saas:up
# Deploy with environment via task input
DEVENV_TASK_INPUT='{"environment": "production"}' devenv tasks run saas-deploy:my-gateway
Provider Summary
| Provider | providerConfig keys | sc up? | Auto-exported profiles |
|---|---|---|---|
zuplo |
project, account, path |
Yes | zuplo, zudoku |
docker-compose |
path, composeFile(opt), tailscale(opt) |
Yes | (none) |
For detailed provider documentation: read references/provider-reference.md
Extensibility
Register custom providers:
saas-controller.externalProviders.my-provider = ./providers/my-provider.nix;
See EXTENDING.md for provider authoring guide and template.
Tailscale Setup
sc up requires one-time Tailscale setup (ACL tags, OAuth client). See references/tailscale-setup.md for step-by-step instructions.
SA Token Provider Setup
Services with secretspec.auth.saToken need the sa-tokens secretspec provider alias configured. The secretspec.toml is auto-generated from service configs at nix eval time â developers only configure the provider backend once.
One-time setup per machine:
secretspec config provider add sa-tokens "keyring://" # macOS Keychain
secretspec config provider add sa-tokens "env://" # Environment variables (CI)
Naming: saToken = "client-willdan" maps to OP_SA_CLIENT_WILLDAN.
Verify: secretspec config provider list
Deeper Questions
For questions not covered here, use DeepWiki MCP:
ask_question("afterthought/saas-controller", "<your question>")
Or read the source at github:afterthought/saas-controller.