feat: add nginx proxy for theme injection (cleaner approach)
This commit is contained in:
@@ -1,14 +1,13 @@
|
|||||||
services:
|
services:
|
||||||
asciinema:
|
asciinema_backend:
|
||||||
image: ${ASCIINEMA_IMAGE:-ghcr.io/asciinema/asciinema-server:latest}
|
image: ${ASCIINEMA_IMAGE:-ghcr.io/asciinema/asciinema-server:latest}
|
||||||
container_name: ${ASCIINEMA_COMPOSE_PROJECT_NAME}_app
|
container_name: ${ASCIINEMA_COMPOSE_PROJECT_NAME}_backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- compose_network
|
- compose_network
|
||||||
volumes:
|
volumes:
|
||||||
- asciinema_data:/var/opt/asciinema
|
- asciinema_data:/var/opt/asciinema
|
||||||
- ./custom.exs:/opt/app/etc/custom.exs:ro
|
- ./custom.exs:/opt/app/etc/custom.exs:ro
|
||||||
- ./theme:/opt/app/etc/theme:ro
|
|
||||||
environment:
|
environment:
|
||||||
SECRET_KEY_BASE: ${ASCIINEMA_SECRET_KEY}
|
SECRET_KEY_BASE: ${ASCIINEMA_SECRET_KEY}
|
||||||
URL_HOST: ${ASCIINEMA_TRAEFIK_HOST}
|
URL_HOST: ${ASCIINEMA_TRAEFIK_HOST}
|
||||||
@@ -20,6 +19,18 @@ services:
|
|||||||
SMTP_FROM_ADDRESS: ${EMAIL_FROM}
|
SMTP_FROM_ADDRESS: ${EMAIL_FROM}
|
||||||
SIGN_UP_DISABLED: ${ASCIINEMA_SIGN_UP_DISABLED:-false}
|
SIGN_UP_DISABLED: ${ASCIINEMA_SIGN_UP_DISABLED:-false}
|
||||||
DEFAULT_AVATAR: gravatar
|
DEFAULT_AVATAR: gravatar
|
||||||
|
|
||||||
|
asciinema:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: ${ASCIINEMA_COMPOSE_PROJECT_NAME}_app
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- compose_network
|
||||||
|
volumes:
|
||||||
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
- ./theme:/theme:ro
|
||||||
|
depends_on:
|
||||||
|
- asciinema_backend
|
||||||
labels:
|
labels:
|
||||||
- 'traefik.enable=${ASCIINEMA_TRAEFIK_ENABLED}'
|
- 'traefik.enable=${ASCIINEMA_TRAEFIK_ENABLED}'
|
||||||
# Main web interface - HTTP to HTTPS redirect
|
# Main web interface - HTTP to HTTPS redirect
|
||||||
@@ -35,7 +46,7 @@ services:
|
|||||||
- 'traefik.http.middlewares.${ASCIINEMA_COMPOSE_PROJECT_NAME}-compress.compress=true'
|
- 'traefik.http.middlewares.${ASCIINEMA_COMPOSE_PROJECT_NAME}-compress.compress=true'
|
||||||
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${ASCIINEMA_COMPOSE_PROJECT_NAME}-compress,security-headers@file'
|
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${ASCIINEMA_COMPOSE_PROJECT_NAME}-compress,security-headers@file'
|
||||||
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-web-secure.service=${ASCIINEMA_COMPOSE_PROJECT_NAME}'
|
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-web-secure.service=${ASCIINEMA_COMPOSE_PROJECT_NAME}'
|
||||||
- 'traefik.http.services.${ASCIINEMA_COMPOSE_PROJECT_NAME}.loadbalancer.server.port=4000'
|
- 'traefik.http.services.${ASCIINEMA_COMPOSE_PROJECT_NAME}.loadbalancer.server.port=8080'
|
||||||
# Admin interface - HTTP to HTTPS redirect
|
# Admin interface - HTTP to HTTPS redirect
|
||||||
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web.middlewares=${ASCIINEMA_COMPOSE_PROJECT_NAME}-redirect-web-secure'
|
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web.middlewares=${ASCIINEMA_COMPOSE_PROJECT_NAME}-redirect-web-secure'
|
||||||
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web.rule=Host(`admin.${ASCIINEMA_TRAEFIK_HOST}`)'
|
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web.rule=Host(`admin.${ASCIINEMA_TRAEFIK_HOST}`)'
|
||||||
@@ -48,7 +59,7 @@ services:
|
|||||||
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web-secure.entrypoints=web-secure'
|
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web-secure.entrypoints=web-secure'
|
||||||
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web-secure.middlewares=${ASCIINEMA_COMPOSE_PROJECT_NAME}-auth,${ASCIINEMA_COMPOSE_PROJECT_NAME}-compress,security-headers@file'
|
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web-secure.middlewares=${ASCIINEMA_COMPOSE_PROJECT_NAME}-auth,${ASCIINEMA_COMPOSE_PROJECT_NAME}-compress,security-headers@file'
|
||||||
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web-secure.service=${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin'
|
- 'traefik.http.routers.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin-web-secure.service=${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin'
|
||||||
- 'traefik.http.services.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin.loadbalancer.server.port=4002'
|
- 'traefik.http.services.${ASCIINEMA_COMPOSE_PROJECT_NAME}-admin.loadbalancer.server.port=8080'
|
||||||
# Network
|
# Network
|
||||||
- 'traefik.docker.network=${NETWORK_NAME}'
|
- 'traefik.docker.network=${NETWORK_NAME}'
|
||||||
# Watchtower
|
# Watchtower
|
||||||
|
|||||||
@@ -15,58 +15,3 @@ config :asciinema, Asciinema.Emails.Mailer,
|
|||||||
verify: :verify_none,
|
verify: :verify_none,
|
||||||
versions: [:"tlsv1.2", :"tlsv1.3"]
|
versions: [:"tlsv1.2", :"tlsv1.3"]
|
||||||
]
|
]
|
||||||
|
|
||||||
# Custom theme configuration - inject custom CSS and favicon
|
|
||||||
defmodule AsciinemaWeb.CustomThemePlug do
|
|
||||||
@moduledoc """
|
|
||||||
Plug to inject custom CSS and favicon into HTML responses
|
|
||||||
"""
|
|
||||||
import Plug.Conn
|
|
||||||
|
|
||||||
def init(opts), do: opts
|
|
||||||
|
|
||||||
def call(conn, _opts) do
|
|
||||||
register_before_send(conn, fn conn ->
|
|
||||||
if html_response?(conn) do
|
|
||||||
inject_custom_theme(conn)
|
|
||||||
else
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp html_response?(conn) do
|
|
||||||
case get_resp_header(conn, "content-type") do
|
|
||||||
[content_type | _] -> String.contains?(content_type, "text/html")
|
|
||||||
[] -> false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp inject_custom_theme(conn) do
|
|
||||||
custom_head = """
|
|
||||||
<link rel="stylesheet" href="/theme/custom.css">
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/theme/favicon.svg">
|
|
||||||
"""
|
|
||||||
|
|
||||||
case conn.resp_body do
|
|
||||||
body when is_binary(body) ->
|
|
||||||
new_body = String.replace(body, "</head>", "#{custom_head}</head>")
|
|
||||||
%{conn | resp_body: new_body}
|
|
||||||
_ ->
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Configure Phoenix endpoint to serve custom theme files
|
|
||||||
config :asciinema, AsciinemaWeb.Endpoint,
|
|
||||||
# Serve theme files from /opt/app/etc/theme
|
|
||||||
static_dirs: %{
|
|
||||||
at: "/theme",
|
|
||||||
from: "/opt/app/etc/theme",
|
|
||||||
gzip: false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Inject the custom theme Plug into the endpoint
|
|
||||||
config :asciinema, AsciinemaWeb.Endpoint,
|
|
||||||
plug: AsciinemaWeb.CustomThemePlug
|
|
||||||
|
|||||||
40
asciinema/nginx/nginx.conf
Normal file
40
asciinema/nginx/nginx.conf
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Map to determine which backend to use based on host
|
||||||
|
map $http_host $backend_port {
|
||||||
|
default 4000;
|
||||||
|
~^admin\. 4002;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Proxy to actual asciinema server (port depends on subdomain)
|
||||||
|
location / {
|
||||||
|
proxy_pass http://asciinema_backend:$backend_port;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Inject custom JavaScript into HTML responses
|
||||||
|
sub_filter '</head>' '<script src="/theme/inject.js"></script></head>';
|
||||||
|
sub_filter_once on;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Serve theme files directly
|
||||||
|
location /theme/ {
|
||||||
|
alias /theme/;
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 29 KiB |
22
asciinema/theme/inject.js
Normal file
22
asciinema/theme/inject.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Inject custom theme CSS and favicon
|
||||||
|
(function() {
|
||||||
|
// Inject custom CSS
|
||||||
|
var link = document.createElement('link');
|
||||||
|
link.rel = 'stylesheet';
|
||||||
|
link.href = '/theme/custom.css';
|
||||||
|
document.head.appendChild(link);
|
||||||
|
|
||||||
|
// Inject custom favicon
|
||||||
|
var favicon = document.createElement('link');
|
||||||
|
favicon.rel = 'icon';
|
||||||
|
favicon.type = 'image/svg+xml';
|
||||||
|
favicon.href = '/theme/favicon.svg';
|
||||||
|
|
||||||
|
// Remove existing favicons first
|
||||||
|
var existingFavicons = document.querySelectorAll('link[rel*="icon"]');
|
||||||
|
existingFavicons.forEach(function(el) {
|
||||||
|
el.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.head.appendChild(favicon);
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user