Stack docker-compose Roundcube 1.7.4 (nginx, php-fpm, MariaDB) avec skin vBlog.io
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sa4nUG3GtbdEaPS6fA8eq1
This commit is contained in:
21 files changed
+669
No files matched your search
@@ -0,0 +1,49 @@
|
||||
# Copier en .env puis adapter : cp .env.example .env
|
||||
# Pas de commentaire en fin de ligne : Compose peut l'intégrer à la valeur.
|
||||
|
||||
COMPOSE_PROJECT_NAME=roundcube
|
||||
|
||||
# --- Versions (images épinglées) ---
|
||||
# Tag roundcube/roundcubemail:<RC_VERSION>-fpm-alpine
|
||||
RC_VERSION=1.7.4
|
||||
# MariaDB LTS / nginx branche stable
|
||||
MARIADB_VERSION=11.8
|
||||
NGINX_VERSION=1.30-alpine
|
||||
|
||||
# --- Exposition HTTP (prévue derrière un reverse proxy TLS) ---
|
||||
# 0.0.0.0 pour écouter sur toutes les interfaces
|
||||
HTTP_BIND=127.0.0.1
|
||||
HTTP_PORT=8080
|
||||
|
||||
# --- Base de données ---
|
||||
# Générer avec : openssl rand -base64 24
|
||||
DB_NAME=roundcubemail
|
||||
DB_USER=roundcube
|
||||
DB_PASSWORD=change-me
|
||||
DB_ROOT_PASSWORD=change-me
|
||||
|
||||
# --- Clé de chiffrement Roundcube (des_key) ---
|
||||
# 24 caractères exactement ; ne plus la changer ensuite. openssl rand -base64 18
|
||||
RC_DES_KEY=change-me-24-characters!
|
||||
|
||||
# --- Serveurs de messagerie ---
|
||||
# Préfixes : ssl:// (TLS implicite, 993/465), tls:// (STARTTLS, 143/587), aucun = clair
|
||||
IMAP_HOST=ssl://mail.example.org
|
||||
IMAP_PORT=993
|
||||
SMTP_HOST=tls://mail.example.org
|
||||
SMTP_PORT=587
|
||||
# Domaine ajouté aux identifiants sans @ (ex. example.org), vide = aucun
|
||||
USERNAME_DOMAIN=
|
||||
|
||||
# --- Plugins ---
|
||||
# Plugins fournis avec Roundcube, à activer
|
||||
RC_PLUGINS=archive,zipdownload,managesieve,markasjunk
|
||||
# Paquets composer installés au démarrage (ex. johndoh/contextmenu), à activer aussi dans RC_PLUGINS
|
||||
RC_COMPOSER_PLUGINS=
|
||||
|
||||
# --- Divers ---
|
||||
# Taille max des pièces jointes : PHP (upload/post) et nginx (client_max_body_size)
|
||||
UPLOAD_MAX_FILESIZE=25M
|
||||
# Dictionnaires aspell intégrés à l'image (build), séparés par des espaces
|
||||
ASPELL_DICTS=fr en
|
||||
TZ=Europe/Paris
|
||||
@@ -0,0 +1 @@
|
||||
.env
|
||||
@@ -0,0 +1,107 @@
|
||||
# Roundcube Webmail en docker-compose
|
||||
|
||||
Stack Roundcube **1.7.4** prête à l'emploi : nginx + php-fpm + MariaDB, skin Elastic recompilé avec la charte vBlog.io (surcharges LESS dans `theme/`).
|
||||
|
||||
```
|
||||
:8080 (127.0.0.1 par défaut, derrière un reverse proxy TLS)
|
||||
│
|
||||
┌─────────────▼─────────────┐ réseau frontend (sortie IMAP/SMTP)
|
||||
│ nginx 1.30-alpine │ sert le statique depuis le volume www (ro)
|
||||
└─────────────┬─────────────┘
|
||||
│ FastCGI :9000
|
||||
┌─────────────▼─────────────┐
|
||||
│ roundcube (php-fpm 8.4) │ image locale = roundcube/roundcubemail:1.7.4-fpm-alpine
|
||||
│ │ + skin Elastic recompilé + aspell + mailcap
|
||||
└─────────────┬─────────────┘
|
||||
│ réseau backend (internal: pas d'accès sortant)
|
||||
┌─────────────▼─────────────┐
|
||||
│ mariadb 11.8 (LTS) │
|
||||
└───────────────────────────┘
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Démarrage
|
||||
|
||||
```sh
|
||||
cp .env.example .env # renseigner mots de passe, RC_DES_KEY (24 car.), IMAP/SMTP
|
||||
docker compose up -d --build
|
||||
docker compose ps # les 3 services doivent passer "healthy"
|
||||
```
|
||||
|
||||
Puis `http://127.0.0.1:8080/`. Le premier démarrage copie les sources dans le volume `www` et crée le schéma SQL (`bin/initdb.sh`), compter ~30 s.
|
||||
|
||||
## Arborescence
|
||||
|
||||
| Chemin | Rôle |
|
||||
|---|---|
|
||||
| `compose.yaml` | Les 3 services, volumes, réseaux, healthchecks |
|
||||
| `.env.example` | Toutes les variables (versions, BDD, IMAP/SMTP, plugins...) |
|
||||
| `roundcube/Dockerfile` | Build multi-étage : compilation LESS (node) puis image finale |
|
||||
| `roundcube/zz-healthcheck.conf` | `ping.path` php-fpm pour le healthcheck |
|
||||
| `nginx/templates/default.conf.template` | vhost, rendu par l'entrypoint nginx (envsubst `NGINX_*`) |
|
||||
| `config/*.php` | Config Roundcube additionnelle, montée sur `/var/roundcube/config` |
|
||||
| `theme/` | Surcharges du skin Elastic (`_variables.less`, `_styles.less`, `fonts/`, `images/`) |
|
||||
| `scripts/sync-theme.sh` | Réimporte une customisation (défaut `../roundcube-custom/vblog`) |
|
||||
|
||||
## Choix techniques
|
||||
|
||||
- **Image officielle `fpm-alpine`** ([roundcube/roundcubemail-docker](https://github.com/roundcube/roundcubemail-docker)) : son entrypoint génère `config.docker.inc.php` depuis les `ROUNDCUBEMAIL_*`, inclut `/var/roundcube/config/*.php`, initialise/migre la base. Pas de réimplémentation.
|
||||
- **Volume `www` partagé** (rw côté Roundcube, ro côté nginx) : pattern documenté upstream pour la variante FPM. À chaque démarrage, l'entrypoint resynchronise `/usr/src/roundcubemail` vers le volume (`bin/installto.sh`) : une image reconstruite (version ou thème) est donc propagée au simple `up -d`.
|
||||
- **nginx** : depuis 1.7, seul `public_html/` est le docroot et les ressources sont appelées en `/static.php/<chemin>`. Le vhost les sert directement depuis le volume (préfixes et extensions alignés sur `ALLOWED_PATHS`/`SUPPORTED_TYPES` de `static.php`, cache 30 j, les URL portent `?s=<mtime>`). Seuls `index.php` et `static.php` atteignent PHP ; `installer.php` et le reste renvoient 404.
|
||||
- **Thème compilé au build** : Elastic est livré en CSS minifié ; `_variables.less`/`_styles.less` ne sont pris en compte qu'à la compilation (`@import (optional)` dans `styles/variables.less` et `styles/styles.less`). Un étage `node:22-alpine` recompile `styles`, `print` et `embed` avec `lessc` (même commande que `skins/elastic/Makefile`). Aucune dépendance Node dans l'image finale.
|
||||
- **Dictionnaires aspell et `mailcap` intégrés au build** plutôt qu'installés à chaque démarrage (`ROUNDCUBEMAIL_ASPELL_DICTS` fait un `apk add` au runtime). `mailcap` fournit `/etc/mime.types`, absent de l'image upstream (sinon warning « Mimetype to file extension mapping doesn't work properly »).
|
||||
- **`des_key` imposée par `.env`** : sinon l'entrypoint la génère aléatoirement dans `config.inc.php`, perdue si le volume `www` est recréé.
|
||||
- **Healthchecks** : MariaDB `healthcheck.sh --connect --innodb_initialized` ; Roundcube : port FPM 9000 ; nginx : `GET /fpm-ping` relayé à php-fpm (chaîne nginx → FPM validée, URL limitée à 127.0.0.1). Ordre de démarrage piloté par `depends_on: condition: service_healthy`.
|
||||
- **Réseau `backend` en `internal: true`** : MariaDB n'a ni port publié ni accès sortant.
|
||||
|
||||
## Thème vBlog.io
|
||||
|
||||
Les fichiers de `theme/` et `config/20-vblog.inc.php` sont une copie de `roundcube-custom/vblog/`. Pour resynchroniser après modification :
|
||||
|
||||
```sh
|
||||
sh scripts/sync-theme.sh [dossier_source]
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Pour revenir à l'Elastic d'origine : vider `theme/`, supprimer `config/20-vblog.inc.php`, rebuild.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **Variables** : voir `.env.example` (commentaires sur leur propre ligne : Compose peut intégrer un commentaire de fin de ligne à la valeur).
|
||||
- **Options Roundcube** : déposer des `*.php` dans `config/` (inclus par ordre alphabétique), puis `docker compose restart roundcube`. Référence : `config/defaults.inc.php` dans le conteneur. `config/10-base.inc.php` fixe langue, `proxy_whitelist`, durée de session, rate-limit de login.
|
||||
- **Plugins** : intégrés → `RC_PLUGINS` ; tiers → `RC_COMPOSER_PLUGINS` (installés par composer au démarrage, nécessite l'accès à packagist) + ajout dans `RC_PLUGINS`.
|
||||
- **Taille des pièces jointes** : `UPLOAD_MAX_FILESIZE` applique la même limite à PHP et nginx. Le message encodé en base64 pèse ~1,37× la pièce jointe.
|
||||
|
||||
## Reverse proxy TLS
|
||||
|
||||
La stack écoute en HTTP sur `127.0.0.1:8080`. Le proxy frontal (Traefik, HAProxy, nginx...) doit transmettre `X-Forwarded-For` et `X-Forwarded-Proto: https`. Restreindre `proxy_whitelist` (`config/10-base.inc.php`) à l'IP du proxy en production. Si Roundcube est publié sous un sous-chemin, régler `ROUNDCUBEMAIL_REQUEST_PATH`.
|
||||
|
||||
## Exploitation
|
||||
|
||||
```sh
|
||||
docker compose logs -f roundcube # logs Roundcube et php-fpm (log_driver = stdout)
|
||||
|
||||
# Sauvegarde : base + volume enigma si le plugin est utilisé
|
||||
docker compose exec -T mariadb sh -c 'mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" --single-transaction "$MARIADB_DATABASE"' > roundcube-$(date +%F).sql
|
||||
```
|
||||
|
||||
**Mise à jour Roundcube** : modifier `RC_VERSION` dans `.env` puis `docker compose up -d --build`. L'entrypoint exécute `installto.sh` puis `initdb.sh --update` (migrations SQL). Sauvegarder la base avant.
|
||||
|
||||
**Proxy TLS d'entreprise pendant le build** (npm, apk) : fournir sa CA en secret BuildKit, optionnel :
|
||||
|
||||
```sh
|
||||
docker build -f roundcube/Dockerfile --secret id=ca,src=/chemin/ca.pem -t local/roundcube:1.7.4 .
|
||||
docker compose up -d --no-build
|
||||
```
|
||||
|
||||
## Validation effectuée
|
||||
|
||||
Stack construite et démarrée avec Docker 29.6 / Compose 5.3 : 3 services `healthy`, schéma SQL créé, login IMAP réussi contre un Dovecot 2.4.5 de test (utilisateur enregistré en base), charte vBlog appliquée (captures `docs/`), `installer.php` et `static.php/config/...` en 404, statique servi par nginx avec cache. Non testé dans l'environnement de validation (dépôt Alpine bloqué) : l'étape `apk add` des dictionnaires/mailcap, et l'envoi SMTP.
|
||||
|
||||
## Sources
|
||||
|
||||
- Image et entrypoint : https://github.com/roundcube/roundcubemail-docker et https://hub.docker.com/r/roundcube/roundcubemail
|
||||
- Options de config : https://github.com/roundcube/roundcubemail/blob/master/config/defaults.inc.php
|
||||
- Skin Elastic, points d'extension LESS : https://github.com/roundcube/roundcubemail/tree/master/skins/elastic
|
||||
- Image nginx (templates envsubst) : https://hub.docker.com/_/nginx ; image MariaDB (`healthcheck.sh`) : https://hub.docker.com/_/mariadb
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
name: ${COMPOSE_PROJECT_NAME:-roundcube}
|
||||
|
||||
# Roundcube Webmail : nginx (frontal) -> php-fpm (Roundcube) -> MariaDB
|
||||
# Variables : voir .env.example
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:${MARIADB_VERSION:-11.8}
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD manquant dans .env}
|
||||
MARIADB_DATABASE: ${DB_NAME:-roundcubemail}
|
||||
MARIADB_USER: ${DB_USER:-roundcube}
|
||||
MARIADB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD manquant dans .env}
|
||||
MARIADB_AUTO_UPGRADE: "1"
|
||||
volumes:
|
||||
- db:/var/lib/mysql
|
||||
networks: [backend]
|
||||
healthcheck:
|
||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
roundcube:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: roundcube/Dockerfile
|
||||
args:
|
||||
RC_VERSION: ${RC_VERSION:-1.7.4}
|
||||
ASPELL_DICTS: ${ASPELL_DICTS:-fr en}
|
||||
image: ${RC_IMAGE:-local/roundcube}:${RC_VERSION:-1.7.4}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
ROUNDCUBEMAIL_DB_TYPE: mysql
|
||||
ROUNDCUBEMAIL_DB_HOST: mariadb
|
||||
ROUNDCUBEMAIL_DB_PORT: 3306
|
||||
ROUNDCUBEMAIL_DB_NAME: ${DB_NAME:-roundcubemail}
|
||||
ROUNDCUBEMAIL_DB_USER: ${DB_USER:-roundcube}
|
||||
ROUNDCUBEMAIL_DB_PASSWORD: ${DB_PASSWORD}
|
||||
ROUNDCUBEMAIL_DES_KEY: ${RC_DES_KEY:?RC_DES_KEY manquant dans .env (24 caractères)}
|
||||
ROUNDCUBEMAIL_DEFAULT_HOST: ${IMAP_HOST:?IMAP_HOST manquant dans .env}
|
||||
ROUNDCUBEMAIL_DEFAULT_PORT: ${IMAP_PORT:-993}
|
||||
ROUNDCUBEMAIL_SMTP_SERVER: ${SMTP_HOST:?SMTP_HOST manquant dans .env}
|
||||
ROUNDCUBEMAIL_SMTP_PORT: ${SMTP_PORT:-587}
|
||||
ROUNDCUBEMAIL_USERNAME_DOMAIN: ${USERNAME_DOMAIN:-}
|
||||
ROUNDCUBEMAIL_PLUGINS: ${RC_PLUGINS:-archive,zipdownload,managesieve,markasjunk}
|
||||
ROUNDCUBEMAIL_COMPOSER_PLUGINS: ${RC_COMPOSER_PLUGINS:-}
|
||||
ROUNDCUBEMAIL_SKIN: elastic
|
||||
ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE: ${UPLOAD_MAX_FILESIZE:-25M}
|
||||
TZ: ${TZ:-Europe/Paris}
|
||||
volumes:
|
||||
- www:/var/www/html # sources Roundcube (partagées avec nginx)
|
||||
- ./config:/var/roundcube/config:ro # surcharges PHP (*.php inclus dans l'ordre alpha)
|
||||
- temp:/tmp/roundcube-temp
|
||||
- enigma:/var/roundcube/enigma # clés OpenPGP si plugin enigma
|
||||
networks: [backend, frontend] # frontend = sortie vers IMAP/SMTP
|
||||
healthcheck:
|
||||
test: ["CMD", "php", "-r", "exit(@fsockopen('127.0.0.1', 9000) ? 0 : 1);"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 6
|
||||
start_period: 120s # 1er démarrage : copie des sources + initdb
|
||||
|
||||
nginx:
|
||||
image: nginx:${NGINX_VERSION:-1.30-alpine}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
roundcube:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
NGINX_PHP_FPM: roundcube:9000
|
||||
NGINX_CLIENT_MAX_BODY_SIZE: ${UPLOAD_MAX_FILESIZE:-25M}
|
||||
NGINX_ENVSUBST_FILTER: ^NGINX_ # n'interpole que nos variables
|
||||
TZ: ${TZ:-Europe/Paris}
|
||||
volumes:
|
||||
- www:/var/www/html:ro
|
||||
- ./nginx/templates:/etc/nginx/templates:ro
|
||||
ports:
|
||||
- "${HTTP_BIND:-127.0.0.1}:${HTTP_PORT:-8080}:80"
|
||||
networks: [frontend]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/fpm-ping | grep -q pong"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
db:
|
||||
www:
|
||||
temp:
|
||||
enigma:
|
||||
|
||||
networks:
|
||||
backend:
|
||||
internal: true # MariaDB sans accès sortant ni exposition
|
||||
frontend:
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
// Surcharges Roundcube communes. Tout fichier *.php de ce dossier est inclus
|
||||
// (ordre alphabétique) après la config générée depuis les variables d'environnement.
|
||||
// Référence : config/defaults.inc.php dans l'image (/var/www/html/config/defaults.inc.php)
|
||||
|
||||
$config['language'] = 'fr_FR';
|
||||
$config['timezone'] = 'auto';
|
||||
|
||||
// Derrière un reverse proxy TLS : faire confiance aux en-têtes X-Forwarded-*
|
||||
// des réseaux Docker / du proxy (à restreindre à l'IP du proxy en production)
|
||||
$config['proxy_whitelist'] = ['172.16.0.0/12', '192.168.0.0/16', '10.0.0.0/8'];
|
||||
$config['use_https'] = false; // true si le proxy n'envoie pas X-Forwarded-Proto
|
||||
|
||||
// Session / sécurité
|
||||
$config['session_lifetime'] = 30; // minutes
|
||||
$config['login_rate_limit'] = 3; // tentatives par minute et par IP
|
||||
$config['ip_check'] = false; // true si IP client stable (casse derrière NAT/mobile)
|
||||
|
||||
// Validation des certificats IMAP/SMTP (laisser actif ; désactiver uniquement pour un lab auto-signé)
|
||||
// $config['imap_conn_options'] = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]];
|
||||
// $config['smtp_conn_options'] = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false]];
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
// À fusionner dans config/config.inc.php
|
||||
// Réf. options : config/defaults.inc.php (product_name, skin_logo, support_url)
|
||||
|
||||
$config['product_name'] = 'vLab Webmail';
|
||||
|
||||
// Chemins commençant par "/" = relatifs au dossier du skin (skins/elastic/)
|
||||
// Logo vblog.io (emblème) :
|
||||
// - version claire : logo marine/orange, pour la page de login en mode clair
|
||||
// - version blanche : pour le menu latéral (fond marine) et le mode sombre
|
||||
$config['skin_logo'] = [
|
||||
'elastic:login' => '/images/vblog-logo.svg',
|
||||
'elastic:login[dark]' => '/images/vblog-logo-white.svg',
|
||||
'elastic:*' => '/images/vblog-logo-white.svg',
|
||||
'elastic:*[dark]' => '/images/vblog-logo-white.svg',
|
||||
'elastic:*[link]' => 'https://vblog.io',
|
||||
];
|
||||
|
||||
// Lien "Aide" vers le blog (optionnel)
|
||||
// $config['support_url'] = 'https://vblog.io';
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,65 @@
|
||||
# Roundcube 1.7 : seul public_html/ est exposé (index.php, static.php).
|
||||
# Les ressources statiques sont référencées en /static.php/<chemin> ; nginx les
|
||||
# sert directement depuis le volume partagé et ne délègue à PHP que le reste.
|
||||
|
||||
upstream roundcube_fpm {
|
||||
server ${NGINX_PHP_FPM};
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
|
||||
root /var/www/html/public_html;
|
||||
index index.php;
|
||||
|
||||
client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE};
|
||||
server_tokens off;
|
||||
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "same-origin" always;
|
||||
|
||||
# Statique servi par nginx (même liste de préfixes que ALLOWED_PATHS de static.php)
|
||||
location ~ ^/static\.php/((?:skins|plugins|program/js|program/resources)/.+\.(?:css|js|map|png|gif|jpe?g|svg|ico|webp|avif|woff2?|ttf|eot|html|json))$ {
|
||||
alias /var/www/html/$1;
|
||||
expires 30d;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location = /favicon.ico {
|
||||
alias /var/www/html/skins/elastic/images/favicon.ico;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Points d'entrée PHP autorisés (installer.php volontairement exclu)
|
||||
location ~ ^/(?:index|static)\.php(?:/|$) {
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
fastcgi_pass roundcube_fpm;
|
||||
fastcgi_read_timeout 300s;
|
||||
}
|
||||
|
||||
# Healthcheck : ping du pool php-fpm, local uniquement
|
||||
location = /fpm-ping {
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
access_log off;
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_NAME /fpm-ping;
|
||||
fastcgi_param SCRIPT_FILENAME /fpm-ping;
|
||||
fastcgi_pass roundcube_fpm;
|
||||
}
|
||||
|
||||
location = / {
|
||||
rewrite ^ /index.php last;
|
||||
}
|
||||
|
||||
# Tout le reste (installer.php, fichiers cachés...) : refusé
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Image Roundcube (php-fpm) + skin Elastic recompilé avec les surcharges de theme/
|
||||
#
|
||||
# Le skin Elastic est livré compilé (styles.min.css). Ses points d'extension
|
||||
# _variables.less / _styles.less ne sont pris en compte qu'à la compilation LESS :
|
||||
# on recompile donc ici, dans un étage Node jetable, puis on recopie le skin
|
||||
# dans /usr/src/roundcubemail (source que l'entrypoint synchronise vers /var/www/html).
|
||||
|
||||
ARG RC_VERSION=1.7.4
|
||||
|
||||
FROM roundcube/roundcubemail:${RC_VERSION}-fpm-alpine AS upstream
|
||||
|
||||
# --- Étage 1 : compilation LESS du skin Elastic ---
|
||||
FROM node:22-alpine AS theme
|
||||
ARG LESS_VERSION=4.9.1
|
||||
ARG LESS_CLEANCSS_VERSION=1.6.0
|
||||
# Secret optionnel "ca" : CA d'un proxy TLS d'entreprise (docker build --secret id=ca,src=ca.pem)
|
||||
RUN --mount=type=secret,id=ca,required=false \
|
||||
NODE_EXTRA_CA_CERTS=/run/secrets/ca npm install -g --no-fund --no-audit \
|
||||
less@${LESS_VERSION} less-plugin-clean-css@${LESS_CLEANCSS_VERSION}
|
||||
|
||||
COPY --from=upstream /usr/src/roundcubemail/skins/elastic /build/elastic
|
||||
COPY theme/ /build/theme/
|
||||
|
||||
WORKDIR /build/elastic
|
||||
# Surcharges optionnelles : un theme/ vide redonne l'Elastic d'origine
|
||||
RUN set -eu; \
|
||||
for f in _variables.less _styles.less; do \
|
||||
if [ -f /build/theme/$f ]; then cp /build/theme/$f styles/; echo "surcharge: $f"; fi; \
|
||||
done; \
|
||||
if [ -d /build/theme/fonts ]; then cp -r /build/theme/fonts/. fonts/; fi; \
|
||||
if [ -d /build/theme/images ]; then cp -r /build/theme/images/. images/; fi; \
|
||||
for css in styles print embed; do \
|
||||
lessc --clean-css="--s1 --advanced" styles/$css.less > styles/$css.min.css; \
|
||||
done; \
|
||||
ls -l styles/*.min.css
|
||||
|
||||
# --- Étage 2 : image finale ---
|
||||
FROM upstream
|
||||
ARG ASPELL_DICTS="fr en"
|
||||
# Dictionnaires installés au build (plutôt qu'à chaque démarrage via ROUNDCUBEMAIL_ASPELL_DICTS)
|
||||
# mailcap fournit /etc/mime.types, utilisé par Roundcube pour le mapping type MIME <-> extension
|
||||
RUN --mount=type=secret,id=ca,required=false \
|
||||
set -eu; \
|
||||
if [ -s /run/secrets/ca ]; then export SSL_CERT_FILE=/run/secrets/ca; fi; \
|
||||
pkgs="mailcap"; \
|
||||
for d in $(echo "$ASPELL_DICTS" | tr ',' ' '); do pkgs="$pkgs aspell-$d"; done; \
|
||||
apk add --no-cache $pkgs
|
||||
COPY --from=theme /build/elastic/ /usr/src/roundcubemail/skins/elastic/
|
||||
|
||||
# Healthcheck php-fpm : réponse au ping FastCGI via le pool
|
||||
COPY roundcube/zz-healthcheck.conf /usr/local/etc/php-fpm.d/zz-healthcheck.conf
|
||||
@@ -0,0 +1,5 @@
|
||||
; Expose /fpm-ping sur le pool www, interrogé par nginx (healthcheck de bout en bout)
|
||||
[www]
|
||||
ping.path = /fpm-ping
|
||||
ping.response = pong
|
||||
access.suppress_path[] = /fpm-ping
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Importe une customisation Elastic (format roundcube-custom/vblog) dans theme/ et config/
|
||||
# Usage : scripts/sync-theme.sh [dossier_source] (défaut : ../roundcube-custom/vblog)
|
||||
set -eu
|
||||
cd "$(dirname "$0")/.."
|
||||
SRC=${1:-../roundcube-custom/vblog}
|
||||
[ -d "$SRC" ] || { echo "Source introuvable : $SRC" >&2; exit 1; }
|
||||
|
||||
rm -rf theme && mkdir -p theme
|
||||
for f in _variables.less _styles.less; do
|
||||
if [ -f "$SRC/$f" ]; then cp "$SRC/$f" theme/; fi
|
||||
done
|
||||
for d in fonts images; do
|
||||
if [ -d "$SRC/$d" ]; then cp -r "$SRC/$d" theme/; fi
|
||||
done
|
||||
if [ -f "$SRC/config-vblog.inc.php" ]; then
|
||||
cp "$SRC/config-vblog.inc.php" config/20-vblog.inc.php
|
||||
fi
|
||||
echo "Thème importé depuis $SRC. Appliquer : docker compose up -d --build"
|
||||
@@ -0,0 +1,75 @@
|
||||
// Compléments CSS charte vBlog.io pour le skin Elastic (Roundcube)
|
||||
// Emplacement : skins/elastic/styles/_styles.less
|
||||
|
||||
// --- Police Poppins auto-hébergée (pas d'appel à Google Fonts) ---
|
||||
// Fichiers à copier dans skins/elastic/fonts/ (source : paquet npm @fontsource/poppins, licence OFL)
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('../fonts/poppins-latin-400-normal.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('../fonts/poppins-latin-400-italic.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('../fonts/poppins-latin-700-normal.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('../fonts/poppins-latin-700-italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
body, button, input, optgroup, select, textarea, .popover {
|
||||
font-family: Poppins, Roboto, sans-serif;
|
||||
}
|
||||
|
||||
// --- Bouton principal de la page de login en orange (CTA du blog) ---
|
||||
#login-form .btn-primary {
|
||||
background-color: @vblog-accent;
|
||||
border-color: @vblog-accent;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: darken(@vblog-accent, 8%);
|
||||
border-color: darken(@vblog-accent, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Mode sombre : liens lisibles sur fond sombre, bouton "Écrire" en orange ---
|
||||
// :where() ramène la spécificité à celle de la règle de base "a { color: @color-link }"
|
||||
// de global.less : seuls les liens "nus" changent, pas les listes ni les boutons.
|
||||
:where(html.dark-mode) a {
|
||||
color: lighten(@vblog-primary, 40%);
|
||||
|
||||
&:hover {
|
||||
color: lighten(@vblog-primary, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
html.dark-mode {
|
||||
#taskmenu .action-buttons a {
|
||||
color: @vblog-accent;
|
||||
}
|
||||
|
||||
// Elastic assombrit le logo en mode sombre (opacity .8) : on le laisse net
|
||||
#logo {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Logo du menu latéral : l'emblème est presque carré, on lui laisse de l'air ---
|
||||
#layout-menu .popover-header img {
|
||||
padding: .6rem;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Charte vBlog.io pour le skin Elastic (Roundcube)
|
||||
// Emplacement : skins/elastic/styles/_variables.less
|
||||
// Source des couleurs : kit Elementor de vblog.io
|
||||
// https://vblog.io/wp-content/uploads/elementor/css/post-6.css (.elementor-kit-6)
|
||||
// primary #314977 · secondary #000000 · text #7A7A7A · accent #FF7B10 · police Poppins
|
||||
|
||||
@vblog-primary: #314977; // bleu marine
|
||||
@vblog-accent: #FF7B10; // orange
|
||||
|
||||
// --- Couleur principale (boutons, sélection, focus, cases à cocher, quota...) ---
|
||||
@color-main: @vblog-primary;
|
||||
@color-link: @vblog-primary;
|
||||
@color-link-hover: darken(@color-link, 12%);
|
||||
|
||||
// --- Menu latéral gauche : fond marine, action "Écrire" en orange ---
|
||||
@color-taskmenu-background: @vblog-primary;
|
||||
@color-taskmenu-button-selected-background: darken(@vblog-primary, 8%);
|
||||
@color-taskmenu-button-background-hover: darken(@vblog-primary, 8%);
|
||||
@color-taskmenu-button-action: @vblog-accent;
|
||||
@color-taskmenu-button-action-hover: lighten(@vblog-accent, 10%);
|
||||
|
||||
// --- Accent orange sur les badges des messages récents ---
|
||||
@color-list-recent-badge-background: @vblog-accent;
|
||||
@color-list-badge-background: @vblog-primary;
|
||||
|
||||
// --- Mode sombre : le marine est trop sombre sur fond sombre, on l'éclaircit ---
|
||||
@color-dark-list-selected: lighten(@vblog-primary, 40%);
|
||||
@color-dark-input-border-focus: lighten(@vblog-primary, 30%);
|
||||
@color-dark-information: @vblog-primary;
|
||||
@color-dark-scrollbar-thumb: lighten(@vblog-primary, 10%);
|
||||
@color-dark-blockquote-0: lighten(@vblog-primary, 35%);
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ThinItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraLight.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraLightItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Light.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-LightItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Regular.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Italic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Medium.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-MediumItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-SemiBold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-SemiBoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Bold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-BoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraBold.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-ExtraBoldItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-Black.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) Poppins-BlackItalic.ttf: Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2593.62 1936.98" xmlns:c2pa="http://c2pa.org/manifest"><metadata><c2pa:manifest>AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpkYTI3NDZjNi1mNzFiLTRmYTktODExZS05M2NkYWY4NjhhNzQAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaA91XmY9vYc/lQWs5mc/nSgAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo5NDg4N2FjYy0zZWRlLTRkNTEtYjEwNC1jNzI4ODJlYzJjODVscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNo0cSopkBTFmCEdCRaDm4D2QAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggHLDHh6IB6u0VT/kooPSrah5drQE82Y5rJoe/qvpz5TqkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaMasuQR/8QV+DkHEEuN8x9AAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCAT0buA0oWePCeILrY7wuF+ctrG5lgvGRcyRirNXcHZLGRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBjNZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmRhMjc0NmM2LWY3MWItNGZhOS04MTFlLTkzY2RhZjg2OGE3NC9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOmYwZWJlY2QzLWY1ZWItNGU1OS1hYzAwLWMyYWQ1OTM4MzAxZXJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCAcsMeHogHq7RVP+Sig9KtqHl2tATzZjmsmh7+q+nPlOqJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggANyWZH5B4dxS/hb406toS2r01DxDX6NJMAeOnX/BNKeiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggmc3m2Mzqiz5Id6nTzYaBsHtDc0/mJtVh/TILchty2U90Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALine truncated
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="_Слой_1" data-name="Слой 1">
|
||||
<path class="cls-1" d="M403.71,122.09c-14.13-24.78-14.02-55.55.33-80.27,14.42-24.69,41.07-40.01,69.62-40.01h335c28.62,0,55.31,15.45,69.66,40.32l423.47,733.13,371.83-645.99c-1.32-6.84-1.98-13.4-1.98-19.86,0-67.97,61.31-122.42,133.2-106.66,9.37,1.08,19.66,4.54,28.95,9.96,5.01,2.82,10.14,6.7,15.26,11.7,25.73,19.51,41.44,51.03,41.44,85.01,0,49.58-33.31,92.57-80.38,105.42l-438.37,761.55c-14.35,24.87-41.07,40.32-69.77,40.32s-55.31-15.45-69.66-40.32L762.25,162.74h-150.37l710.12,1248.35c45.72,14.13,76.98,55.88,76.98,104.29,0,60.38-49.12,109.5-109.52,109.5s-109.37-49.12-109.37-109.5c0-7.52.95-15.12,2.93-23.43L403.71,122.09ZM2582.94,41.85c-14.62-24.74-41.23-39.73-69.72-39.73h-360.58c-28.86,0-55.85,15.37-70.1,40.48l-615.84,1072.01c-43.11,15.37-72.34,56.6-72.34,102.7,0,60.35,48.72,109.45,109.07,109.45s109.82-49.1,109.82-109.45c0-9-1.5-18.37-4.12-27.74L2199.12,162.92h175.42l-322.9,556.87c-43.7,14.03-75.53,54.51-75.53,102.86,0,59.93,48.59,108.52,108.52,108.52s108.52-48.59,108.52-108.52c0-7.74-.88-15.27-2.42-22.55l392.21-678.05c14.24-24.74,14.24-55.47,0-80.21ZM1917.3,1008.41c-59.94,0-108.52,48.59-108.52,108.52,0,9.53,1.62,18.61,3.93,27.42l-289.78,505.88c-45.35,79.09-126.7,125.94-217.78,125.94s-172.42-46.85-217.78-125.94L215.9,137.06c2.25-8.25,3-16.12,3-23.99,0-60.35-49.1-109.45-109.45-109.45S0,52.72,0,113.07c0,48.35,31.11,89.96,76.84,104.2l871.1,1513.18c74.22,129.31,207.66,206.53,357.21,206.53s283-77.21,357.21-206.9l293.29-511.94c40.93-15.53,70.17-54.82,70.17-101.2,0-59.93-48.59-108.52-108.52-108.52Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2593.62 1936.98" xmlns:c2pa="http://c2pa.org/manifest"><metadata><c2pa:manifest>AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpmYTA2ZmM0My0yMGFkLTRmYzMtOTc4Zi00NDdmNWE1ZDNiNmEAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaFN7GjaL+DvcZ7qDIvssT+QAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDoyMTQxNjZhMS01OWQ5LTQ1MTAtYjdlNi1mNjc1NjM0YjNiYjhscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNotc0ool/uwYHS0xMCXfhROQAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFgg0gLQY2DQpyuLaKHGYmxL9pIGWRXgVZQndUlPC3BBZ/GkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaCHV0xW1SMJqd+3ehqhKsZUAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCCef7I8bNY42SEO0ST7/4olLBw27oaPO19GdQejLNz5HWRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBjNZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmZhMDZmYzQzLTIwYWQtNGZjMy05NzhmLTQ0N2Y1YTVkM2I2YS9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjI4ZGFhZTE0LWNiNDgtNDNhOC05M2NkLTZjYjliN2ZjZjhjY3JjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCDSAtBjYNCnK4toocZibEv2kgZZFeBVlCd1SU8LcEFn8aJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFgg7mshbHpJrugvO3izq6qTUyqOAO23FFoU7v69VayZMmOiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggFBFyHyxF4DRsGQWlQaYvja1e/2VaVq3EX3/njPu9PpV0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALine truncated
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #314977;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="_Слой_1" data-name="Слой 1">
|
||||
<path class="cls-1" d="M403.71,122.09c-14.13-24.78-14.02-55.55.33-80.27,14.42-24.69,41.07-40.01,69.62-40.01h335c28.62,0,55.31,15.45,69.66,40.32l423.47,733.13,371.83-645.99c-1.32-6.84-1.98-13.4-1.98-19.86,0-67.97,61.31-122.42,133.2-106.66,9.37,1.08,19.66,4.54,28.95,9.96,5.01,2.82,10.14,6.7,15.26,11.7,25.73,19.51,41.44,51.03,41.44,85.01,0,49.58-33.31,92.57-80.38,105.42l-438.37,761.55c-14.35,24.87-41.07,40.32-69.77,40.32s-55.31-15.45-69.66-40.32L762.25,162.74h-150.37l710.12,1248.35c45.72,14.13,76.98,55.88,76.98,104.29,0,60.38-49.12,109.5-109.52,109.5s-109.37-49.12-109.37-109.5c0-7.52.95-15.12,2.93-23.43L403.71,122.09ZM2582.94,41.85c-14.62-24.74-41.23-39.73-69.72-39.73h-360.58c-28.86,0-55.85,15.37-70.1,40.48l-615.84,1072.01c-43.11,15.37-72.34,56.6-72.34,102.7,0,60.35,48.72,109.45,109.07,109.45s109.82-49.1,109.82-109.45c0-9-1.5-18.37-4.12-27.74L2199.12,162.92h175.42l-322.9,556.87c-43.7,14.03-75.53,54.51-75.53,102.86,0,59.93,48.59,108.52,108.52,108.52s108.52-48.59,108.52-108.52c0-7.74-.88-15.27-2.42-22.55l392.21-678.05c14.24-24.74,14.24-55.47,0-80.21ZM1917.3,1008.41c-59.94,0-108.52,48.59-108.52,108.52,0,9.53,1.62,18.61,3.93,27.42l-289.78,505.88c-45.35,79.09-126.7,125.94-217.78,125.94s-172.42-46.85-217.78-125.94L215.9,137.06c2.25-8.25,3-16.12,3-23.99,0-60.35-49.1-109.45-109.45-109.45S0,52.72,0,113.07c0,48.35,31.11,89.96,76.84,104.2l871.1,1513.18c74.22,129.31,207.66,206.53,357.21,206.53s283-77.21,357.21-206.9l293.29-511.94c40.93-15.53,70.17-54.82,70.17-101.2,0-59.93-48.59-108.52-108.52-108.52Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
Reference in new issue
Block a user