# Git and automatic deployment

Bastide builds from a Git repository. This page explains how to connect your GitHub accounts to an organisation, which branch goes to production, how a push goes live on its own, how to trigger a deployment from another tool with a hook, and what your repository's `glowsoft.json` file can declare.

## Concepts

- **Project repository**: the Git address the project builds, as `https://`, `ssh://` or `git://`. It is set at creation, or later in the project's **Réglages** (Settings), section « Source et construction » (source and build), field « Dépôt Git » (Git repository).
- **GitHub connection**: an installation of the Bastide GitHub application on a GitHub account (personal or organisation), attached to your Bastide organisation. It opens the private repositories you chose and brings in pushes.
- **Production branch**: the only branch whose pushes go to production.
- **Deploy hook**: a signed address another tool calls to deploy a branch to an environment.

## Connect GitHub

The connection is made at organisation level, with the Administrator (`admin`) role.

1. Open the organisation's **Réglages** (Settings), « GitHub » card, or click « + Raccorder un compte GitHub » (connect a GitHub account) on **Nouveau projet** (New project).
2. Click « Installer l'application » (install the application). GitHub asks which account to install it on, and which repositories to open to it.
3. GitHub sends you back to the console with a single-use code (valid for ten minutes). The console sends it once, then removes it from the address. If you started from **Nouveau projet**, you return there.

The card then lists each connected account, its kind (« Organisation GitHub » or « Compte personnel »), its date and its state. On **Nouveau projet**, the repositories your connections open are listed from most recently pushed to oldest (300 at most): you pick rather than paste an address.

What the connection does:

- Each build of a private repository receives a token minted for it, limited to that repository and to reading. No token is kept.
- Pushes and pull requests on the repository reach the platform, signed by GitHub.
- The platform publishes each deployment's state on its commit, and keeps a single comment up to date on each pull request.

« Retirer » (remove) undoes a connection: it stays in the list, dated and signed, and later builds of that account's repositories run without credentials, so a private repository can no longer be fetched. The application stays installed on GitHub: uninstall it there.

The matching operations, reserved for people and absent from the MCP catalogue, are [`organization.github_connection.list`](../reference/operations/organization.github_connection.list.md), [`organization.github_connection.create`](../reference/operations/organization.github_connection.create.md), [`organization.github_connection.revoke`](../reference/operations/organization.github_connection.revoke.md) and [`organization.github_repository.list`](../reference/operations/organization.github_repository.list.md).

### What the application may do on your side

The scope is published and asks for no repository administration:

| Permission | Level | Why |
| --- | --- | --- |
| Metadata | read | Know the repository: name, default branch, visibility. |
| Contents | read | Clone the commit to build. Nothing is pushed. |
| Pull requests | write | Post, then keep up to date, the single comment on a pull request. |
| Commit statuses | write | Publish each deployment's state on its commit. |
| Hooks | write | Receive pushes and pull requests. |

One optional permission is added only for a project that turns it on: pushing, on a `bastide/...` branch the platform creates, the migration that fixes the database's security posture, then opening its pull request. Never the main branch; you review and merge.

### Other forges

A GitLab, Forgejo or Gitea repository, or any reachable Git server, is built from its address. For a private repository, the address may carry its credentials: they are never shown again, and must be re-entered if you change the address. However, the hook that announces pushes from these forges cannot yet be set up from the console or the API: to deploy on every push from them, call a deploy hook (below) from your CI.

## The production branch and deploying on every push

With no configuration, the project's default branch goes to production on every push, and no other branch deploys. The default branch is set in the project's **Réglages**, section « Source et construction », field « Branche de production » (production branch). Changing it decides the next push; the live version does not change.

To send other branches elsewhere, declare it in `glowsoft.json` (see below). When a push arrives:

1. The platform reads `glowsoft.json` at the tip of the default branch, never at the pushed commit: a branch does not decide its own fate.
2. It works out where the branch lands: the production branch first, by exact match; then the patterns, in order, the first match winning.
3. A branch that lands nowhere deploys nothing.
4. Otherwise a deployment is queued on the target environment. Two deliveries of the same commit fall on the same deployment.

A pull request opened from a branch no rule names gets its [preview](previews.md). A pull request from a fork is only built after a member authorises it.

### Monorepos

In the project's **Réglages**, section « Source et construction »:

- « Racine du projet » (project root): the repository folder to build, relative to its root. Empty means the repository root.
- « Chemins inclus » (included paths): the paths outside the root to bring into the build, one per line.

With `"build": { "skipUnaffected": true }` in `glowsoft.json`, a push that does not touch the project is « Sauté » (skipped): no build slot, no minute used.

## Deploy hooks

A hook is an address you call to deploy, from a content management system, a scheduled task or another pipeline. It needs neither an account nor a token: every call is signed.

- It is bound to one branch-environment pair, chosen at creation, and deploys no other.
- A project holds twenty active hooks at most.
- Its secret (`dhsec_...`) is shown once, at creation.
- It is revoked on its own, without touching the others.

