Enterprise em auto-hospedagem

No Studio Cloud, os recursos corporativos são liberados por uma assinatura Enterprise. Instalações auto-hospedadas não têm assinatura, então elas são liberadas por configuração de ambiente.

Há duas partes para fazer isso certo, e pular a segunda é o motivo mais comum de os recursos parecerem não fazer nada:

  1. Habilite os recursos com ENTERPRISE_ENABLED.
  2. Dê a eles uma organização à qual se aplicar. Whitelabeling, redação de PII, grupos de permissões, data drains e escopo de auditoria leem todos suas configurações da organização proprietária do workspace. Uma instalação em que todos trabalham em workspaces pessoais não tem organização de onde tirar essas configurações.

Habilite o conjunto de recursos

Defina a chave principal e sua variável equivalente no cliente. As duas são obrigatórias — o valor do servidor decide o acesso, e o valor NEXT_PUBLIC_ decide o que a interface de configurações mostra.

ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true

Isso liga organizações, grupos de permissões, SSO, whitelabeling, logs de auditoria, políticas de sessão, retenção de dados, data drains, workspace forks, o direito ao Sandbox e a inbox. Os sandboxes continuam indisponíveis até que o provider remoto e a base dedicada de Function estejam configurados.

Desligando um recurso

Cada recurso mantém sua própria flag, e uma flag definida explicitamente sempre vence a chave principal. Para rodar o conjunto sem data drains:

ENTERPRISE_ENABLED=true
NEXT_PUBLIC_ENTERPRISE_ENABLED=true
DATA_DRAINS_ENABLED=false
NEXT_PUBLIC_DATA_DRAINS_ENABLED=false

As flags individuais também funcionam por conta própria, se você preferir habilitar um recurso por vez e deixar a chave principal sem definir.

RecursoVariável do servidorVariável do cliente
Tudo abaixoENTERPRISE_ENABLEDNEXT_PUBLIC_ENTERPRISE_ENABLED
OrganizaçõesORGANIZATIONS_ENABLEDNEXT_PUBLIC_ORGANIZATIONS_ENABLED
Grupos de permissõesACCESS_CONTROL_ENABLEDNEXT_PUBLIC_ACCESS_CONTROL_ENABLED
Login com SAML e OIDCSSO_ENABLEDNEXT_PUBLIC_SSO_ENABLED
Marca personalizadaWHITELABELING_ENABLEDNEXT_PUBLIC_WHITELABELING_ENABLED
Logs de auditoriaAUDIT_LOGS_ENABLEDNEXT_PUBLIC_AUDIT_LOGS_ENABLED
Políticas de sessãoSESSION_POLICIES_ENABLEDNEXT_PUBLIC_SESSION_POLICIES_ENABLED
Exclusão por retenção de dadosDATA_RETENTION_ENABLEDNEXT_PUBLIC_DATA_RETENTION_ENABLED
Data drainsDATA_DRAINS_ENABLEDNEXT_PUBLIC_DATA_DRAINS_ENABLED
Workspace forksFORKING_ENABLED—
Inbox do Studio MailerINBOX_ENABLEDNEXT_PUBLIC_INBOX_ENABLED
SandboxesSANDBOXES_ENABLEDNEXT_PUBLIC_SANDBOXES_ENABLED

Os sandboxes também precisam de um provider de execução remota e de uma imagem base dedicada de Function. Construa e configure essa base antes de habilitar a interface; sandboxes personalizados do workspace instalam seus pacotes em cima dela.

Para o E2B:

E2B_API_KEY=... \
  bun run apps/core-api/scripts/build-function-e2b-template.ts \
  --name studio-function

SANDBOX_PROVIDER=e2b
E2B_ENABLED=true
E2B_API_KEY=...
E2B_FUNCTION_TEMPLATE_ID=<studio-function-template>:<studio-function-build-id>
E2B_FUNCTION_TEMPLATE_GENERATION=<release-epoch-ms>
SANDBOXES_ENABLED=true
NEXT_PUBLIC_SANDBOXES_ENABLED=true

O builder usa a base code-interpreter-v1 mantida pelo E2B, atribui uma nova geração de release e imprime os dois valores de runtime. O --generation continua disponível para automação de release, e o --base-template aceita uma sobreposição de base imutável quando uma instalação deliberadamente mantém a sua.

Para o Daytona, use o ID de snapshot imutável impresso pelo builder. A API key precisa de write:snapshots para construir e de write:sandboxes para executar:

DAYTONA_API_KEY=... \
  bun run apps/core-api/scripts/build-function-daytona-snapshot.ts \
  --name studio-function-2026-08-03 \
  --parity-manifest /tmp/function-sandbox-manifest.json

