Skip to content

Commit e338631

Browse files
committed
feat(github): add GitHub App integration
Implement GitHub App based authentication as an alternative to personal access tokens for GitHub-backed repositories. The integration stores the app configuration and installation metadata, exchanges app credentials for installation access tokens, and uses those tokens when Weblate performs GitHub repository operations.
1 parent 1f091d3 commit e338631

22 files changed

Lines changed: 2102 additions & 8 deletions

docs/admin/code-hosting.rst

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,38 @@ access to the repository. To push changes back, you still need to add
237237
the Hosted Weblate :guilabel:`weblate` GitHub user as a collaborator with write
238238
access, see :ref:`hosted-push`.
239239

240-
If you are not using the app, add the Weblate webhook in the repository
240+
For self-hosted Weblate, create a GitHub App on each GitHub host you want
241+
Weblate to connect to and configure :setting:`GITHUB_APP_CREDENTIALS` with the
242+
app ID, app slug, private key, and webhook secret from that app.
243+
244+
When creating the GitHub App, copy :guilabel:`App ID` from the app settings,
245+
use the slug from the app URL as ``app_slug``, generate a private key and use
246+
its PEM contents as ``private_key``, and choose a webhook secret to store as
247+
``webhook_secret``.
248+
249+
In the GitHub App webhook settings, enable webhooks and use the Weblate
250+
GitHub hook URL as the :guilabel:`Webhook URL`. When Weblate is configured for
251+
multiple GitHub hosts, include the GitHub host in the URL query string:
252+
253+
.. code-block:: text
254+
255+
https://weblate.example.com/hooks/github/?host=github.example.com
256+
257+
The ``host`` value has to match the host key in :setting:`GITHUB_APP_CREDENTIALS`.
258+
Use ``github.com`` for GitHub.com and the web hostname for GitHub Enterprise
259+
Server. The query parameter is not added by GitHub; it is part of the webhook
260+
URL you configure in the GitHub App.
261+
262+
The host is needed because GitHub App IDs and installation IDs are scoped to a
263+
single GitHub host. If the same Weblate instance handles GitHub.com and one or
264+
more GitHub Enterprise Server instances, the same numeric ID can exist on more
265+
than one host. The host value lets Weblate choose the correct app credentials
266+
before validating the webhook signature and before creating or updating the
267+
stored installation. If the parameter is omitted, Weblate tries to infer the
268+
host from a unique app ID, an existing installation, or a matching webhook
269+
secret, but an explicit host keeps the first delivery unambiguous.
270+
271+
If you are not using a GitHub App, add the Weblate webhook in the repository
241272
settings (:guilabel:`Webhooks`) to receive notifications on every push to a
242273
GitHub repository, as shown on the image below:
243274

@@ -253,6 +284,7 @@ types and consumes just the :guilabel:`push` event.
253284
.. seealso::
254285

255286
* :http:post:`/hooks/github/`
287+
* :setting:`GITHUB_APP_CREDENTIALS`
256288
* :ref:`hosted-push`
257289

258290
.. _code-hosting-github-pull-requests:

docs/admin/config.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,50 @@ List for credentials for GitHub servers.
10601060

10611061
.. _Creating a GitHub personal access token: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token
10621062

1063+
.. setting:: GITHUB_APP_CREDENTIALS
1064+
1065+
GITHUB_APP_CREDENTIALS
1066+
----------------------
1067+
1068+
List of Weblate GitHub App credentials for GitHub.com and GitHub Enterprise
1069+
Server hosts.
1070+
1071+
.. code-block:: python
1072+
1073+
GITHUB_APP_CREDENTIALS = {
1074+
"github.com": {
1075+
"app_id": "12345",
1076+
"app_slug": "weblate-app",
1077+
"private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
1078+
"webhook_secret": "your-webhook-secret",
1079+
},
1080+
"github.example.com": {
1081+
"app_id": "67890",
1082+
"app_slug": "weblate-enterprise-app",
1083+
"private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
1084+
"webhook_secret": "your-enterprise-webhook-secret",
1085+
},
1086+
}
1087+
1088+
The dictionary keys are GitHub web hostnames. Use ``github.com`` for
1089+
GitHub.com and the web hostname, not the API URL, for GitHub Enterprise
1090+
Server.
1091+
1092+
Use the :guilabel:`App ID` from the GitHub App settings as ``app_id``, the
1093+
slug from the app URL as ``app_slug``, a generated private key PEM as
1094+
``private_key``, and the configured app webhook secret as ``webhook_secret``.
1095+
1096+
The :guilabel:`Webhook URL` configured in the GitHub App should include the
1097+
matching host when Weblate is configured with multiple GitHub hosts:
1098+
1099+
.. code-block:: text
1100+
1101+
https://weblate.example.com/hooks/github/?host=github.example.com
1102+
1103+
.. seealso::
1104+
1105+
* :ref:`code-hosting-github-notifications`
1106+
10631107
.. setting:: BITBUCKETSERVER_CREDENTIALS
10641108

