App
Crea, distribuisci e controlla le app Beeuni dall'SDK e da React.
Le App sono distribuzioni limitate al progetto, ciascuna con un URL stabile,
release immutabili, macchine per gruppo di processi e volumi persistenti. Usa app.capabilities per scegliere il ramo. Questa pagina descrive l’interfaccia dell’SDK: la
facade apps su un handle di progetto, le chiamate per artefatti, release, macchine e volumi,
i token di accesso, i tipi esportati e gli hook React.
Per il modello, la configurazione delle release, i tipi di sorgente, la CLI, l’URL stabile e il comportamento di riattivazione a freddo, leggi Apps.
const apps = beeuni.project(projectId).apps;
La facade apps
| Metodo | Incapsula | Cosa fa |
|---|---|---|
apps.list() |
GET /projects/:pid/apps |
Elenca le App che il chiamante può aprire (un responsabile del progetto le vede tutte) |
apps.create(input) |
POST …/apps |
Crea un’App e le assegna un URL stabile |
apps.get(appId) |
GET …/apps/:id |
Legge un’App |
apps.update(appId, input) |
PATCH …/apps/:id |
La rinomina o modifica macchina, timeout di inattività, modalità di esecuzione (always_on), uses o labels. La modalità di esecuzione viene applicata entro 5 minuti; una modifica alla macchina viene applicata alla release successiva |
apps.remove(appId) |
DELETE …/apps/:id |
Elimina l’App, le sue macchine e tutte le immagini delle release; restituisce images: {released, pending}. I suoi volumi conservano uno snapshot final per 7 giorni |
apps.start(appId) |
POST …/apps/:id/start |
Imposta desired_state su running e preriscalda il runtime. Un’App statica risponde 409 static_app_no_runtime |
apps.stop(appId) |
POST …/apps/:id/stop |
Sospende subito il calcolo; la richiesta successiva lo riprende. Un’App statica risponde 409 static_app_no_runtime: non ha un runtime e serve i contenuti finché ha una distribuzione attiva |
apps.rollback(appId, deploymentId) |
POST …/apps/:id/rollback |
Crea una NUOVA release dall’artefatto e dalla configurazione di una release pronta e la distribuisce come un deploy (202). I volumi conservano i propri dati |
Gli artefatti sono l’input immutabile di una distribuzione:
| Metodo | Incapsula | Cosa fa |
|---|---|---|
apps.artifacts.register(input) |
POST …/apps/artifacts |
Registra un archive o un oci_image; restituisce l’URL di caricamento per un archivio |
apps.artifacts.uploadArchive(bytes, options?) |
— | Registra, carica, calcola l’hash e finalizza un .tar.gz in un’unica chiamata |
apps.artifacts.finalize(artifactId, input) |
POST …/apps/artifacts/:id/finalize |
Conferma sha256 e size_bytes per un caricamento manuale |
Le release (distribuzioni) sono immutabili e numerate:
| Metodo | Incapsula | Cosa fa |
|---|---|---|
apps.deployments.create(appId, input) |
POST …/apps/:id/deployments |
Avvia una release da un artefatto, una sorgente e un config di release facoltativo |
apps.deployments.list(appId) |
GET …/apps/:id/deployments |
Elenca la cronologia delle distribuzioni |
apps.deployments.get(appId, deploymentId) |
GET …/deployments/:did |
Legge una distribuzione e i suoi eventi |
apps.deployments.logs(appId, deploymentId, options?) |
GET …/deployments/:did/logs |
Legge i log del runtime con un cursore |
apps.deployments.remove(appId, deploymentId) |
DELETE …/deployments/:did |
Elimina una distribuzione e la sua immagine; 409 per la distribuzione attiva o una build in corso. Restituisce image: 'released' | 'pending' | 'none' |
Macchine, scalabilità ed exec (un’App server; un’App senza macchine risponde
409 app_no_machines):
| Metodo | Incapsula | Cosa fa |
|---|---|---|
apps.machines.list(appId) |
GET …/apps/:id/machines |
Ogni macchina: machine_id, process_group, state, release, vm, volume |
apps.machines.start/stop/restart(appId, machineId) |
POST …/machines/:mid/{start,stop,restart} |
Agisce su una macchina; si risolve con l’elenco delle macchine |
apps.machines.destroy(appId, machineId) |
DELETE …/machines/:mid |
Distrugge una macchina. Il suo volume rimane; il suo gruppo esegue una macchina in meno |
apps.scale(appId, count) |
PUT …/apps/:id/scale |
Macchine per gruppo di processi ({ worker: 3 }, 0–20), applicate in background (202). Si risolve con scale e machines |
apps.exec(appId, { command, machine_id?, timeout_s? }) |
POST …/apps/:id/exec |
Esegue un comando in una macchina in esecuzione (project.app.admin; registrato ai fini di audit). timeout_s 1–300, valore predefinito 60. Si risolve con exit_code, stdout, stderr (1 MiB ciascuno), truncated |
Volumi (dischi persistenti):
| Metodo | Incapsula | Cosa fa |
|---|---|---|
apps.volumes.list(appId) |
GET …/apps/:id/volumes |
Ogni volume: name, process_group, machine_id, size_gb, status, attached, snapshot_retention_days |
apps.volumes.create(appId, { name, size_gb, process_group?, snapshot_retention_days? }) |
POST …/volumes |
Crea in anticipo un volume; una macchina successiva del suo gruppo con un mount di quel nome lo utilizza |
apps.volumes.update(appId, volumeId, { size_gb?, snapshot_retention_days? }) |
PATCH …/volumes/:vid |
Aumenta le dimensioni di un volume (non le riduce mai; massimo 100 GB) o ne modifica la conservazione (1–60 giorni) |
apps.volumes.destroy(appId, volumeId, confirmName) |
DELETE …/volumes/:vid?confirm=<name> |
project.app.admin. Rifiutato con 409 volume_attached mentre una macchina lo monta. Conserva uno snapshot final per 7 giorni |
apps.volumes.snapshots(appId, volumeId) |
GET …/volumes/:vid/snapshots |
Snapshot daily, manual e final |
apps.volumes.snapshot(appId, volumeId) |
POST …/volumes/:vid/snapshots |
Uno snapshot manual immediato |
apps.volumes.restore(appId, volumeId, snapshotId) |
POST …/volumes/:vid/restore |
project.app.admin. La macchina si arresta, il volume torna allo snapshot, la macchina si avvia su di esso (202) |
apps.volumes.fork(appId, volumeId, { name, snapshot_id? }) |
POST …/volumes/:vid/fork |
Un nuovo volume non collegato nella stessa App, da uno snapshot o dai contenuti attuali |
Token di accesso (funzionalità member_tokens; 409 app_capability_unsupported
quando l’App non la possiede):
| Metodo | Incapsula | Cosa fa |
|---|---|---|
apps.token(appId) |
POST …/apps/:id/token |
Un token di accesso all’App valido per 15 minuti, che identifica il chiamante |
L’accesso è la policy di autorizzazione propria dell’App:
| Metodo | Incapsula | Cosa fa |
|---|---|---|
apps.access.get(appId) |
GET …/apps/:id/access |
Legge la policy. Richiede project.app.read |
apps.access.update(appId, input) |
PATCH …/apps/:id/access |
Sostituisce la policy e ne incrementa la revisione |
apps.access.session(appId) |
POST …/apps/:id/access-session |
Genera un URL valido per cinque minuti che viene scambiato con un cookie limitato all’host |
Distribuire un sito statico
uploadArchive esegue l’intero handshake dell’artefatto: registra l’artefatto,
lo verifica rispetto a max_bytes, invia i byte con PUT, calcola lo SHA-256 e
finalizza.
const apps = beeuni.project(projectId).apps;
const app = await apps.create({ slug: 'docs', name: 'Docs' });
const artifact = await apps.artifacts.uploadArchive(tarGzBytes, {
onProgress: (uploaded, total) => console.log(`${uploaded}/${total}`),
});
const deployment = await apps.deployments.create(app.app_id, {
artifact_id: artifact.artifact_id,
source: { kind: 'static', spa: true },
});
console.log(app.url, deployment.status); // https://…apps.beeuni.ai queued
create accetta anche i campi della macchina: cpu, memory_gb,
disk_gb, idle_timeout_seconds e always_on. Omettili per usare i valori predefiniti.
Le App non hanno un budget mensile: i crediti dell’account pagano il calcolo. Un’App server
sempre attiva viene fatturata 24/7; una on-demand solo mentre è attiva.
always_on: true esegue un’App server 24/7
(websocket, loop in background); false la arresta dopo idle_timeout_seconds
e la riattiva alla richiesta successiva. Un’App statica non ha un runtime: always_on è
false.
L’API ignora monthly_budget_usd se un client meno recente lo invia (200, nessun
errore). Nessuna risposta dell’App lo include.
uses elenca, per slug, le App
per cui questa App può ottenere token di accesso e che può raggiungere tramite il suo mount dei binding; per
impostazione predefinita non ce ne sono e update(appId, { uses }) sostituisce l’elenco (vedi
App usate da un’App).
create e update restituiscono warnings, che è sempre [].
const app = await beeuni.project(projectId).apps.create({ slug: 'worker', name: 'Worker', always_on: true });
console.log(app.always_on, app.hosting_type); // true null (until the first deploy)
Attendi la distribuzione interrogandone periodicamente lo stato:
async function waitForReady(appId: string, deploymentId: string) {
for (;;) {
const { deployment } = await apps.deployments.get(appId, deploymentId);
if (deployment.status === 'ready') return deployment;
if (deployment.status === 'failed' || deployment.status === 'cancelled') {
throw new Error(deployment.error ?? deployment.error_code ?? deployment.status);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}
I valori di stato sono queued, validating, building, provisioning,
checking, ready, failed e cancelled.
Distribuire un’immagine OCI
Registra il riferimento immutabile dell’immagine, poi dichiara il comando del processo e la porta di destinazione pubblica:
const registered = await apps.artifacts.register({
kind: 'oci_image',
image: 'ghcr.io/hekmat/service:2026-08-07',
});
await apps.deployments.create(app.app_id, {
artifact_id: registered.artifact.artifact_id,
source: {
kind: 'oci_image',
image: 'ghcr.io/hekmat/service:2026-08-07',
command: ['node', 'server.js'],
port: 3000,
readiness_path: '/health',
},
});
register restituisce upload: null per un oci_image. Solo un archive ottiene un
URL di caricamento.
CreateAppDeploymentInput accetta anche environment (valori del runtime non segreti),
secrets (chiave del runtime associata al nome del segreto del progetto) e config (AppReleaseConfigInput, la
configurazione della release). Un config non valido
risponde 400 app_config_invalid con issues: [{ path, message }]; un mount
su un runtime senza volumi risponde 400 app_volumes_unsupported.
await apps.deployments.create(app.app_id, {
artifact_id: registered.artifact.artifact_id,
source: { kind: 'oci_image', image, command: ['node', 'server.js'] },
config: {
processes: { app: '', worker: 'node worker.js' },
http_service: { internal_port: 3000, access: 'app', processes: ['app'] },
mounts: [{ source: 'data', destination: '/data', initial_size_gb: 10, processes: ['app'] }],
deploy: { release_command: 'node migrate.js' },
},
});
Leggere i log del runtime
let cursor = 0;
for (;;) {
const page = await apps.deployments.logs(app.app_id, deployment.deployment_id, {
after: cursor,
limit: 200,
});
for (const entry of page.entries) console.log(entry.source, entry.line);
cursor = page.next_cursor;
if (page.entries.length === 0) break;
}
Ogni voce contiene cursor, time, source (app, appd, caddy) e
line. Una riga caddy è il log degli accessi: metodo, URI, stato, durata e
header della risposta. Non contiene mai gli header della richiesta, perché contengono le
credenziali del gateway (il token e l’identità del visitatore, il token di ingresso del provider).
Macchine, scalabilità e volumi
const apps = beeuni.project(projectId).apps;
const machines = await apps.machines.list(app.app_id);
await apps.scale(app.app_id, { worker: 3 }); // 202: applied in the background
const { exit_code, stdout } = await apps.exec(app.app_id, { command: ['ls', '/data'] });
const [data] = await apps.volumes.list(app.app_id);
const snapshot = await apps.volumes.snapshot(app.app_id, data.volume_id);
await apps.volumes.restore(app.app_id, data.volume_id, snapshot.snapshot_id); // project.app.admin
const copy = await apps.volumes.fork(app.app_id, data.volume_id, { name: 'data_copy' });
Una macchina mantiene il proprio machine_id tra una release e l’altra; ogni release sostituisce il suo
box (instance_id). Una macchina monta un volume alla volta. Un ripristino crea una
nuova versione del volume: i contenuti sostituiti restano nella sua cronologia. Uno
snapshot di un volume montato può non includere l’ultimo secondo circa di scritture.
Un backend Convex
Un backend Convex è una normale App server con un volume, creata dal
template convex (beeuni apps launch --template convex --name db; vedi
Template convex).
L’SDK non ha chiamate specifiche per Convex. Leggi la chiave amministratore dalla macchina con
exec e concedi a un’App UI l’accesso al backend con uses:
const db = (await apps.list()).find((a) => a.slug === 'db')!;
const { stdout } = await apps.exec(db.app_id, { command: ['sh', '-c', 'cd /convex && ./generate_admin_key.sh'] });
// The admin key controls all code and data: never log it or ship it to a browser.
await apps.update(site.app_id, { uses: ['db'] }); // the site may mint db tokens and bind to it
db.services contiene gli URL del backend: http (l’URL del client Convex) e
site (azioni HTTP).
La tua App sa già chi la sta guardando
Un’App ospitata da Beeuni viene aperta da qualcuno che Beeuni ha già autenticato. Il gateway delle App lo autentica prima che venga servito il primo byte, quindi la tua App non ha bisogno di un proprio login: nessuna seconda password, nessuna schermata di consenso, nessun reindirizzamento.
Nel browser:
import { createBeeuni, beeuniAppViewerToken } from '@beeuni/sdk';
import { useBeeuniAppViewer } from '@beeuni/sdk/react';
const beeuni = createBeeuni({
backendUrl: '/_beeuni/api/v1', // the gate, on your App's own origin
getToken: beeuniAppViewerToken(), // the viewer's own App-scoped token
});
function Header() {
const { status, viewer } = useBeeuniAppViewer();
return <span>{status === 'viewer' ? viewer.email : 'Signed out'}</span>;
}
Il browser chiama /_beeuni/api/v1/* sull’origine della tua App, mai
https://api.beeuni.ai/v1 direttamente: l’API non risponde alle richieste preliminari CORS provenienti
dall’origine di un’App, quindi una chiamata diretta fallisce nel browser. Il gateway inoltra ogni
chiamata all’API Beeuni come visitatore autenticato, con il token del visitatore limitato
all’App. Regole:
- Richiede
viewer_token_scope: 'api'. Altrimenti risponde403 viewer_api_disabled. - Agisce solo per un visitatore autenticato dal gateway (login Beeuni o un link
di accesso). In assenza di un visitatore, risponde
401 no_viewer_identity. - Risponde solo a richieste provenienti dall’origine dell’App. A una richiesta cross-site
risponde
403 cross_site_request. - Inoltra
/v1/*tranne/v1/oauth/*e gli endpoint MCP, che rispondono404. Il cookie della tua App e il suo headerAuthorizationnon raggiungono mai l’API, e l’API non può impostare un cookie sulla tua App.
Sul server della tua App, il gateway inserisce l’identità firmata in ogni richiesta:
import { readAppViewer, createAppViewerBeeuni } from '@beeuni/sdk/server';
const viewer = await readAppViewer(request);
// { userId, email, name, picture, groupIds, groups, role, projectId, accountId, appId, accessMode, token }
if (!viewer) return new Response('Not found', { status: 404 });
// and, for an `api`-scoped App, act as them:
const beeuni = await createAppViewerBeeuni(request, { backendUrl });
await beeuni.projects.list(); // their projects, their role
readAppViewer verifica un HMAC sull’header con BEEUNI_APP_VIEWER_SECRET,
che Beeuni inserisce nella tua App al momento del deploy. Un header contraffatto non supera mai la verifica: il
gateway elimina qualsiasi copia fornita dal client prima dell’inoltro, e la firma viene
creata con un segreto derivato per ogni App.
Quali informazioni riceve la tua App
Un’impostazione nella policy di accesso dell’App: Settings → Access in Beeuni,
beeuni apps access <app> --viewer off|identity|api, oppure viewer_token_scope
su PATCH /projects/:id/apps/:appId/access:
| Ambito | La tua App riceve |
|---|---|
identity (predefinito) |
Id, email, nome, immagine, ruolo nell’account e gruppi Beeuni del visitatore (id e nomi, solo nell’account di questa App), oltre a un token profile email. Sufficiente per mostrare a ogni persona i propri dati e limitare l’accesso per gruppo. |
api |
Quanto sopra, più un token che agisce come quella persona sull’API Beeuni, entro i limiti del suo ruolo. |
off |
Nulla. |
Il token non è mai la sessione Beeuni dell’utente: dura un’ora, include solo quegli ambiti e ogni token emesso da un’App viene invalidato quando l’App viene eliminata o la sua policy di accesso cambia.
Uso del token del visitatore
- Sul tuo server, leggilo a ogni richiesta. Il gateway inserisce un token valido in
x-beeuni-app-viewer-tokenin ogni richiesta inoltrata tramite proxy. Invialo comeAuthorization: Bearerall’API Beeuni:createAppViewerBeeuni(request)fa esattamente questo. Crea un client per ogni richiesta; non memorizzare il token. - Dopo una modifica alla policy di accesso, i vecchi token smettono di funzionare. La richiesta successiva
attraverso il gateway ne contiene uno nuovo. Nel browser,
beeuniAppViewerToken()si ripristina automaticamente: quando l’API rifiuta il suo token, l’SDK rilegge/_beeuni/viewere ripete la chiamata una volta. - Il ruolo del visitatore è il limite massimo. Il token può fare esattamente ciò che
il visitatore può fare in Beeuni, mai di più. In un progetto con autorizzazioni per gli agenti, un
membernecessita di un’autorizzazione per l’agente avviato dalla tua App: senza,POST /projects/:id/sessionsrisponde403 no_agent_access. Concedi ai tuoi visitatori l’accesso all’agente, non un ruolo più ampio. - Quando è presente un visitatore. È presente quando una persona apre l’App con il proprio login Beeuni, oppure
con un link di accesso, che la autentica come quella persona. Un’App
passwordnon ne ha: la password dimostra la conoscenza di un segreto condiviso, non l’identità di una persona. Un’Apppublicne ha uno solo se il visitatore arriva tramite un link di accesso; il semplice URL resta anonimo. - È una credenziale. Beeuni esclude gli header delle richieste dal log degli accessi dell’App
(
beeuni apps logs). Non registrare l’header nei log del tuo codice.
Un’App servita sul proprio dominio (non *.apps.beeuni.ai) non ha un gateway
davanti: usa invece Accedi con Beeuni.
Un solo membro, in ogni runtime
Beeuni garantisce l’identità della stessa persona in tre contesti: il browser interroga il gateway
(/_beeuni/viewer), il server dell’App legge l’header firmato e un’App che
verifica i token di autenticazione (un backend Convex o il tuo server) riceve un
token firmato da Beeuni. readBeeuniMember converte ciascuna di queste forme in un unico
BeeuniMember, e requireBeeuniMember decide chi può procedere. Nessun
framework e nessuna dipendenza: lo stesso codice funziona in un browser, Node, Bun, un
Worker e una funzione Convex.
import {
readBeeuniMember, requireBeeuniMember, verifyBeeuniToken,
beeuniToken, beeuniBinding, fetchBeeuniAppViewer, BeeuniMemberError,
} from '@beeuni/sdk';
// Browser: who is looking (null when nobody).
const me = readBeeuniMember(await fetchBeeuniAppViewer());
// Browser → an App this App uses, on the same origin: its URL and a token fetcher.
const db = beeuniBinding('db');
const convex = new ConvexReactClient(db.url);
convex.setAuth(db.token);
// React: read the member with useQuery(api.members.me), never useConvexAuth() (see below).
// Browser: a token for this App itself, or for an App it uses.
const token = await beeuniToken()();
const dbToken = beeuniToken({ audience: 'db' });
// A Convex function: the runtime verified the token; enforce the rules.
const member = requireBeeuniMember(await ctx.auth.getUserIdentity(), { groups: ['Finance'] });
// A server App: verify the bearer. No options = the BEEUNI_AUTH_* env Beeuni injects.
// Any other server: pass the App's `auth` values.
const caller = await verifyBeeuniToken(bearer, { issuer: app.auth.issuer, audience: app.auth.audience, jwks: app.auth.jwks_uri });
In React, non chiamare useConvexAuth(): genera un’eccezione con un semplice
ConvexProvider (richiede ConvexProviderWithAuth e beeuniBinding non viene
verificato con quel provider). Leggi il membro autenticato con una query:
// convex/members.ts
export const me = query({ args: {}, handler: async (ctx) => readBeeuniMember(await ctx.auth.getUserIdentity()) });
const me = useQuery(api.members.me); // undefined: loading. null: signed out. Otherwise the member.
readBeeuniMember restituisce null per un chiamante anonimo. Usa
requireBeeuniMember solo dove la funzione deve generare un’eccezione.
| Campo | Valore |
|---|---|
userId |
Id utente Beeuni. Stabile; usalo come chiave delle righe. |
email, name, picture |
Dal profilo del membro. null se sconosciuto. |
groups, groupIds |
I gruppi Beeuni del membro in questo account, per nome e per id. |
role |
Ruolo nell’account: owner, admin o member. |
accountId, projectId |
Dove risiede l’App. |
requireBeeuniMember(claims, { groups, roles }) restituisce il membro oppure genera
BeeuniMemberError con code unauthenticated (nessuno, oppure un token che
non ha superato una verifica) o forbidden (al di fuori dei gruppi o dei ruoli). Ogni elenco significa
“uno qualsiasi di questi”; un gruppo corrisponde per nome o id; un elenco vuoto non ammette nessuno.
Token di autenticazione
Un unico emittente per progetto firma i token di ogni App: iss =
<API origin>/v1/projects/<projectId>, aud = l’id dell’App a cui il token è
destinato, 15 minuti. Il suo set di chiavi è pubblico su <issuer>/jwks.json, identificato da
<issuer>/.well-known/openid-configuration. Ogni App contiene i valori che
verificano i suoi token: app.auth.issuer, app.auth.audience, app.auth.jwks_uri.
Un token contiene sub, email, name, picture, groups, group_ids,
role, account_id e project_id; il token di un agente contiene
kind: 'agent' e nessun gruppo o ruolo.
| Funzione | Cosa fa |
|---|---|
beeuniToken({ audience? }) |
Una funzione per recuperare token (nel formato che i client realtime accettano come callback di autenticazione) per l’App stessa (predefinito) o per un’App che usa. Legge /_beeuni/token sull’origine dell’App stessa. Il risultato viene memorizzato nella cache fino a poco prima della scadenza; { forceRefreshToken: true } lo richiede di nuovo. Restituisce null in assenza di un visitatore autenticato e null più un unico avviso nella console per un’App che non usa (403 app_not_linked). Non genera mai eccezioni. |
beeuniBinding(slug, { origin? }) |
{ url, token } per un’App usata da questa App: url = <origin>/_beeuni/apps/<slug> (HTTP e WebSocket, stessa origine, senza CORS), token = beeuniToken({ audience: slug }). |
verifyBeeuniToken(bearer, { issuer?, audience?, jwks? }) |
Verifica la firma ES256, la scadenza (tolleranza di 60 s), l’emittente e il destinatario. Valori predefiniti: BEEUNI_AUTH_ISSUER, BEEUNI_AUTH_AUDIENCE, BEEUNI_AUTH_JWKS. Beeuni inserisce tutti e tre nel runtime di ogni App server (emittente: il progetto; destinatario: l’id dell’App stessa; set di chiavi: un URI data:), quindi verifyBeeuniToken(bearer) funziona in quel contesto senza opzioni. Non impostare mai BEEUNI_AUTH_* in env di un’App server: il prefisso BEEUNI_ è riservato e beeuni validate lo rifiuta. Il set di chiavi è un oggetto, testo JSON, un URI data: o un URL https (auth.jwks_uri, recuperato una volta per processo). Ogni errore genera BeeuniMemberError, inclusa l’assenza di un set di chiavi o di un destinatario: una sola chiave del progetto firma i token di tutte le App, quindi è aud a impedire l’accesso con il token di un’altra App. audience: false accetta un token per qualsiasi App del progetto. |
apps.token(appId) |
Un token che identifica il chiamante, dall’API (POST …/apps/:id/token). Una sessione agente ne riceve uno che identifica il suo agente. |
Un membro rimosso dal progetto o da un gruppo perde l’accesso entro la durata di un token; il controllo di accesso dell’App lo respinge al successivo caricamento della pagina. I gruppi Beeuni richiedono il piano Enterprise; i ruoli funzionano con tutti i piani.
Gestire l’accesso
await apps.access.update(app.app_id, {
mode: 'restricted',
member_ids: [memberId],
group_ids: [groupId],
});
const preview = await apps.access.session(app.app_id);
window.open(preview.url); // valid for five minutes
AppAccessConfig riporta password_configured, mai la password o il suo
hash. Imposta una password con { mode: 'password', password }. Ogni aggiornamento
incrementa revision, revocando i cookie dell’App esistenti.
Tipi
Ogni tipo qui sotto viene esportato da @beeuni/sdk.
| Tipo | Cosa contiene |
|---|---|
App |
Identità, capabilities, labels, scale, services (AppServiceSummary[]), machines (total, running, stopped), volumes (count, size_gb), config (AppReleaseConfig della release in produzione, oppure null), uses, used_by, auth, url, access_mode, access_revision, desired_state, active_deployment_id, hosting_type (della release attiva: static, sandbox oppure null prima del primo deployment), machine (la dimensione del gruppo che serve la release in produzione), idle_timeout_seconds, always_on, retained_deployments, warnings (solo creazione e aggiornamento; sempre []), last_request_at, viewer_can_access |
AppCapability |
'deployments' | 'rollback' | 'preview' | 'sleep' | 'static' | 'member_tokens' | 'machines' | 'scale' | 'exec' | 'volumes' | 'snapshots' |
AppReleaseConfig · AppReleaseConfigInput |
La configurazione risolta della release (processes, services, mounts, vm, deploy) · la forma definita dall’autore (processes, http_service, services, mounts, vm, deploy e le forme abbreviate port, command, readiness_path, always_on, resources) |
AppServiceConfig · AppServiceAccess · AppAutoStop · AppCheck |
name, internal_port, protocol, access, auto_stop_machines, auto_start_machines, min_machines_running, processes, checks · 'public' | 'app' | 'admins' · 'off' | 'stop' | 'suspend' · type, port, path, interval_s, timeout_s, grace_period_s |
AppMountConfig · AppVmConfig · AppDeployStrategy |
source, destination, initial_size_gb, snapshot_retention_days, processes · cpu, memory_gb, disk_gb, processes · 'rolling' | 'immediate' | 'bluegreen' | 'canary' |
AppServiceSummary |
name, protocol, access, internal_port, processes, url (null quando l’etichetta dell’host sarebbe troppo lunga) |
AppMachine · AppMachineState |
machine_id, instance_id, process_group, state, release (deployment_id, version), vm, volume, created_at, started_at, stopped_at · 'started' | 'starting' | 'stopped' | 'stopping' | 'provisioning' |
AppVolume · AppVolumeSnapshot · CreateAppVolumeInput |
volume_id, name, process_group, machine_id, size_gb, status, attached, snapshot_retention_days, created_at · snapshot_id, kind (daily, manual, final), commit_id, created_at · name, size_gb, process_group, snapshot_retention_days |
AppExecInput · AppExecResult |
command, machine_id, timeout_s · machine_id, exit_code, stdout, stderr, truncated |
AppAuth |
issuer, audience (l’id dell’App), jwks_uri: i valori pubblici che verificano i token di accesso dell’App |
AppToken |
token, expires_at |
BeeuniTokenOptions · BeeuniTokenFetcher · BeeuniBinding · VerifyBeeuniTokenOptions |
Le funzioni di supporto per i token di accesso descritte sopra |
AppDeployment |
version, status, source_kind, hosting_type (static: servito dallo storage, senza runtime; sandbox: macchine), hosting_provider, runtime_spec, build_spec, error_code, attempt_count, created_by, actor_type, source_session_id, config (la configurazione risolta della release), strategy, release_command_status (running, succeeded, failed oppure null) |
AppRollback |
Il risultato con cui si risolve apps.rollback: App più la release in coda in deployment |
AppDeploymentDetail |
Un deployment più il suo events: eventi del ciclo di vita e log di build (righe build_log e un evento log_truncated quando la build ha prodotto più di 5.000 righe) |
AppAccessConfig |
mode, revision, member_ids, group_ids, password_configured |
AppAccessMode |
'private' | 'project' | 'restricted' | 'public' | 'password' |
AppSource |
StaticAppSource | BundleAppSource | DockerfileAppSource | OciImageAppSource |
AppArtifact |
kind, status, sha256, size_bytes, image_reference |
AppLogEntry · AppLogsResponse |
Una riga di log e una pagina più next_cursor |
apps.list() e useProjectApps restituiscono solo le App che il chiamante può aprire
in base alla policy di accesso di ciascuna App (privata: il creatore; limitata: i
membri e i gruppi elencati; progetto, password e pubblica: il team del progetto). Un responsabile del progetto
vede tutte le App, quindi un’App privata resta gestibile quando il suo creatore
lascia il progetto. viewer_can_access è quindi true per ogni App elencata. Un get di
un’App che il chiamante non può aprire risponde 404, non 403. Considera undefined come
sconosciuto, non come negato.
AppAccessMode è un’impostazione di visibilità per risorsa che si aggiunge al modello dei ruoli,
non un ruolo. restricted identifica utenti e gruppi, gli stessi tipi di entità usati dal
modello dei ruoli. Vedi
Account e accesso.
DockerfileAppSource e OciImageAppSource richiedono command. port è la
porta del servizio principale: omettila solo per una release con soli worker (un config
con processes e nessun servizio). StaticAppSource e BundleAppSource non accettano
nessuno dei due.
Hook React
@beeuni/sdk/react esporta sette hook per le App.
useProjectApps(projectId)
Le App che il chiamante può aprire, più le relative mutazioni del ciclo di vita. Ogni mutazione invalida l’inventario in caso di successo.
import { useProjectApps } from '@beeuni/sdk/react';
function AppList({ projectId }: { projectId: string }) {
const apps = useProjectApps(projectId);
if (!apps.data) return null;
return (
<ul>
{apps.data.map((app) => (
<li key={app.app_id}>
<a href={app.url}>{app.slug}</a>
<button onClick={() => apps.stop.mutate(app.app_id)}>Stop</button>
</li>
))}
</ul>
);
}
Restituisce i campi della query più create, update, start, stop e
remove.
useAppDeployments(projectId, appId)
La cronologia immutabile dei deployment, recuperata di nuovo ogni 5 s affinché una build in esecuzione avanzi autonomamente.
const deployments = useAppDeployments(projectId, appId);
await deployments.deploy.mutateAsync({
artifact_id: artifact.artifact_id,
source: { kind: 'static', spa: true },
});
await deployments.rollback.mutateAsync(previousDeploymentId);
Entrambe le mutazioni invalidano l’elenco dei deployment e l’inventario delle App.
useAppDeployment(projectId, appId, deploymentId)
Un deployment e i suoi eventi, incluso il log di build (AppDeploymentDetail). Esegue
il polling ogni 2 s finché il deployment non è ready, failed o cancelled. Passa
null come deploymentId per mantenerlo inattivo.
const detail = useAppDeployment(projectId, appId, deploymentId);
const log = (detail.data?.events ?? [])
.map((event) => (event.type === 'build_log' ? event.message : `[${event.type}] ${event.message}`))
.join('\n');
Una build conserva le prime 2.500 e le ultime 2.500 righe. Le righe vengono scritte in batch, al massimo 1 s dopo essere state stampate. Un deployment non riuscito o annullato conserva il log della build per 14 giorni.
useAppAccess(projectId, appId, options?)
La policy di accesso e una sessione di accesso di breve durata. Le due parti sono query separate e ciascuna è facoltativa.
const access = useAppAccess(projectId, appId, {
policy: canEditAccess,
session: app.viewer_can_access,
});
access.policy.data; // AppAccessConfig
access.session.data; // { url, expires_at }
await access.update.mutateAsync({ mode: 'project' });
| Opzione | Valore predefinito | Usa false quando |
|---|---|---|
policy |
true |
L’interfaccia mostra solo un’anteprima dell’App. GET …/access è una lettura amministrativa e risponde 403 a un chiamante senza permessi di gestione del progetto. |
session |
true |
Il chiamante non può aprire l’App. Passa app.viewer_can_access (sempre true per un’App elencata). |
Una griglia di App che lascia entrambe le opzioni su true esegue una lettura della policy e una
creazione di sessione per ogni App, e ciascuna restituisce 403 per un membro che non può aprire
quell’App.
useAppMachines(projectId, appId)
Le macchine di un’App, interrogate ogni 5 s, più start, stop, restart,
destroy (ciascuna accetta un machineId) e scale (accetta
{ [group]: count }). Ogni mutazione aggiorna le macchine e l’inventario delle App.
appMachinesKey(projectId, appId) è la chiave della query.
const machines = useAppMachines(projectId, app.app_id);
await machines.scale.mutateAsync({ worker: 2 });
await machines.restart.mutateAsync(machineId);
useAppVolumes(projectId, appId)
I volumi di un’App, più snapshot (volumeId), restore
({ volumeId, snapshotId }), fork ({ volumeId, name, snapshotId? }),
extend ({ volumeId, sizeGb }) e destroy ({ volumeId, name }). Ogni
mutazione aggiorna i volumi. appVolumesKey(projectId, appId) è la chiave
della query.
const volumes = useAppVolumes(projectId, app.app_id);
await volumes.snapshot.mutateAsync(volumeId);
await volumes.restore.mutateAsync({ volumeId, snapshotId });
useAppVolumeSnapshots(projectId, appId, volumeId)
Gli snapshot di un volume. Passa null come volumeId per mantenerla inattiva.
Errori
import { featureDisabledKey, isFeatureDisabledError } from '@beeuni/sdk';
try {
await beeuni.project(projectId).apps.list();
} catch (error) {
if (isFeatureDisabledError(error)) {
console.log(`${featureDisabledKey(error)} is off for this project`);
}
}
Altre risposte da gestire:
409per uno slug duplicato,402conapp_quota_exceededquando l’account ha raggiunto il limite di App e400conapp_machine_out_of_rangeper una specifica fuori dai limiti consentiti.400 app_not_found(slugs) per uno slug sconosciuto inuses.409 app_capability_unsupported(capability) per una chiamata che l’App non offre.- Un deployment:
400 app_config_invalid(issues),400 app_volumes_unsupported. Un rollback:409 release_not_ready. - Macchine ed exec:
409 app_no_machines,404 machine_not_found,409 machine_not_running,400 process_group_unknown,400 scale_out_of_range,400 command_required. - Volumi:
400 volume_name_invalid,volume_size_invalid,volume_shrink_refused,volume_retention_invalid,snapshot_invalid,confirmation_required;404 volume_not_found;409 volume_attached.
token() risponde con Cache-Control: no-store.