SANDBOX_PROVIDER=daytona
DAYTONA_API_KEY=...
DAYTONA_FUNCTION_SNAPSHOT_ID=<snapshot-uuid>
SANDBOXES_ENABLED=true
NEXT_PUBLIC_SANDBOXES_ENABLED=true

SANDBOXES_ENABLED concede o direito de auto-hospedagem no lado do servidor. NEXT_PUBLIC_SANDBOXES_ENABLED projeta a prontidão do provider para o navegador e expõe o Shell mais o gerenciamento de Sandboxes personalizados. Defina a flag pública somente depois que o provider selecionado tiver credenciais e uma base imutável de Function válida configurada. O valor de linguagem do Function nunca depende dessas flags, então um block Python salvo não pode ser silenciosamente serializado ou executado como JavaScript.

JavaScript sem import ou require não usa esse provider remoto e continua rodando na VM isolada local quando todas as flags de Sandbox estão desligadas. Python, Shell, JavaScript com imports externos e Sandboxes personalizados selecionados falham com um erro explícito de configuração até que a base remota de Function esteja pronta.

As ferramentas function_execute e run_code do Chat usam uma imagem de shell separada, inclusive para JavaScript sem imports. Se a instalação usa essas ferramentas de código, configure também a imagem produzida pelo processo de release da imagem de shell para o provider selecionado:

# E2B
MOTHERSHIP_E2B_TEMPLATE_ID=<mothership-shell-template-ref>

# Daytona
DAYTONA_SHELL_SNAPSHOT_ID=<mothership-shell-snapshot-ref>

Esses valores são selecionados apenas para as chamadas de ferramentas de código do Studio no workflow e do Chat no workspace. Eles nunca substituem nem servem de fallback para E2B_FUNCTION_TEMPLATE_ID ou DAYTONA_FUNCTION_SNAPSHOT_ID; os blocks Function e os sandboxes personalizados do workspace continuam usando a base dedicada de Function.

Use o E2B como linha de base do release antes de construir ou promover o Daytona:

# 1. Verify the exact E2B Function build and capture its accepted package/runtime surface.
E2B_ENABLED=true \
E2B_API_KEY=... \
E2B_FUNCTION_TEMPLATE_ID=<studio-function-template>:<studio-function-build-id> \
E2B_FUNCTION_TEMPLATE_GENERATION=<release-epoch-ms> \
SANDBOX_PARITY_MANIFEST_OUT=/tmp/function-sandbox-manifest.json \
  bun run apps/core-api/scripts/verify-sandbox-parity.ts

# 2. Pin Daytona's reconstructed packages to that accepted E2B manifest.
DAYTONA_API_KEY=... \
  bun run apps/core-api/scripts/build-function-daytona-snapshot.ts \
  --name studio-function-2026-08-03 \
  --parity-manifest /tmp/function-sandbox-manifest.json

# 3. Verify the immutable Daytona snapshot against the same baseline before promotion.
SANDBOX_PROVIDER=daytona \
DAYTONA_API_KEY=... \
DAYTONA_FUNCTION_SNAPSHOT_ID=<snapshot-uuid> \
SANDBOX_PARITY_MANIFEST_BASELINE=/tmp/function-sandbox-manifest.json \
  bun run apps/core-api/scripts/verify-sandbox-parity.ts

E2B_FUNCTION_TEMPLATE_ID e DAYTONA_FUNCTION_SNAPSHOT_ID falham de forma segura quando não estão definidos ou são mutáveis. O valor do E2B precisa ser uma referência exata <template>:<build-id>, e E2B_FUNCTION_TEMPLATE_GENERATION precisa ser o valor monotônico impresso pelo mesmo build. O valor do Daytona precisa ser um ID de snapshot, não um nome. O Studio não faz fallback para MOTHERSHIP_E2B_TEMPLATE_ID nem DAYTONA_SHELL_SNAPSHOT_ID; as imagens de shell, Function, documento e Pi têm contratos de pacote e cadências de release separados.

Atribua a cada base de Function do E2B promovida uma geração maior que a de qualquer instalação anterior. Um rollback é uma nova promoção e, portanto, também precisa de uma geração nova e maior; não reutilize a geração do release antigo.

A retenção de dados é o único recurso que exclui dados. Sua flag controla a passagem de limpeza, não a tela de configurações — as janelas de retenção são sempre configuráveis. Nada é excluído até você habilitá-la, e mesmo então somente conforme as janelas que você configurou explicitamente. O Studio nunca aplica os padrões do plano hospedado a uma instalação auto-hospedada.

Escolha um modelo de organização

Padrão 1: uma organização para toda a instância

