Compare commits
8 Commits
main
...
5558db6d56
| Author | SHA1 | Date | |
|---|---|---|---|
| 5558db6d56 | |||
| 1e925a2b07 | |||
| 080c4238e8 | |||
| ffde884f5b | |||
| 2a58833190 | |||
| c30863ab9b | |||
| 2cca79cf04 | |||
| 033cec6573 |
@@ -2,10 +2,17 @@
|
|||||||
# Production environment for the Docker stack.
|
# Production environment for the Docker stack.
|
||||||
#
|
#
|
||||||
# cp .env.production.example .env.production
|
# cp .env.production.example .env.production
|
||||||
# docker compose run --rm --no-deps app php artisan key:generate --show
|
# # fill in at least DB_PASSWORD below — docker-compose.yml refuses to
|
||||||
|
# # interpolate at all without it, even for the next command
|
||||||
|
# docker compose --env-file .env.production run --rm --no-deps landing-app php artisan key:generate --show
|
||||||
#
|
#
|
||||||
# Everything the containers need is here; docker-compose.yml sets the database,
|
# Everything the containers need is here; docker-compose.yml sets the database,
|
||||||
# cache, session and queue drivers itself so they cannot drift.
|
# cache, session and queue drivers itself so they cannot drift.
|
||||||
|
#
|
||||||
|
# This file only matters for a manual deploy. A platform that injects its own
|
||||||
|
# "Environment Variables" (Dokploy, Coolify, ...) exports them as real process
|
||||||
|
# environment variables instead, which docker-compose.yml's `${VAR}`
|
||||||
|
# interpolation reads directly — no file needed on the server at all.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
APP_NAME="Pireon Admin"
|
APP_NAME="Pireon Admin"
|
||||||
|
|||||||
30
Dockerfile
30
Dockerfile
@@ -20,13 +20,13 @@ WORKDIR /app
|
|||||||
# sideways staying sideways. pcntl lets the queue worker shut down cleanly
|
# sideways staying sideways. pcntl lets the queue worker shut down cleanly
|
||||||
# instead of being killed mid-job.
|
# instead of being killed mid-job.
|
||||||
RUN install-php-extensions \
|
RUN install-php-extensions \
|
||||||
pdo_pgsql \
|
pdo_pgsql \
|
||||||
gd \
|
gd \
|
||||||
exif \
|
exif \
|
||||||
intl \
|
intl \
|
||||||
zip \
|
zip \
|
||||||
pcntl \
|
pcntl \
|
||||||
opcache \
|
opcache \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends postgresql-client \
|
&& apt-get install -y --no-install-recommends postgresql-client \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
@@ -49,11 +49,11 @@ COPY composer.json composer.lock ./
|
|||||||
# application cannot boot before its code is here. They are run explicitly in
|
# application cannot boot before its code is here. They are run explicitly in
|
||||||
# the runtime stage instead, where everything exists.
|
# the runtime stage instead, where everything exists.
|
||||||
RUN composer install \
|
RUN composer install \
|
||||||
--no-dev \
|
--no-dev \
|
||||||
--no-scripts \
|
--no-scripts \
|
||||||
--no-autoloader \
|
--no-autoloader \
|
||||||
--prefer-dist \
|
--prefer-dist \
|
||||||
--no-interaction
|
--no-interaction
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ RUN touch .env \
|
|||||||
FROM base AS runtime
|
FROM base AS runtime
|
||||||
|
|
||||||
LABEL org.opencontainers.image.title="Pireon Admin" \
|
LABEL org.opencontainers.image.title="Pireon Admin" \
|
||||||
org.opencontainers.image.description="Admin panel and REST API for the collective"
|
org.opencontainers.image.description="Admin panel and REST API for the collective"
|
||||||
|
|
||||||
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
COPY docker/Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|
||||||
@@ -113,9 +113,9 @@ RUN php artisan package:discover --ansi \
|
|||||||
|
|
||||||
RUN chown -R www-data:www-data storage bootstrap/cache
|
RUN chown -R www-data:www-data storage bootstrap/cache
|
||||||
|
|
||||||
ENV SERVER_NAME=:80
|
ENV SERVER_NAME=:8080
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["entrypoint"]
|
ENTRYPOINT ["entrypoint"]
|
||||||
|
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -1364,7 +1364,7 @@ php artisan site:maintenance --up
|
|||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php artisan test # 394 tests, 1128 aserciones
|
php artisan test # 391 tests, 1121 aserciones
|
||||||
```
|
```
|
||||||
|
|
||||||
En cada push y cada pull request, [`.github/workflows/tests.yml`](.github/workflows/tests.yml)
|
En cada push y cada pull request, [`.github/workflows/tests.yml`](.github/workflows/tests.yml)
|
||||||
@@ -1458,7 +1458,7 @@ párrafo aparte sin relación programática con él.
|
|||||||
## Estructura
|
## Estructura
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose.yml stack de producción (app, worker, scheduler, postgres)
|
docker-compose.yml stack de producción (landing-app, worker, scheduler, postgres)
|
||||||
docker-compose.test.yml la suite contra Postgres, desechable
|
docker-compose.test.yml la suite contra Postgres, desechable
|
||||||
Dockerfile imagen única para los tres contenedores PHP
|
Dockerfile imagen única para los tres contenedores PHP
|
||||||
docker/ Caddyfile, php.ini y entrypoint
|
docker/ Caddyfile, php.ini y entrypoint
|
||||||
@@ -1499,16 +1499,29 @@ Cuatro contenedores y una base de datos:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.production.example .env.production
|
cp .env.production.example .env.production
|
||||||
docker compose run --rm --no-deps app php artisan key:generate --show # pégalo en APP_KEY
|
# Ahora edita .env.production: al menos DB_PASSWORD tiene que tener un valor
|
||||||
|
# real, porque docker-compose.yml no arranca sin él — ni siquiera para el
|
||||||
|
# comando siguiente, que solo interpola el fichero entero antes de ejecutar
|
||||||
|
# nada. APP_KEY puede quedarse vacío por ahora, se rellena con este paso:
|
||||||
|
|
||||||
|
docker compose --env-file .env.production run --rm --no-deps landing-app php artisan key:generate --show # pégalo en APP_KEY
|
||||||
docker compose --env-file .env.production up -d --build
|
docker compose --env-file .env.production up -d --build
|
||||||
|
|
||||||
# el primer administrador, igual que en local
|
# el primer administrador, igual que en local
|
||||||
docker compose exec app php artisan user:role tu@email.com super_admin
|
docker compose exec landing-app php artisan user:role tu@email.com super_admin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Todas las variables de `docker-compose.yml` llegan por interpolación `${VAR}`,
|
||||||
|
no por `env_file:` — así que esto funciona igual con `--env-file`, con
|
||||||
|
variables ya exportadas en el shell, o con el panel de "Environment
|
||||||
|
Variables" de una plataforma como Dokploy o Coolify, que las inyecta como
|
||||||
|
variables de entorno reales del proceso que lanza `docker compose`. Ninguna
|
||||||
|
de las tres formas necesita que exista un fichero `.env.production` en el
|
||||||
|
servidor.
|
||||||
|
|
||||||
| Contenedor | Qué hace |
|
| Contenedor | Qué hace |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `app` | FrankenPHP sirviendo el panel y la API |
|
| `landing-app` | FrankenPHP sirviendo el panel y la API |
|
||||||
| `worker` | procesa imágenes y manda los avisos del buzón |
|
| `worker` | procesa imágenes y manda los avisos del buzón |
|
||||||
| `scheduler` | cierra convocatorias, barre imágenes pendientes, sincroniza repos |
|
| `scheduler` | cierra convocatorias, barre imágenes pendientes, sincroniza repos |
|
||||||
| `postgres` | la base de datos |
|
| `postgres` | la base de datos |
|
||||||
@@ -1587,7 +1600,7 @@ docker compose -f docker-compose.test.yml run --rm test
|
|||||||
|
|
||||||
Levanta un Postgres en memoria, corre la suite entera contra él y lo tira. Es la
|
Levanta un Postgres en memoria, corre la suite entera contra él y lo tira. Es la
|
||||||
comprobación que convierte «debería funcionar» en «funciona», y conviene hacerla
|
comprobación que convierte «debería funcionar» en «funciona», y conviene hacerla
|
||||||
antes del primer despliegue. **394 tests en verde contra Postgres 17.**
|
antes del primer despliegue. **391 tests en verde contra Postgres 17.**
|
||||||
|
|
||||||
Usa una etapa propia de la imagen: la de producción se construye con `--no-dev`
|
Usa una etapa propia de la imagen: la de producción se construye con `--no-dev`
|
||||||
y ahí no está ni el runner de tests.
|
y ahí no está ni el runner de tests.
|
||||||
|
|||||||
@@ -86,14 +86,6 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
])
|
])
|
||||||
|
|
||||||
// Language switcher, rendered next to the user menu.
|
// Language switcher, rendered next to the user menu.
|
||||||
// First render hook to fire on every page, panel and login alike:
|
|
||||||
// the one place that has to be true regardless of which screen
|
|
||||||
// somebody lands on first.
|
|
||||||
->renderHook(
|
|
||||||
PanelsRenderHook::BODY_START,
|
|
||||||
fn (): string => view('filament.partials.environment-banner')->render(),
|
|
||||||
)
|
|
||||||
|
|
||||||
->renderHook(
|
->renderHook(
|
||||||
PanelsRenderHook::USER_MENU_BEFORE,
|
PanelsRenderHook::USER_MENU_BEFORE,
|
||||||
fn (): string => view('filament.partials.language-switcher')->render(),
|
fn (): string => view('filament.partials.language-switcher')->render(),
|
||||||
|
|||||||
@@ -6,31 +6,54 @@
|
|||||||
# is one less thing to run, back up and forget to secure. Swapping it in later
|
# is one less thing to run, back up and forget to secure. Swapping it in later
|
||||||
# is three environment variables.
|
# is three environment variables.
|
||||||
#
|
#
|
||||||
|
# Every application value below comes from `${VAR}` interpolation, not
|
||||||
|
# `env_file:` — so this works the same way whichever way the variables reach
|
||||||
|
# `docker compose`: a real shell environment, a platform's own "Environment
|
||||||
|
# Variables" panel (Dokploy, Coolify, ...), or an explicit `--env-file`:
|
||||||
|
#
|
||||||
# docker compose --env-file .env.production up -d --build
|
# docker compose --env-file .env.production up -d --build
|
||||||
#
|
#
|
||||||
|
# `APP_KEY` is deliberately not required here even though the application
|
||||||
|
# cannot run without it: `docker/entrypoint.sh` checks it instead, with a
|
||||||
|
# message that says how to generate one. Requiring it at this level would also
|
||||||
|
# break generating it in the first place — `docker compose run --rm --no-deps
|
||||||
|
# landing-app php artisan key:generate --show` has to work before `APP_KEY`
|
||||||
|
# exists.
|
||||||
|
#
|
||||||
|
# The web service is named `landing-app`, not `app`: Compose aliases a
|
||||||
|
# service to its own name on every network it joins, `dokploy-network`
|
||||||
|
# included, and that network is shared with whatever else Dokploy runs on the
|
||||||
|
# same host. A service called plain `app` collides with the equally common
|
||||||
|
# `app` another compose project puts on the same network — its containers
|
||||||
|
# start resolving `app` to *this* one, on a port nothing here is listening on.
|
||||||
|
# That is exactly what broke `oauth-iam`'s PHP-FPM upstream the moment both
|
||||||
|
# stacks were up together, and it stops happening the moment the name isn't
|
||||||
|
# generic enough to collide.
|
||||||
|
#
|
||||||
|
# `landing-app` is not published on a host port either: it only joins the
|
||||||
|
# external `dokploy-network` so Dokploy's Traefik can reach it, and TLS is
|
||||||
|
# terminated there before anything reaches this container — same reason
|
||||||
|
# `docker/Caddyfile` trusts `X-Forwarded-*` only from private ranges. The
|
||||||
|
# domain itself is configured through Dokploy's own "Domains" tab, which
|
||||||
|
# injects its own Traefik labels — don't also add them here, Traefik ends up
|
||||||
|
# with two routers for the same Host() and picks one unpredictably.
|
||||||
|
#
|
||||||
|
# A manual deploy without Dokploy needs its own reverse proxy joined to that
|
||||||
|
# network (with its own labels/config), or `landing-app` added back to
|
||||||
|
# `default` with a published port.
|
||||||
|
#
|
||||||
# The web container is the only one that migrates, so a deploy runs them once
|
# The web container is the only one that migrates, so a deploy runs them once
|
||||||
# rather than once per replica.
|
# rather than once per replica.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
name: landing
|
||||||
|
|
||||||
x-app: &app
|
x-app: &app
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: pireon-admin:latest
|
image: pireon-admin:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file: .env.production
|
|
||||||
environment:
|
|
||||||
DB_CONNECTION: pgsql
|
|
||||||
DB_HOST: postgres
|
|
||||||
DB_PORT: 5432
|
|
||||||
|
|
||||||
# The maintenance flag and the health badge are read on nearly every
|
|
||||||
# request, so the cache has to be shared by every container — a per-process
|
|
||||||
# one would have the panel and the API disagreeing about whether the site
|
|
||||||
# is down.
|
|
||||||
CACHE_STORE: database
|
|
||||||
SESSION_DRIVER: database
|
|
||||||
QUEUE_CONNECTION: database
|
|
||||||
volumes:
|
volumes:
|
||||||
# Uploaded images live here, and they are the one thing in this stack that
|
# Uploaded images live here, and they are the one thing in this stack that
|
||||||
# cannot be rebuilt from the repository.
|
# cannot be rebuilt from the repository.
|
||||||
@@ -39,23 +62,95 @@ x-app: &app
|
|||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
|
x-app-env: &app-env
|
||||||
|
APP_NAME: ${APP_NAME:-Pireon Admin}
|
||||||
|
APP_ENV: ${APP_ENV:-production}
|
||||||
|
APP_KEY: ${APP_KEY}
|
||||||
|
APP_DEBUG: ${APP_DEBUG:-false}
|
||||||
|
APP_URL: ${APP_URL}
|
||||||
|
APP_LOCALE: ${APP_LOCALE:-es}
|
||||||
|
APP_FALLBACK_LOCALE: ${APP_FALLBACK_LOCALE:-es}
|
||||||
|
|
||||||
|
LOG_CHANNEL: ${LOG_CHANNEL:-stack}
|
||||||
|
LOG_STACK: ${LOG_STACK:-single}
|
||||||
|
LOG_LEVEL: ${LOG_LEVEL:-warning}
|
||||||
|
|
||||||
|
# Fixed by the service names below, not something a deploy should override —
|
||||||
|
# otherwise the panel and the API could disagree about the site's state.
|
||||||
|
DB_CONNECTION: pgsql
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_DATABASE: ${DB_DATABASE:-pireon}
|
||||||
|
DB_USERNAME: ${DB_USERNAME:-pireon}
|
||||||
|
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required. Set it wherever this stack's other variables come from.}
|
||||||
|
CACHE_STORE: database
|
||||||
|
SESSION_DRIVER: database
|
||||||
|
QUEUE_CONNECTION: database
|
||||||
|
|
||||||
|
PIREON_SERVER: ${PIREON_SERVER:-https://oauth.pireon.dev}
|
||||||
|
PIREON_CLIENT_ID: ${PIREON_CLIENT_ID}
|
||||||
|
PIREON_CLIENT_SECRET: ${PIREON_CLIENT_SECRET}
|
||||||
|
PIREON_REDIRECT_URI: ${PIREON_REDIRECT_URI}
|
||||||
|
PIREON_SCOPES: ${PIREON_SCOPES:-account:read}
|
||||||
|
PIREON_PASSWORD_GRANT: ${PIREON_PASSWORD_GRANT:-true}
|
||||||
|
PIREON_INTROSPECTION_TTL: ${PIREON_INTROSPECTION_TTL:-60}
|
||||||
|
PIREON_DEFAULT_ROLE: ${PIREON_DEFAULT_ROLE:-viewer}
|
||||||
|
PIREON_REQUIRE_ACTIVE: ${PIREON_REQUIRE_ACTIVE:-true}
|
||||||
|
PIREON_REQUIRE_APPROVED: ${PIREON_REQUIRE_APPROVED:-true}
|
||||||
|
|
||||||
|
SITE_URL: ${SITE_URL}
|
||||||
|
SITE_PREFIX_DEFAULT_LOCALE: ${SITE_PREFIX_DEFAULT_LOCALE:-false}
|
||||||
|
SITE_CACHE_SECONDS: ${SITE_CACHE_SECONDS:-60}
|
||||||
|
SITE_PREVIEW_TOKEN: ${SITE_PREVIEW_TOKEN}
|
||||||
|
SITE_RATE_LIMIT: ${SITE_RATE_LIMIT:-600}
|
||||||
|
SITE_TRUSTED_IPS: ${SITE_TRUSTED_IPS:-}
|
||||||
|
SITE_INGEST_TOKEN: ${SITE_INGEST_TOKEN}
|
||||||
|
SITE_TELEMETRY_RATE_LIMIT: ${SITE_TELEMETRY_RATE_LIMIT:-60}
|
||||||
|
SITE_TELEMETRY_MAX_BATCH: ${SITE_TELEMETRY_MAX_BATCH:-50}
|
||||||
|
SITE_ACCEPT_SUBMISSIONS: ${SITE_ACCEPT_SUBMISSIONS:-true}
|
||||||
|
SITE_HONEYPOT_FIELD: ${SITE_HONEYPOT_FIELD:-website}
|
||||||
|
SITE_SUBMISSION_RATE_LIMIT: ${SITE_SUBMISSION_RATE_LIMIT:-3}
|
||||||
|
SITE_SUBMISSION_HOURLY_LIMIT: ${SITE_SUBMISSION_HOURLY_LIMIT:-60}
|
||||||
|
SITE_SUBMISSION_DEDUPE_MINUTES: ${SITE_SUBMISSION_DEDUPE_MINUTES:-1440}
|
||||||
|
SITE_SUBMISSION_MAIL: ${SITE_SUBMISSION_MAIL:-false}
|
||||||
|
|
||||||
|
MEDIA_DISK: ${MEDIA_DISK:-public}
|
||||||
|
MEDIA_MAX_PIXELS: ${MEDIA_MAX_PIXELS:-50000000}
|
||||||
|
|
||||||
|
GIT_PROVIDER: ${GIT_PROVIDER:-gitea}
|
||||||
|
GITEA_URL: ${GITEA_URL:-}
|
||||||
|
GITEA_TOKEN: ${GITEA_TOKEN:-}
|
||||||
|
GITEA_ORG: ${GITEA_ORG:-}
|
||||||
|
GIT_SYNC_CRON: ${GIT_SYNC_CRON:-hourly}
|
||||||
|
|
||||||
|
MAIL_MAILER: ${MAIL_MAILER:-log}
|
||||||
|
MAIL_HOST: ${MAIL_HOST:-}
|
||||||
|
MAIL_PORT: ${MAIL_PORT:-587}
|
||||||
|
MAIL_USERNAME: ${MAIL_USERNAME:-}
|
||||||
|
MAIL_PASSWORD: ${MAIL_PASSWORD:-}
|
||||||
|
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS:-admin@example.org}
|
||||||
|
MAIL_FROM_NAME: ${MAIL_FROM_NAME:-Pireon Admin}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
landing-app:
|
||||||
<<: *app
|
<<: *app
|
||||||
environment:
|
environment:
|
||||||
DB_CONNECTION: pgsql
|
<<: *app-env
|
||||||
DB_HOST: postgres
|
|
||||||
DB_PORT: 5432
|
|
||||||
CACHE_STORE: database
|
|
||||||
SESSION_DRIVER: database
|
|
||||||
QUEUE_CONNECTION: database
|
|
||||||
|
|
||||||
# Only here: migrations run once per deploy, not once per container.
|
# Only here: migrations run once per deploy, not once per container.
|
||||||
RUN_MIGRATIONS: "true"
|
RUN_MIGRATIONS: "true"
|
||||||
ports:
|
expose:
|
||||||
- "${APP_PORT:-8000}:80"
|
- "8080"
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
- default
|
||||||
|
|
||||||
|
# No Traefik labels here on purpose: the domain is configured through
|
||||||
|
# Dokploy's own "Domains" tab for this app, which injects its own labels
|
||||||
|
# at deploy time. Hand-writing another set on top of those, for the same
|
||||||
|
# Host(), gave Traefik two routers to choose between — it only broke once
|
||||||
|
# both existed together, which is exactly what happened.
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-fsS", "http://localhost/up"]
|
test: ["CMD", "curl", "-fsS", "http://localhost:8080/up"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
@@ -67,6 +162,7 @@ services:
|
|||||||
worker:
|
worker:
|
||||||
<<: *app
|
<<: *app
|
||||||
command: ["php", "artisan", "queue:work", "--tries=3", "--max-time=3600", "--sleep=3"]
|
command: ["php", "artisan", "queue:work", "--tries=3", "--max-time=3600", "--sleep=3"]
|
||||||
|
environment: *app-env
|
||||||
|
|
||||||
# Waits for the web container, not just the database: it is the one that
|
# Waits for the web container, not just the database: it is the one that
|
||||||
# migrates, and a worker that starts against an empty schema spends its
|
# migrates, and a worker that starts against an empty schema spends its
|
||||||
@@ -74,7 +170,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
app:
|
landing-app:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "php", "artisan", "queue:monitor", "default"]
|
test: ["CMD", "php", "artisan", "queue:monitor", "default"]
|
||||||
@@ -89,6 +185,7 @@ services:
|
|||||||
scheduler:
|
scheduler:
|
||||||
<<: *app
|
<<: *app
|
||||||
command: ["php", "artisan", "schedule:work"]
|
command: ["php", "artisan", "schedule:work"]
|
||||||
|
environment: *app-env
|
||||||
|
|
||||||
# The FrankenPHP base image ships a healthcheck that probes Caddy's admin
|
# The FrankenPHP base image ships a healthcheck that probes Caddy's admin
|
||||||
# API, and this container does not serve HTTP — inherited, it could never
|
# API, and this container does not serve HTTP — inherited, it could never
|
||||||
@@ -109,7 +206,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
app:
|
landing-app:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
stop_grace_period: 30s
|
stop_grace_period: 30s
|
||||||
|
|
||||||
@@ -119,7 +216,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: ${DB_DATABASE:-pireon}
|
POSTGRES_DB: ${DB_DATABASE:-pireon}
|
||||||
POSTGRES_USER: ${DB_USERNAME:-pireon}
|
POSTGRES_USER: ${DB_USERNAME:-pireon}
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD in .env.production}
|
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required. Set it wherever this stack's other variables come from.}
|
||||||
|
|
||||||
# Content is written by a handful of people and read constantly; the
|
# Content is written by a handful of people and read constantly; the
|
||||||
# default collation rules are what the JSON slug lookups sort under.
|
# default collation rules are what the JSON slug lookups sort under.
|
||||||
@@ -141,3 +238,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
storage:
|
storage:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'environment' => [
|
|
||||||
'production' => 'Producció — això és real',
|
|
||||||
'other' => 'Entorn de :name',
|
|
||||||
],
|
|
||||||
|
|
||||||
'concurrency' => [
|
'concurrency' => [
|
||||||
'title' => 'No s\'ha desat',
|
'title' => 'No s\'ha desat',
|
||||||
'body' => 'Algú altre ha canviat això mentre ho editaves. Recarrega la pàgina per veure la versió actual: si ho deses ara, perdries aquest canvi.',
|
'body' => 'Algú altre ha canviat això mentre ho editaves. Recarrega la pàgina per veure la versió actual: si ho deses ara, perdries aquest canvi.',
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'environment' => [
|
|
||||||
'production' => 'Production — this is real',
|
|
||||||
'other' => ':name environment',
|
|
||||||
],
|
|
||||||
|
|
||||||
'concurrency' => [
|
'concurrency' => [
|
||||||
'title' => 'Not saved',
|
'title' => 'Not saved',
|
||||||
'body' => 'Somebody else changed this while you were editing it. Reload the page to see the current version — saving now would overwrite that change.',
|
'body' => 'Somebody else changed this while you were editing it. Reload the page to see the current version — saving now would overwrite that change.',
|
||||||
|
|||||||
@@ -30,11 +30,6 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'environment' => [
|
|
||||||
'production' => 'Producción — esto es real',
|
|
||||||
'other' => 'Entorno de :name',
|
|
||||||
],
|
|
||||||
|
|
||||||
'concurrency' => [
|
'concurrency' => [
|
||||||
'title' => 'No se ha guardado',
|
'title' => 'No se ha guardado',
|
||||||
'body' => 'Alguien más ha cambiado esto mientras lo editabas. Recarga la página para ver la versión actual: si guardas ahora, perderías ese cambio.',
|
'body' => 'Alguien más ha cambiado esto mientras lo editabas. Recarga la página para ver la versión actual: si guardas ahora, perderías ese cambio.',
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
{{-- Rendered at the very top of every page in the panel, on every install.
|
|
||||||
|
|
||||||
The failure this exists for runs in one direction only: mistaking a real
|
|
||||||
deployment for a throwaway copy, not the other way round. Nobody edits a
|
|
||||||
local clone carefully because they think it is production — the mistake
|
|
||||||
that costs something is the reverse. So `local` stays silent, and
|
|
||||||
anything else says so, loudly in the one case that matters. --}}
|
|
||||||
|
|
||||||
@unless (app()->environment('local'))
|
|
||||||
<div
|
|
||||||
role="status"
|
|
||||||
@class([
|
|
||||||
'w-full py-1 text-center text-xs font-semibold tracking-wide text-white uppercase',
|
|
||||||
'bg-danger-600' => app()->environment('production'),
|
|
||||||
'bg-warning-600' => ! app()->environment('production'),
|
|
||||||
])
|
|
||||||
>
|
|
||||||
{{ app()->environment('production')
|
|
||||||
? __('panel.environment.production')
|
|
||||||
: __('panel.environment.other', ['name' => app()->environment()]) }}
|
|
||||||
</div>
|
|
||||||
@endunless
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Tests\Feature\Panel;
|
|
||||||
|
|
||||||
use Database\Seeders\RolePermissionSeeder;
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
||||||
use PHPUnit\Framework\Attributes\Test;
|
|
||||||
use Tests\FakesPireon;
|
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The one banner that has to be right in the one direction that matters.
|
|
||||||
*
|
|
||||||
* Nobody edits a local clone carefully because they mistake it for
|
|
||||||
* production — the expensive mistake runs the other way. So the only
|
|
||||||
* environment that stays silent is `local`; every other one says so, and
|
|
||||||
* production says so the loudest.
|
|
||||||
*/
|
|
||||||
class EnvironmentBannerTest extends TestCase
|
|
||||||
{
|
|
||||||
use FakesPireon, RefreshDatabase;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$this->seed(RolePermissionSeeder::class);
|
|
||||||
$this->actingAs($this->userWithRoles('admin'));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setEnvironment(string $environment): void
|
|
||||||
{
|
|
||||||
$this->app['env'] = $environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Test]
|
|
||||||
public function nothing_is_shown_in_a_local_install(): void
|
|
||||||
{
|
|
||||||
$this->setEnvironment('local');
|
|
||||||
|
|
||||||
$this->get('/admin')
|
|
||||||
->assertOk()
|
|
||||||
->assertDontSee(__('panel.environment.production'))
|
|
||||||
->assertDontSeeText('bg-danger-600', escape: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Test]
|
|
||||||
public function production_says_so_in_the_loudest_colour(): void
|
|
||||||
{
|
|
||||||
$this->setEnvironment('production');
|
|
||||||
|
|
||||||
$this->get('/admin')
|
|
||||||
->assertOk()
|
|
||||||
->assertSee(__('panel.environment.production'));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Test]
|
|
||||||
public function anything_else_names_itself(): void
|
|
||||||
{
|
|
||||||
$this->setEnvironment('staging');
|
|
||||||
|
|
||||||
$this->get('/admin')
|
|
||||||
->assertOk()
|
|
||||||
->assertSee(__('panel.environment.other', ['name' => 'staging']));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user