Compare commits

...

3 Commits

Author SHA1 Message Date
Admin
168cb52ed0 fix(admin): use --color-surface for drawer bg (--color-bg was undefined)
Some checks failed
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 58s
Release / Check ui (push) Successful in 26s
Release / Test backend (push) Successful in 38s
Release / Docker / caddy (push) Successful in 45s
CI / Backend (pull_request) Successful in 26s
Release / Docker / ui (push) Failing after 10s
CI / UI (pull_request) Successful in 42s
Release / Docker / runner (push) Failing after 19s
Release / Docker / backend (push) Successful in 1m42s
Release / Gitea Release (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 18:53:58 +05:00
Admin
e1621a3ec2 fix(infra): move Redis to prod, fix LibreTranslate config loading
All checks were successful
CI / Backend (push) Successful in 46s
CI / UI (push) Successful in 52s
Release / Docker / caddy (push) Successful in 34s
Release / Test backend (push) Successful in 48s
Release / Check ui (push) Successful in 49s
CI / Backend (pull_request) Successful in 25s
CI / UI (pull_request) Successful in 56s
Release / Docker / runner (push) Successful in 1m46s
Release / Docker / ui (push) Successful in 2m30s
Release / Docker / backend (push) Successful in 2m47s
Release / Gitea Release (push) Successful in 13s
- Add Redis sidecar to prod docker-compose; backend connects locally (redis:6379)
- Caddy layer4 now proxies redis.libnovel.cc:6380 → local redis:6379 (not homelab LAN)
- Remove HOMELAB_REDIS_ADDR; homelab runner connects out to prod Redis via rediss://
- Remove local Redis from homelab runner compose; drop redis_data volume
- Fix config.Load() missing LibreTranslate section — LIBRETRANSLATE_URL was never read
2026-03-31 18:26:32 +05:00
Admin
10c7a48bc6 fix(admin): move mobile nav toggle into content area to avoid z-index conflict
Some checks failed
CI / Backend (push) Successful in 43s
CI / UI (push) Successful in 59s
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 27s
Release / Docker / caddy (push) Failing after 39s
CI / UI (pull_request) Successful in 25s
CI / Backend (pull_request) Successful in 44s
Release / Docker / ui (push) Successful in 2m5s
Release / Docker / runner (push) Successful in 2m27s
Release / Docker / backend (push) Successful in 3m12s
Release / Gitea Release (push) Has been skipped
The fixed top bar was hidden behind the main site navbar (z-50).
Replace with an inline 'Admin menu' button at the top of the content.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 17:07:28 +05:00
5 changed files with 56 additions and 54 deletions

View File

@@ -58,9 +58,9 @@
# ── Redis TCP proxy via layer4 ────────────────────────────────────────────
# Exposes prod Redis over TLS for Asynq job enqueueing from the homelab runner.
# Listens on :6380 (all interfaces). TLS is terminated here using the cert
# Listens on :6380 (all interfaces). TLS is terminated here using the cert
# for redis.libnovel.cc; traffic is proxied to the local Redis sidecar.
# Requires the caddy-l4 module in the custom Caddy build.
# Requires the caddy-l4 module in the custom Caddy build.
layer4 {
:6380 {
route {
@@ -73,7 +73,7 @@
}
proxy {
upstream redis:6379
}
}
}
}
}

View File

@@ -203,6 +203,11 @@ func Load() Config {
URL: envOr("POCKET_TTS_URL", ""),
},
LibreTranslate: LibreTranslate{
URL: envOr("LIBRETRANSLATE_URL", ""),
APIKey: envOr("LIBRETRANSLATE_API_KEY", ""),
},
HTTP: HTTP{
Addr: envOr("BACKEND_HTTP_ADDR", ":8080"),
},

View File

@@ -126,6 +126,26 @@ services:
timeout: 5s
retries: 5
# ─── Redis (Asynq task queue — accessed locally by backend, remotely by homelab runner) ──
redis:
image: redis:7-alpine
restart: unless-stopped
command: >
redis-server
--appendonly yes
--requirepass "${REDIS_PASSWORD}"
# No public port — backend reaches it via internal network.
# Homelab runner reaches it via Caddy TLS proxy on :6380 → redis:6379.
expose:
- "6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
# ─── Backend API ──────────────────────────────────────────────────────────────
backend:
image: kalekber/libnovel-backend:${GIT_TAG:-latest}
@@ -151,6 +171,8 @@ services:
condition: service_healthy
valkey:
condition: service_healthy
redis:
condition: service_healthy
# No public port — all traffic is routed via Caddy.
expose:
- "8080"
@@ -164,10 +186,9 @@ services:
GLITCHTIP_DSN: "${GLITCHTIP_DSN}"
OTEL_EXPORTER_OTLP_ENDPOINT: "${OTEL_EXPORTER_OTLP_ENDPOINT}"
OTEL_SERVICE_NAME: "backend"
# Asynq task queue — backend enqueues jobs to homelab Redis via Caddy TLS proxy.
# Set to "rediss://:password@redis.libnovel.cc:6380" in Doppler prd config.
# Leave empty to fall back to PocketBase polling.
REDIS_ADDR: "${REDIS_ADDR}"
# Asynq task queue — backend enqueues jobs to local Redis sidecar.
# Homelab runner connects to the same Redis via Caddy TLS proxy on :6380.
REDIS_ADDR: "redis:6379"
REDIS_PASSWORD: "${REDIS_PASSWORD}"
healthcheck:
test: ["CMD", "/healthcheck", "http://localhost:8080/health"]
@@ -382,12 +403,10 @@ services:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3 (QUIC)
- "6380:6380" # Redis TCP proxy (TLS) for homelab → Asynq
- "6380:6380" # Redis TCP proxy (TLS) for homelab runner → Asynq
environment:
DOMAIN: "${DOMAIN}"
CADDY_ACME_EMAIL: "${CADDY_ACME_EMAIL}"
# Homelab Redis address — Caddy TCP-proxies inbound :6380 to this.
HOMELAB_REDIS_ADDR: "${HOMELAB_REDIS_ADDR:?HOMELAB_REDIS_ADDR required for Redis TCP proxy}"
env_file:
- path: ./crowdsec/.crowdsec.env
required: false
@@ -421,6 +440,7 @@ volumes:
pb_data:
meili_data:
valkey_data:
redis_data:
caddy_data:
caddy_config:
caddy_logs:

