Add builder service: scaffold, ASC API, devices UI, fastlane profile manager

Phase 1-3 of the builder subsystem on the Mac mini:
- Express + SQLite + sessions scaffolding, LAN-only service on port 3090
- App Store Connect JWT client (ES256 signing, devices/profiles/bundleIds)
- Device management UI with Apple-side registration
- Fastlane sigh wrapper with profile cache + auto-install to ~/Library/
- launchd plist + deploy script for Mac mini supervision
This commit is contained in:
trey
2026-04-11 13:28:01 -05:00
parent a1e60d390d
commit e9b6936904
19 changed files with 1666 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
const $ = (sel) => document.querySelector(sel);
const toast = (msg, kind = '') => {
const t = $('#toast');
t.textContent = msg;
t.className = 'toast show ' + kind;
setTimeout(() => t.classList.remove('show'), 3000);
};
async function load() {
const res = await fetch('/api/settings');
if (res.status === 401) { location.href = '/login'; return; }
const s = await res.json();
$('[name=asc_key_id]').value = s.asc_key_id || '';
$('[name=asc_issuer_id]').value = s.asc_issuer_id || '';
$('[name=unraid_url]').value = s.unraid_url || '';
$('[name=unraid_token]').value = s.unraid_token || '';
$('#p8-status').textContent = s.asc_key_uploaded
? `✓ .p8 uploaded for key ${s.asc_key_id}`
: 'No .p8 uploaded yet';
}
async function saveForm(formEl, keys) {
const data = Object.fromEntries(keys.map(k => [k, formEl.querySelector(`[name=${k}]`)?.value || '']));
const res = await fetch('/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (res.ok) toast('Saved', 'success');
else toast('Save failed', 'error');
}
$('#asc-form').addEventListener('submit', async (e) => {
e.preventDefault();
await saveForm(e.target, ['asc_key_id', 'asc_issuer_id']);
// Upload .p8 if one was selected
const file = $('#p8-input').files[0];
if (file) {
const fd = new FormData();
fd.append('p8', file);
const res = await fetch('/api/settings/p8', { method: 'POST', body: fd });
if (res.ok) {
toast('.p8 uploaded', 'success');
load();
} else {
const err = await res.json().catch(() => ({}));
toast(err.error || 'Upload failed', 'error');
}
}
});
$('#unraid-form').addEventListener('submit', async (e) => {
e.preventDefault();
await saveForm(e.target, ['unraid_url', 'unraid_token']);
});
$('#test-asc').addEventListener('click', async () => {
const res = await fetch('/api/settings/test-asc', { method: 'POST' });
const data = await res.json();
if (res.ok) toast(`Connected — ${data.device_count} devices in portal`, 'success');
else toast(data.error || 'Connection failed', 'error');
});
$('#test-unraid').addEventListener('click', async () => {
const res = await fetch('/api/settings/test-unraid', { method: 'POST' });
const data = await res.json();
if (res.ok) toast(`Connected to unraid — ${data.app_count} apps`, 'success');
else toast(data.error || 'Connection failed', 'error');
});
load();