10651109
BITBUCKETSERVER_CREDENTIALS

docs/admin/install/docker.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,46 @@ Or the path to a file containing the Python dictionary:
13801380

13811381
:ref:`Configuring code hosting credentials in Docker <docker-vcs-config>`
13821382

1383+
GitHub App credentials can be configured using dedicated environment
1384+
variables for one GitHub host:
1385+
1386+
.. code-block:: shell
1387+
1388+
WEBLATE_GITHUB_APP_HOST=github.example.com
1389+
WEBLATE_GITHUB_APP_ID=12345
1390+
WEBLATE_GITHUB_APP_SLUG=weblate-enterprise-app
1391+
WEBLATE_GITHUB_APP_PRIVATE_KEY_FILE=/run/secrets/github-app-private-key
1392+
WEBLATE_GITHUB_APP_WEBHOOK_SECRET=webhook-secret
1393+
1394+
When configuring the GitHub App webhook URL, include the same host:
1395+
1396+
.. code-block:: text
1397+
1398+
https://weblate.example.com/hooks/github/?host=github.example.com
1399+
1400+
Alternatively the Python dictionary can be provided as a string or file for
1401+
multiple GitHub hosts:
1402+
1403+
.. code-block:: shell
1404+
1405+
WEBLATE_GITHUB_APP_CREDENTIALS_FILE=/path/to/github-app-credentials
1406+
1407+
.. envvar:: WEBLATE_GITHUB_APP_HOST
1408+
.. envvar:: WEBLATE_GITHUB_APP_ID
1409+
.. envvar:: WEBLATE_GITHUB_APP_SLUG
1410+
.. envvar:: WEBLATE_GITHUB_APP_PRIVATE_KEY
1411+
.. envvar:: WEBLATE_GITHUB_APP_PRIVATE_KEY_FILE
1412+
.. envvar:: WEBLATE_GITHUB_APP_WEBHOOK_SECRET
1413+
.. envvar:: WEBLATE_GITHUB_APP_CREDENTIALS
1414+
.. envvar:: WEBLATE_GITHUB_APP_CREDENTIALS_FILE
1415+
1416+
Configures :ref:`code-hosting-github-notifications` by changing
1417+
:setting:`GITHUB_APP_CREDENTIALS`.
1418+
1419+
.. seealso::
1420+
1421+
:setting:`GITHUB_APP_CREDENTIALS`
1422+
13831423
.. envvar:: WEBLATE_GITLAB_USERNAME
13841424
.. envvar:: WEBLATE_GITLAB_TOKEN
13851425
.. envvar:: WEBLATE_GITLAB_HOST

weblate/settings_docker.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
get_env_bool,
3131
get_env_credentials,
3232
get_env_float,
33+
get_env_github_app_credentials,
3334
get_env_int,
3435
get_env_int_or_none,
3536
get_env_json,
@@ -246,6 +247,9 @@
246247
# Please see the documentation for more details.
247248
GITHUB_CREDENTIALS = get_env_credentials("GITHUB")
248249

250+
# Weblate GitHub app configuration for one-click installation flow.
251+
GITHUB_APP_CREDENTIALS = get_env_github_app_credentials()
252+
249253
# Azure DevOps username, token, and organization for sending pull requests.
250254
# Please see the documentation for more details.
251255
AZURE_DEVOPS_CREDENTIALS = get_env_credentials("AZURE_DEVOPS")

weblate/settings_example.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,17 @@
201201
# Please see the documentation for more details.
202202
GITHUB_CREDENTIALS = {}
203203

204+
# Weblate GitHub app configuration for one-click installation flow.
205+
# Please see the documentation for more details.
206+
GITHUB_APP_CREDENTIALS = {
207+
# "github.com": {
208+
# "app_id": "12345",
209+
# "app_slug": "weblate-app",
210+
# "private_key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----",
211+
# "webhook_secret": "secret",
212+
# },
213+
}
214+
204215
# Azure DevOps username and token for sending pull requests.
205216
# Please see the documentation for more details.
206217
AZURE_DEVOPS_CREDENTIALS = {}

