Compare commits

...

22 Commits

Author SHA1 Message Date
gitea-actions
bcfecb2281 chore: bump version to v0.1.27
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 17s
2026-04-07 13:33:46 +00:00
Matthieu
90147bd93b fix(infra) : fix public dir permissions in deploy.sh
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:33:39 +02:00
gitea-actions
4d106e9625 chore: bump version to v0.1.26
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 18s
2026-04-07 13:29:50 +00:00
Matthieu
9748862684 fix(infra) : add deploy.sh with maintenance mode like Inventory
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Maintenance is handled by nginx-proxy on the host, not inside the
container. deploy.sh extracts maintenance.html from the container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:29:42 +02:00
gitea-actions
1904c999ec chore: bump version to v0.1.25
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 18s
2026-04-07 13:25:29 +00:00
Matthieu
81266dd64b fix(infra) : update proxy port to 8086 and add maintenance mode
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:25:23 +02:00
gitea-actions
c5e2800e4c chore: bump version to v0.1.24
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 18s
2026-04-07 13:09:32 +00:00
Matthieu
ef1c14f8da feat : add app:create-user console command
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:09:23 +02:00
gitea-actions
7e5080859d chore: bump version to v0.1.23
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 2m16s
2026-04-07 12:59:29 +00:00
Matthieu
414916a20d fix(ci) : pin node:22-alpine instead of lts (now node 24 / npm 11)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:59:19 +02:00
gitea-actions
70c05946bd chore: bump version to v0.1.22
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 18s
2026-04-07 12:56:03 +00:00
Matthieu
ede55b9f08 fix(ci) : regenerate package-lock.json for npm ci compatibility
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:55:54 +02:00
gitea-actions
c61b24bea3 chore: bump version to v0.1.21
Some checks failed
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Failing after 10s
2026-04-07 12:53:26 +00:00
Matthieu
389bfbef13 refactor(infra) : align prod setup with Lesstime pattern
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Single container with supervisord (Nginx + PHP-FPM), 3-stage
Dockerfile build, pre-built image from registry, port 8086.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:53:18 +02:00
gitea-actions
34adb01cbb chore: bump version to v0.1.20
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 51s
2026-04-07 12:40:39 +00:00
Matthieu
212a37f8dc fix(infra) : hardcode prod port 8086 like other apps
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:40:30 +02:00
gitea-actions
5cd7fc305f chore: bump version to v0.1.19
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Successful in 52s
2026-04-07 12:33:31 +00:00
Matthieu
9109e387b9 fix(ci) : set APP_ENV=prod in production Dockerfile
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Without APP_ENV=prod, Symfony defaults to dev and tries to load
DoctrineFixturesBundle which is excluded by --no-dev.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:32:53 +02:00
gitea-actions
0d87574ea2 chore: bump version to v0.1.18
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 13s
2026-04-07 12:31:13 +00:00
Matthieu
957e05342d chore : bump @malio/layer-ui to 1.2.2 and update config reference
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:31:04 +02:00
gitea-actions
e4f00c322d chore: bump version to v0.1.17
Some checks failed
Auto Tag Develop / tag (push) Successful in 5s
Build & Push Docker Image / build (push) Failing after 12s
2026-04-07 10:09:05 +00:00
Matthieu
0cb063cdd7 fix : add Tailwind theme colors and maintenance.html
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- tailwind.config.ts: full theme with primary/secondary/tertiary + m-* CSS vars
- infra/prod/maintenance.html: maintenance page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:08:58 +02:00
12 changed files with 11945 additions and 15028 deletions

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.1.16'
app.version: '0.1.27'

26524
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,25 @@
{
"name": "coltura-frontend",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
},
"dependencies": {
"@malio/layer-ui": "^1.2.0",
"@nuxt/icon": "^2.2.1",
"@nuxtjs/i18n": "^10.2.3",
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.3",
"nuxt": "^4.3.1",
"nuxt-toast": "^1.4.0",
"pinia": "^3.0.4",
"vue": "^3.5.29",
"vue-router": "^4.6.4"
}
"name": "coltura-frontend",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
},
"dependencies": {
"@malio/layer-ui": "^1.2.2",
"@nuxt/icon": "^2.2.1",
"@nuxtjs/i18n": "^10.2.3",
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.3",
"nuxt": "^4.3.1",
"nuxt-toast": "^1.4.0",
"pinia": "^3.0.4",
"vue": "^3.5.29",
"vue-router": "^4.6.4"
}
}

