Fix fresh-install migration blocker + add preflight, seed admin, first-run endpoints

Validated a clean install on MySQL 5.6 end to end and fixed the blockers.

- migrations/env.py: force alembic_version.version_num to VARCHAR(128) in its
  own committed connection before running migrations. It was VARCHAR(32); the
  revision id 7d02_widen_notification_employee_cols (37 chars) truncated, so the
  next migration's version bump matched 0 rows and `flask db upgrade` died at
  7d03 on a fresh DB. Now upgrades run clean to head.
- flask db-utils preflight: checks Python, required env, DB connectivity, and
  the MySQL 5.6 utf8mb4 index flags (innodb_large_prefix/Barracuda) - the 767
  prerequisite - and prints exact fixes. Exits non-zero on blockers.
- flask seed admin --username --email [--password]: real first-admin command
  (generates + prints a password once). The docs referenced it but only the
  dev-only test-user existed.
- setup endpoints for a UI-driven first run: /setup/needs-admin,
  /setup/create-admin (guarded to zero-users), /setup/seed-reference.
- Wizard: drop the PC-access-domain question (always .device.geaerospace.net);
  the setting keeps its default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-10 11:28:22 -04:00
parent 8c198938c2
commit 24d5b00dc2
5 changed files with 216 additions and 10 deletions

View File

@@ -773,6 +773,15 @@ export const pluginsApi = {
}
export const setupApi = {
needsAdmin() {
return api.get('/setup/needs-admin')
},
createAdmin(data) {
return api.post('/setup/create-admin', data)
},
seedReference() {
return api.post('/setup/seed-reference')
},
seedStarter() {
return api.post('/setup/seed-starter')
},

View File

@@ -25,10 +25,6 @@
<label>Site base URL <span class="hint">(blank = use the browsing origin)</span></label>
<input v-model="form.site_base_url" type="url" class="form-control" placeholder="https://shopdb.example.net" />
</div>
<div class="form-group">
<label>PC access domain <span class="hint">(for remote-access links)</span></label>
<input v-model="form.pc_access_domain" type="text" class="form-control" placeholder="device.geaerospace.net" />
</div>
</div>
<!-- Plugins -->
@@ -168,7 +164,7 @@ const step = ref(0)
const current = computed(() => steps[step.value])
const form = ref({
facility_name: '', site_base_url: '', pc_access_domain: '',
facility_name: '', site_base_url: '',
map_width: null, map_height: null,
})
const plugins = ref([])
@@ -226,13 +222,13 @@ const envLines = computed(() => {
// Which settings each step owns, so Next only saves what changed on that step.
const stepSettings = {
site: ['facility_name', 'site_base_url', 'pc_access_domain'],
site: ['facility_name', 'site_base_url'],
map: ['map_width', 'map_height'],
}
onMounted(async () => {
// Load current values for the settings the wizard edits.
const keys = ['facility_name', 'site_base_url', 'pc_access_domain', 'map_width', 'map_height']
const keys = ['facility_name', 'site_base_url', 'map_width', 'map_height']
for (const key of keys) {
try {
const response = await settingsApi.get(key)