Key points
- There is exactly one GitHub App per NopsAI installation. It is installed on any number of accounts, and each installation grants the repositories chosen on GitHub.
- NopsAI creates the App from a manifest and stores the App ID, private key, and webhook secret itself. Nobody copies a key by hand.
- The App is created public, because GitHub only lets an account other than the owner install an App that is public. Public means installable, not advertised.
- An installation on the account that owns the App is enabled on sight. Any other account is held as
Pending approvaland stays inert until an operator approves it. - Only the webhook URL has to be reachable from the internet. The redirect and setup URLs are opened in the operator’s own browser, so NopsAI itself can stay private.
Before you start
- Account
- A GitHub organization you own, or a personal GitHub account
- Reachable webhook
- A public address in front of git-bot, such as a tunnel or reverse proxy. NopsAI itself does not need to be reachable.
- Access
system.updateonsystem:config, which the setup wizard and System > Git Apps both require
Steps
- 01
Start the connect flow
Use the GitHub step of the first-install wizard, or System > Git Apps on an existing install. Name an organization to create the App there; leave it empty for your personal account. Only a real organization belongs in that field — a personal username is not one, and GitHub answers a 404.
Verify- The browser leaves for GitHub and shows the App creation page pre-filled from the manifest.
- 02
Approve the App on GitHub
GitHub creates the App and returns you to NopsAI, which exchanges the one-time code for the credentials and stores them. Nothing is typed in by hand.
Verify- The Git Apps card shows a connected App ID and the address GitHub will deliver to.
- 03
Install it and choose repositories
NopsAI sends you on to the install page, where you pick the account and the repositories the App may see. Repeat this for every additional organization or account.
Verify- The account appears under GitHub accounts with a repository count.
- 04
Approve accounts you did not install yourself
An installation from an account other than the App owner arrives as
Pending approval. Read the account, then use Approve on the row to enable it.Important- Approving is what makes an installation live. Until then NopsAI reads none of its repositories and runs none of its triggers.
Verify- The row moves from
Pending approvaltoConnected.
How it works
Registration is GitHub’s manifest handshake, in three moves. NopsAI builds the manifest and a single-use state and hands both to the browser, which posts the manifest to GitHub because GitHub accepts manifests only from an operator’s own session. The operator approves the App, and GitHub redirects back with a one-time code. NopsAI exchanges that code at POST /app-manifests/{code}/conversions for the App ID, slug, private key, and webhook secret, writes the two secrets to the credential store, and sends the operator straight on to the install page.
Installing is a separate step from creating, and it is where repository access is granted: the operator picks the account and the repositories on GitHub, and GitHub calls the App’s setup URL with the installation id. NopsAI never trusts that id from the query string — it verifies the installation against GitHub with an App-authenticated call before storing it.
Because the App is public, anyone who reaches its install URL can attach their own account to it. NopsAI therefore decides whether a new installation counts. The account recorded as github_app_owner at registration is trusted on sight; every other account is stored with pending_approval and disabled. A held installation is genuinely inert rather than merely flagged: git-bot skips disabled installations, so no repository is read and no trigger runs for it.
GitHub un-suspending a held installation does not approve it, and an installation an operator has already ruled on keeps that ruling when later events refresh its metadata. When the owning account is unknown and cannot be resolved, every new installation is held: being unable to tell whether an account is trusted is never treated as trusted.
The App lifecycle events installation and installation_repositories are delivered to every App whether or not it subscribes, and GitHub rejects a manifest that lists them. They are therefore absent from the App’s event list and still received, which is what keeps the installation catalog in step when accounts are added, suspended, or removed directly on GitHub.
git-bot re-reads the App ID, private key, and webhook secret from NopsAI on an interval, so connecting or rotating an App takes effect without restarting the container.
Field reference
github_app_idsetting/git-apps/github.yamlstringRequiredNone
The App GitHub issued at registration. One per NopsAI installation.
4671352services/nopsai/git_apps_handlers.go
github_app_slugsetting/git-apps/github.yamlstringOptionalNone
URL name of the App, used to build the install link github.com/apps/{slug}/installations/new.
nopsai-exampleservices/nopsai/git_apps_registration_model.go
github_app_ownersetting/git-apps/github.yamlstringOptionalNone
Account that owns the App. Installations on this account are enabled on sight; every other account is held for approval. Backfilled from GitHub for an App registered before NopsAI recorded one.
nopsaiThis is the whole trust decision for a public App. An empty value holds every new installation rather than trusting any.
services/nopsai/git_apps_registration_handlers.go
github_webhook_urlsetting/git-apps/github.yamlstringOptionalDerived from public_url
Where GitHub delivers events. The only address GitHub itself fetches, so it has to reach git-bot from the internet. A value with no path gets /webhook appended.
https://your-tunnel.example.com/webhookservices/nopsai/git_apps_registration_model.go
github_installations[].installation_idsetting/git-apps/github.yamlstringRequiredNone
Installation GitHub created when the App was installed on an account.
155436772config/config.go
github_installations[].account_loginsetting/git-apps/github.yamlstringOptionalNone
Account the App is installed on. Matched against github_app_owner to decide whether the installation is trusted on sight.
nopsaiconfig/config.go
github_installations[].enabledsetting/git-apps/github.yamlbooleanOptionaltrue
Whether NopsAI uses this installation. git-bot skips disabled installations, so a disabled entry reads no repositories and starts no runs.
trueservices/git-bot/internal/service/github_resolver.go
github_installations[].pending_approvalsetting/git-apps/github.yamlbooleanOptionalfalse
Set when the installation arrived from an account other than the App owner. It stays disabled until an operator approves it, and GitHub un-suspending it does not clear the flag.
trueThis is what stops a stranger who found the public App URL from attaching their organization to your installation.
services/nopsai/git_apps_registration_handlers.go
Examples
github_app_id: "4671352"
github_app_slug: nopsai-example
github_app_owner: nopsai
github_webhook_url: https://your-tunnel.example.com/webhook
github_private_key_credential_ref: credential://system/github/app-private-key
github_webhook_credential_ref: credential://system/github/webhook-secret
github_installations:
- installation_id: "155436772"
account_login: nopsai
account_type: organization
enabled: true
- installation_id: "778899"
account_login: partner-org
account_type: organization
enabled: false
pending_approval: trueLimits
- A private GitHub App can only be installed on the account that owns it, which is why NopsAI creates the App public. An App registered before this, or switched to "Only on this account" in GitHub, is limited to a single account.
- The App owner is fixed at creation. Moving the App to a different organization is a GitHub-side transfer, not a NopsAI setting.
installationandinstallation_repositoriescannot be subscribed to in the manifest. They arrive regardless; asking for them makes the manifest invalid.
Implementation evidence
doc/git-apps.mdConnect flow, approval rules, GitOps schema, and git-bot routing.
services/nopsai/git_apps_registration_handlers.goManifest handshake, installation verification, and the approval guard.
services/nopsai/git_apps_registration_model.goManifest contents, event and permission set, and webhook URL normalization.
config/config.goStored App and installation schema.

