fix(deploy): apply Caddyfile changes on update (dir mount + recreate)
A single-file bind mount of the Caddyfile kept the original inode, so a git pull that replaces the file never reached a running caddy, and up -d does not recreate it for a content-only change — Caddy fixes (e.g. the 0.9.18 WebSocket fix) silently never deployed. Mount the docker/caddy directory instead (reflects the live file) and force-recreate the caddy container on every install/update so Caddyfile changes reliably take effect. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>feat/v1-foundation v0.9.19
parent
92969eb0dc
commit
a61feb1bef
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -13,6 +13,17 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
|
||||||
|
|
||||||
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
|
_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._
|
||||||
|
|
||||||
|
## [0.9.19] - 2026-06-19
|
||||||
|
|
||||||
|
### Behoben
|
||||||
|
- **Caddyfile-Änderungen wurden bei einem Update nicht übernommen.** Die Caddy-Config war als
|
||||||
|
**einzelne Datei** gemountet; ein `git pull` ersetzt die Datei (neue Inode), aber der laufende
|
||||||
|
Container hängt weiter an der alten — und `docker compose up -d` baut Caddy bei reiner
|
||||||
|
Inhaltsänderung nicht neu. Dadurch kamen Caddy-Fixes (z. B. der WebSocket-Fix aus 0.9.18) ohne
|
||||||
|
manuelles Eingreifen nie an. Jetzt wird das **Verzeichnis** `docker/caddy` gemountet (spiegelt die
|
||||||
|
Datei live) und `install.sh` baut den Caddy-Container bei jedem Deploy gezielt neu
|
||||||
|
(`up -d --force-recreate caddy`), sodass Caddyfile-Änderungen zuverlässig wirksam werden.
|
||||||
|
|
||||||
## [0.9.18] - 2026-06-19
|
## [0.9.18] - 2026-06-19
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// First tagged release is v0.1.0 (semantic, not -dev).
|
// First tagged release is v0.1.0 (semantic, not -dev).
|
||||||
'version' => '0.9.18',
|
'version' => '0.9.19',
|
||||||
|
|
||||||
// Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod
|
// Deployed commit + branch. install.sh bakes these into .env from the host's .git (the prod
|
||||||
// image ships no .git); the Versions page prefers them and falls back to a live .git read in
|
// image ships no .git); the Versions page prefers them and falls back to a live .git read in
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,11 @@ services:
|
||||||
# its X-Forwarded-Proto. Leave as 127.0.0.1/32 when Caddy terminates TLS itself.
|
# its X-Forwarded-Proto. Leave as 127.0.0.1/32 when Caddy terminates TLS itself.
|
||||||
TRUSTED_PROXY_CIDR: "${TRUSTED_PROXY_CIDR:-127.0.0.1/32}"
|
TRUSTED_PROXY_CIDR: "${TRUSTED_PROXY_CIDR:-127.0.0.1/32}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
# Mount the DIRECTORY (not the single Caddyfile): a single-file bind mount keeps the
|
||||||
|
# original inode, so a `git pull` that REPLACES the file would not reach a running
|
||||||
|
# container. A directory mount always reflects the current file, so `caddy reload` /
|
||||||
|
# a recreate picks up Caddyfile changes on update.
|
||||||
|
- ./docker/caddy:/etc/caddy:ro
|
||||||
- caddy-data:/data # ACME account + certs — MUST persist across recreate
|
- caddy-data:/data # ACME account + certs — MUST persist across recreate
|
||||||
- caddy-config:/config
|
- caddy-config:/config
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,9 @@ esac
|
||||||
# ── [5/9] stack ──────────────────────────────────────────────────────
|
# ── [5/9] stack ──────────────────────────────────────────────────────
|
||||||
phase 5/9 "Stack starten"
|
phase 5/9 "Stack starten"
|
||||||
$COMPOSE up -d
|
$COMPOSE up -d
|
||||||
|
# Caddyfile changes ship as edits to a bind-mounted file; `up -d` does NOT recreate caddy for a
|
||||||
|
# content-only change, so force it to re-read the (possibly updated) Caddyfile on every deploy.
|
||||||
|
$COMPOSE up -d --force-recreate caddy >/dev/null 2>&1 || true
|
||||||
info "Container gestartet"
|
info "Container gestartet"
|
||||||
|
|
||||||
# ── host watchers (restart + update sentinels) — idempotent, best-effort ────
|
# ── host watchers (restart + update sentinels) — idempotent, best-effort ────
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,14 @@
|
||||||
namespace Tests\Feature;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Http\Middleware\PanelScheme;
|
use App\Http\Middleware\PanelScheme;
|
||||||
|
use App\Livewire\System\Index as SystemIndex;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
use App\Services\DeploymentService;
|
use App\Services\DeploymentService;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
use Livewire\Livewire;
|
||||||
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
@ -100,6 +103,17 @@ class ExternalTlsModeTest extends TestCase
|
||||||
$this->runPanelScheme(Request::create('http://evil.example.com/dashboard'));
|
$this->runPanelScheme(Request::create('http://evil.example.com/dashboard'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_status_card_does_not_claim_lets_encrypt_in_external_mode(): void
|
||||||
|
{
|
||||||
|
$this->actingAs(User::factory()->create(['must_change_password' => false]));
|
||||||
|
Setting::put('panel_domain', 'panel.example.com');
|
||||||
|
app(DeploymentService::class)->setTlsMode('external');
|
||||||
|
|
||||||
|
Livewire::test(SystemIndex::class)
|
||||||
|
->assertSee(__('system.tls_active_external_title'))
|
||||||
|
->assertDontSee('Let\'s Encrypt');
|
||||||
|
}
|
||||||
|
|
||||||
public function test_reverb_client_uses_wss_on_443_for_the_active_domain(): void
|
public function test_reverb_client_uses_wss_on_443_for_the_active_domain(): void
|
||||||
{
|
{
|
||||||
Setting::put('panel_domain', 'panel.example.com');
|
Setting::put('panel_domain', 'panel.example.com');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue