SSD Nodes Learn 🎉 VPS da $4.99/mese
Guide Matt ConnorDi Matt Connor

Come configurare OpenTag su VPS per agenti AI

Guida al self-hosting di OpenTag per gestire le menzioni Slack e GitHub. Configura TLS ingress, verifica firme webhook e token per eseguire agenti in sicurezza su VPS.

Cosa fa OpenTag quando menzioni un agente

OpenTag trasforma una @menzione in un thread di Slack o in una issue di GitHub nell'esecuzione di un agente di programmazione su una macchina di tua proprietà. Qualcuno commenta @opentag investigate this su una issue. Un listener riceve l'evento dalla piattaforma, ne verifica la firma, associa la menzione a un progetto collegato, avvia un agente di programmazione su un checkout locale e pubblica il risultato nello stesso thread.

Il progetto è rilasciato con licenza MIT ed è disponibile su amplifthq/opentag. Ad agosto 2026, la versione più recente è la v0.9.0, pubblicata il 28 luglio 2026, distribuita come pacchetto npm. Non esiste un'immagine container ufficiale, pertanto la versione da bloccare è quella npm. Ogni comando riportato di seguito ne blocca la versione.

Questo diventa un progetto da VPS piuttosto che da laptop a causa del funzionamento di GitHub. GitHub invia gli eventi del repository effettuando una richiesta HTTP a un URL che registri una sola volta; di conseguenza, tale URL deve rispondere allo stesso indirizzo anche in futuro.

I quattro componenti dinamici

Il listener riceve gli eventi della piattaforma; ogni piattaforma ne ha uno dedicato. Il listener di GitHub è un endpoint HTTP sulla porta 3050 al percorso /github/webhooks. Il listener della Slack Events API è sulla porta 3040 a /slack/events. Slack può anche operare in Socket Mode, modalità in cui l'applicazione apre un WebSocket in uscita e non richiede alcuna porta in entrata.

Il dispatcher è il coordinatore. È in ascolto sulla porta 3030 per impostazione predefinita, mantiene lo stato dell'esecuzione in un file di database locale definito da OPENTAG_DATABASE_PATH e registra una traccia di audit per ogni esecuzione. Nessun elemento esterno al sistema dovrebbe mai raggiungere questa porta.

Il runner è il demone locale. Esegue il polling per verificare la presenza di lavoro, acquisisce un'esecuzione, ne detiene il lease e invia un heartbeat ogni 15 secondi (valore predefinito) finché l'esecuzione è attiva. Rifiuta qualsiasi esecuzione acquisita il cui target di progetto sia mancante o non presente nella allowlist della propria configurazione; questo controllo impedisce che un evento GitHub indirizzi l'agente verso un repository non autorizzato.

L'executor è l'agente di codifica vero e proprio. OpenTag lo avvia tramite ACP (agent client protocol), un protocollo JSON-RPC che utilizza lo standard input e output; l'agente viene quindi eseguito come processo figlio all'interno di una directory di lavoro assegnata da OpenTag. I nomi integrati includono echo, codex, claude-code, cursor, opencode, hermes e openclaw. Inizia con echo, l'executor fornito con la configurazione di esempio, poiché permette di verificare il corretto funzionamento dell'intero percorso prima che un modello interagisca con il tuo codice.

L'ordine non cambia mai: evento della piattaforma, verifica della firma, registrazione dell'esecuzione, acquisizione, agente, risposta nel thread.

Perché un portatile e un tunnel non sono sufficienti

La guida alla configurazione di GitHub suggerisce di eseguire ngrok http 3050 e incollare l'host del tunnel nel webhook del repository. Questo funziona per i primi dieci minuti. Un host di tunnel gratuito cambia a ogni riavvio del processo e cessa di esistere quando il portatile va in sospensione. GitHub mantiene il vecchio URL del payload e continua a tentare l'invio, quindi la scheda Recent Deliveries nelle impostazioni del webhook si riempie di errori mentre il thread rimane silenzioso. Nessuno se ne accorge per una settimana, perché un webhook che non fa nulla appare esattamente come un bot di cui nessuno ha parlato.

Un VPS risolve i due problemi che causano l'interruzione. Il nome DNS non cambia, quindi l'URL del payload che incolli una volta rimane corretto. La macchina non va in sospensione, quindi un commento alle 02:00 riceve una risposta. Configura correttamente la macchina prima di procedere: i primi dieci minuti su un nuovo VPS copre l'utente di accesso e il firewall che questa guida assume come prerequisiti.