View File

@@ -11,25 +11,10 @@
# - MEILI_URL → https://search.libnovel.cc (Caddy-proxied)
# - VALKEY_ADDR → unset (not exposed publicly)
# - RUNNER_SKIP_INITIAL_CATALOGUE_REFRESH=true
# - Redis service for Asynq task queue (local to homelab, exposed to prod via Caddy TCP proxy)
# - REDIS_ADDR → rediss://redis.libnovel.cc:6380 (prod Redis via Caddy TLS proxy)
# - LibreTranslate service for machine translation (internal network only)
services:
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis_data:/data
command: >
redis-server
--appendonly yes
--requirepass "${REDIS_PASSWORD}"
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
libretranslate:
image: libretranslate/libretranslate:latest
restart: unless-stopped
@@ -55,8 +40,6 @@ services:
restart: unless-stopped
stop_grace_period: 135s
depends_on:
redis:
condition: service_healthy
libretranslate:
condition: service_healthy
environment:
@@ -91,9 +74,10 @@ services:
LIBRETRANSLATE_URL: "http://libretranslate:5000"
LIBRETRANSLATE_API_KEY: "${LIBRETRANSLATE_API_KEY}"
# ── Asynq / Redis (local service) ───────────────────────────────────────
# The runner connects to the local Redis sidecar.
REDIS_ADDR: "redis:6379"
# ── Asynq / Redis (prod Redis via Caddy TLS proxy) ──────────────────────
# The runner connects to prod Redis over TLS: rediss://redis.libnovel.cc:6380.
# Caddy on prod terminates TLS and proxies to the local redis:6379 sidecar.
REDIS_ADDR: "${REDIS_ADDR}"
REDIS_PASSWORD: "${REDIS_PASSWORD}"
# ── Runner tuning ───────────────────────────────────────────────────────
@@ -117,6 +101,5 @@ services:
retries: 3
volumes:
redis_data:
libretranslate_models:
libretranslate_db:

View File

@@ -29,36 +29,18 @@
<!-- Mobile sidebar overlay -->
{#if sidebarOpen}
<button
class="fixed inset-0 z-20 bg-black/50 md:hidden"
class="fixed inset-0 z-40 bg-black/50 md:hidden"
onclick={() => (sidebarOpen = false)}
aria-label="Close sidebar"
></button>
{/if}
<div class="flex min-h-[calc(100vh-4rem)] gap-0">
<!-- Mobile top bar -->
<div class="md:hidden fixed top-0 left-0 right-0 z-10 h-16 flex items-center px-4 border-b border-(--color-border) bg-(--color-bg)">
<button
onclick={() => (sidebarOpen = !sidebarOpen)}
class="p-2 rounded-md text-(--color-muted) hover:text-(--color-text) hover:bg-(--color-surface-2) transition-colors"
aria-label="Toggle navigation"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{#if sidebarOpen}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
{:else}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
{/if}
</svg>
</button>
<span class="ml-3 text-sm font-semibold text-(--color-muted) uppercase tracking-widest">Admin</span>
</div>
<!-- Sidebar -->
<aside
class="
fixed top-0 left-0 h-full z-30 w-56 shrink-0 border-r border-(--color-border) px-3 py-6 flex flex-col gap-6
bg-(--color-bg) transition-transform duration-200
fixed top-0 left-0 h-full z-50 w-56 shrink-0 border-r border-(--color-border) px-3 py-6 flex flex-col gap-6
bg-(--color-surface) transition-transform duration-200
{sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
md:relative md:translate-x-0 md:w-48 md:z-auto md:top-auto md:h-auto
"
@@ -105,7 +87,19 @@
</aside>
<!-- Main content -->
<main class="flex-1 min-w-0 px-4 py-6 md:px-8 pt-20 md:pt-6">
<main class="flex-1 min-w-0 px-4 py-6 md:px-8">
<!-- Mobile nav toggle -->
<button
onclick={() => (sidebarOpen = true)}
class="md:hidden mb-4 flex items-center gap-2 text-sm text-(--color-muted) hover:text-(--color-text) transition-colors"
aria-label="Open navigation"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
Admin menu
</button>
{@render children?.()}
</main>
</div>