Melhor quando todos na instalação pertencem à mesma empresa. Defina um nome e todo usuário entra nessa organização automaticamente no cadastro, com seus workspaces criados sob a propriedade da organização.

INSTANCE_ORG_NAME="Acme Inc"

Opcionalmente, fixe o slug e o owner:

INSTANCE_ORG_SLUG=acme-inc
INSTANCE_ORG_OWNER_EMAIL=admin@acme.com

A organização é criada na primeira vez que um usuário se cadastra. Se INSTANCE_ORG_OWNER_EMAIL não estiver definido, ou apontar para um usuário que ainda não existe, o primeiro usuário a se cadastrar se torna o owner; transfira a propriedade depois com a Admin API. O provisionamento é idempotente e seguro em múltiplas réplicas.

O modo de organização de instância só se aplica quando o billing está desativado. Com o billing ativado, as organizações são criadas pelo fluxo normal de assinatura e essas variáveis são ignoradas.

Instalações existentes

Usuários e workspaces criados antes de você definir INSTANCE_ORG_NAME ficam onde estão. Mova-os de uma vez com o script de backfill, que adiciona todo usuário à organização e anexa seus workspaces:

# Preview
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
  bun run apps/core-api/scripts/consolidate-users-into-organization.ts

# Apply
DATABASE_URL=... INSTANCE_ORG_NAME="Acme Inc" \
  bun run apps/core-api/scripts/consolidate-users-into-organization.ts --apply

Ele roda em modo de simulação, a menos que você passe --apply, e é seguro executar novamente. Usuários que já pertencem a outra organização são reportados e ignorados, já que um usuário só pode pertencer a uma.

Padrão 2: várias organizações que você mesmo gerencia

Melhor quando uma instalação atende vários times que não devem ver os dados uns dos outros. Deixe INSTANCE_ORG_NAME sem definir e provisione as organizações pela Admin API.

Defina uma chave de admin primeiro:

ADMIN_API_KEY=$(openssl rand -hex 32)

Crie uma organização

O owner não pode já pertencer a outra organização.

curl -X POST https://studio.example.com/api/v1/admin/organizations \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Inc", "ownerId": "user_123", "slug": "acme-inc"}'

Adicione membros

curl -X POST https://studio.example.com/api/v1/admin/organizations/$ORG_ID/members \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"userId": "user_456", "role": "member"}'

Mova um workspace para a organização

Recursos com escopo de organização só se aplicam a workspaces de propriedade da organização.

curl -X POST https://studio.example.com/api/v1/admin/dashboard/workspaces/$WORKSPACE_ID/move \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"destinationOrganizationId\": \"$ORG_ID\"}"

Configure as configurações da organização

Marca, retenção e políticas de sessão podem ser definidas pela API em vez da interface.

curl -X PATCH https://studio.example.com/api/v1/admin/organizations/$ORG_ID/whitelabel \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"brandName": "Acme AI", "hidePoweredByStudio": true}'
curl -X PATCH https://studio.example.com/api/v1/admin/organizations/$ORG_ID/data-retention \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"logRetentionHours": 2160}'
curl -X PATCH https://studio.example.com/api/v1/admin/organizations/$ORG_ID/session-policy \
  -H "x-admin-key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"maxSessionHours": 168, "idleTimeoutHours": 48}'

Excluir uma organização exige repetir seu slug, porque a exclusão cascateia para membros, convites e grupos de permissões, e desanexa seus workspaces:

curl -X DELETE "https://studio.example.com/api/v1/admin/organizations/$ORG_ID?confirmSlug=acme-inc" \
  -H "x-admin-key: $ADMIN_API_KEY"

Verificando se funcionou

Se um recurso está habilitado mas nada aparece, confira estes pontos nesta ordem.

A seção de configurações não aparece. A variável equivalente NEXT_PUBLIC_ não está definida, ou a aplicação não foi reiniciada depois de adicioná-la. As variáveis de cliente são lidas no build e na inicialização.

A seção aparece, mas a API retorna 403. A variável do lado do servidor está faltando, enquanto a equivalente no cliente está definida. Defina as duas.

O recurso está ligado, mas não tem efeito dentro de um workspace. O workspace não pertence a uma organização. Verifique workspace_mode e organization_id:

SELECT id, name, workspace_mode, organization_id FROM workspace;

Um workspace com personal ou organization_id nulo não vai receber marca, redação de PII, grupos de permissões nem drains. Use o script de backfill ou o endpoint de movimentação de workspace.

A retenção está configurada, mas nada é excluído. DATA_RETENTION_ENABLED não está definido. Configurar as janelas e rodar a passagem de limpeza são chaves separadas por design.

Relacionados