weblate/templates/trans/component_create.html

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
<a class="nav-link" data-bs-target="#scratch" data-bs-toggle="tab" href="#">{% translate "Start from scratch" %}</a>
4141
</li>
4242
{% endif %}
43+
{% if github_app_available %}
44+
<li class="nav-item" role="presentation">
45+
<a class="nav-link" data-bs-target="#github" data-bs-toggle="tab" href="#">{% translate "From GitHub" %}</a>
46+
</li>
47+
{% endif %}
4348
</ul>
4449
{% endblock nav_pills %}
4550

@@ -178,6 +183,65 @@
178183
</div>
179184
{% endif %}
180185

186+
{% if github_app_available %}
187+
<div class="tab-pane" id="github">
188+
<p>
189+
{% translate "Pick a repository from a connected GitHub account. The component creation form will be pre-filled with the clone URL, default branch, and GitHub VCS driver." %}
190+
{% if github_app_install_url %}
191+
<a href="{{ github_app_install_url }}"
192+
class="btn btn-outline-primary btn-sm float-end">{% translate "Add or configure account" %}</a>
193+
{% endif %}
194+
</p>
195+
{% if github_app_repositories %}
196+
<table class="table table-striped">
197+
<thead>
198+
<tr>
199+
<th>{% translate "Repository" %}</th>
200+
<th>{% translate "Account" %}</th>
201+
<th>{% translate "Branch" %}</th>
202+
<th>{% translate "Visibility" %}</th>
203+
<th>{% translate "Actions" %}</th>
204+
</tr>
205+
</thead>
206+
<tbody>
207+
{% for repo in github_app_repositories %}
208+
<tr>
209+
<td>
210+
<strong>{{ repo.full_name }}</strong>
211+
{% if repo.description %}
212+
<br>
213+
<small class="text-body-secondary">{{ repo.description }}</small>
214+
{% endif %}
215+
</td>
216+
<td>{{ repo.account_name }}</td>
217+
<td>{{ repo.default_branch }}</td>
218+
<td>
219+
{% if repo.private %}
220+
<span class="badge text-bg-warning">{% translate "Private" %}</span>
221+
{% else %}
222+
<span class="badge text-bg-success">{% translate "Public" %}</span>
223+
{% endif %}
224+
</td>
225+
<td>
226+
<a href="{% url 'create-component-vcs' %}?repo={{ repo.clone_url }}&branch={{ repo.default_branch }}&vcs=github"
227+
class="btn btn-outline-primary btn-sm">{% translate "Import" %}</a>
228+
</td>
229+
</tr>
230+
{% endfor %}
231+
</tbody>
232+
</table>
233+
{% else %}
234+
<p class="text-body-secondary">
235+
{% if github_app_install_url %}
236+
{% translate "No repositories available yet. Install the Weblate GitHub app on a GitHub organization or user account to make repositories available here." %}
237+
{% else %}
238+
{% translate "No repositories available from connected GitHub accounts." %}
239+
{% endif %}
240+
</p>
241+
{% endif %}
242+
</div>
243+
{% endif %}
244+
181245
</div>
182246
{% endif %}
183247

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{% extends "base.html" %}
2+
3+
{% load i18n %}
4+
5+
{% block breadcrumbs %}
6+
<li class="breadcrumb-item">{% translate "Install Weblate GitHub app" %}</li>
7+
{% endblock breadcrumbs %}
8+
9+
{% block content %}
10+
11+
<div class="card">
12+
<div class="card-header">
13+
<h4 class="card-title">{% translate "Choose GitHub host" %}</h4>
14+
</div>
15+
<div class="card-body">
16+
<p class="text-body-secondary">
17+
{% translate "Select the GitHub or GitHub Enterprise instance where you want to install the Weblate GitHub app." %}
18+
</p>
19+
<div class="d-grid gap-2">
20+
{% for choice in install_choices %}
21+
<a href="{{ choice.install_url }}" class="btn btn-outline-primary">
22+
{% blocktranslate with hostname=choice.hostname %}Install on {{ hostname }}{% endblocktranslate %}
23+
</a>
24+
{% endfor %}
25+
</div>
26+
</div>
27+
</div>
28+
29+
{% endblock content %}

0 commit comments

Comments
 (0)