### Create and revoke a hook

In the console, the « Crochets de déploiement » (deploy hooks) card sits on **Webhooks** and in the project's **Réglages**, section « Crochets et cache » (hooks and cache). Pick the environment, the branch and « À quoi il sert » (what it is for), then copy the secret shown. Creating one requires the maintainer (`maintainer`) role and a person: it is the [`deployment.hook.create`](../reference/operations/deployment.hook.create.md) operation, outside the MCP catalogue. An agent lists hooks with `webhook_list` and revokes one with `deployment_hook_revoke`.

### Call a hook

`POST https://api.bastide.app/v1/deploy-hooks/<dhk_... identifier>`, with the header `Bastide-Signature: t=<Unix seconds>,v1=<hex>`, where `v1` is the HMAC-SHA256 of the secret over `<t>.<body>`. The body is empty or a JSON object; `branch` and `environmentId`, if present, must name the hook's pair.

```sh
t=$(date +%s)
body='{}'
sig=$(printf '%s.%s' "$t" "$body" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -X POST "https://api.bastide.app/v1/deploy-hooks/$HOOK" \
  -H "Bastide-Signature: t=$t,v1=$sig" -H 'content-type: application/json' -d "$body"
```

An accepted call returns `202` and `deploymentId`. If a deployment of the same branch, with the same variables, is already in progress, that one is returned (`deduplicated: true`).

| Status | Code | Meaning |
| --- | --- | --- |
| 403 | `signature_invalide` | Wrong signature, or unknown or revoked hook, or environment gone: the response is the same. |
| 403 | `horodatage_perime` | `t` is more than five minutes from the platform's clock. |
| 403 | `couple_refuse` | The body names another branch or environment. |
| 429 | `plafond_de_declenchements` | Sixty triggers in the rolling hour, for the project. Read `Retry-After`. |
| 409 | `quota_de_construction_atteint` | The monthly build minutes are used up. |

## Configure in the repository: glowsoft.json

A `glowsoft.json` file at the repository root describes what the repository decides for itself. It is optional, read at the tip of the default branch, and weighs 64 KiB at most. An unknown or unapplied field is refused by name, never ignored.

```json
{
  "version": 1,
  "branches": {
    "production": "main",
    "patterns": [
      { "pattern": "develop", "environment": "staging" },
      { "pattern": "feat/**", "environment": "preview" }
    ]
  },
  "probe": { "path": "/sante", "startupBudgetSeconds": 90 },
  "build": { "skipUnaffected": true }
}
```

| Field | What it declares |
| --- | --- |
| `version` | Required, equals `1`. |
| `branches` | `production`, a single string; `patterns`, 32 rules at most, each a pattern and an environment. `preview` gives one preview per branch. |
| `probe`, `port` | The health probe and the port of a supplied `Dockerfile`: see [Frameworks](frameworks.md). |
| `services`, `crons` | Services and scheduled tasks: see [Frameworks](frameworks.md) and [Scheduled tasks](cron.md). |
| `requests` | A request's timeout (30 to 3,600 s, 300 by default) and maximum body (up to 5,120 MiB, 100 by default). |
| `redirects`, `headers`, `rewrites` | Edge rules, in the `vercel.json` shape, 2,048 at most per block: see [Routing](routing.md). |
| `comments` | `enabled: false` removes the pull request comment; `exclude` removes it for some branches. |
| `build` | `skipUnaffected` for a monorepo. |
| `linkedProjects` | Up to ten projects of the same organisation, whose address is injected into a variable. |

Pattern rules: characters `A-Z a-z 0-9 . _ / * + -`; `*` within a segment, `**` on a whole segment; four wildcards at most. A pattern cannot target `production`, two patterns cannot target the same long-lived environment (only `preview` allows it), and a pattern placed after `**` is refused as unreachable.

These are refused by name: `rootDirectory`, `outputDirectory`, `installCommand`, `buildCommand`, `framework`, `nodeVersion`, `volumes` and `env`. Environment variables are never declared in a versioned file: see [Environments and variables](environments.md).

## Common errors

| Code | Remedy |
| --- | --- |
| `declaration_refusee` | `glowsoft.json` is invalid or declares more services or tasks than the tier allows. Fix the file on the default branch. |
| `config_illisible` | The file could not be read at the tip of the default branch: check that the repository responds. |
| `reference_introuvable` | The requested branch, tag or commit does not exist in the repository. |
| `fetch_failed` | The repository could not be fetched: a private repository the GitHub connection does not open, or a wrong address. Tick the repository in the GitHub application, then redeploy. |
| `remote_host_refused` | The repository host does not resolve, or resolves to a private address: the platform only reaches public hosts. |
| `root_directory_missing` | The « Racine du projet » does not exist in the repository at this commit. |

A connection whose installation was removed on GitHub shows as unreachable: redo it from the « GitHub » card.

See also: [Deploy](deploy.md), [Previews](previews.md), [Frameworks](frameworks.md), [Routing](routing.md), [Webhooks](webhooks.md), [Organisations](organizations.md).
