aems-agent 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aems_agent-0.2.0/.github/workflows/build.yml +271 -0
- aems_agent-0.2.0/.github/workflows/ci.yml +47 -0
- aems_agent-0.2.0/.gitignore +41 -0
- aems_agent-0.2.0/LICENSE +21 -0
- aems_agent-0.2.0/PKG-INFO +161 -0
- aems_agent-0.2.0/README.md +133 -0
- aems_agent-0.2.0/packaging/aems-agent.spec +110 -0
- aems_agent-0.2.0/packaging/build.py +234 -0
- aems_agent-0.2.0/packaging/linux/README.md +50 -0
- aems_agent-0.2.0/packaging/macos/README.md +25 -0
- aems_agent-0.2.0/packaging/windows/installer.nsi +83 -0
- aems_agent-0.2.0/pyproject.toml +77 -0
- aems_agent-0.2.0/src/aems_agent/__init__.py +15 -0
- aems_agent-0.2.0/src/aems_agent/app.py +160 -0
- aems_agent-0.2.0/src/aems_agent/cli.py +290 -0
- aems_agent-0.2.0/src/aems_agent/config.py +235 -0
- aems_agent-0.2.0/src/aems_agent/license_enforcement.py +243 -0
- aems_agent-0.2.0/src/aems_agent/license_validation.py +331 -0
- aems_agent-0.2.0/src/aems_agent/routes.py +653 -0
- aems_agent-0.2.0/src/aems_agent/security.py +119 -0
- aems_agent-0.2.0/src/aems_agent/tray.py +167 -0
- aems_agent-0.2.0/tests/__init__.py +1 -0
- aems_agent-0.2.0/tests/conftest.py +72 -0
- aems_agent-0.2.0/tests/test_cli.py +61 -0
- aems_agent-0.2.0/tests/test_config.py +156 -0
- aems_agent-0.2.0/tests/test_license_enforcement.py +165 -0
- aems_agent-0.2.0/tests/test_license_validation.py +203 -0
- aems_agent-0.2.0/tests/test_routes.py +436 -0
- aems_agent-0.2.0/tests/test_security.py +72 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
name: Build AEMS Agent
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
version:
|
|
10
|
+
description: "Version tag (for manual runs)"
|
|
11
|
+
required: false
|
|
12
|
+
default: "dev"
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build-windows:
|
|
20
|
+
runs-on: windows-latest
|
|
21
|
+
env:
|
|
22
|
+
WIN_CODESIGN_CERT_PFX_BASE64: ${{ secrets.WIN_CODESIGN_CERT_PFX_BASE64 }}
|
|
23
|
+
WIN_CODESIGN_CERT_PASSWORD: ${{ secrets.WIN_CODESIGN_CERT_PASSWORD }}
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Set up Python
|
|
28
|
+
uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.11"
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: |
|
|
34
|
+
pip install pyinstaller
|
|
35
|
+
pip install -e ".[dev]"
|
|
36
|
+
|
|
37
|
+
- name: Install NSIS
|
|
38
|
+
run: choco install nsis -y
|
|
39
|
+
|
|
40
|
+
- name: Add NSIS to PATH
|
|
41
|
+
run: echo "C:\Program Files (x86)\NSIS" >> $env:GITHUB_PATH
|
|
42
|
+
|
|
43
|
+
- name: Build with PyInstaller and NSIS installer
|
|
44
|
+
run: python packaging/build.py --platform windows
|
|
45
|
+
|
|
46
|
+
- name: Verify installer exists
|
|
47
|
+
run: |
|
|
48
|
+
if (Test-Path "dist\aems-agent-setup.exe") {
|
|
49
|
+
Write-Host "Installer built: dist\aems-agent-setup.exe"
|
|
50
|
+
(Get-Item "dist\aems-agent-setup.exe").Length
|
|
51
|
+
} else {
|
|
52
|
+
Write-Host "::warning::Installer not found. Running NSIS directly..."
|
|
53
|
+
& "C:\Program Files (x86)\NSIS\makensis.exe" /DDIST_DIR=dist\aems-agent /DOUTPUT_DIR=dist packaging\windows\installer.nsi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
- name: Check Windows signing credentials
|
|
57
|
+
id: win_sign_check
|
|
58
|
+
run: |
|
|
59
|
+
if ($env:WIN_CODESIGN_CERT_PFX_BASE64 -and $env:WIN_CODESIGN_CERT_PASSWORD) {
|
|
60
|
+
echo "available=true" >> $env:GITHUB_OUTPUT
|
|
61
|
+
Write-Host "Code signing credentials found, will sign installer."
|
|
62
|
+
} else {
|
|
63
|
+
echo "available=false" >> $env:GITHUB_OUTPUT
|
|
64
|
+
Write-Host "::warning::Code signing credentials not configured. Installer will be unsigned."
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
- name: Sign Windows installer (Authenticode)
|
|
68
|
+
if: steps.win_sign_check.outputs.available == 'true'
|
|
69
|
+
run: |
|
|
70
|
+
$pfxPath = Join-Path $env:RUNNER_TEMP "codesign.pfx"
|
|
71
|
+
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:WIN_CODESIGN_CERT_PFX_BASE64))
|
|
72
|
+
$signtool = (Get-Command signtool.exe).Source
|
|
73
|
+
& $signtool sign /fd SHA256 /f $pfxPath /p $env:WIN_CODESIGN_CERT_PASSWORD /tr http://timestamp.digicert.com /td SHA256 dist\aems-agent-setup.exe
|
|
74
|
+
& $signtool verify /pa /v dist\aems-agent-setup.exe
|
|
75
|
+
|
|
76
|
+
- name: Upload artifact
|
|
77
|
+
uses: actions/upload-artifact@v4
|
|
78
|
+
with:
|
|
79
|
+
name: aems-agent-windows
|
|
80
|
+
path: |
|
|
81
|
+
dist/aems-agent-setup.exe
|
|
82
|
+
dist/aems-agent/
|
|
83
|
+
|
|
84
|
+
build-macos:
|
|
85
|
+
runs-on: macos-latest
|
|
86
|
+
env:
|
|
87
|
+
MACOS_CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
|
|
88
|
+
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
|
|
89
|
+
MACOS_DEVELOPER_IDENTITY: ${{ secrets.MACOS_DEVELOPER_IDENTITY }}
|
|
90
|
+
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
91
|
+
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
92
|
+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@v4
|
|
95
|
+
|
|
96
|
+
- name: Set up Python
|
|
97
|
+
uses: actions/setup-python@v5
|
|
98
|
+
with:
|
|
99
|
+
python-version: "3.11"
|
|
100
|
+
|
|
101
|
+
- name: Install dependencies
|
|
102
|
+
run: |
|
|
103
|
+
pip install pyinstaller
|
|
104
|
+
pip install -e ".[dev]"
|
|
105
|
+
|
|
106
|
+
- name: Build with PyInstaller
|
|
107
|
+
run: python packaging/build.py --platform macos
|
|
108
|
+
|
|
109
|
+
- name: Check macOS signing credentials
|
|
110
|
+
id: mac_sign_check
|
|
111
|
+
run: |
|
|
112
|
+
ALL_PRESENT=true
|
|
113
|
+
for v in MACOS_CERT_P12_BASE64 MACOS_CERT_PASSWORD MACOS_DEVELOPER_IDENTITY APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
|
|
114
|
+
if [ -z "${!v}" ]; then
|
|
115
|
+
ALL_PRESENT=false
|
|
116
|
+
break
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
echo "available=$ALL_PRESENT" >> "$GITHUB_OUTPUT"
|
|
120
|
+
if [ "$ALL_PRESENT" = "false" ]; then
|
|
121
|
+
echo "::warning::macOS signing/notarization credentials not configured. DMG will be unsigned."
|
|
122
|
+
else
|
|
123
|
+
echo "Signing credentials found, will sign and notarize."
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
- name: Import Developer ID certificate
|
|
127
|
+
if: steps.mac_sign_check.outputs.available == 'true'
|
|
128
|
+
run: |
|
|
129
|
+
KEYCHAIN_PATH="$RUNNER_TEMP/aems-signing.keychain-db"
|
|
130
|
+
KEYCHAIN_PASSWORD="$(openssl rand -hex 16)"
|
|
131
|
+
CERT_PATH="$RUNNER_TEMP/developer_id.p12"
|
|
132
|
+
python - <<'PY'
|
|
133
|
+
import base64
|
|
134
|
+
import os
|
|
135
|
+
from pathlib import Path
|
|
136
|
+
|
|
137
|
+
data = os.environ["MACOS_CERT_P12_BASE64"]
|
|
138
|
+
out = Path(os.environ["RUNNER_TEMP"]) / "developer_id.p12"
|
|
139
|
+
out.write_bytes(base64.b64decode(data))
|
|
140
|
+
PY
|
|
141
|
+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
142
|
+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
|
143
|
+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
144
|
+
security list-keychains -d user -s "$KEYCHAIN_PATH"
|
|
145
|
+
security default-keychain -s "$KEYCHAIN_PATH"
|
|
146
|
+
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$MACOS_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
|
|
147
|
+
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
|
148
|
+
|
|
149
|
+
- name: Sign macOS app bundle and DMG
|
|
150
|
+
if: steps.mac_sign_check.outputs.available == 'true'
|
|
151
|
+
run: |
|
|
152
|
+
codesign --force --deep --options runtime --timestamp --sign "$MACOS_DEVELOPER_IDENTITY" "dist/AEMS Agent.app"
|
|
153
|
+
codesign --verify --deep --strict --verbose=2 "dist/AEMS Agent.app"
|
|
154
|
+
codesign --force --timestamp --sign "$MACOS_DEVELOPER_IDENTITY" "dist/AEMS-Agent.dmg"
|
|
155
|
+
codesign --verify --verbose=2 "dist/AEMS-Agent.dmg"
|
|
156
|
+
|
|
157
|
+
- name: Notarize and staple macOS DMG
|
|
158
|
+
if: steps.mac_sign_check.outputs.available == 'true'
|
|
159
|
+
run: |
|
|
160
|
+
xcrun notarytool submit "dist/AEMS-Agent.dmg" \
|
|
161
|
+
--apple-id "$APPLE_ID" \
|
|
162
|
+
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
|
163
|
+
--team-id "$APPLE_TEAM_ID" \
|
|
164
|
+
--wait
|
|
165
|
+
xcrun stapler staple "dist/AEMS-Agent.dmg"
|
|
166
|
+
spctl --assess --type open --context context:primary-signature --verbose=4 "dist/AEMS-Agent.dmg"
|
|
167
|
+
|
|
168
|
+
- name: Upload artifact
|
|
169
|
+
uses: actions/upload-artifact@v4
|
|
170
|
+
with:
|
|
171
|
+
name: aems-agent-macos
|
|
172
|
+
path: |
|
|
173
|
+
dist/AEMS-Agent.dmg
|
|
174
|
+
dist/AEMS Agent.app/
|
|
175
|
+
|
|
176
|
+
build-linux:
|
|
177
|
+
runs-on: ubuntu-latest
|
|
178
|
+
steps:
|
|
179
|
+
- uses: actions/checkout@v4
|
|
180
|
+
|
|
181
|
+
- name: Set up Python
|
|
182
|
+
uses: actions/setup-python@v5
|
|
183
|
+
with:
|
|
184
|
+
python-version: "3.11"
|
|
185
|
+
|
|
186
|
+
- name: Install dependencies
|
|
187
|
+
run: |
|
|
188
|
+
pip install pyinstaller
|
|
189
|
+
pip install -e ".[dev]"
|
|
190
|
+
|
|
191
|
+
- name: Build with PyInstaller
|
|
192
|
+
run: python packaging/build.py --platform linux
|
|
193
|
+
|
|
194
|
+
- name: Upload artifact
|
|
195
|
+
uses: actions/upload-artifact@v4
|
|
196
|
+
with:
|
|
197
|
+
name: aems-agent-linux
|
|
198
|
+
path: dist/aems-agent/
|
|
199
|
+
|
|
200
|
+
release:
|
|
201
|
+
needs: [build-windows, build-macos, build-linux]
|
|
202
|
+
runs-on: ubuntu-latest
|
|
203
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
204
|
+
steps:
|
|
205
|
+
- uses: actions/checkout@v4
|
|
206
|
+
|
|
207
|
+
- name: Download all artifacts
|
|
208
|
+
uses: actions/download-artifact@v4
|
|
209
|
+
with:
|
|
210
|
+
path: artifacts/
|
|
211
|
+
|
|
212
|
+
- name: Collect release assets
|
|
213
|
+
id: assets
|
|
214
|
+
run: |
|
|
215
|
+
ASSETS=""
|
|
216
|
+
|
|
217
|
+
if [ -f artifacts/aems-agent-windows/aems-agent-setup.exe ]; then
|
|
218
|
+
ASSETS="$ASSETS artifacts/aems-agent-windows/aems-agent-setup.exe"
|
|
219
|
+
fi
|
|
220
|
+
if [ -f artifacts/aems-agent-macos/AEMS-Agent.dmg ]; then
|
|
221
|
+
ASSETS="$ASSETS artifacts/aems-agent-macos/AEMS-Agent.dmg"
|
|
222
|
+
fi
|
|
223
|
+
if [ -d artifacts/aems-agent-linux ]; then
|
|
224
|
+
tar -czf artifacts/aems-agent-linux.tar.gz -C artifacts aems-agent-linux
|
|
225
|
+
ASSETS="$ASSETS artifacts/aems-agent-linux.tar.gz"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
: > artifacts/sha256sums.txt
|
|
229
|
+
for f in $ASSETS; do
|
|
230
|
+
sha256sum "$f" >> artifacts/sha256sums.txt
|
|
231
|
+
done
|
|
232
|
+
|
|
233
|
+
VERSION="${GITHUB_REF#refs/tags/v}"
|
|
234
|
+
cat > artifacts/release-manifest.json <<EOF
|
|
235
|
+
{
|
|
236
|
+
"name": "aems-agent",
|
|
237
|
+
"version": "${VERSION}",
|
|
238
|
+
"generated_at_utc": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
|
239
|
+
"checksum_file": "sha256sums.txt"
|
|
240
|
+
}
|
|
241
|
+
EOF
|
|
242
|
+
|
|
243
|
+
ASSETS="$ASSETS artifacts/sha256sums.txt artifacts/release-manifest.json"
|
|
244
|
+
ASSETS=$(echo "$ASSETS" | xargs)
|
|
245
|
+
echo "files=$ASSETS" >> "$GITHUB_OUTPUT"
|
|
246
|
+
|
|
247
|
+
- name: Install cosign
|
|
248
|
+
uses: sigstore/cosign-installer@v3.7.0
|
|
249
|
+
|
|
250
|
+
- name: Sign release manifest (keyless)
|
|
251
|
+
run: |
|
|
252
|
+
cosign sign-blob --yes artifacts/release-manifest.json \
|
|
253
|
+
--output-signature artifacts/release-manifest.sig \
|
|
254
|
+
--output-certificate artifacts/release-manifest.pem
|
|
255
|
+
|
|
256
|
+
- name: Append signature assets
|
|
257
|
+
id: signed_assets
|
|
258
|
+
run: |
|
|
259
|
+
FILES="${{ steps.assets.outputs.files }} artifacts/release-manifest.sig artifacts/release-manifest.pem"
|
|
260
|
+
FILES=$(echo "$FILES" | xargs)
|
|
261
|
+
echo "files=$FILES" >> "$GITHUB_OUTPUT"
|
|
262
|
+
|
|
263
|
+
- name: Create Release
|
|
264
|
+
env:
|
|
265
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
266
|
+
run: |
|
|
267
|
+
VERSION="${GITHUB_REF#refs/tags/v}"
|
|
268
|
+
gh release create "v${VERSION}" \
|
|
269
|
+
--title "AEMS Agent v${VERSION}" \
|
|
270
|
+
--notes "AEMS Local Bridge Agent v${VERSION} - Windows (.exe), macOS (.dmg), Linux (.tar.gz), plus signed manifest/checksums." \
|
|
271
|
+
${{ steps.signed_assets.outputs.files }}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ${{ matrix.os }}
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
15
|
+
python-version: ['3.10', '3.11', '3.12']
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: python -m pytest -v --tb=short
|
|
29
|
+
|
|
30
|
+
lint:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Set up Python
|
|
36
|
+
uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: '3.11'
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: pip install -e ".[dev]"
|
|
42
|
+
|
|
43
|
+
- name: Check formatting (black)
|
|
44
|
+
run: black --check src/ tests/
|
|
45
|
+
|
|
46
|
+
- name: Lint (ruff)
|
|
47
|
+
run: ruff check src/ tests/
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg-info/
|
|
7
|
+
*.egg
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
venv/
|
|
14
|
+
.venv/
|
|
15
|
+
ENV/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
*~
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Testing
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.coverage
|
|
31
|
+
htmlcov/
|
|
32
|
+
.mypy_cache/
|
|
33
|
+
|
|
34
|
+
# PyInstaller
|
|
35
|
+
*.spec.bak
|
|
36
|
+
|
|
37
|
+
# Logs
|
|
38
|
+
*.log
|
|
39
|
+
|
|
40
|
+
# Config (user-specific)
|
|
41
|
+
config/
|
aems_agent-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 AEMS Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aems-agent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: AEMS Local Bridge Agent — local filesystem access for exam PDFs
|
|
5
|
+
Author: AEMS Team
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: cryptography>=43.0.0
|
|
10
|
+
Requires-Dist: fastapi>=0.104.0
|
|
11
|
+
Requires-Dist: httpx>=0.25.0
|
|
12
|
+
Requires-Dist: pydantic>=2.0.0
|
|
13
|
+
Requires-Dist: pyjwt[crypto]>=2.9.0
|
|
14
|
+
Requires-Dist: typer>=0.9.0
|
|
15
|
+
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
18
|
+
Requires-Dist: httpx>=0.25.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: mypy>=1.5.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=7.4.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
24
|
+
Provides-Extra: tray
|
|
25
|
+
Requires-Dist: pillow>=10.0.0; extra == 'tray'
|
|
26
|
+
Requires-Dist: pystray>=0.19.0; extra == 'tray'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# AEMS Local Bridge Agent
|
|
30
|
+
|
|
31
|
+
A lightweight companion service that runs on `localhost` and provides REST API access to the local filesystem, enabling the [AEMS](https://github.com/artkula/aems) web app to read/write exam PDFs to a user-chosen folder.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### pip (recommended for development)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install aems-agent
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Binary installers
|
|
42
|
+
|
|
43
|
+
Download pre-built installers from [Releases](https://github.com/artkula/aems-agent/releases):
|
|
44
|
+
|
|
45
|
+
| Platform | File | Notes |
|
|
46
|
+
|----------|------|-------|
|
|
47
|
+
| Windows | `aems-agent-setup.exe` | Installs to `%LOCALAPPDATA%\AEMS Agent` |
|
|
48
|
+
| macOS | `AEMS-Agent.dmg` | Drag to Applications |
|
|
49
|
+
| Linux | `aems-agent-linux.tar.gz` | Extract and run `./aems-agent run` |
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Start the agent (default: http://127.0.0.1:61234)
|
|
55
|
+
aems-agent run
|
|
56
|
+
|
|
57
|
+
# Start with system tray icon
|
|
58
|
+
aems-agent run --tray
|
|
59
|
+
|
|
60
|
+
# Custom port/host
|
|
61
|
+
aems-agent run --port 9000 --host 0.0.0.0
|
|
62
|
+
|
|
63
|
+
# Enforce runtime license policy
|
|
64
|
+
aems-agent run --license-policy warn
|
|
65
|
+
aems-agent run --license-policy soft-block
|
|
66
|
+
aems-agent run --license-policy hard-block
|
|
67
|
+
|
|
68
|
+
# Show auth token
|
|
69
|
+
aems-agent token
|
|
70
|
+
|
|
71
|
+
# Set exam storage directory
|
|
72
|
+
aems-agent set-path /path/to/exams
|
|
73
|
+
|
|
74
|
+
# Show config directory location
|
|
75
|
+
aems-agent config-dir
|
|
76
|
+
|
|
77
|
+
# Store a license JWT token
|
|
78
|
+
aems-agent license-store "<jwt-token>"
|
|
79
|
+
|
|
80
|
+
# Validate token signature + claims + heartbeat
|
|
81
|
+
aems-agent license-check \
|
|
82
|
+
--license-url https://license.domain.com \
|
|
83
|
+
--issuer https://license.domain.com \
|
|
84
|
+
--audience aems-agent
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
Config files are stored in a platform-specific directory:
|
|
90
|
+
|
|
91
|
+
| Platform | Path |
|
|
92
|
+
|----------|------|
|
|
93
|
+
| Windows | `%APPDATA%\AEMS\agent\` |
|
|
94
|
+
| macOS | `~/.config/aems/agent/` |
|
|
95
|
+
| Linux | `~/.config/aems/agent/` (or `$XDG_CONFIG_HOME/aems/agent/`) |
|
|
96
|
+
|
|
97
|
+
Files:
|
|
98
|
+
- `config.json` - storage path, port, allowed origins, license settings
|
|
99
|
+
- `auth_token` - bearer token for API authentication
|
|
100
|
+
- `license.jwt` - stored license token
|
|
101
|
+
- `agent.log` - rotating log file
|
|
102
|
+
|
|
103
|
+
Runtime license policy modes:
|
|
104
|
+
- `warn`: agent starts and logs validation failures.
|
|
105
|
+
- `soft-block`: agent starts in limited mode when license is invalid. Write operations (`PUT/DELETE /files/*`, `PUT /config/path`) are blocked until license becomes valid again.
|
|
106
|
+
- `hard-block`: startup fails and exits non-zero when license is invalid/revoked/grace-expired; runtime checks also terminate the process non-zero on hard-block failures.
|
|
107
|
+
|
|
108
|
+
## API Endpoints
|
|
109
|
+
|
|
110
|
+
| Method | Path | Auth | Description |
|
|
111
|
+
|--------|------|------|-------------|
|
|
112
|
+
| GET | `/status` | No | Alive check |
|
|
113
|
+
| GET | `/health` | Yes | Detailed health with disk info |
|
|
114
|
+
| GET/PUT | `/config/path` | Yes | Get/set storage path |
|
|
115
|
+
| GET | `/files/{assignment_id}` | Yes | List submissions |
|
|
116
|
+
| GET/PUT/DELETE | `/files/{aid}/{sid}` | Yes | Manage submission PDFs |
|
|
117
|
+
| GET/PUT | `/files/{aid}/{sid}/annotated` | Yes | Manage annotated PDFs |
|
|
118
|
+
| POST | `/pair/initiate` | No | Start browser pairing |
|
|
119
|
+
| POST | `/pair/complete` | No | Complete pairing |
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
git clone https://github.com/artkula/aems-agent.git
|
|
125
|
+
cd aems-agent
|
|
126
|
+
python -m pip install -e ".[dev]"
|
|
127
|
+
python -m pytest -v
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Release Trust and Verification
|
|
131
|
+
|
|
132
|
+
Release pipeline:
|
|
133
|
+
- `.github/workflows/build.yml`
|
|
134
|
+
- Windows tagged releases are Authenticode-signed.
|
|
135
|
+
- macOS tagged releases are code-signed, notarized, and stapled.
|
|
136
|
+
- `release-manifest.json` and `sha256sums.txt` are signed with cosign as supplemental integrity proof.
|
|
137
|
+
|
|
138
|
+
Verification examples:
|
|
139
|
+
|
|
140
|
+
Windows:
|
|
141
|
+
```powershell
|
|
142
|
+
Get-AuthenticodeSignature .\aems-agent-setup.exe | Format-List
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
macOS:
|
|
146
|
+
```bash
|
|
147
|
+
codesign --verify --deep --strict --verbose=2 "AEMS Agent.app"
|
|
148
|
+
spctl --assess --type open --context context:primary-signature --verbose=4 "AEMS-Agent.dmg"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Cosign manifest verification:
|
|
152
|
+
```bash
|
|
153
|
+
cosign verify-blob \
|
|
154
|
+
--certificate release-manifest.pem \
|
|
155
|
+
--signature release-manifest.sig \
|
|
156
|
+
release-manifest.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# AEMS Local Bridge Agent
|
|
2
|
+
|
|
3
|
+
A lightweight companion service that runs on `localhost` and provides REST API access to the local filesystem, enabling the [AEMS](https://github.com/artkula/aems) web app to read/write exam PDFs to a user-chosen folder.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### pip (recommended for development)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install aems-agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Binary installers
|
|
14
|
+
|
|
15
|
+
Download pre-built installers from [Releases](https://github.com/artkula/aems-agent/releases):
|
|
16
|
+
|
|
17
|
+
| Platform | File | Notes |
|
|
18
|
+
|----------|------|-------|
|
|
19
|
+
| Windows | `aems-agent-setup.exe` | Installs to `%LOCALAPPDATA%\AEMS Agent` |
|
|
20
|
+
| macOS | `AEMS-Agent.dmg` | Drag to Applications |
|
|
21
|
+
| Linux | `aems-agent-linux.tar.gz` | Extract and run `./aems-agent run` |
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Start the agent (default: http://127.0.0.1:61234)
|
|
27
|
+
aems-agent run
|
|
28
|
+
|
|
29
|
+
# Start with system tray icon
|
|
30
|
+
aems-agent run --tray
|
|
31
|
+
|
|
32
|
+
# Custom port/host
|
|
33
|
+
aems-agent run --port 9000 --host 0.0.0.0
|
|
34
|
+
|
|
35
|
+
# Enforce runtime license policy
|
|
36
|
+
aems-agent run --license-policy warn
|
|
37
|
+
aems-agent run --license-policy soft-block
|
|
38
|
+
aems-agent run --license-policy hard-block
|
|
39
|
+
|
|
40
|
+
# Show auth token
|
|
41
|
+
aems-agent token
|
|
42
|
+
|
|
43
|
+
# Set exam storage directory
|
|
44
|
+
aems-agent set-path /path/to/exams
|
|
45
|
+
|
|
46
|
+
# Show config directory location
|
|
47
|
+
aems-agent config-dir
|
|
48
|
+
|
|
49
|
+
# Store a license JWT token
|
|
50
|
+
aems-agent license-store "<jwt-token>"
|
|
51
|
+
|
|
52
|
+
# Validate token signature + claims + heartbeat
|
|
53
|
+
aems-agent license-check \
|
|
54
|
+
--license-url https://license.domain.com \
|
|
55
|
+
--issuer https://license.domain.com \
|
|
56
|
+
--audience aems-agent
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
Config files are stored in a platform-specific directory:
|
|
62
|
+
|
|
63
|
+
| Platform | Path |
|
|
64
|
+
|----------|------|
|
|
65
|
+
| Windows | `%APPDATA%\AEMS\agent\` |
|
|
66
|
+
| macOS | `~/.config/aems/agent/` |
|
|
67
|
+
| Linux | `~/.config/aems/agent/` (or `$XDG_CONFIG_HOME/aems/agent/`) |
|
|
68
|
+
|
|
69
|
+
Files:
|
|
70
|
+
- `config.json` - storage path, port, allowed origins, license settings
|
|
71
|
+
- `auth_token` - bearer token for API authentication
|
|
72
|
+
- `license.jwt` - stored license token
|
|
73
|
+
- `agent.log` - rotating log file
|
|
74
|
+
|
|
75
|
+
Runtime license policy modes:
|
|
76
|
+
- `warn`: agent starts and logs validation failures.
|
|
77
|
+
- `soft-block`: agent starts in limited mode when license is invalid. Write operations (`PUT/DELETE /files/*`, `PUT /config/path`) are blocked until license becomes valid again.
|
|
78
|
+
- `hard-block`: startup fails and exits non-zero when license is invalid/revoked/grace-expired; runtime checks also terminate the process non-zero on hard-block failures.
|
|
79
|
+
|
|
80
|
+
## API Endpoints
|
|
81
|
+
|
|
82
|
+
| Method | Path | Auth | Description |
|
|
83
|
+
|--------|------|------|-------------|
|
|
84
|
+
| GET | `/status` | No | Alive check |
|
|
85
|
+
| GET | `/health` | Yes | Detailed health with disk info |
|
|
86
|
+
| GET/PUT | `/config/path` | Yes | Get/set storage path |
|
|
87
|
+
| GET | `/files/{assignment_id}` | Yes | List submissions |
|
|
88
|
+
| GET/PUT/DELETE | `/files/{aid}/{sid}` | Yes | Manage submission PDFs |
|
|
89
|
+
| GET/PUT | `/files/{aid}/{sid}/annotated` | Yes | Manage annotated PDFs |
|
|
90
|
+
| POST | `/pair/initiate` | No | Start browser pairing |
|
|
91
|
+
| POST | `/pair/complete` | No | Complete pairing |
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
git clone https://github.com/artkula/aems-agent.git
|
|
97
|
+
cd aems-agent
|
|
98
|
+
python -m pip install -e ".[dev]"
|
|
99
|
+
python -m pytest -v
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Release Trust and Verification
|
|
103
|
+
|
|
104
|
+
Release pipeline:
|
|
105
|
+
- `.github/workflows/build.yml`
|
|
106
|
+
- Windows tagged releases are Authenticode-signed.
|
|
107
|
+
- macOS tagged releases are code-signed, notarized, and stapled.
|
|
108
|
+
- `release-manifest.json` and `sha256sums.txt` are signed with cosign as supplemental integrity proof.
|
|
109
|
+
|
|
110
|
+
Verification examples:
|
|
111
|
+
|
|
112
|
+
Windows:
|
|
113
|
+
```powershell
|
|
114
|
+
Get-AuthenticodeSignature .\aems-agent-setup.exe | Format-List
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
macOS:
|
|
118
|
+
```bash
|
|
119
|
+
codesign --verify --deep --strict --verbose=2 "AEMS Agent.app"
|
|
120
|
+
spctl --assess --type open --context context:primary-signature --verbose=4 "AEMS-Agent.dmg"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Cosign manifest verification:
|
|
124
|
+
```bash
|
|
125
|
+
cosign verify-blob \
|
|
126
|
+
--certificate release-manifest.pem \
|
|
127
|
+
--signature release-manifest.sig \
|
|
128
|
+
release-manifest.json
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|