Skip to content

Commit 0ec85a8

Browse files
ci: add public repository setup
1 parent 8bbabfe commit 0ec85a8

6 files changed

Lines changed: 413 additions & 0 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Summary
2+
3+
-
4+
5+
## Verification
6+
7+
- [ ] `swift test`
8+
- [ ] `swift build`
9+
- [ ] `script/build_app.sh`
10+
- [ ] `script/verify_defaults.sh --app dist/TrimControl.app`
11+
12+
## Release And Defaults Safety
13+
14+
- [ ] User-visible behavior or docs were updated where needed.
15+
- [ ] This change avoids mutating LaunchServices defaults except in explicitly named install/defaults flows.
16+
- [ ] Signing, notarization, DMG, or Homebrew changes were tested against the documented release path when touched.

.github/dependabot.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
day: "monday"
8+
time: "09:00"
9+
timezone: "America/New_York"
10+
groups:
11+
github-actions:
12+
patterns:
13+
- "*"
14+
labels:
15+
- "dependencies"
16+
- "github-actions"
17+
commit-message:
18+
prefix: "chore"
19+
20+
- package-ecosystem: "swift"
21+
directory: "/"
22+
schedule:
23+
interval: "weekly"
24+
day: "monday"
25+
time: "09:30"
26+
timezone: "America/New_York"
27+
open-pull-requests-limit: 5
28+
labels:
29+
- "dependencies"
30+
- "swift"
31+
commit-message:
32+
prefix: "chore"

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
concurrency:
14+
group: ci-${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
test-and-build:
19+
name: Test and build
20+
runs-on: macos-26
21+
timeout-minutes: 30
22+
23+
steps:
24+
- name: Check out source
25+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
26+
with:
27+
persist-credentials: false
28+
29+
- name: Run tests
30+
run: swift test
31+
32+
- name: Build package
33+
run: swift build
34+
35+
- name: Build app bundle
36+
run: script/build_app.sh
37+
38+
- name: Verify app defaults contract
39+
run: script/verify_defaults.sh --app dist/TrimControl.app
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Baseline non-blocking security scan for public repository maintenance.
2+
name: Security Scan
3+
4+
on:
5+
pull_request:
6+
push:
7+
branches:
8+
- main
9+
- master
10+
- trunk
11+
schedule:
12+
- cron: "17 7 * * 2"
13+
workflow_dispatch:
14+
15+
permissions:
16+
actions: read
17+
contents: read
18+
security-events: write
19+
20+
jobs:
21+
gitleaks:
22+
name: Gitleaks secret scan
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 15
25+
permissions:
26+
actions: read
27+
contents: read
28+
security-events: write
29+
30+
steps:
31+
- name: Checkout repository
32+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
33+
with:
34+
fetch-depth: 0
35+
persist-credentials: false
36+
37+
- name: Install Gitleaks
38+
id: install-gitleaks
39+
env:
40+
GITLEAKS_VERSION: "8.30.1"
41+
run: |
42+
set -euo pipefail
43+
44+
install_dir="${RUNNER_TEMP}/gitleaks"
45+
archive="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
46+
mkdir -p "${install_dir}"
47+
48+
curl -fsSLo "${install_dir}/${archive}" "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${archive}"
49+
curl -fsSLo "${install_dir}/checksums.txt" "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_checksums.txt"
50+
51+
(
52+
cd "${install_dir}"
53+
grep " ${archive}$" checksums.txt > checksums.selected
54+
sha256sum -c checksums.selected
55+
tar -xzf "${archive}" gitleaks
56+
)
57+
58+
echo "binary=${install_dir}/gitleaks" >> "${GITHUB_OUTPUT}"
59+
60+
- name: Run redacted Gitleaks scan
61+
id: gitleaks
62+
continue-on-error: true
63+
run: |
64+
set -euo pipefail
65+
66+
"${{ steps.install-gitleaks.outputs.binary }}" detect \
67+
--source . \
68+
--redact \
69+
--no-banner \
70+
--report-format sarif \
71+
--report-path gitleaks-results.sarif \
72+
--exit-code 1
73+
74+
- name: Upload Gitleaks SARIF
75+
if: always() && hashFiles('gitleaks-results.sarif') != ''
76+
continue-on-error: true
77+
uses: github/codeql-action/upload-sarif@db2c8fe24a75c0f28f87ed1a6fe918a5ccf7b1e6 # v4.31.10
78+
with:
79+
sarif_file: gitleaks-results.sarif
80+
81+
- name: Upload Gitleaks artifact
82+
if: always() && hashFiles('gitleaks-results.sarif') != ''
83+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
84+
with:
85+
name: gitleaks-results-${{ github.run_id }}
86+
path: gitleaks-results.sarif
87+
retention-days: 14
88+
89+
- name: Summarize Gitleaks baseline
90+
if: always()
91+
run: |
92+
{
93+
echo "## Gitleaks"
94+
echo ""
95+
echo "This baseline scan is non-blocking and redacts detected secret values."
96+
echo "Review SARIF/artifacts before switching this workflow to blocking."
97+
} >> "${GITHUB_STEP_SUMMARY}"
98+
99+
osv:
100+
name: OSV dependency scan
101+
runs-on: ubuntu-latest
102+
timeout-minutes: 20
103+
permissions:
104+
actions: read
105+
contents: read
106+
107+
steps:
108+
- name: Checkout repository
109+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
110+
with:
111+
persist-credentials: false
112+
113+
- name: Run OSV dependency scan
114+
continue-on-error: true
115+
uses: google/osv-scanner-action/osv-scanner-action@8dc09193bb540e09b23da07ad7e30bd33bf87018 # v2.3.8
116+
with:
117+
scan-args: |-
118+
--recursive
119+
--format=json
120+
--output=osv-results.json
121+
./
122+
123+
- name: Upload OSV JSON artifact
124+
if: always() && hashFiles('osv-results.json') != ''
125+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
126+
with:
127+
name: osv-results-${{ github.run_id }}
128+
path: osv-results.json
129+
retention-days: 14
130+
131+
- name: Summarize OSV baseline
132+
if: always()
133+
run: |
134+
{
135+
echo "## OSV dependency scan"
136+
echo ""
137+
echo "This baseline scan is non-blocking. Review the JSON artifact for dependency vulnerability evidence."
138+
} >> "${GITHUB_STEP_SUMMARY}"
139+
140+
trivy:
141+
name: Trivy filesystem scan
142+
runs-on: ubuntu-latest
143+
timeout-minutes: 20
144+
permissions:
145+
actions: read
146+
contents: read
147+
security-events: write
148+
149+
steps:
150+
- name: Checkout repository
151+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
152+
with:
153+
persist-credentials: false
154+
155+
- name: Generate Trivy SARIF report
156+
continue-on-error: true
157+
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
158+
with:
159+
scan-type: fs
160+
scan-ref: .
161+
scanners: vuln,misconfig
162+
format: sarif
163+
output: trivy-results.sarif
164+
exit-code: "0"
165+
ignore-unfixed: true
166+
severity: CRITICAL,HIGH
167+
version: v0.70.0
168+
169+
- name: Upload Trivy SARIF
170+
if: always() && hashFiles('trivy-results.sarif') != ''
171+
continue-on-error: true
172+
uses: github/codeql-action/upload-sarif@db2c8fe24a75c0f28f87ed1a6fe918a5ccf7b1e6 # v4.31.10
173+
with:
174+
sarif_file: trivy-results.sarif
175+
176+
- name: Generate Trivy JSON report
177+
continue-on-error: true
178+
uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0
179+
with:
180+
scan-type: fs
181+
scan-ref: .
182+
scanners: vuln,misconfig
183+
format: json
184+
output: trivy-results.json
185+
exit-code: "0"
186+
ignore-unfixed: true
187+
severity: CRITICAL,HIGH
188+
skip-setup-trivy: true
189+
version: v0.70.0
190+
191+
- name: Summarize Trivy findings
192+
if: always()
193+
run: |
194+
set -euo pipefail
195+
196+
if [[ ! -f trivy-results.json ]]; then
197+
echo "Trivy JSON report was not produced." >> "${GITHUB_STEP_SUMMARY}"
198+
exit 0
199+
fi
200+
201+
python3 - <<'PY' >> "${GITHUB_STEP_SUMMARY}"
202+
import json
203+
from pathlib import Path
204+
205+
data = json.loads(Path("trivy-results.json").read_text())
206+
counts = {"CRITICAL": 0, "HIGH": 0, "TOTAL": 0}
207+
for result in data.get("Results", []):
208+
items = []
209+
items.extend(result.get("Vulnerabilities") or [])
210+
items.extend(result.get("Misconfigurations") or [])
211+
for item in items:
212+
severity = item.get("Severity")
213+
if severity in counts:
214+
counts[severity] += 1
215+
counts["TOTAL"] += 1
216+
217+
print("## Trivy filesystem findings")
218+
print("")
219+
print("| Severity | Count |")
220+
print("| --- | ---: |")
221+
print(f"| CRITICAL | {counts['CRITICAL']} |")
222+
print(f"| HIGH | {counts['HIGH']} |")
223+
print(f"| Total reported | {counts['TOTAL']} |")
224+
print("")
225+
print("This baseline scan is non-blocking.")
226+
PY
227+
228+
- name: Upload Trivy JSON artifact
229+
if: always() && hashFiles('trivy-results.json') != ''
230+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
231+
with:
232+
name: trivy-results-${{ github.run_id }}
233+
path: trivy-results.json
234+
retention-days: 14

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ script/verify_defaults.sh --app dist/TrimControl.app
9696

9797
`script/verify_defaults.sh --app dist/TrimControl.app` verifies the bundle contract without requiring current LaunchServices defaults to point at the uninstalled app.
9898

99+
## Release
100+
101+
Public releases use a signed, notarized DMG plus a Homebrew cask update. See
102+
`docs/RELEASE.md` for required GitHub secrets, local preflight checks, tag
103+
policy, and post-release verification.
104+
99105
## Install
100106

101107
```sh

0 commit comments

Comments
 (0)