Slack è l'eccezione. In Socket Mode si connette verso l'esterno e non necessita di un URL pubblico, quindi una distribuzione basata solo su Slack può rimanere chiusa. GitHub non ha un equivalente. I webhook dei repository sono connessioni HTTP in entrata, il che significa un endpoint pubblico, il che implica TLS (transport layer security) e una verifica della firma.

Self-host di OpenTag su Ubuntu da una release bloccata

OpenTag v0.9.0 richiede Node.js 22 o versioni successive. Ubuntu 24.04 distribuisce Node 18 nei propri repository, pertanto è necessario installarlo tramite NodeSource.

curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs
node -v

node -v deve restituire v22 o superiore. Con Node 20, l'installazione genera un avviso EBADENGINE e la CLI può fallire all'avvio.

Assegna al servizio un account dedicato. L'agente viene eseguito con i permessi di questo utente, quindi non deve essere il tuo utente di login e non deve essere root. Utenti con privilegi minimi su una VPS spiega perché questa separazione giustifica il passaggio aggiuntivo.

sudo adduser --disabled-password --gecos "" opentag
sudo loginctl enable-linger opentag
sudo npm install -g @opentag/cli@0.9.0
command -v opentag

command -v opentag dovrebbe restituire un percorso come /usr/bin/opentag. L'impostazione di linger è importante su Linux: OpenTag installa il suo servizio in background tramite systemd e un servizio utente senza linger si interrompe nel momento in cui chiudi la sessione SSH.

Esegui il setup come quell'utente.

sudo -iu opentag opentag setup

Il setup richiede sei informazioni: la lingua della CLI, l'indirizzo di ascolto locale, l'agente di codifica, il progetto locale su cui lavorare, le credenziali della piattaforma da salvare e la modalità di esecuzione. Mantieni l'indirizzo di ascolto su 127.0.0.1, poiché nginx termina il TLS e inoltra le richieste; in questo modo i listener non devono essere raggiungibili dall'esterno. Per GitHub, richiede anche il repository in formato owner/repo, se può aprire pull request, la porta del webhook (3050 di default) e il token. Scegli la modalità servizio in background alla fine. Se hai già una configurazione e vuoi installare il servizio senza prompt, opentag setup --service esegue questa operazione.

La configurazione viene salvata in /home/opentag/.config/opentag/config.json e lo stato di runtime in /home/opentag/.local/state/opentag. Vale la pena controllare manualmente queste chiavi dopo che il setup ha scritto il file.

{
  "runnerId": "runner_local",
  "dispatcherUrl": "http://localhost:3030",
  "runnerToken": "...",
  "approvalMode": "ask",
  "repositories": []
}

Preferisci runnerToken, il bearer token limitato al runner, rispetto al più datato pairingToken condiviso. Il file di configurazione conserva le credenziali in testo semplice a meno che non vengano sostituite con un riferimento a un secret, che legge il valore dall'ambiente o da un file su disco all'avvio. In ogni caso, questo file è l'elemento più sensibile sul server: imposta i permessi a 600, proprietà di opentag, e non inserirlo mai in un repository git. La discussione approfondita è disponibile in mantenere i secret fuori dagli agenti AI.

Verifica l'installazione prima di esporre qualsiasi cosa.

sudo -iu opentag opentag doctor
sudo -iu opentag opentag status

opentag doctor controlla il dispatcher, i binding, i checkout e gli executor. opentag status stampa la configurazione e lo stato di runtime, e può essere limitato a una singola esecuzione una volta che queste esistono. Risolvi tutto ciò che doctor segnala prima di puntare una piattaforma verso questo server.

Inserire TLS davanti e aprire solo due percorsi

Nginx termina il TLS e inoltra esattamente due percorsi. Tutto il resto restituisce un 404, in modo che uno scanner che individua l'host non apprenda nulla su ciò che è in esecuzione dietro di esso.

Scrivere un blocco server semplice sulla porta 80 in /etc/nginx/sites-available/opentag con i due percorsi sottostanti, quindi lasciare che Certbot aggiunga la parte TLS.

sudo apt install -y nginx certbot python3-certbot-nginx
sudo ln -s /etc/nginx/sites-available/opentag /etc/nginx/sites-enabled/opentag
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d opentag.example.com

nginx -t stampa syntax is ok e test is successful, ed è l'unica cosa che impedisce a un errore di battitura di causare un ricaricamento che interrompe il sito. Certbot su Ubuntu 24.04 con nginx copre il rinnovo e le modalità in cui una sfida ACME (automatic certificate management environment) fallisce. Il blocco completato appare così.