View File

@@ -1,12 +1,48 @@
import type { Config } from 'tailwindcss'
import type {Config} from 'tailwindcss'
export default <Partial<Config>>{
content: [
'./components/**/*.{vue,js,ts}',
'./layouts/**/*.vue',
'./pages/**/*.vue',
'./composables/**/*.{js,ts}',
'./plugins/**/*.{js,ts}',
'./app.vue',
],
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['"Helvetica Neue"', 'Helvetica', 'Arial', 'sans-serif']
},
colors: {
primary: {
500: '#222783',
},
secondary: {
500: '#304998'
},
tertiary: {
500: '#F3F4F8'
},
blue: {
500: '#056CF2'
},
m: {
primary: 'rgb(var(--m-primary) / <alpha-value>)',
secondary: 'rgb(var(--m-secondary, 75 77 237) / <alpha-value>)',
tertiary: 'rgb(var(--m-tertiary, 243 244 248) / <alpha-value>)',
border: 'rgb(var(--m-border) / <alpha-value>)',
text: 'rgb(var(--m-text) / <alpha-value>)',
muted: 'rgb(var(--m-muted) / <alpha-value>)',
bg: 'rgb(var(--m-bg) / <alpha-value>)',
surface: 'rgb(var(--m-surface) / <alpha-value>)',
disabled: 'rgb(var(--m-disabled) / <alpha-value>)',
danger: 'rgb(var(--m-danger) / <alpha-value>)',
success: 'rgb(var(--m-success) / <alpha-value>)',
'btn-primary': 'rgb(var(--m-btn-primary) / <alpha-value>)',
'btn-primary-hover': 'rgb(var(--m-btn-primary-hover) / <alpha-value>)',
'btn-primary-active': 'rgb(var(--m-btn-primary-active) / <alpha-value>)',
'btn-secondary': 'rgb(var(--m-btn-secondary) / <alpha-value>)',
'btn-secondary-hover': 'rgb(var(--m-btn-secondary-hover) / <alpha-value>)',
'btn-secondary-active': 'rgb(var(--m-btn-secondary-active) / <alpha-value>)',
'btn-danger': 'rgb(var(--m-btn-danger) / <alpha-value>)',
'btn-danger-hover': 'rgb(var(--m-btn-danger-hover) / <alpha-value>)',
'btn-danger-active': 'rgb(var(--m-btn-danger-active) / <alpha-value>)',
}
}
}
}
}

View File

@@ -2,11 +2,7 @@ APP_ENV=prod
APP_DEBUG=0
APP_SECRET=CHANGE_ME_IN_PRODUCTION
POSTGRES_DB=coltura
POSTGRES_USER=coltura
POSTGRES_PASSWORD=CHANGE_ME_IN_PRODUCTION
APP_PORT=80
DATABASE_URL="postgresql://coltura:CHANGE_ME@host.docker.internal:5432/coltura?serverVersion=16&charset=utf8"
JWT_PASSPHRASE=CHANGE_ME_IN_PRODUCTION
JWT_COOKIE_SECURE=1

View File

