mirror of
https://github.com/ChrispyBacon-dev/DockFlare.git
synced 2026-07-09 17:19:34 +00:00
Merge branch 'stable' into unstable
This commit is contained in:
commit
a6789ac194
16 changed files with 724 additions and 145 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -38,6 +38,17 @@ All notable changes to this project will be documented in this file.
|
|||
### Fixed
|
||||
- **Webmail - Dark Mode:** Placeholder text in the reply composer and input backgrounds in the settings panel were broken in dark mode due to a Vue scoped CSS compilation issue. Fixed throughout.
|
||||
|
||||
### Hotfixes (post-release)
|
||||
- **Inbound - R2 Cron Delivery:** Emails buffered in R2 were not delivered when the EML `To:` header differed from the SMTP envelope recipient (e.g. GitHub mailing list addresses). Mail manager now falls back through payload `resolved_mailbox`, EML delivery headers (`X-GitHub-Recipient-Address`, `Delivered-To`), and the `Received: for <addr>` header.
|
||||
- **Inbound - Worker `list()` Metadata:** Cloudflare R2 `list()` does not return `customMetadata` unless explicitly requested. Added `include: ['customMetadata']` to the cron sweep so envelope metadata is available on retry.
|
||||
- **Inbound - R2 Delete Error Handling:** A transient R2 delete failure in the success path caused a false HTTP 500 response despite the message being delivered. Now try/except with a warning log; cron cleans up any orphaned object.
|
||||
- **Inbound - Empty Message-ID:** Emails with no `Message-ID` header produced an empty string, causing a UNIQUE constraint collision. A UUID fallback is now generated.
|
||||
- **Inbound - Attachment Filename Safety:** Filenames with path traversal components (`..`) are now stripped via `os.path.basename`. Duplicate filenames within the same message get a numeric suffix instead of overwriting each other on disk.
|
||||
- **Worker - KV Availability Guard:** Alias KV lookups in the `email()` handler now check `typeof env.QUOTA_KV !== 'undefined'` before use, matching the pattern already applied to quota checks. A missing KV binding previously caused all alias mail to be silently rejected.
|
||||
- **Worker - Subject Metadata Truncation:** Subject stored in R2 `customMetadata` is now capped at 500 characters to stay within Cloudflare's 2 KB per-object metadata limit.
|
||||
- **Worker - R2 Object TTL:** The cron sweep now expires and deletes `temp_cache/` objects older than 7 days to prevent unbounded accumulation during persistent misconfigurations.
|
||||
- **Inbound - Multi-Domain Header Requirement:** When multiple domains are configured, an inbound webhook without the `X-DockFlare-Domain` header now returns 400 instead of silently using a non-deterministic domain config.
|
||||
|
||||
## [v3.1.0] - 2026-04-16
|
||||
|
||||
> **Cloudflare Context:** Cloudflare's Email Service entered public beta today — the same `send_email` Workers binding that powers DockFlare Mail's outbound relay is now generally available. Read the announcement: [Email for Agents](https://blog.cloudflare.com/email-for-agents/)
|
||||
|
|
|
|||
18
README.MD
18
README.MD
|
|
@ -14,7 +14,7 @@
|
|||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/ChrispyBacon-dev/DockFlare?style=for-the-badge" alt="Stars">
|
||||
</a>
|
||||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/releases"><img src="https://img.shields.io/badge/Release-v3.1.0-blue.svg?style=for-the-badge" alt="Release"></a>
|
||||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/releases"><img src="https://img.shields.io/badge/Release-v3.1.1-blue.svg?style=for-the-badge" alt="Release"></a>
|
||||
<a href="https://hub.docker.com/r/alplat/dockflare"><img src="https://img.shields.io/docker/pulls/alplat/dockflare?style=for-the-badge" alt="Docker Pulls"></a>
|
||||
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/Made%20with-Python-1f425f.svg?style=for-the-badge" alt="Python"></a>
|
||||
<a href="https://github.com/ChrispyBacon-dev/DockFlare/blob/main/LICENSE.MD"><img src="https://img.shields.io/badge/License-GPL--3.0-blue.svg?style=for-the-badge" alt="License"></a>
|
||||
|
|
@ -81,10 +81,14 @@ Detailed architecture guide: [https://dockflare.app/architecture](https://dockfl
|
|||
### One-Liner Install
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
The script checks prerequisites, creates `~/dockflare/`, writes a production-ready `docker-compose.yml`, and starts all services. Open `http://<your-server-ip>:5000` when it finishes and follow the setup wizard.
|
||||
The script will guide you through:
|
||||
1. Choosing an install directory (default: `~/dockflare/`).
|
||||
2. Choosing a local UI port (default: `5000`).
|
||||
3. Optionally configuring a Cloudflare Tunnel for DockFlare itself.
|
||||
4. Optionally enabling the Email profile (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
For full setup documentation, use the project docs site:
|
||||
|
||||
|
|
@ -269,6 +273,14 @@ networks:
|
|||
docker compose up -d
|
||||
```
|
||||
|
||||
The Email Suite is an optional, opt-in feature and will not start by default. To include email services, add the `email` profile:
|
||||
|
||||
```bash
|
||||
docker compose --profile email up -d
|
||||
```
|
||||
|
||||
Email setup and provisioning guide: [Email Overview](https://github.com/ChrispyBacon-dev/DockFlare/blob/stable/dockflare/app/templates/docs/en/Email-Overview.md)
|
||||
|
||||
3. Open `http://your-server-ip:5000` and complete the setup wizard.
|
||||
|
||||
If you are migrating from older environment-based setups, DockFlare can import existing values during onboarding.
|
||||
|
|
|
|||
|
|
@ -45,9 +45,11 @@ export default {
|
|||
const allowedRecipients = JSON.parse(env.ALLOWED_RECIPIENTS || '[]');
|
||||
if (!allowedRecipients.includes(message.to)) {
|
||||
let aliasRecord = null;
|
||||
try {
|
||||
aliasRecord = await env.QUOTA_KV.get('alias::' + message.to, 'json');
|
||||
} catch (_) {}
|
||||
if (typeof env.QUOTA_KV !== 'undefined') {
|
||||
try {
|
||||
aliasRecord = await env.QUOTA_KV.get('alias::' + message.to, 'json');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (!aliasRecord) {
|
||||
message.setReject("Recipient not allowed");
|
||||
|
|
@ -81,7 +83,7 @@ export default {
|
|||
to: message.to,
|
||||
resolved_mailbox: resolvedMailbox || message.to,
|
||||
via_alias: resolvedMailbox ? "1" : "0",
|
||||
subject: message.headers.get("subject") || "",
|
||||
subject: (message.headers.get("subject") || "").slice(0, 500),
|
||||
receivedAt: receivedAt
|
||||
}
|
||||
});
|
||||
|
|
@ -121,6 +123,8 @@ export default {
|
|||
async scheduled(event, env, ctx) {
|
||||
console.log("Cron: scanning R2 temp_cache for buffered emails...");
|
||||
|
||||
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
const now = Date.now();
|
||||
let cursor;
|
||||
let processed = 0;
|
||||
let failed = 0;
|
||||
|
|
@ -129,11 +133,19 @@ export default {
|
|||
const list = await env.EMAIL_BUCKET.list({
|
||||
prefix: "temp_cache/",
|
||||
limit: 100,
|
||||
cursor: cursor
|
||||
cursor: cursor,
|
||||
include: ['customMetadata']
|
||||
});
|
||||
|
||||
for (const object of list.objects) {
|
||||
const r2Key = object.key;
|
||||
|
||||
if (object.uploaded && (now - object.uploaded.getTime()) > MAX_AGE_MS) {
|
||||
console.warn(`Cron: expiring ${r2Key} (age > 7d)`);
|
||||
try { await env.EMAIL_BUCKET.delete(r2Key); } catch (_) {}
|
||||
failed++;
|
||||
continue;
|
||||
}
|
||||
const meta = object.customMetadata || {};
|
||||
const messageId = r2Key.replace("temp_cache/", "").replace(".eml", "");
|
||||
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ Die Aaleitig zeigt dr schnäuschti Wäg, wie du DockFlare mit em ghärtete Socke
|
|||
|
||||
## Option A — Eizeiler-Installation (Empfohle)
|
||||
|
||||
Dr schnäuschti Wäg, DockFlare zum Laufe z bringe, isch dr Installations-Skript uf [dockflare.app](https://dockflare.app):
|
||||
Dr schnäuschti Wäg, DockFlare zum Laufe z bringe, isch dr interaktivi Installations-Skript uf [dockflare.app](https://dockflare.app):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
Dr Skript macht Folgendes:
|
||||
1. Prüeft, ob Docker u Docker Compose verfüegbar si.
|
||||
2. Erstellt `~/dockflare/` u schrybt dort e `docker-compose.yml` ane.
|
||||
3. Erstellt s Docker-Netzwerk `cloudflare-net`, falls's no nid existiert.
|
||||
4. Ladet d Images abe u startet alli Dienscht.
|
||||
5. Zeigt d lokali URL aa, wenn's fertig isch.
|
||||
Dr Skript füehrt di dür folgende Schritt:
|
||||
1. Uswahl vom Installationsverzeuchnis (Standard: `~/dockflare/`).
|
||||
2. Uswahl vom lokale UI-Port (Standard: `5000`).
|
||||
3. Optionali Konfiguration vomene Cloudflare-Tunnel für DockFlare.
|
||||
4. Optionali Aktivierig vom E-Mail-Profil (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
Dernoo schrybt er d `docker-compose.yml`, lot di se aaluege u fragt vor em Starte nochmal nach.
|
||||
|
||||
Sobald alles lauft, mach `http://<your-server-ip>:5000` uf u füehrt di dr Iirichtigsassistent dür.
|
||||
|
||||
> **Optionali Yberschribige** — setz Umgebigsvariable vor em Pipe, zum d Installation aapasse:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Option B — Manuälli Docker-Compose-Irichtig
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ Diese Anleitung zeigt den schnellsten Weg, um DockFlare mit dem gehärteten Sock
|
|||
|
||||
## Option A — Einzeiliges Installations-Skript (Empfohlen)
|
||||
|
||||
Der schnellste Weg, DockFlare zum Laufen zu bringen, ist das Installations-Skript unter [dockflare.app](https://dockflare.app):
|
||||
Der schnellste Weg, DockFlare zum Laufen zu bringen, ist das interaktive Installations-Skript unter [dockflare.app](https://dockflare.app):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
Das Skript führt folgende Schritte durch:
|
||||
1. Prüft, ob Docker und Docker Compose verfügbar sind.
|
||||
2. Erstellt `~/dockflare/` und schreibt dort eine `docker-compose.yml`.
|
||||
3. Erstellt das Docker-Netzwerk `cloudflare-net`, falls es noch nicht existiert.
|
||||
4. Lädt die Images herunter und startet alle Dienste.
|
||||
5. Gibt die lokale URL aus, wenn fertig.
|
||||
Das Skript führt Sie durch folgende Schritte:
|
||||
1. Auswahl des Installationsverzeichnisses (Standard: `~/dockflare/`).
|
||||
2. Auswahl des lokalen UI-Ports (Standard: `5000`).
|
||||
3. Optional: Konfiguration eines Cloudflare-Tunnels für DockFlare selbst.
|
||||
4. Optional: Aktivierung des E-Mail-Profils (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
Anschließend schreibt es die `docker-compose.yml`, ermöglicht eine Überprüfung und fragt vor dem Starten nach.
|
||||
|
||||
Nach dem Start öffnen Sie `http://<your-server-ip>:5000` und schließen Sie den Einrichtungsassistenten ab.
|
||||
|
||||
> **Optionale Überschreibungen** — Setzen Sie Umgebungsvariablen vor dem Ausführen, um die Installation anzupassen:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Option B — Manuelle Docker-Compose-Einrichtung
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ This guide walks through the fastest way to run DockFlare with the hardened sock
|
|||
|
||||
## Option A — One-Liner Install (Recommended)
|
||||
|
||||
The quickest way to get DockFlare running is the install script hosted at [dockflare.app](https://dockflare.app):
|
||||
The quickest way to get DockFlare running is the interactive install script hosted at [dockflare.app](https://dockflare.app):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. Check that Docker and Docker Compose are available.
|
||||
2. Create `~/dockflare/` and write a `docker-compose.yml` there.
|
||||
3. Create the `cloudflare-net` Docker network if it does not exist.
|
||||
4. Pull the images and start all services.
|
||||
5. Print the local URL when done.
|
||||
The script will guide you through:
|
||||
1. Choosing an install directory (default: `~/dockflare/`).
|
||||
2. Choosing a local UI port (default: `5000`).
|
||||
3. Optionally configuring a Cloudflare Tunnel for DockFlare itself.
|
||||
4. Optionally enabling the Email profile (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
It then writes `docker-compose.yml`, lets you review it, and asks before pulling and starting the stack.
|
||||
|
||||
Once running, open `http://<your-server-ip>:5000` and complete the setup wizard.
|
||||
|
||||
> **Optional overrides** — set environment variables before piping to control the install:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Option B — Manual Docker Compose
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ Esta guía explica la forma más rápida de ejecutar DockFlare con el socket pro
|
|||
|
||||
## Opción A — Instalación en un solo comando (Recomendado)
|
||||
|
||||
La forma más rápida de poner en marcha DockFlare es el script de instalación alojado en [dockflare.app](https://dockflare.app):
|
||||
La forma más rápida de poner en marcha DockFlare es el script de instalación interactivo alojado en [dockflare.app](https://dockflare.app):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
El script realizará lo siguiente:
|
||||
1. Comprobar que Docker y Docker Compose están disponibles.
|
||||
2. Crear `~/dockflare/` y escribir un archivo `docker-compose.yml` allí.
|
||||
3. Crear la red Docker `cloudflare-net` si no existe.
|
||||
4. Descargar las imágenes e iniciar todos los servicios.
|
||||
5. Mostrar la URL local al finalizar.
|
||||
El script le guiará a través de:
|
||||
1. Elección del directorio de instalación (predeterminado: `~/dockflare/`).
|
||||
2. Elección del puerto local de la interfaz (predeterminado: `5000`).
|
||||
3. Configuración opcional de un túnel de Cloudflare para DockFlare.
|
||||
4. Activación opcional del perfil de correo electrónico (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
A continuación, escribe el archivo `docker-compose.yml`, permite revisarlo y pregunta antes de iniciar el stack.
|
||||
|
||||
Una vez en ejecución, abra `http://<your-server-ip>:5000` y complete el asistente de configuración.
|
||||
|
||||
> **Opciones de personalización** — establezca variables de entorno antes de ejecutar el comando para controlar la instalación:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Opción B — Docker Compose manual
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ Ce guide présente le moyen le plus rapide d'exécuter DockFlare avec un socket-
|
|||
|
||||
## Option A — Installation en une seule commande (Recommandé)
|
||||
|
||||
La façon la plus rapide de démarrer DockFlare est d'utiliser le script d'installation hébergé sur [dockflare.app](https://dockflare.app) :
|
||||
La façon la plus rapide de démarrer DockFlare est d'utiliser le script d'installation interactif hébergé sur [dockflare.app](https://dockflare.app) :
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
Le script va :
|
||||
1. Vérifier que Docker et Docker Compose sont disponibles.
|
||||
2. Créer `~/dockflare/` et y écrire un fichier `docker-compose.yml`.
|
||||
3. Créer le réseau Docker `cloudflare-net` s'il n'existe pas encore.
|
||||
4. Télécharger les images et démarrer tous les services.
|
||||
5. Afficher l'URL locale une fois terminé.
|
||||
Le script vous guidera à travers :
|
||||
1. Le choix du répertoire d'installation (par défaut : `~/dockflare/`).
|
||||
2. Le choix du port local de l'interface (par défaut : `5000`).
|
||||
3. La configuration optionnelle d'un tunnel Cloudflare pour DockFlare.
|
||||
4. L'activation optionnelle du profil e-mail (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
Il génère ensuite le fichier `docker-compose.yml`, vous permet de le vérifier et demande confirmation avant de démarrer le stack.
|
||||
|
||||
Une fois démarré, ouvrez `http://<your-server-ip>:5000` et suivez l'assistant de configuration.
|
||||
|
||||
> **Substitutions optionnelles** — définissez des variables d'environnement avant la commande pour personnaliser l'installation :
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Option B — Configuration manuelle avec Docker Compose
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ Panduan ini menjelaskan cara tercepat untuk menjalankan DockFlare dengan socket
|
|||
|
||||
## Opsi A — Instalasi Satu Perintah (Direkomendasikan)
|
||||
|
||||
Cara tercepat untuk menjalankan DockFlare adalah menggunakan skrip instalasi yang tersedia di [dockflare.app](https://dockflare.app):
|
||||
Cara tercepat untuk menjalankan DockFlare adalah menggunakan skrip instalasi interaktif yang tersedia di [dockflare.app](https://dockflare.app):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
Skrip akan melakukan hal berikut:
|
||||
1. Memeriksa bahwa Docker dan Docker Compose tersedia.
|
||||
2. Membuat `~/dockflare/` dan menulis file `docker-compose.yml` di sana.
|
||||
3. Membuat jaringan Docker `cloudflare-net` jika belum ada.
|
||||
4. Menarik image dan memulai semua layanan.
|
||||
5. Menampilkan URL lokal setelah selesai.
|
||||
Skrip akan memandu Anda melalui:
|
||||
1. Memilih direktori instalasi (default: `~/dockflare/`).
|
||||
2. Memilih port UI lokal (default: `5000`).
|
||||
3. Konfigurasi opsional tunnel Cloudflare untuk DockFlare.
|
||||
4. Mengaktifkan profil email secara opsional (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
Kemudian menghasilkan `docker-compose.yml`, memungkinkan Anda meninjaunya, dan meminta konfirmasi sebelum memulai stack.
|
||||
|
||||
Setelah berjalan, buka `http://<your-server-ip>:5000` dan selesaikan wizard pengaturan.
|
||||
|
||||
> **Opsi kustomisasi** — atur variabel lingkungan sebelum menjalankan perintah untuk mengontrol instalasi:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Opsi B — Docker Compose Manual
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ Questa guida illustra il modo più veloce per eseguire DockFlare con un `docker-
|
|||
|
||||
## Opzione A — Installazione con un solo comando (Consigliato)
|
||||
|
||||
Il modo più rapido per avviare DockFlare è lo script di installazione disponibile su [dockflare.app](https://dockflare.app):
|
||||
Il modo più rapido per avviare DockFlare è lo script di installazione interattivo disponibile su [dockflare.app](https://dockflare.app):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
Lo script eseguirà le seguenti operazioni:
|
||||
1. Verificare che Docker e Docker Compose siano disponibili.
|
||||
2. Creare `~/dockflare/` e scrivere un file `docker-compose.yml` al suo interno.
|
||||
3. Creare la rete Docker `cloudflare-net` se non esiste ancora.
|
||||
4. Scaricare le immagini e avviare tutti i servizi.
|
||||
5. Stampare l'URL locale al termine.
|
||||
Lo script ti guiderà attraverso:
|
||||
1. La scelta della directory di installazione (predefinita: `~/dockflare/`).
|
||||
2. La scelta della porta locale dell'interfaccia (predefinita: `5000`).
|
||||
3. La configurazione opzionale di un tunnel Cloudflare per DockFlare.
|
||||
4. L'attivazione opzionale del profilo e-mail (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
Genera quindi il file `docker-compose.yml`, ti permette di verificarlo e chiede conferma prima di avviare lo stack.
|
||||
|
||||
Una volta avviato, apri `http://<your-server-ip>:5000` e completa la procedura guidata di configurazione.
|
||||
|
||||
> **Sostituzioni opzionali** — imposta le variabili d'ambiente prima del pipe per controllare l'installazione:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Opzione B — Docker Compose manuale
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@
|
|||
|
||||
## オプション A — ワンライナーインストール(推奨)
|
||||
|
||||
DockFlare を最短で起動するには、[dockflare.app](https://dockflare.app) でホストされているインストールスクリプトを使用します:
|
||||
DockFlare を最短で起動するには、[dockflare.app](https://dockflare.app) でホストされているインタラクティブなインストールスクリプトを使用します:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
スクリプトは以下を実行します:
|
||||
1. Docker と Docker Compose が利用可能かどうかを確認します。
|
||||
2. `~/dockflare/` を作成し、そこに `docker-compose.yml` を書き込みます。
|
||||
3. `cloudflare-net` Docker ネットワークが存在しない場合は作成します。
|
||||
4. イメージをプルしてすべてのサービスを起動します。
|
||||
5. 完了したらローカル URL を表示します。
|
||||
スクリプトは以下の手順を案内します:
|
||||
1. インストールディレクトリの選択(デフォルト:`~/dockflare/`)。
|
||||
2. ローカル UI ポートの選択(デフォルト:`5000`)。
|
||||
3. DockFlare 自身の Cloudflare トンネルの設定(オプション)。
|
||||
4. メールプロファイルの有効化(オプション)(dockflare-mail-manager + dockflare-webmail)。
|
||||
|
||||
その後、`docker-compose.yml` を生成し、確認を求めてからスタックを起動します。
|
||||
|
||||
起動後、`http://<your-server-ip>:5000` を開いてセットアップウィザードを完了してください。
|
||||
|
||||
> **オプションの上書き設定** — パイプする前に環境変数を設定してインストールをカスタマイズできます:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## オプション B — 手動 Docker Compose
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ Ten przewodnik pokazuje najszybszy sposób uruchomienia DockFlare z wzmocnionym
|
|||
|
||||
## Opcja A — Instalacja jednym poleceniem (Zalecane)
|
||||
|
||||
Najszybszym sposobem uruchomienia DockFlare jest skrypt instalacyjny dostępny na [dockflare.app](https://dockflare.app):
|
||||
Najszybszym sposobem uruchomienia DockFlare jest interaktywny skrypt instalacyjny dostępny na [dockflare.app](https://dockflare.app):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
Skrypt wykona następujące czynności:
|
||||
1. Sprawdzi, czy Docker i Docker Compose są dostępne.
|
||||
2. Utworzy katalog `~/dockflare/` i zapisze tam plik `docker-compose.yml`.
|
||||
3. Utworzy sieć Docker `cloudflare-net`, jeśli jeszcze nie istnieje.
|
||||
4. Pobierze obrazy i uruchomi wszystkie usługi.
|
||||
5. Wyświetli lokalny adres URL po zakończeniu.
|
||||
Skrypt przeprowadzi Cię przez:
|
||||
1. Wybór katalogu instalacji (domyślnie: `~/dockflare/`).
|
||||
2. Wybór lokalnego portu interfejsu (domyślnie: `5000`).
|
||||
3. Opcjonalną konfigurację tunelu Cloudflare dla DockFlare.
|
||||
4. Opcjonalne włączenie profilu e-mail (dockflare-mail-manager + dockflare-webmail).
|
||||
|
||||
Następnie generuje plik `docker-compose.yml`, umożliwia jego przegląd i pyta o potwierdzenie przed uruchomieniem stacka.
|
||||
|
||||
Po uruchomieniu otwórz `http://<your-server-ip>:5000` i ukończ kreator konfiguracji.
|
||||
|
||||
> **Opcjonalne parametry** — ustaw zmienne środowiskowe przed uruchomieniem, aby dostosować instalację:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Opcja B — Ręczna konfiguracja Docker Compose
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@
|
|||
|
||||
## 选项 A — 一键安装(推荐)
|
||||
|
||||
启动 DockFlare 最快捷的方式是使用托管在 [dockflare.app](https://dockflare.app) 的安装脚本:
|
||||
启动 DockFlare 最快捷的方式是使用托管在 [dockflare.app](https://dockflare.app) 的交互式安装脚本:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://dockflare.app/install.sh | bash
|
||||
bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
```
|
||||
|
||||
脚本将执行以下操作:
|
||||
1. 检查 Docker 和 Docker Compose 是否可用。
|
||||
2. 创建 `~/dockflare/` 目录并在其中写入 `docker-compose.yml`。
|
||||
3. 如果 `cloudflare-net` Docker 网络不存在则自动创建。
|
||||
4. 拉取镜像并启动所有服务。
|
||||
5. 完成后打印本地访问地址。
|
||||
脚本将引导您完成以下步骤:
|
||||
1. 选择安装目录(默认:`~/dockflare/`)。
|
||||
2. 选择本地 UI 端口(默认:`5000`)。
|
||||
3. 可选:为 DockFlare 配置 Cloudflare 隧道。
|
||||
4. 可选:启用邮件配置文件(dockflare-mail-manager + dockflare-webmail)。
|
||||
|
||||
随后生成 `docker-compose.yml`,允许您查看,并在启动前确认。
|
||||
|
||||
启动后,打开 `http://<your-server-ip>:5000` 并完成设置向导。
|
||||
|
||||
> **可选覆盖参数** — 在管道命令前设置环境变量以自定义安装行为:
|
||||
> ```bash
|
||||
> DOCKFLARE_PORT=8080 DOCKFLARE_DIR=/opt/dockflare curl -fsSL https://dockflare.app/install.sh | bash
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 选项 B — 手动 Docker Compose
|
||||
|
|
|
|||
543
install.sh
Normal file
543
install.sh
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DockFlare Master — Install Script
|
||||
# https://dockflare.app
|
||||
#
|
||||
# Usage (interactive — recommended):
|
||||
# bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
#
|
||||
#
|
||||
# Optional env overrides (non-interactive / pipe mode):
|
||||
# DOCKFLARE_PORT — host port to expose DockFlare UI on (default: 5000)
|
||||
# DOCKFLARE_DIR — install directory (default: $HOME/dockflare)
|
||||
# DOCKFLARE_UID — UID for data volume ownership (default: 65532)
|
||||
# DOCKFLARE_GID — GID for data volume ownership (default: 65532)
|
||||
# DOCKFLARE_EMAIL — set to "true" to enable email profile (default: false)
|
||||
# DOCKFLARE_TLD — base domain e.g. example.com (required if EMAIL=true)
|
||||
# DOCKFLARE_DOMAIN — DockFlare master domain (default: dockflare.$DOCKFLARE_TLD)
|
||||
#
|
||||
# Examples:
|
||||
# bash <(curl -fsSL https://dockflare.app/install.sh)
|
||||
# local run
|
||||
# bash install.sh
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
DOCKFLARE_PORT="${DOCKFLARE_PORT:-5000}"
|
||||
DOCKFLARE_DIR="${DOCKFLARE_DIR:-$HOME/dockflare}"
|
||||
DOCKFLARE_UID="${DOCKFLARE_UID:-65532}"
|
||||
DOCKFLARE_GID="${DOCKFLARE_GID:-65532}"
|
||||
DOCKFLARE_EMAIL="${DOCKFLARE_EMAIL:-false}"
|
||||
DOCKFLARE_TLD="${DOCKFLARE_TLD:-}"
|
||||
DOCKFLARE_DOMAIN="${DOCKFLARE_DOMAIN:-}"
|
||||
|
||||
BOLD="\033[1m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[0;33m"
|
||||
RED="\033[0;31m"
|
||||
CYAN="\033[0;36m"
|
||||
BLUE="\033[0;34m"
|
||||
ORANGE="\033[0;33m"
|
||||
RESET="\033[0m"
|
||||
|
||||
# Detect interactive mode (stdin is a TTY)
|
||||
if [ -t 0 ]; then
|
||||
IS_INTERACTIVE=true
|
||||
else
|
||||
IS_INTERACTIVE=false
|
||||
fi
|
||||
|
||||
banner() {
|
||||
# Split each line at position 35: DOCK (blue) | FLARE (orange)
|
||||
local lines=(
|
||||
" ██████╗ ██████╗ ██████╗██╗ ██╗███████╗██╗ █████╗ ██████╗ ███████╗"
|
||||
" ██╔══██╗██╔═══██╗██╔════╝██║ ██╔╝██╔════╝██║ ██╔══██╗██╔══██╗██╔════╝"
|
||||
" ██║ ██║██║ ██║██║ █████╔╝ █████╗ ██║ ███████║██████╔╝█████╗ "
|
||||
" ██║ ██║██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ ██╔══██║██╔══██╗██╔══╝ "
|
||||
" ██████╔╝╚██████╔╝╚██████╗██║ ██╗██║ ███████╗██║ ██║██║ ██║███████╗"
|
||||
" ╚═════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝"
|
||||
)
|
||||
echo ""
|
||||
for line in "${lines[@]}"; do
|
||||
echo -e "${BLUE}${BOLD}${line:0:35}${ORANGE}${line:35}${RESET}"
|
||||
done
|
||||
echo ""
|
||||
echo -e " ${BOLD}Master Installer${RESET} · https://dockflare.app"
|
||||
echo ""
|
||||
}
|
||||
|
||||
info() { echo -e " ${GREEN}✔${RESET} $*"; }
|
||||
warn() { echo -e " ${YELLOW}⚠${RESET} $*"; }
|
||||
error() { echo -e " ${RED}✖${RESET} $*" >&2; }
|
||||
section() { echo ""; echo -e " ${BOLD}$*${RESET}"; echo " $(printf '─%.0s' {1..60})"; }
|
||||
|
||||
# prompt <var_name> <label> <default>
|
||||
# Reads user input; falls back to default if empty or non-interactive.
|
||||
prompt() {
|
||||
local var_name="$1"
|
||||
local label="$2"
|
||||
local default="$3"
|
||||
local input=""
|
||||
|
||||
if [ "$IS_INTERACTIVE" = true ]; then
|
||||
echo -e "\n ${CYAN}?${RESET} ${label}"
|
||||
printf " [default: %s]: " "$default"
|
||||
read -r input </dev/tty
|
||||
fi
|
||||
|
||||
[ -z "$input" ] && input="$default"
|
||||
printf -v "$var_name" '%s' "$input"
|
||||
}
|
||||
|
||||
# prompt_yn <var_name> <label> <default: y|n>
|
||||
# Sets named variable to "true" or "false".
|
||||
prompt_yn() {
|
||||
local var_name="$1"
|
||||
local label="$2"
|
||||
local default="${3:-n}"
|
||||
local hint input=""
|
||||
|
||||
[ "$default" = "y" ] && hint="Y/n" || hint="y/N"
|
||||
|
||||
if [ "$IS_INTERACTIVE" = true ]; then
|
||||
echo -e "\n ${CYAN}?${RESET} ${label}"
|
||||
printf " [%s]: " "$hint"
|
||||
read -r input </dev/tty
|
||||
fi
|
||||
|
||||
[ -z "$input" ] && input="$default"
|
||||
|
||||
case "$input" in
|
||||
[Yy]*) printf -v "$var_name" '%s' "true" ;;
|
||||
*) printf -v "$var_name" '%s' "false" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
banner
|
||||
|
||||
# Stash original defaults so "start over" can reset to them
|
||||
DOCKFLARE_DIR_ORIG="$DOCKFLARE_DIR"
|
||||
DOCKFLARE_PORT_ORIG="$DOCKFLARE_PORT"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Interactive configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
while true; do
|
||||
|
||||
if [ "$IS_INTERACTIVE" = true ]; then
|
||||
section "Configuration"
|
||||
echo -e " Press ${BOLD}Enter${RESET} to accept each default."
|
||||
else
|
||||
section "Configuration (non-interactive — using defaults / env overrides)"
|
||||
fi
|
||||
|
||||
# 1. Install directory
|
||||
prompt DOCKFLARE_DIR "Where should DockFlare be installed?" "$DOCKFLARE_DIR"
|
||||
|
||||
# 2. Port
|
||||
prompt DOCKFLARE_PORT "Local UI port — DockFlare will be reachable at http://localhost:${DOCKFLARE_PORT} (change if that port is already in use)" "$DOCKFLARE_PORT"
|
||||
|
||||
# 3. Email features
|
||||
EMAIL_ENABLED=false
|
||||
if [ "$DOCKFLARE_EMAIL" = "true" ]; then
|
||||
EMAIL_ENABLED=true
|
||||
else
|
||||
prompt_yn EMAIL_ENABLED "Enable DockFlare Email features? (dockflare-mail-manager + dockflare-webmail)" "n"
|
||||
fi
|
||||
|
||||
if [ "$EMAIL_ENABLED" = "true" ]; then
|
||||
# 3a. Base domain
|
||||
prompt DOCKFLARE_TLD "Enter your base domain (e.g. example.com):" "${DOCKFLARE_TLD:-}"
|
||||
|
||||
if [ -z "$DOCKFLARE_TLD" ]; then
|
||||
error "A base domain is required when Email features are enabled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3b. DockFlare master domain
|
||||
_default_domain="dockflare.${DOCKFLARE_TLD}"
|
||||
prompt DOCKFLARE_DOMAIN "DockFlare master domain?" "${DOCKFLARE_DOMAIN:-$_default_domain}"
|
||||
|
||||
# 3c. Webmail domain
|
||||
_default_mail="mail.${DOCKFLARE_TLD}"
|
||||
prompt MAIL_DOMAIN "Webmail domain?" "${_default_mail}"
|
||||
|
||||
echo ""
|
||||
info "Base domain: ${DOCKFLARE_TLD}"
|
||||
info "DockFlare domain: ${DOCKFLARE_DOMAIN}"
|
||||
info "Webmail domain: ${MAIL_DOMAIN}"
|
||||
else
|
||||
# 4. Cloudflare Tunnel self-expose for DockFlare (email disabled path)
|
||||
TUNNEL_ENABLED=false
|
||||
prompt_yn TUNNEL_ENABLED "Expose DockFlare via a Cloudflare Tunnel? (sets up dockflare labels)" "n"
|
||||
|
||||
if [ "$TUNNEL_ENABLED" = "true" ]; then
|
||||
prompt DOCKFLARE_DOMAIN "DockFlare tunnel domain? (e.g. dockflare.example.com)" "${DOCKFLARE_DOMAIN:-}"
|
||||
|
||||
if [ -z "$DOCKFLARE_DOMAIN" ]; then
|
||||
error "A domain is required to set up the Cloudflare Tunnel label."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Placeholders kept in compose so the user can manually fill them in later
|
||||
DOCKFLARE_DOMAIN="dockflare.TLD"
|
||||
fi
|
||||
|
||||
MAIL_DOMAIN="mail.dockflare.TLD"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
info "Install directory: ${DOCKFLARE_DIR}"
|
||||
info "UI port: ${DOCKFLARE_PORT}"
|
||||
info "Email profile: ${EMAIL_ENABLED}"
|
||||
if [ "$EMAIL_ENABLED" = "false" ]; then
|
||||
if [ "${TUNNEL_ENABLED:-false}" = "true" ]; then
|
||||
info "Tunnel domain: ${DOCKFLARE_DOMAIN}"
|
||||
else
|
||||
info "Cloudflare Tunnel: disabled (labels left as comments)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary confirmation — skip loop in non-interactive mode
|
||||
CONFIG_OK=true
|
||||
prompt_yn CONFIG_OK "Does this look correct? (No = start over)" "y"
|
||||
[ "$CONFIG_OK" = "true" ] && break
|
||||
|
||||
echo ""
|
||||
warn "Starting over — your answers have been cleared."
|
||||
DOCKFLARE_DIR="${DOCKFLARE_DIR_ORIG}"
|
||||
DOCKFLARE_PORT="${DOCKFLARE_PORT_ORIG}"
|
||||
DOCKFLARE_TLD=""
|
||||
DOCKFLARE_DOMAIN=""
|
||||
|
||||
done
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Preflight checks
|
||||
# -----------------------------------------------------------------------------
|
||||
section "Checking prerequisites"
|
||||
|
||||
if ! command -v docker &>/dev/null; then
|
||||
error "Docker is not installed. Please install Docker first: https://docs.docker.com/get-docker/"
|
||||
exit 1
|
||||
fi
|
||||
info "Docker found: $(docker --version)"
|
||||
|
||||
COMPOSE_CMD=""
|
||||
if docker compose version &>/dev/null 2>&1; then
|
||||
COMPOSE_CMD="docker compose"
|
||||
elif command -v docker-compose &>/dev/null; then
|
||||
COMPOSE_CMD="docker-compose"
|
||||
else
|
||||
error "Docker Compose (v2) is not available. Please install it: https://docs.docker.com/compose/install/"
|
||||
exit 1
|
||||
fi
|
||||
info "Docker Compose found: $($COMPOSE_CMD version --short 2>/dev/null || $COMPOSE_CMD version)"
|
||||
|
||||
if ! docker info &>/dev/null; then
|
||||
error "Docker daemon is not running or current user lacks permissions."
|
||||
error "Try: sudo usermod -aG docker \$USER then log out and back in."
|
||||
exit 1
|
||||
fi
|
||||
info "Docker daemon is running"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Install directory
|
||||
# -----------------------------------------------------------------------------
|
||||
section "Setting up install directory"
|
||||
|
||||
if [ -d "$DOCKFLARE_DIR" ]; then
|
||||
info "Directory exists: $DOCKFLARE_DIR"
|
||||
if [ -f "$DOCKFLARE_DIR/docker-compose.yml" ]; then
|
||||
warn "Existing docker-compose.yml found at ${DOCKFLARE_DIR}/docker-compose.yml"
|
||||
echo ""
|
||||
echo -e " How would you like to proceed?"
|
||||
echo -e " ${BOLD} 1)${RESET} Overwrite the existing file"
|
||||
echo -e " ${BOLD} 2)${RESET} Rename it (timestamped backup) and continue"
|
||||
echo -e " ${BOLD} 3)${RESET} Abort — exit so you can review the file manually"
|
||||
echo ""
|
||||
|
||||
COMPOSE_ACTION=""
|
||||
if [ "$IS_INTERACTIVE" = true ]; then
|
||||
printf " [1/2/3, default: 1]: "
|
||||
read -r COMPOSE_ACTION </dev/tty
|
||||
fi
|
||||
[ -z "$COMPOSE_ACTION" ] && COMPOSE_ACTION="1"
|
||||
|
||||
case "$COMPOSE_ACTION" in
|
||||
2)
|
||||
_backup="${DOCKFLARE_DIR}/docker-compose.yml.$(date +%Y-%m-%d_%H%M%S)"
|
||||
mv "$DOCKFLARE_DIR/docker-compose.yml" "$_backup"
|
||||
info "Existing file renamed to: $_backup"
|
||||
;;
|
||||
3)
|
||||
echo ""
|
||||
echo -e " ${YELLOW}Aborted.${RESET} No changes made."
|
||||
echo -e " Review your file at: ${DOCKFLARE_DIR}/docker-compose.yml"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
info "Existing file will be overwritten."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
mkdir -p "$DOCKFLARE_DIR"
|
||||
info "Created directory: $DOCKFLARE_DIR"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Docker network
|
||||
# -----------------------------------------------------------------------------
|
||||
section "Preparing Docker network"
|
||||
|
||||
if docker network inspect cloudflare-net &>/dev/null 2>&1; then
|
||||
info "Network 'cloudflare-net' already exists"
|
||||
else
|
||||
docker network create cloudflare-net
|
||||
info "Created network: cloudflare-net"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build compose fragments based on configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Self-expose labels block for the dockflare service.
|
||||
# The entire labels: key is included or fully commented out to avoid YAML null-mapping errors.
|
||||
# - Email enabled OR tunnel enabled → labels: uncommented with real domain
|
||||
# - Neither → entire block commented out (safe placeholder for later)
|
||||
if [ "$EMAIL_ENABLED" = "true" ] || [ "${TUNNEL_ENABLED:-false}" = "true" ]; then
|
||||
LABELS_BLOCK="\
|
||||
labels:
|
||||
- dockflare.enable=true
|
||||
- dockflare.hostname=${DOCKFLARE_DOMAIN}
|
||||
- dockflare.service=http://dockflare:5000
|
||||
#- dockflare.access.group=YOUR-ACCESS-GROUP-ID"
|
||||
else
|
||||
LABELS_BLOCK="\
|
||||
# -- Cloudflare Tunnel labels (via DockFlare) OPTIONAL --
|
||||
# Uncomment and replace dockflare.TLD with your domain to expose DockFlare via its own tunnel:
|
||||
#labels:
|
||||
# - dockflare.enable=true
|
||||
# - dockflare.hostname=dockflare.TLD
|
||||
# - dockflare.service=http://dockflare:5000
|
||||
# - dockflare.access.group=YOUR-ACCESS-GROUP-ID"
|
||||
fi
|
||||
|
||||
# Webmail label comment — only needed when placeholders are still in place
|
||||
if [ "$EMAIL_ENABLED" = "true" ]; then
|
||||
WEBMAIL_LABEL_NOTE=""
|
||||
else
|
||||
WEBMAIL_LABEL_NOTE=" # Replace dockflare.TLD / mail.dockflare.TLD with your actual domain"$'\n'
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Write docker-compose.yml
|
||||
# -----------------------------------------------------------------------------
|
||||
section "Writing docker-compose.yml"
|
||||
|
||||
cat > "$DOCKFLARE_DIR/docker-compose.yml" <<COMPOSE
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
|
||||
docker-socket-proxy:
|
||||
image: tecnativa/docker-socket-proxy:v0.4.1
|
||||
container_name: docker-socket-proxy
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
driver: "none"
|
||||
environment:
|
||||
- DOCKER_HOST=unix:///var/run/docker.sock
|
||||
- CONTAINERS=1
|
||||
- EVENTS=1
|
||||
- NETWORKS=1
|
||||
- IMAGES=1
|
||||
- POST=1
|
||||
- PING=1
|
||||
- INFO=1
|
||||
- EXEC=1
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- dockflare-internal
|
||||
|
||||
dockflare-init:
|
||||
image: alpine:3.20
|
||||
command: ["sh", "-c", "chown -R ${DOCKFLARE_UID}:${DOCKFLARE_GID} /app/data"]
|
||||
volumes:
|
||||
- dockflare_data:/app/data
|
||||
networks:
|
||||
- dockflare-internal
|
||||
restart: "no"
|
||||
|
||||
dockflare:
|
||||
image: alplat/dockflare:stable
|
||||
container_name: dockflare
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${DOCKFLARE_PORT}:5000" # Optional: comment out once exposed via Cloudflare Tunnel with an Access Policy to restrict access to tunnel-only
|
||||
${LABELS_BLOCK}
|
||||
volumes:
|
||||
- dockflare_data:/app/data
|
||||
environment:
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- REDIS_DB_INDEX=0
|
||||
- DOCKER_HOST=tcp://docker-socket-proxy:2375
|
||||
#- LOG_LEVEL=DEBUG
|
||||
depends_on:
|
||||
docker-socket-proxy:
|
||||
condition: service_started
|
||||
dockflare-init:
|
||||
condition: service_completed_successfully
|
||||
redis:
|
||||
condition: service_started
|
||||
networks:
|
||||
- cloudflare-net
|
||||
- dockflare-internal
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: dockflare-redis
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--save", "", "--appendonly", "no"]
|
||||
logging:
|
||||
driver: "none"
|
||||
volumes:
|
||||
- dockflare_redis:/data
|
||||
networks:
|
||||
- dockflare-internal
|
||||
|
||||
dockflare-mail-manager:
|
||||
image: alplat/dockflare-mail-manager:stable
|
||||
container_name: dockflare-mail-manager
|
||||
restart: unless-stopped
|
||||
profiles: ["email"]
|
||||
environment:
|
||||
- DOCKFLARE_MASTER_URL=http://dockflare:5000
|
||||
- MAIL_DATA_PATH=/data
|
||||
volumes:
|
||||
- mail_data:/data
|
||||
depends_on:
|
||||
dockflare:
|
||||
condition: service_started
|
||||
networks:
|
||||
- cloudflare-net
|
||||
- dockflare-internal
|
||||
|
||||
dockflare-webmail:
|
||||
image: alplat/dockflare-webmail:stable
|
||||
container_name: dockflare-webmail
|
||||
restart: unless-stopped
|
||||
profiles: ["email"]
|
||||
environment:
|
||||
- DOCKFLARE_MASTER_URL=https://${DOCKFLARE_DOMAIN}
|
||||
labels:
|
||||
${WEBMAIL_LABEL_NOTE} - dockflare.enable=true
|
||||
- dockflare.hostname=${MAIL_DOMAIN}
|
||||
- dockflare.service=http://dockflare-webmail:80
|
||||
depends_on:
|
||||
dockflare-mail-manager:
|
||||
condition: service_started
|
||||
networks:
|
||||
- cloudflare-net
|
||||
- dockflare-internal
|
||||
|
||||
volumes:
|
||||
dockflare_data:
|
||||
dockflare_redis:
|
||||
mail_data:
|
||||
|
||||
networks:
|
||||
cloudflare-net:
|
||||
name: cloudflare-net
|
||||
external: true
|
||||
dockflare-internal:
|
||||
name: dockflare-internal
|
||||
COMPOSE
|
||||
|
||||
info "docker-compose.yml written to $DOCKFLARE_DIR"
|
||||
echo ""
|
||||
echo -e " ${CYAN}Review:${RESET} ${DOCKFLARE_DIR}/docker-compose.yml"
|
||||
|
||||
SHOW_COMPOSE=false
|
||||
prompt_yn SHOW_COMPOSE "Show the generated docker-compose.yml?" "n"
|
||||
if [ "$SHOW_COMPOSE" = "true" ]; then
|
||||
echo ""
|
||||
echo -e " ${BOLD}$(printf '─%.0s' {1..60})${RESET}"
|
||||
cat "$DOCKFLARE_DIR/docker-compose.yml"
|
||||
echo -e " ${BOLD}$(printf '─%.0s' {1..60})${RESET}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Pull images & start
|
||||
# -----------------------------------------------------------------------------
|
||||
PROFILE_FLAGS=""
|
||||
if [ "$EMAIL_ENABLED" = "true" ]; then
|
||||
PROFILE_FLAGS="--profile email"
|
||||
fi
|
||||
|
||||
START_NOW=true
|
||||
prompt_yn START_NOW "Pull images and start DockFlare now?" "y"
|
||||
|
||||
if [ "$START_NOW" = "false" ]; then
|
||||
echo ""
|
||||
echo -e " ${YELLOW}Skipped. To start manually:${RESET}"
|
||||
if [ "$EMAIL_ENABLED" = "true" ]; then
|
||||
echo -e " cd ${DOCKFLARE_DIR} && ${COMPOSE_CMD} --profile email up -d"
|
||||
else
|
||||
echo -e " cd ${DOCKFLARE_DIR} && ${COMPOSE_CMD} up -d"
|
||||
fi
|
||||
echo ""
|
||||
echo -e " ${YELLOW}${BOLD}Security recommendation:${RESET}"
|
||||
echo -e " Once DockFlare is fully configured, it is recommended to remove the exposed port"
|
||||
echo -e " and restrict access exclusively via a Cloudflare Tunnel with an Access Policy."
|
||||
echo -e " See the ${BOLD}ports:${RESET} comment in ${DOCKFLARE_DIR}/docker-compose.yml for details."
|
||||
echo ""
|
||||
echo -e " ${CYAN}Docs:${RESET} https://dockflare.app/docs"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
section "Pulling images"
|
||||
$COMPOSE_CMD -f "$DOCKFLARE_DIR/docker-compose.yml" $PROFILE_FLAGS pull
|
||||
|
||||
section "Starting DockFlare"
|
||||
$COMPOSE_CMD -f "$DOCKFLARE_DIR/docker-compose.yml" $PROFILE_FLAGS up -d
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Done
|
||||
# -----------------------------------------------------------------------------
|
||||
LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
|
||||
|
||||
echo ""
|
||||
echo -e " ${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
||||
echo -e " ${GREEN}${BOLD} DockFlare is running!${RESET}"
|
||||
echo -e " ${GREEN}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
||||
echo ""
|
||||
echo -e " ${BOLD}Local access:${RESET} http://localhost:${DOCKFLARE_PORT}"
|
||||
echo -e " ${BOLD}Network access:${RESET} http://${LOCAL_IP}:${DOCKFLARE_PORT}"
|
||||
echo -e " ${BOLD}Install path:${RESET} ${DOCKFLARE_DIR}"
|
||||
echo ""
|
||||
echo -e " ${YELLOW}${BOLD}Next steps:${RESET}"
|
||||
echo -e " ${YELLOW}1.${RESET} Open the URL above and complete the setup wizard"
|
||||
echo -e " ${YELLOW}2.${RESET} Add your Cloudflare API credentials"
|
||||
echo -e " ${YELLOW}3.${RESET} DockFlare will start managing your tunnels automatically"
|
||||
if [ "$EMAIL_ENABLED" = "true" ]; then
|
||||
echo ""
|
||||
echo -e " ${YELLOW}${BOLD}Email profile:${RESET}"
|
||||
echo -e " ${YELLOW}4.${RESET} DockFlare will be reachable at https://${DOCKFLARE_DOMAIN}"
|
||||
echo -e " ${YELLOW}5.${RESET} Webmail will be reachable at https://${MAIL_DOMAIN}"
|
||||
echo -e " ${YELLOW}6.${RESET} Restart if needed: cd ${DOCKFLARE_DIR} && docker compose --profile email up -d"
|
||||
fi
|
||||
echo ""
|
||||
echo -e " ${YELLOW}${BOLD}Security recommendation:${RESET}"
|
||||
echo -e " Once DockFlare is fully configured, it is recommended to remove the exposed local port"
|
||||
echo -e " and restrict access exclusively via a Cloudflare Tunnel with an CloudFlare Zero Trust Access Policy."
|
||||
echo -e " See the ${BOLD}ports:${RESET} comment in ${DOCKFLARE_DIR}/docker-compose.yml for details."
|
||||
echo ""
|
||||
echo -e " ${CYAN}Docs:${RESET} https://dockflare.app/docs"
|
||||
echo ""
|
||||
|
|
@ -184,10 +184,14 @@ def inbound():
|
|||
return jsonify({"error": "unknown domain"}), 401
|
||||
secret = domain_cfg['webhook_secret']
|
||||
else:
|
||||
cur = get_db().execute("SELECT webhook_secret FROM domain_configs LIMIT 1")
|
||||
row = cur.fetchone()
|
||||
secret = row['webhook_secret'] if row else config.WEBHOOK_SECRET
|
||||
domain_cfg = None
|
||||
db = get_db()
|
||||
count = db.execute("SELECT COUNT(*) FROM domain_configs").fetchone()[0]
|
||||
if count > 1:
|
||||
log.warning("Inbound webhook: X-DockFlare-Domain header missing with %d domains configured", count)
|
||||
return jsonify({"error": "domain header required"}), 400
|
||||
row = db.execute("SELECT * FROM domain_configs LIMIT 1").fetchone()
|
||||
domain_cfg = row if row else None
|
||||
secret = domain_cfg['webhook_secret'] if domain_cfg else config.WEBHOOK_SECRET
|
||||
|
||||
if not _verify_signature(request, secret):
|
||||
return jsonify({"error": "invalid signature"}), 401
|
||||
|
|
@ -257,6 +261,12 @@ def inbound():
|
|||
)
|
||||
break
|
||||
|
||||
if not to_address:
|
||||
for addr in parsed.get('delivered_to_addresses', []):
|
||||
if db.execute("SELECT 1 FROM mailboxes WHERE address=?", (addr,)).fetchone():
|
||||
to_address = addr
|
||||
break
|
||||
|
||||
if not to_address and domain_cfg and domain_cfg['catch_all_mailbox']:
|
||||
catch_all = domain_cfg['catch_all_mailbox']
|
||||
if db.execute("SELECT 1 FROM mailboxes WHERE address=?", (catch_all,)).fetchone():
|
||||
|
|
@ -307,10 +317,21 @@ def inbound():
|
|||
))
|
||||
msg_id = cur.lastrowid
|
||||
|
||||
used_filenames = set()
|
||||
for att in parsed['attachments']:
|
||||
att_dir = os.path.join(config.ATTACHMENTS_PATH, str(msg_id))
|
||||
os.makedirs(att_dir, exist_ok=True)
|
||||
safe_filename = att['filename'].replace('/', '_').replace('\\', '_')
|
||||
raw_name = os.path.basename(att['filename'] or '').replace('/', '_').replace('\\', '_').strip('. ')
|
||||
safe_filename = raw_name or 'unnamed_attachment'
|
||||
if safe_filename in used_filenames:
|
||||
stem, sep, ext = safe_filename.rpartition('.')
|
||||
base = stem if sep else safe_filename
|
||||
tail = (sep + ext) if sep else ''
|
||||
counter = 2
|
||||
while f"{base}_{counter}{tail}" in used_filenames:
|
||||
counter += 1
|
||||
safe_filename = f"{base}_{counter}{tail}"
|
||||
used_filenames.add(safe_filename)
|
||||
att_path = os.path.join(att_dir, safe_filename)
|
||||
with open(att_path, 'wb') as f:
|
||||
f.write(att['data'])
|
||||
|
|
@ -441,7 +462,10 @@ def inbound():
|
|||
})
|
||||
_check_and_send_auto_reply(db, to_address, parsed, domain_cfg)
|
||||
|
||||
delete_from_r2(r2_key, domain_cfg)
|
||||
try:
|
||||
delete_from_r2(r2_key, domain_cfg)
|
||||
except Exception:
|
||||
log.warning("Inbound: R2 delete failed for %s — will clean on next cron", r2_key)
|
||||
|
||||
log.info("Inbound delivered: message=%s to=%s db_id=%s",
|
||||
msg_uuid, to_address, msg_id)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import email
|
||||
import email.policy
|
||||
import email.utils
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
import nh3
|
||||
|
||||
|
|
@ -21,12 +23,13 @@ _ALLOWED_ATTRIBUTES = {
|
|||
def parse_eml(eml_bytes):
|
||||
msg = email.message_from_bytes(eml_bytes, policy=email.policy.default)
|
||||
parsed = {
|
||||
'message_id': msg.get('Message-ID', '').strip('<>'),
|
||||
'message_id': msg.get('Message-ID', '').strip('<>') or str(uuid.uuid4()),
|
||||
'from_address': '',
|
||||
'from_name': '',
|
||||
'to_addresses': [],
|
||||
'cc_addresses': [],
|
||||
'bcc_addresses': [],
|
||||
'delivered_to_addresses': [],
|
||||
'subject': msg.get('Subject', ''),
|
||||
'date': msg.get('Date'),
|
||||
'in_reply_to': msg.get('In-Reply-To', '').strip('<>'),
|
||||
|
|
@ -54,6 +57,20 @@ def parse_eml(eml_bytes):
|
|||
addr for _, addr in pairs if addr
|
||||
]
|
||||
|
||||
for delivery_header in ['Delivered-To', 'X-Original-To', 'X-Forwarded-To', 'X-GitHub-Recipient-Address', 'destinations']:
|
||||
for val in msg.get_all(delivery_header) or []:
|
||||
_, addr = email.utils.parseaddr(str(val))
|
||||
if addr and addr not in parsed['delivered_to_addresses']:
|
||||
parsed['delivered_to_addresses'].append(addr)
|
||||
|
||||
for received in msg.get_all('Received') or []:
|
||||
m = re.search(r'\bfor\s+<([^>]+)>', str(received))
|
||||
if m:
|
||||
addr = m.group(1).strip()
|
||||
if addr and addr not in parsed['delivered_to_addresses']:
|
||||
parsed['delivered_to_addresses'].append(addr)
|
||||
break
|
||||
|
||||
try:
|
||||
if parsed['date']:
|
||||
dt = email.utils.parsedate_to_datetime(parsed['date'])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue