init: projet complet copymail pour Raspberry Pi

Stack: Nginx + PHP 8.2 + MariaDB + Postfix + Dovecot + Samba
- install.sh: script installation automatisé pour RPi OS Bookworm
- Interface web: visionneuse mails + admin (comptes, domaine, dashboard)
- Chaque compte mail génère automatiquement un share Samba
- Processeur cron: extraction pièces jointes via zbateson/mail-mime-parser
- Zéro injection SQL (PDO + prepared statements)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jules
2026-05-04 09:36:35 +02:00
commit 0be1b9119a
21 changed files with 1627 additions and 0 deletions

122
web/index.php Normal file
View File

@@ -0,0 +1,122 @@
<?php
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/auth.php';
require_once __DIR__ . '/includes/layout.php';
auth_check();
$message = '';
// Suppression d'un email traité
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_source'])) {
$id = (int)$_POST['delete_source'];
$st = db()->prepare('SELECT sf.title, CONCAT(sf.file_name) as fname, GROUP_CONCAT(f.path) as fpaths
FROM source_file sf LEFT JOIN files f ON sf.id = f.source_file_id
WHERE sf.id = ? GROUP BY sf.id');
$st->execute([$id]);
$row = $st->fetch();
if ($row) {
if ($row['fpaths']) {
foreach (explode(',', $row['fpaths']) as $path) {
if (file_exists($path)) unlink($path);
}
}
db()->prepare('DELETE FROM source_file WHERE id = ?')->execute([$id]);
flash('success', "Entrée « " . htmlspecialchars($row['title']) . " » supprimée.");
}
header('Location: /');
exit;
}
$rows = db()->query(
'SELECT sf.id, sf.title, sf.sender, sf.date_processing, a.username,
GROUP_CONCAT(f.id, ":", f.file_name, ":", f.path ORDER BY f.id SEPARATOR "|") as files
FROM source_file sf
LEFT JOIN accounts a ON sf.account_id = a.id
LEFT JOIN files f ON sf.id = f.source_file_id
GROUP BY sf.id
ORDER BY sf.date_processing DESC'
)->fetchAll();
html_head('Courriers');
html_navbar('mail');
?>
<div class="container-fluid">
<?php flash_render(); ?>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0"><i class="bi bi-envelope-fill me-2"></i>Courriers traités</h5>
<span class="badge bg-secondary"><?= count($rows) ?> entrée(s)</span>
</div>
<?php if (empty($rows)): ?>
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>Aucun courrier traité pour l'instant.
</div>
<?php else: ?>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Date</th>
<th>Compte</th>
<th>Sujet</th>
<th>Expéditeur</th>
<th>Pièces jointes</th>
<th class="text-center">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $r): ?>
<tr>
<td class="text-nowrap text-muted small">
<?= htmlspecialchars(date('d/m/Y H:i', strtotime($r['date_processing']))) ?>
</td>
<td>
<?php if ($r['username']): ?>
<span class="badge bg-primary"><?= htmlspecialchars($r['username']) ?></span>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($r['title'] ?? '(sans sujet)') ?></td>
<td class="small text-muted"><?= htmlspecialchars($r['sender'] ?? '') ?></td>
<td>
<?php if ($r['files']): ?>
<?php foreach (explode('|', $r['files']) as $f): ?>
<?php [$fid, $fname, $fpath] = array_pad(explode(':', $f, 3), 3, ''); ?>
<?php if (file_exists($fpath)): ?>
<a href="/mail/download.php?id=<?= (int)$fid ?>" class="badge bg-info text-decoration-none me-1">
<i class="bi bi-download me-1"></i><?= htmlspecialchars(substr($fname, 0, 20)) ?>
</a>
<?php else: ?>
<span class="badge bg-secondary me-1" title="Fichier introuvable">
<?= htmlspecialchars(substr($fname, 0, 20)) ?>
</span>
<?php endif; ?>
<?php endforeach; ?>
<?php else: ?>
<span class="text-muted small">Aucune pièce jointe</span>
<?php endif; ?>
</td>
<td class="text-center">
<form method="post" onsubmit="return confirm('Supprimer cette entrée ?')">
<input type="hidden" name="delete_source" value="<?= $r['id'] ?>">
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
</div>
<script>
setTimeout(() => location.reload(), 30000);
</script>
<?php html_foot(); ?>