@@ -1,86 +1,84 @@
ARG DOCKER_PHP_VERSION=8.4.6
# --- Stage 1: Build backend ---
FROM php:8.4-cli AS backend-build
FROM php:${DOCKER_PHP_VERSION}-fpm-bullseye AS php-base
ARG DOCKER_NODE_VERSION=24.12.0
ENV DOCKER_NODE_VERSION="${DOCKER_NODE_VERSION}"
# Installer les dépendances et extensions PHP nécessaires
RUN apt-get update && apt-get install -y \
libicu-dev \
libpq-dev \
libpng-dev \
libzip-dev \
libxml2-dev \
ca-certificates \
gnupg \
libbz2-dev \
libgmp-dev \
libldap2-dev \
libonig-dev \
libsodium-dev \
libxslt1-dev \
zlib1g-dev \
libssl-dev \
wget \
git \
unzip \
&& docker-php-ext-install -j$(nproc) \
intl \
zip \
bcmath \
bz2 \
calendar \
exif \
gd \
gettext \
gmp \
ldap \
pcntl \
pdo_pgsql \
soap \
sockets \
sysvsem \
xsl \
&& docker-php-ext-enable opcache \
&& rm -rf /var/lib/apt/lists/* /tmp/*
libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \
unzip curl git \
&& docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \
&& rm -rf /var/lib/apt/lists/*
# Installation de node
RUN wget -qO- "https://nodejs.org/dist/v${DOCKER_NODE_VERSION}/node-v${DOCKER_NODE_VERSION}-linux-x64.tar.xz" | tar xJC /tmp/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/bin /usr/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/include /usr/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/lib /usr/ && \
cp -r /tmp/node-v${DOCKER_NODE_VERSION}-linux-x64/share /usr/ && \
rm -rf /tmp/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Installation de composer
RUN curl --insecure https://getcomposer.org/composer.phar -o /usr/bin/composer && chmod +x /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN APP_ENV=prod APP_DEBUG=0 composer install --no-dev --no-scripts --no-interaction
WORKDIR /var/www/html
COPY bin bin/
COPY config config/
COPY migrations migrations/
COPY public public/
COPY src src/
# Copier les fichiers projet
COPY . /var/www/html
RUN composer dump-autoload --optimize --no-dev
# Installation des dépendances PHP (prod)
RUN composer install --no-dev --optimize-autoloader --no-interaction
# --- Stage 2: Build frontend ---
FROM node:22-alpine AS frontend-build
# Génération des clés JWT si absentes
RUN php bin/console lexik:jwt:generate-keypair --skip-if-exists
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
# Build du frontend
RUN cd frontend && npm ci && npm run build:dist && rm -rf node_modules
COPY frontend/ ./
ENV CI=1 \
NUXT_TELEMETRY_DISABLED=1 \
NUXT_PUBLIC_API_BASE=/api \
NUXT_PUBLIC_APP_BASE=/
RUN npm run generate
# --- Stage 3: Production image ---
FROM php:8.4-fpm AS production
RUN apt-get update && apt-get install -y \
libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \
nginx supervisor \
&& docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \
&& rm -rf /var/lib/apt/lists/*
# PHP production config
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
# PHP-FPM: forward worker output to stderr for docker logs
RUN echo "catch_workers_output = yes" >> /usr/local/etc/php-fpm.d/www.conf \
&& echo "decorate_workers_output = no" >> /usr/local/etc/php-fpm.d/www.conf
# Nginx: log to stdout/stderr
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
# Remove default nginx site
RUN rm -f /etc/nginx/sites-enabled/default
# Configs
COPY infra/prod/supervisord.conf /etc/supervisor/conf.d/app.conf
COPY infra/prod/nginx.conf /etc/nginx/sites-enabled/coltura.conf
# Backend from stage 1
COPY --from=backend-build /app /var/www/html
# Frontend from stage 2
COPY --from=frontend-build /app/frontend/.output/public /var/www/html/frontend/.output/public
# Maintenance page
COPY infra/prod/maintenance.html /var/www/html/public/maintenance.html
# Symfony needs a .env file to boot (variables are overridden by env_file in docker-compose)
RUN echo "APP_ENV=prod" > /var/www/html/.env
# Permissions
RUN chown -R www-data:www-data /var/www/html/var /var/www/html/frontend/dist
RUN mkdir -p /var/www/html/var /var/www/html/config/jwt \
&& chown -R www-data:www-data /var/www/html/var
# PHP prod config
COPY infra/prod/php-prod.ini /usr/local/etc/php/php.ini
WORKDIR /var/www/html
EXPOSE 80
EXPOSE 9000
# ── Nginx stage ──
FROM nginx:1.27-alpine AS nginx
COPY infra/prod/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=php-base /var/www/html/public /var/www/html/public
COPY --from=php-base /var/www/html/frontend/dist /var/www/html/frontend/dist
CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/app.conf"]

39
infra/prod/deploy.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
TAG="${1:-latest}"
export COLTURA_IMAGE_TAG="$TAG"
echo "==> Deploying coltura:${TAG}..."
echo "==> Enabling maintenance mode..."
touch maintenance.on
echo "==> Pulling image..."
sudo docker compose pull
echo "==> Starting container..."
sudo docker compose up -d
echo "==> Waiting for container to be ready..."
sleep 3
echo "==> Extracting maintenance page..."
mkdir -p public
sudo docker compose cp app:/var/www/html/public/maintenance.html public/maintenance.html
sudo chown -R www-data:www-data public
echo "==> Running migrations..."
sudo docker compose exec -T -u www-data app php bin/console doctrine:migrations:migrate --no-interaction
echo "==> Clearing cache..."
sudo docker compose exec -T -u www-data app php bin/console cache:clear --env=prod
sudo docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod
echo "==> Disabling maintenance mode..."
rm -f maintenance.on
VERSION=$(sudo docker compose exec -T app cat config/version.yaml | grep 'app.version' | awk -F"'" '{print $2}')
echo "==> Deployed v${VERSION}"

View File

@@ -1,42 +1,12 @@
services:
php:
container_name: php-coltura-fpm
build:
context: ../../
dockerfile: infra/deploy/Dockerfile
target: php-base
environment:
APP_ENV: prod
APP_DEBUG: 0
DATABASE_URL: "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?serverVersion=16&charset=utf8"
volumes:
- uploads_data:/var/www/html/var/uploads
depends_on:
- db
restart: unless-stopped
nginx:
container_name: nginx-coltura
build:
context: ../../
dockerfile: infra/deploy/Dockerfile
target: nginx
depends_on:
- php
app:
image: gitea.malio.fr/malio-dev/coltura:${COLTURA_IMAGE_TAG:-latest}
container_name: coltura-app
env_file: .env
ports:
- "${APP_PORT:-80}:80"
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
- "8086:80"
volumes:
- pg_data:/var/lib/postgresql/data
- ./config/jwt:/var/www/html/config/jwt:ro
extra_hosts:
- "host.docker.internal:host-gateway"
restart: unless-stopped
volumes:
pg_data:
uploads_data:

View File

@@ -21,7 +21,7 @@ server {
}
location / {
proxy_pass http://127.0.0.1:8083;
proxy_pass http://127.0.0.1:8086;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@@ -2,16 +2,24 @@ server {
listen 80;
server_name _;
root /var/www/html/frontend/dist;
root /var/www/html/frontend/.output/public;
index index.html;
client_max_body_size 55m;
access_log /dev/stdout;
error_log /dev/stderr;
location ^~ /api/ {
root /var/www/html/public;
try_files $uri /index.php?$query_string;
}
location ^~ /bundles/ {
root /var/www/html/public;
try_files $uri =404;
}
location = /api/login_check {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
@@ -19,19 +27,15 @@ server {
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param PATH_INFO /login_check;
fastcgi_param REQUEST_URI /login_check;
fastcgi_pass php:9000;
}
location ^~ /bundles/ {
root /var/www/html/public;
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
}
location ~ ^/index\.php(/|$) {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
fastcgi_param DOCUMENT_ROOT /var/www/html/public;
fastcgi_pass php:9000;
fastcgi_pass 127.0.0.1:9000;
internal;
}
location ~ \.php$ {

View File

@@ -0,0 +1,28 @@
[supervisord]
nodaemon=true
user=root
logfile=/dev/null
logfile_maxbytes=0
pidfile=/var/run/supervisord.pid
[program:php-fpm]
command=php-fpm -F
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stopasgroup=true
stopsignal=QUIT
[program:nginx]
command=nginx -g "daemon off;"
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stopasgroup=true
stopsignal=QUIT

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
#[AsCommand(
name: 'app:create-user',
description: 'Create a new user',
)]
class CreateUserCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly UserPasswordHasherInterface $passwordHasher,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('username', InputArgument::REQUIRED, 'Username')
->addArgument('password', InputArgument::REQUIRED, 'Plain password')
->addOption('admin', null, InputOption::VALUE_NONE, 'Grant ROLE_ADMIN')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$username = $input->getArgument('username');
$plainPassword = $input->getArgument('password');
$user = new User();
$user->setUsername($username);
$user->setPassword($this->passwordHasher->hashPassword($user, $plainPassword));
if ($input->getOption('admin')) {
$user->setRoles(['ROLE_ADMIN']);
}
$this->em->persist($user);
$this->em->flush();
$io->success(sprintf('User "%s" created%s.', $username, $input->getOption('admin') ? ' with ROLE_ADMIN' : ''));
return Command::SUCCESS;
}
}