Repository navigation
215 lines (186 loc) · 7.71 KB
/
Copy pathplugins-registry.yml
File metadata and controls
215 lines (186 loc) · 7.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
name: Plugins Registry
# plugins/registry/: the third-party plugin list, its icons, its check and the
# OAuth files the registry container serves.
# - Pull requests: check the entry rules and icons, then connect to each plugin
# the pull request adds or changes. A maintainer connects each new listing on
# dev, signing in where it needs it, and reviews its tools before it reaches main.
# - Dev: write plugins/registry/published/, a copy of plugins.json stamped with
# generated_at and each icon's SHA-256, and plugins.json.sig, its signature.
# The release pull request from dev carries it to main, which the registry
# container is built from, so nothing pushes to main. The source list is never
# rewritten, and the private key never leaves this secret.
on:
pull_request:
branches: [main, dev]
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [dev]
paths:
- 'plugins/registry/plugins.json'
- 'plugins/registry/icons/**'
# Checks the list on any branch; on dev, also signs it again.
workflow_dispatch:
# A newer push to a pull request replaces its run; a signing is never cut short.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
env:
# The app compiles in the same key.
REGISTRY_PUBLIC_KEY: 9498a1606105a625feeaa655648df03885410421a387478a72ec763188f10395
SIGN_MESSAGE: 'chore(registry): sign the plugin list'
jobs:
# Filtered per job, not per workflow: a workflow skipped by its paths leaves
# its checks pending, while a skipped job reports success.
detect-changes:
name: Detect changes
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
outputs:
registry_changed: ${{ steps.changed_paths.outputs.registry_changed }}
published_changed: ${{ steps.changed_paths.outputs.published_changed }}
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Filter changed paths
id: changed_paths
uses: dorny/paths-filter@v4
with:
filters: |
registry_changed:
- 'plugins/registry/**'
- 'surfsense_local/backend/modules/plugins/lists/first_party/plugins.json'
- '.github/workflows/plugins-registry.yml'
- '.github/workflows/plugins-registry-submission.yml'
- '.github/ISSUE_TEMPLATE/plugin-submit.yml'
published_changed:
- 'plugins/registry/published/plugins.json*'
# Only the sign job writes the signed copy. The release pull request from dev
# into main carries that job's commits, so it is let through.
refuse-hand-signed-list:
name: Refuse hand edits to the signed list
runs-on: ubuntu-latest
needs: detect-changes
if: >-
needs.detect-changes.outputs.published_changed == 'true' &&
github.event.pull_request.user.login != 'github-actions[bot]' &&
!(github.head_ref == 'dev' && github.base_ref == 'main')
steps:
- name: Fail
run: |
echo "::error::plugins/registry/published/ is written by the sign job on dev only. Edit plugins/registry/plugins.json instead."
exit 1
check-registry:
name: Check registry
runs-on: ubuntu-latest
needs: detect-changes
# Runs on a manual run too, when detect-changes is skipped.
if: >-
always() &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.registry_changed == 'true')
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
# The merge's first parent is the list this pull request is based on.
fetch-depth: 2
- name: Setup uv
uses: astral-sh/setup-uv@v10.2.0
with:
enable-cache: true
python-version: '3.12'
- name: Install dependencies
working-directory: plugins/registry/check
run: uv sync
- name: Lint
working-directory: plugins/registry/check
run: uv run ruff check .
- name: Check formatting
working-directory: plugins/registry/check
run: uv run ruff format --check .
- name: Run tests
working-directory: plugins/registry/check
run: uv run pytest
# Ids are unique across both lists, so it reads the first-party list too.
- name: Check the entry rules and icons
working-directory: plugins/registry/check
run: uv run python -m registry_check static
# Connects to each plugin the pull request adds or changes, after the entry
# rules refused loopback and credentials. Pull requests only: a third-party
# server down for a minute must never hold up signing the list. A submission
# branch is one commit on dev, so its run, started by the bot, compares the same way.
- name: Connect to each new or changed plugin
if: >-
github.event_name == 'pull_request' ||
(github.event_name == 'workflow_dispatch' &&
startsWith(github.ref, 'refs/heads/registry/plugin-submission-'))
working-directory: plugins/registry/check
run: |
base="$RUNNER_TEMP/base-plugins.json"
git show HEAD^1:plugins/registry/plugins.json > "$base" 2>/dev/null \
|| echo '{"schema_version": 1, "plugins": []}' > "$base"
uv run python -m registry_check live --base "$base"
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
- name: Test the OAuth callback page
run: node --test 'plugins/registry/oauth/tests/*.test.mjs'
sign:
name: Sign the registry
runs-on: ubuntu-latest
# Its commit touches only published/, outside the push paths above; the
# message check guards a manual run or a future path change.
if: >-
github.ref == 'refs/heads/dev' &&
(github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' &&
!startsWith(github.event.head_commit.message, 'chore(registry): sign the plugin list')))
permissions:
contents: write
# Two signings at once would race to push.
concurrency:
group: plugins-registry-sign
cancel-in-progress: false
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
ref: dev
- name: Setup uv
uses: astral-sh/setup-uv@v10.2.0
with:
enable-cache: true
python-version: '3.12'
- name: Install dependencies
working-directory: plugins/registry/check
run: uv sync
# Never sign a list that would not pass review.
- name: Check the entry rules and icons
working-directory: plugins/registry/check
run: uv run python -m registry_check static
- name: Sign
working-directory: plugins/registry/check
env:
REGISTRY_SIGNING_KEY: ${{ secrets.REGISTRY_SIGNING_KEY }}
run: uv run python -m registry_check sign
- name: Verify with the public key
working-directory: plugins/registry/check
env:
REGISTRY_VERIFY_COMMITTED: '1'
run: |
uv run python -m registry_check verify --public-key "$REGISTRY_PUBLIC_KEY"
uv run pytest tests/test_signing.py -k committed
- name: Commit the signed list
run: |
git add plugins/registry/published/plugins.json plugins/registry/published/plugins.json.sig
if git diff --cached --quiet; then
echo "Nothing to sign."
exit 0
fi
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git commit -m "$SIGN_MESSAGE"
git push origin HEAD:dev