Compare commits

..

No commits in common. "a5581cf077fc759d4ce82d3be4f321d888a0b762" and "5404f6676b3d910a2fc80eec0bcbf5025b837e58" have entirely different histories.

4 changed files with 24 additions and 98 deletions

3
.gitignore vendored
View File

@ -31,9 +31,6 @@ Thumbs.db
# Claude Code agent state (lokale Definitionen, nicht versioniert)
.claude/
# Kimi Code / Sixth plugin state
.sixth/
# xgit binary (generated)
xgit

View File

@ -53,7 +53,7 @@ steps:
- mkdir -p ~/.ssh
- cp /root/ssh-keys/rebreak-deploy ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh-keyscan -H staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
- ssh-keyscan -H admin.staging.rebreak.org > ~/.ssh/known_hosts 2>/dev/null
- tar czf admin-output.tar.gz -C apps/admin/.output .
- scp -i ~/.ssh/id_ed25519 admin-output.tar.gz root@staging.rebreak.org:/srv/rebreak/apps/admin/.output-incoming.tar.gz
- ssh -i ~/.ssh/id_ed25519 root@staging.rebreak.org 'bash /srv/rebreak/scripts/deploy-admin-from-artifact.sh'

View File

@ -195,60 +195,18 @@
<UIcon
name="i-heroicons-lock-closed"
class="w-5 h-5 text-purple-600 dark:text-purple-400"
:class="{ 'animate-spin': lockPhase === 'loading' || lockPhase === 'checking' }"
:class="{ 'animate-spin': lockPhase === 'loading' }"
/>
<p class="text-sm font-bold text-purple-900 dark:text-purple-200">
Lock-Profil
</p>
</div>
<!-- Progress steps -->
<div class="flex items-center gap-2 text-xs mb-4">
<span
class="px-2 py-1 rounded-full"
:class="lockPhase === 'loading' ? 'bg-purple-200 text-purple-800 dark:bg-purple-700 dark:text-purple-100' : 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'"
>
1. Server starten
</span>
<span class="text-gray-400"></span>
<span
class="px-2 py-1 rounded-full"
:class="lockPhase === 'waiting' ? 'bg-purple-200 text-purple-800 dark:bg-purple-700 dark:text-purple-100' : (lockPhase === 'checking' || lockPhase === 'success' || lockPhase === 'error') ? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'"
>
2. QR-Code scannen
</span>
<span class="text-gray-400"></span>
<span
class="px-2 py-1 rounded-full"
:class="lockPhase === 'checking' ? 'bg-purple-200 text-purple-800 dark:bg-purple-700 dark:text-purple-100' : lockPhase === 'success' ? 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400'"
>
3. Prüfen
</span>
</div>
<!-- QR code -->
<div v-if="lockPhase === 'waiting' && lockQrUrl" class="text-center space-y-3">
<div class="bg-white p-3 rounded-xl inline-block">
<img :src="lockQrUrl" alt="Lock-Profil QR-Code" class="w-40 h-40">
</div>
<p class="text-xs text-purple-700 dark:text-purple-300">
Scanne den Code mit der iPhone-Kamera und installiere das Lock-Profil.
</p>
<UButton
size="sm"
color="primary"
:loading="lockPhase === 'checking'"
@click="checkInlineLockProfile"
>
Installation prüfen
</UButton>
</div>
<div v-if="lockPhase === 'loading'" class="text-sm text-purple-700 dark:text-purple-300">
Lock-Profil-Server wird gestartet
Lock-Profil wird per MDM auf das iPhone gepusht
</div>
<div v-if="lockPhase === 'success'" class="text-sm text-green-700 dark:text-green-300">
Lock-Profil installiert. Das Gerät aktualisiert den Schutz in Kürze.
Lock-Profil-Installation initiiert. Das Gerät aktualisiert den Schutz in Kürze.
</div>
<div v-if="lockPhase === 'error'" class="text-sm text-red-700 dark:text-red-300">
{{ lockError || "Lock-Profil-Installation fehlgeschlagen" }}
@ -325,6 +283,7 @@ const {
stopLocalProfileServer,
getInstalledProfiles,
mdmPush,
mdmInstallLockProfile,
} = useTauri();
const enrollmentPhase = ref<"idle" | "loading" | "waiting" | "checking" | "success" | "error">("idle");
@ -333,10 +292,9 @@ const enrollmentQrUrl = ref<string>("");
const enrollmentError = ref<string | null>(null);
const enrollmentLogs = ref<string[]>([]);
const lockPhase = ref<"idle" | "loading" | "waiting" | "checking" | "success" | "error">("idle");
const lockPhase = ref<"idle" | "loading" | "success" | "error">("idle");
const lockError = ref<string | null>(null);
const lockLogs = ref<string[]>([]);
const lockQrUrl = ref<string>("");
const LOCK_PROFILE_PATH = "/Users/chahinebrini/mono/rebreak-monorepo/ops/mdm/profiles/rebreak-content-filter-sideload.mobileconfig";
@ -822,15 +780,6 @@ async function checkInlineEnrollment() {
}
await refreshMdmStatus();
// After successful enrollment, automatically continue to lock profile if needed.
if (!localLock.value) {
enrollmentLogs.value.push("→ Enrollment abgeschlossen. Starte Lock-Profil …");
closeInlineEnrollment();
await startInlineLockProfile();
return;
}
enrollmentPhase.value = "success";
} catch (e: any) {
enrollmentError.value = e?.message ?? "Prüfung fehlgeschlagen";
@ -848,60 +797,29 @@ function closeInlineEnrollment() {
}
async function startInlineLockProfile() {
if (!props.iphone?.udid) return;
lockPhase.value = "loading";
lockError.value = null;
lockLogs.value = [];
lockQrUrl.value = "";
try {
lockLogs.value.push("→ Starte lokalen Server für Lock-Profil …");
const serverInfo = await startLocalProfileServer(LOCK_PROFILE_PATH);
lockLogs.value.push(`Server gestartet: ${serverInfo.url}`);
lockLogs.value.push("→ Installiere Lock-Profil per MDM …");
const result = await mdmInstallLockProfile(props.iphone.udid, LOCK_PROFILE_PATH);
lockLogs.value.push(`Command UUID: ${result.command_uuid}`);
lockQrUrl.value = await QRCode.toDataURL(serverInfo.qr_payload, {
width: 192,
margin: 2,
});
lockPhase.value = "waiting";
} catch (e: any) {
lockError.value = e?.message ?? "Lock-Profil-Server konnte nicht gestartet werden";
lockLogs.value.push(`${lockError.value}`);
lockPhase.value = "error";
}
}
async function checkInlineLockProfile() {
if (!props.iphone) return;
lockPhase.value = "checking";
lockError.value = null;
try {
const ids = await getInstalledProfiles();
props.iphone.installedProfileIDs = ids;
if (!ids.includes(LOCK_PROFILE_ID)) {
lockError.value = "Lock-Profil noch nicht installiert. Bitte QR-Code scannen und Profil installieren.";
lockPhase.value = "error";
return;
}
lockLogs.value.push("✓ Lock-Profil erkannt");
await refreshMdmStatus();
lockPhase.value = "success";
} catch (e: any) {
lockError.value = e?.message ?? "Prüfung fehlgeschlagen";
lockError.value = e?.message ?? "Lock-Profil-Installation fehlgeschlagen";
lockLogs.value.push(`${lockError.value}`);
lockPhase.value = "error";
}
}
function closeInlineLockProfile() {
stopLocalProfileServer();
lockPhase.value = "idle";
lockError.value = null;
lockLogs.value = [];
lockQrUrl.value = "";
}
</script>

View File

@ -3,9 +3,12 @@ use serde::{Deserialize, Serialize};
use std::fs;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use tiny_http::{Response, Server};
static SERVER_RUNNING: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalServerInfo {
pub url: String,
@ -14,6 +17,10 @@ pub struct LocalServerInfo {
#[tauri::command]
pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServerInfo> {
if SERVER_RUNNING.load(Ordering::SeqCst) {
return Err(AppError::new("Local server is already running"));
}
let path = PathBuf::from(profile_path);
if !path.exists() {
return Err(AppError::new(format!(
@ -41,6 +48,8 @@ pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServer
let qr_payload = url.clone();
let profile_bytes = fs::read(&path)?;
SERVER_RUNNING.store(true, Ordering::SeqCst);
thread::spawn(move || {
for request in server.incoming_requests() {
let response = Response::from_data(profile_bytes.clone())
@ -53,6 +62,7 @@ pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServer
);
let _ = request.respond(response);
}
SERVER_RUNNING.store(false, Ordering::SeqCst);
});
Ok(LocalServerInfo { url, qr_payload })
@ -61,7 +71,8 @@ pub fn start_local_profile_server(profile_path: String) -> AppResult<LocalServer
#[tauri::command]
pub fn stop_local_profile_server() -> AppResult<()> {
// tiny_http does not support graceful shutdown out of the box.
// Old server threads keep running, but each new profile gets a fresh port.
// In a real implementation, store the server handle and close it.
SERVER_RUNNING.store(false, Ordering::SeqCst);
Ok(())
}