feat(caddy): single owner via CADDY_MANAGER env flag
One Caddy serves the whole container and POST /load replaces the entire config, so two instances pushing would clobber each other. Now only the instance with CADDY_MANAGER=true (production) pushes, seeds routes from the Caddyfile, and accepts route mutations (others get 403). /api/auth/config exposes caddyManaged so the non-owner Settings UI shows the Caddy section read-only. The installer sets the flag on the production .env only.
This commit is contained in:
@ -191,6 +191,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
const [semaphoreTestResult, setSemaphoreTestResult] = useState<string | null>(null);
|
||||
|
||||
const [caddyEnabled, setCaddyEnabled] = useState(false);
|
||||
const [caddyManaged, setCaddyManaged] = useState(true);
|
||||
const [caddyAdminUrl, setCaddyAdminUrl] = useState('http://localhost:2019');
|
||||
const [caddyStatus, setCaddyStatus] = useState<'unknown' | 'available' | 'unavailable'>('unknown');
|
||||
const [caddyRoutes, setCaddyRoutes] = useState<CaddyRoute[]>([]);
|
||||
@ -219,7 +220,10 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
loadDbInfo();
|
||||
fetch('/api/auth/config')
|
||||
.then(r => r.json())
|
||||
.then(d => { if (d.effectiveRedirectUri) setEffectiveRedirectUri(d.effectiveRedirectUri); })
|
||||
.then(d => {
|
||||
if (d.effectiveRedirectUri) setEffectiveRedirectUri(d.effectiveRedirectUri);
|
||||
setCaddyManaged(d.caddyManaged !== false);
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
@ -906,21 +910,34 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className={`text-[10px] font-semibold font-mono ${caddyEnabled ? 'text-sky-400' : 'text-slate-600'}`}>
|
||||
{caddyEnabled ? 'ENABLED' : 'DISABLED'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCaddyEnabled((v: boolean) => !v)}
|
||||
className={`relative w-10 h-5 rounded-full transition-all duration-200 focus:outline-none ${caddyEnabled ? 'bg-sky-600 shadow-[0_0_10px_rgba(2,132,199,0.4)]' : 'bg-slate-800 border border-slate-700'}`}
|
||||
>
|
||||
<span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-all duration-200 ${caddyEnabled ? 'left-5' : 'left-0.5'}`} />
|
||||
</button>
|
||||
{caddyManaged ? (
|
||||
<>
|
||||
<span className={`text-[10px] font-semibold font-mono ${caddyEnabled ? 'text-sky-400' : 'text-slate-600'}`}>
|
||||
{caddyEnabled ? 'ENABLED' : 'DISABLED'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCaddyEnabled((v: boolean) => !v)}
|
||||
className={`relative w-10 h-5 rounded-full transition-all duration-200 focus:outline-none ${caddyEnabled ? 'bg-sky-600 shadow-[0_0_10px_rgba(2,132,199,0.4)]' : 'bg-slate-800 border border-slate-700'}`}
|
||||
>
|
||||
<span className={`absolute top-0.5 w-4 h-4 bg-white rounded-full shadow transition-all duration-200 ${caddyEnabled ? 'left-5' : 'left-0.5'}`} />
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-[10px] font-semibold font-mono text-slate-500">MANAGED BY PRODUCTION</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-slate-800/60" />
|
||||
|
||||
{!caddyManaged && (
|
||||
<p className="text-[11px] font-mono text-slate-500 leading-relaxed">
|
||||
Caddy is centrally managed by the production instance (ghostgrid). Manage proxy routes there.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{caddyManaged && (
|
||||
<div className={`space-y-5 transition-opacity duration-200 ${!caddyEnabled ? 'opacity-40 pointer-events-none' : ''}`}>
|
||||
<FieldRow label="Caddy Admin URL" hint="Default: http://localhost:2019">
|
||||
<Input value={caddyAdminUrl} onChange={setCaddyAdminUrl} placeholder="http://localhost:2019" monospace icon={<Globe className="w-3.5 h-3.5" />} />
|
||||
@ -1047,6 +1064,7 @@ export default function Settings({ currentUser: _currentUser }: SettingsProps) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SectionCard>
|
||||
|
||||
{/* ── Database ── */}
|
||||
|
||||
Reference in New Issue
Block a user