server {
    listen 443 ssl;
    server_name opentag.example.com;

    ssl_certificate     /etc/letsencrypt/live/opentag.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/opentag.example.com/privkey.pem;

    client_max_body_size 2m;

    location = /github/webhooks {
        proxy_pass http://127.0.0.1:3050;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }

    location = /slack/events {
        proxy_pass http://127.0.0.1:3040;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }

    location / {
        return 404;
    }
}

Il = in location = /github/webhooks è una corrispondenza esatta, e proxy_pass senza nulla dopo la porta trasmette l'URI originale invariato. Rimuovere il = e ogni percorso sotto /github/webhooks/ viene inoltrato a sua volta, il che rappresenta una superficie di attacco maggiore di quella necessaria al listener.

Il firewall rimane ristretto.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

Le porte 3030, 3040 e 3050 non vengono mai aperte. Confermare che siano vincolate al loopback anziché a ogni interfaccia.

sudo ss -tlnp

Ogni riga OpenTag dovrebbe leggere 127.0.0.1:3030 o simile. Una riga che legge 0.0.0.0:3050 significa che il listener si sta offrendo all'intero Internet e solo ufw lo sta bloccando; basta un errore nel firewall per attivare un agente esposto. Nozioni di base sul firewall ufw spiega cosa sta realmente facendo quel "deny" predefinito.

Due controlli verificano la porta d'ingresso. curl -I https://opentag.example.com/ restituisce 404 da nginx, il che dimostra che il certificato è valido e il catch-all è chiuso. Una richiesta a /slack/events o /github/webhooks priva di firma non deve mai restituire 200.

Verificare ogni firma, poiché l'URL è pubblico

Chiunque può trovare l'URL del payload. Si trova nelle impostazioni del repository, nella cronologia del browser o in uno screenshot incollato in un ticket. La firma è l'unico elemento che distingue una consegna reale di GitHub da una richiesta digitata manualmente.

GitHub firma ogni consegna con il webhook secret e invia il risultato nell'header x-hub-signature-256. OpenTag verifica tale header rispetto a platforms.github.webhookSecret. Le note di hardening del progetto stabiliscono la regola in modo diretto: non accettare eventi sorgente non firmati su /github/webhooks. Slack firma ogni richiesta con SLACK_SIGNING_SECRET e include un timestamp, in modo che un corpo catturato non possa essere riprodotto ore dopo.

Ignorare questo aspetto non è un rischio trascurabile. Un endpoint non verificato accetta un payload issue_comment scritto a mano contenente @opentag e OpenTag esegue quindi un coding agent, con il tuo token, nel tuo checkout, seguendo le istruzioni di uno sconosciuto. La risposta viene inviata a qualsiasi thread indicato dal payload contraffatto.

OpenTag aggiunge due livelli di protezione. Le consegne sorgente sono tracciate tramite delivery ID, quindi la riconsegna dello stesso evento non avvia una seconda esecuzione. Le chiamate al runner accettano chiavi di idempotenza, pertanto la riproduzione di una richiesta restituisce successo senza aggiungere un ulteriore evento di audit.

I rate limit sono configurabili e devono essere attivi. OPENTAG_RATE_LIMIT_WINDOW_MS e OPENTAG_RATE_LIMIT_MAX_REQUESTS limitano la frequenza delle richieste, OPENTAG_MAX_REQUEST_BODY_BYTES limita il corpo del messaggio e un payload di dimensioni eccessive viene rifiutato con 413 request_body_too_large. OPENTAG_RATE_LIMIT_DISABLED=true esiste per lo sviluppo locale e non deve essere presente su un server pubblico. Un'ulteriore regola dalle stesse note: un URL di relay pubblico deve utilizzare HTTPS e la CLI consente HTTP in chiaro solo per localhost.

Quali scope del token sono effettivamente necessari per il bot?

Su GitHub, OpenTag utilizza un personal access token granulare anziché una GitHub App. La documentazione specifica che il percorso tramite App è pianificato ma non rappresenta l'impostazione predefinita attuale della CLI, il che comporta una conseguenza spesso trascurata: il bot pubblica i commenti a nome dell'utente che ha creato il token. Crea il token utilizzando un account che sei disposto a vedere citato in ogni risposta di triage.

Limita lo scope seguendo rigorosamente la guida all'installazione. Seleziona Only select repositories e scegline uno. Concedi i permessi Issues: Read and write e Pull requests: Read and write. Questo è sufficiente per leggere una menzione e rispondere nel thread.

Nota cosa manca: l'accesso in scrittura al codice. OpenTag non esegue il push di branch a meno che preparePullRequestBranch non sia impostato su true, ed esiste un parametro separato githubApplyToken affinché il token che scrive il codice non sia lo stesso che scrive i commenti. Mantienili separati e non attivare il token di scrittura finché il percorso di lettura e commento non è stato testato per alcune settimane.

La configurazione da evitare è un token con Contents: Read and write su All repositories. Chiunque possa commentare in uno qualsiasi di quei repository può ora controllare un agente che dispone di diritti di commit, e la traccia di audit indicherà il proprietario del token come autore dell'azione. Estendi lo scope un repository alla volta, solo dopo che l'agente ha dimostrato di averne bisogno.

Su Slack gli scope del bot sono app_mentions:read, chat:write, reactions:write e channels:history. I canali privati richiedono inoltre groups:history oltre alla sottoscrizione all'evento message.groups. La Socket Mode richiede un token a livello di app con connections:write, quello che inizia con xapp-. channels:history legge la cronologia dei messaggi nei canali pubblici in cui il bot è stato aggiunto; pertanto, aggiungi il bot solo ai canali in cui è necessario, anziché ovunque.

Risoluzione end-to-end di un problema

Il webhook è il primo punto di verifica. Nel repository, apri Settings, poi Webhooks e infine Add webhook. L'URL del payload è https://opentag.example.com/github/webhooks, il tipo di contenuto è application/json e il secret è quello generato durante il setup. Sottoscrivi solo Issue comments e Pull request review comments, senza aggiungere altro.

GitHub invia una consegna di ping non appena salvi. Apri Recent Deliveries e verifica che la richiesta abbia raggiunto il server. Un errore 502 indica che nginx non è riuscito a raggiungere il listener; si tratta di un problema locale, non di GitHub.

Ora utilizzalo. Apri una issue che descrive un bug e commenta:

@opentag triage this. Reproduce the report against the current main branch, then reply with the file and function most likely responsible, plus the test you would write first.

Ecco cosa dovrebbe accadere, in ordine. Recent Deliveries registra la consegna issue_comment con una risposta 2xx. Il dispatcher registra un'esecuzione. Il runner la prende in carico e avvia l'invio di heartbeat. L'executor apre il checkout ed esegue il lavoro. La risposta arriva come commento nello stesso thread della issue. sudo -iu opentag opentag status mostra l'esecuzione mentre è in corso, permettendoti di monitorarla invece di procedere per tentativi.

Imposta approvalMode su ask prima della prima esecuzione reale. In modalità ask, l'esecuzione va in pausa e attende l'intervento umano prima di compiere qualsiasi azione che modifichi lo stato. Esistono anche le modalità auto e autonomous, che risultano utili in seguito, su un repository di cui hai letto un mese di trascrizioni.

Sul lato Slack, la stessa esecuzione inizia con /bind owner/repo nel canale, seguito da una menzione. Il bot risponde anche a /help, /status, /doctor, /stop e /unbind confirm. Limita chi può modificare i binding tramite OPENTAG_SLACK_BINDING_ADMIN_USER_IDS, una lista di ID utente Slack separati da virgola, poiché un binding rappresenta la mappatura tra un canale pubblico e un checkout sul tuo server.

Il triage è un buon primo percorso perché esegue operazioni di sola lettura e la risposta è facile da valutare. La revisione è il passo successivo, in cui l'agente commenta un diff invece di una issue: un agente di revisione pull request self-hosted utilizza la stessa architettura puntata sulle pull request. Se desideri che l'agente raggiunga i tuoi sistemi durante il lavoro, questo è il compito dei server MCP su una VPS.

Cosa succede quando l'agente sbaglia davanti a tutti?

Sbaglierà. La questione è quanto costa questo errore.

Una risposta errata su un problema pubblico è un commento sotto un nome che il tuo team riconosce, e GitHub invia un'email a tutti gli iscritti nel momento in cui viene pubblicato. Eliminare il commento non richiama l'email. Lo stesso vale per una notifica Slack. Pianifica la risposta ipotizzando che sia errata in pubblico, piuttosto che corretta in privato.

Quattro scelte limitano il danno e sono più importanti di qualsiasi prompt tu scriva.

  • Esegui in modalità ask, in modo che l'agente proponga, una persona approvi e un piano errato costi solo un clic.
  • Lascia preparePullRequestBranch al suo valore predefinito false, così il risultato peggiore di un'esecuzione errata è un commento sbagliato invece di un branch errato.
  • Collega inizialmente un solo repository e un solo canale. Il runner rifiuta qualsiasi esecuzione il cui target di progetto si trovi al di fuori della sua allowlist locale, quindi un repository non collegato non può trascinare l'agente al suo interno.
  • Mantieni il token di commento separato da qualsiasi token di applicazione, in modo che la revoca dell'accesso in scrittura non comprometta anche il triage.

Slack ha un comando /stop per un'esecuzione che sta prendendo la direzione sbagliata. Ogni esecuzione lascia anche un registro di audit che contiene la menzione che l'ha avviata e ciò che l'agente ha fatto; è questo che leggi in seguito per capire dove ha sbagliato.

La parte sociale conta quanto la configurazione. Inserisci il bot in un canale dove le persone si aspettano una macchina e sanno che può sbagliare. Una risposta errata data con sicurezza in un canale di quaranta persone che presumono sia stata revisionata da un umano costa più di quanto il triage abbia fatto risparmiare. Scrivi nella descrizione del canale chi possiede il bot e chi ne controlla l'output.

Backup, aggiornamenti e blocco della versione

Due percorsi contengono tutto il necessario: /home/opentag/.config/opentag/config.json e /home/opentag/.local/state/opentag. Il primo contiene le credenziali, il secondo lo storico delle esecuzioni e il file del database. Esegui il backup di entrambi con permessi 600 e conservali esternamente al server. Perderli comporta la ricreazione di token e binding, non la ricostruzione del server.

Gli aggiornamenti consistono in un incremento della versione e in un riavvio.

sudo npm install -g @opentag/cli@0.9.0
sudo -iu opentag opentag service stop
sudo -iu opentag opentag service start
sudo -iu opentag opentag doctor

Blocca la versione invece di seguire @latest. Questo software esegue un agente di programmazione sul tuo repository con un token attivo; pertanto, una release pubblicata durante la notte rappresenta una modifica non revisionata. La policy di sicurezza non prevede backport e le correzioni vengono rilasciate solo nell'ultima versione disponibile. Bloccare la versione significa leggere il changelog e procedere intenzionalmente. Non significa restare sulla v0.9.0 per sempre. Lo storico fino a luglio 2026 mostra diverse release al mese, il che costituisce un valido motivo per leggere le note di rilascio prima di ogni aggiornamento.

FAQ

È necessario un VPS per eseguire OpenTag o basta un portatile?

Un portatile è sufficiente per Slack, poiché la Socket Mode apre un WebSocket in uscita e non richiede porte in entrata. GitHub è diverso. I webhook dei repository inviano dati tramite HTTP in entrata a un URL registrato una sola volta; di conseguenza, l'indirizzo deve rimanere costante e deve rispondere anche quando il sistema è inattivo. L'host di un tunnel fornito da un account gratuito cambia a ogni riavvio e GitHub continua a inviare richieste a quello vecchio, causando voci di errore nella scheda Recent Deliveries del repository e l'assenza di risposte nel thread. Un VPS con un nome DNS fisso e un certificato elimina entrambi i problemi.

Quali permessi GitHub richiede OpenTag?

Un personal access token granulare limitato a Only select repositories, con Issues: Read and write e Pull requests: Read and write. Questo è sufficiente per leggere una menzione e rispondere nel thread. L'accesso in scrittura al codice non è necessario a meno che non si imposti preparePullRequestBranch su true per permettere a OpenTag di eseguire il push di branch; esiste inoltre un parametro separato githubApplyToken affinché il token per la scrittura del codice rimanga distinto da quello per i commenti. Evitare l'uso di un token con permessi di scrittura sui contenuti per tutti i repository, poiché chiunque possa commentare in uno di essi potrebbe potenzialmente controllare un agente in grado di eseguire commit.

Come posso interrompere un'esecuzione che sta andando male?

Slack dispone di un comando /stop specifico per questo scopo. Sul server, opentag status mostra i processi in esecuzione e opentag service stop arresta il daemon, terminando l'intera pipeline anziché la singola esecuzione. Per evitare di dover ricorrere a queste soluzioni, impostare approvalMode su ask in modo che le esecuzioni si mettano in pausa in attesa di un intervento umano prima di apportare modifiche, e mantenere preparePullRequestBranch su false affinché un'esecuzione errata produca un commento invece di un branch.

Perché il mio webhook restituisce 502 mentre il thread rimane silenzioso?

L'errore 502 proviene da nginx, non da OpenTag, e indica che il proxy non è riuscito a raggiungere il listener. /var/log/nginx/error.log mostrerà connect() failed (111: Connection refused) while connecting to upstream. Il listener è arrestato oppure si trova su una porta diversa da quella indicata nella riga proxy_pass. Eseguire sudo ss -tlnp e verificare che ci sia un processo in ascolto sulla porta 127.0.0.1:3050 per GitHub e 127.0.0.1:3040 per Slack, quindi eseguire opentag doctor per controllare i binding e gli executor.

#opentag#ai-agents#slack#github#webhooks#self-hosting