Files
docker/remarkable/web/setup.php
Borgal b63131c0c5 Add reMarkable backup system with web UI
- Docker container with rmapi cloud backup, PDF conversion pipeline
- Web UI: file browser, multi-select, delete/move, thumbnail preview
- Sync: backup from reMarkable cloud, rmdoc→PDF conversion via rmc+Inkscape
- Excluded-files mechanism to prevent deleted items from returning after sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 23:43:36 +01:00

119 lines
4.9 KiB
PHP
Executable File

<?php
declare(strict_types=1);
require __DIR__ . '/auth.php';
$RMAPI = '/usr/local/bin/rmapi';
$flash = '';
$flashOk = true;
function h(string $v): string {
return htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// rmapi-Status prüfen
function rmapiStatus(string $bin): array {
exec($bin . ' ls / 2>&1', $out, $code);
return ['ok' => $code === 0, 'out' => implode("\n", $out)];
}
// Token einrichten
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['code'])) {
$code = trim($_POST['code']);
$code = preg_replace('/[^a-zA-Z0-9]/', '', $code);
if (strlen($code) === 8) {
$descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$proc = proc_open(escapeshellarg($RMAPI) . ' ls /', $descriptors, $pipes);
if (is_resource($proc)) {
fwrite($pipes[0], $code . "\n");
fclose($pipes[0]);
$out = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($proc);
$flashOk = $exitCode === 0;
$flash = $flashOk ? 'Token erfolgreich eingerichtet!' : 'Fehler: Ungültiger Code oder Verbindungsproblem.';
}
} else {
$flash = 'Code muss genau 8 Zeichen lang sein.';
$flashOk = false;
}
}
$status = rmapiStatus($RMAPI);
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>reMarkable Setup</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='18' fill='%23000'/%3E%3Ctext y='.9em' font-size='72' font-family='Georgia,serif' font-weight='bold' fill='white' x='50%25' text-anchor='middle'%3Erm%3C/text%3E%3C/svg%3E">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#f4f4f4;color:#222;min-height:100vh;display:flex;align-items:center;justify-content:center}
.card{background:#fff;border-radius:12px;padding:32px;width:440px;max-width:95vw;box-shadow:0 4px 24px rgba(0,0,0,.1)}
h1{font-size:20px;font-weight:700;margin-bottom:8px}
.subtitle{font-size:14px;color:#666;margin-bottom:24px}
.status{padding:12px 16px;border-radius:8px;font-size:14px;margin-bottom:20px;display:flex;align-items:center;gap:10px}
.status.ok{background:#dcfce7;border:1px solid #86efac;color:#166534}
.status.err{background:#fee2e2;border:1px solid #fca5a5;color:#991b1b}
.flash{padding:10px 16px;border-radius:8px;font-size:14px;margin-bottom:16px}
.flash.ok{background:#dcfce7;border:1px solid #86efac;color:#166534}
.flash.err{background:#fee2e2;border:1px solid #fca5a5;color:#991b1b}
label{display:block;font-size:13px;font-weight:500;margin-bottom:6px;color:#444}
input[type=text]{width:100%;padding:10px 12px;border:1px solid #ccc;border-radius:8px;font-size:16px;letter-spacing:4px;text-align:center;margin-bottom:12px}
.btn{width:100%;padding:10px;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;border:none;background:#2563eb;color:#fff}
.btn:hover{background:#1d4ed8}
.hint{font-size:13px;color:#666;margin-top:16px;line-height:1.6}
.hint a{color:#2563eb}
.back{display:block;text-align:center;margin-top:20px;font-size:13px;color:#2563eb;text-decoration:none}
</style>
</head>
<body>
<div class="card">
<h1>📄 reMarkable Setup</h1>
<p class="subtitle">Verbindung zur reMarkable Cloud einrichten</p>
<div class="status <?= $status['ok'] ? 'ok' : 'err' ?>">
<?= $status['ok'] ? '✓ Verbunden mit reMarkable Cloud' : '✗ Nicht verbunden — Token fehlt oder abgelaufen' ?>
</div>
<?php if ($flash !== ''): ?>
<div class="flash <?= $flashOk ? 'ok' : 'err' ?>"><?= h($flash) ?></div>
<?php endif; ?>
<?php if (!$status['ok']): ?>
<form method="post">
<label for="code">One-Time-Code</label>
<input type="text" id="code" name="code" maxlength="8" placeholder="xxxxxxxx" autocomplete="off" autofocus>
<button type="submit" class="btn">Verbinden</button>
</form>
<p class="hint">
Code abrufen unter:<br>
<a href="https://my.remarkable.com/device/desktop/connect" target="_blank">
my.remarkable.com/device/desktop/connect
</a>
</p>
<?php else: ?>
<form method="post">
<label for="code">Token erneuern (neuer One-Time-Code)</label>
<input type="text" id="code" name="code" maxlength="8" placeholder="xxxxxxxx" autocomplete="off">
<button type="submit" class="btn btn-secondary" style="background:#6b7280">Token erneuern</button>
</form>
<p class="hint">
Neuen Code abrufen unter:<br>
<a href="https://my.remarkable.com/device/desktop/connect" target="_blank">
my.remarkable.com/device/desktop/connect
</a>
</p>
<?php endif; ?>
<a href="/" class="back">← Zurück zur Übersicht</a>
</div>
</body>
</html>