Implement shared Caddy setup with initializing and unavailable pages for various apps
- Refactor domain scripts for Ghost, Gitea, Jellyfin, Metabase, n8n, NocoDB, Open WebUI, OpenClaw, PicoClaw, Plausible, Signoz, Uptime Kuma, and Vaultwarden to utilize a common Caddy setup script. - Introduce `caddy-setup.sh` for managing Caddy configurations and handling app initialization states. - Create `initializing.html` and `unavailable.html` pages to provide user feedback during app deployment and downtime. - Update domain handling logic to ensure seamless transitions between initializing and operational states. - Enhance user experience by providing visual indicators for app status during setup and maintenance.
This commit is contained in:
57
_common/DESIGN.md
Normal file
57
_common/DESIGN.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
# DESIGN.md — Excloud App Status Pages
|
||||
---
|
||||
|
||||
## The Concept
|
||||
|
||||
**"Control room calm."** These pages exist in a moment of vulnerability — someone just paid for something and is waiting for it to exist. The design absorbs anxiety by projecting quiet confidence. Not "please wait," but "we've got this."
|
||||
|
||||
## The Hook
|
||||
|
||||
Two different *symbols* that communicate state without words:
|
||||
|
||||
- **Initializing:** A radar-like pulse beacon — concentric rings expanding outward from a warm glowing core. It reads as "scanning, reaching, deploying" — active progress without a progress bar (which would be dishonest since we don't know percentages).
|
||||
- **Unavailable:** Two vertical pause bars — the universal "paused" symbol. Instantly communicates "temporarily stopped, not broken." It's the difference between a dead screen and a deliberate pause.
|
||||
|
||||
Both use the same design language but are immediately distinguishable at a glance.
|
||||
|
||||
## Typography Rationale
|
||||
|
||||
System font stack. This is deliberate, not lazy — these pages need to load *instantly* on first paint with zero layout shift. A custom font would flash or delay, exactly when the user needs immediate reassurance. The system stack also feels "native" to the platform, like a real system notification rather than a marketing page pretending nothing is wrong.
|
||||
|
||||
Tight negative letter-spacing on headings (-0.02em) gives them weight without needing a heavier font. The all-caps labels (0.15em tracking) create structural hierarchy — they read as metadata, not prose.
|
||||
|
||||
## Color Rationale
|
||||
|
||||
Warm amber/copper accent (#c4956a for initializing, #b5874f for unavailable) against near-black. Why warm:
|
||||
|
||||
- Cool blues/greens say "corporate status page" — the thing you see when AWS is down and you're panicking. Wrong association.
|
||||
- Warm amber says "campfire," "instrument panel," "something alive." It's the color of active signals, not error states.
|
||||
- The unavailable page shifts slightly cooler/darker in its amber — still the same family, but perceptibly "dimmer," like a light that's been turned down, not off.
|
||||
|
||||
The backgrounds use extremely subtle texture — a grid for initializing (structured, "things are happening in order"), dots for unavailable (quieter, ambient). Both are masked to only appear in the center, creating depth without clutter.
|
||||
|
||||
## Layout / Structure Rationale
|
||||
|
||||
Vertically centered, single column, no card/container. The content floats in space rather than sitting in a box. Cards create a boundary between "the page" and "the content" — here the content IS the page. There's nowhere else to look, nothing else to do. The design embraces that.
|
||||
|
||||
The status bar at the bottom (pill-shaped, border, dot + text) acts as a grounding element — it's the one piece that feels "UI" rather than "page," giving the user something concrete to anchor to. "This page refreshes automatically" is the key information and it's given its own component.
|
||||
|
||||
## What Was Rejected
|
||||
|
||||
- **Progress bars / steps.** Considered showing "Step 1: Pulling images, Step 2: Starting containers" etc. Rejected because (a) we don't have real-time progress data from inside the VM, so it would be fake, and (b) non-technical users don't care about docker pulls. Honesty over theater.
|
||||
- **Large app icons/logos.** Considered fetching and displaying the app's icon. Rejected — we don't have guaranteed access to icons at the Caddy level (it's static HTML), and a missing/broken image would undermine trust more than no image at all.
|
||||
- **Animated gradient backgrounds.** The obvious "premium loading screen" move. Rejected — it reads as marketing rather than infrastructure. These pages should feel like part of the *platform*, not a splash screen.
|
||||
- **Any JavaScript.** Could have done smoother animations or real-time status checks via JS. Rejected — pure CSS keeps the page weight near zero, works with CSP restrictions, and the meta-refresh approach is honest about what's happening (polling, not streaming).
|
||||
|
||||
## Tone & Texture
|
||||
|
||||
The initializing page should feel like watching a satellite deploy — calm, methodical, the machinery is working. The grid texture reinforces "structured process."
|
||||
|
||||
The unavailable page should feel like a brief intermission — the lights are dimmed, not off. The dot texture is softer, the pause symbol is immediately readable, and "taking a moment" is human language, not tech jargon.
|
||||
|
||||
Both avoid the word "error." Neither page is an error. One is a process, the other is a pause.
|
||||
|
||||
## The Small Detail Nobody Will Notice
|
||||
|
||||
The beacon rings on the initializing page are staggered by 0.8s, not the typical 0.5s. This creates a slower, more deliberate rhythm — closer to breathing than to a loading spinner. It subconsciously signals "this is supposed to take a minute" rather than "something is stuck."
|
||||
130
_common/caddy-setup.sh
Normal file
130
_common/caddy-setup.sh
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Shared Caddy setup with initializing/unavailable pages.
|
||||
#
|
||||
# Usage in domain.sh:
|
||||
# source /var/excloud/scripts/caddy-setup.sh
|
||||
# setup_initializing_page "$DOMAIN" "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
# ... start containers ...
|
||||
# wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" &
|
||||
#
|
||||
# For apps with custom Caddyfile (e.g. signoz with multi-route), use:
|
||||
# write_loading_pages "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
# setup_initializing_page "$DOMAIN" "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
# ... start containers ...
|
||||
# wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" "$CUSTOM_CADDYFILE_CONTENT" &
|
||||
|
||||
SCRIPT_DIR_CADDY="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
write_loading_pages() {
|
||||
local app_name="$1"
|
||||
local app_dir="$2"
|
||||
|
||||
local display_name
|
||||
display_name="$(echo "$app_name" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')"
|
||||
|
||||
mkdir -p "${app_dir}/.excloud"
|
||||
|
||||
cp "${SCRIPT_DIR_CADDY}/initializing.html" "${app_dir}/.excloud/initializing.html"
|
||||
cp "${SCRIPT_DIR_CADDY}/unavailable.html" "${app_dir}/.excloud/unavailable.html"
|
||||
|
||||
sed -i "s/APP_DISPLAY_NAME/${display_name}/g" "${app_dir}/.excloud/initializing.html"
|
||||
sed -i "s/APP_DISPLAY_NAME/${display_name}/g" "${app_dir}/.excloud/unavailable.html"
|
||||
}
|
||||
|
||||
# Phase 1: Serve the initializing page immediately via Caddy file_server.
|
||||
setup_initializing_page() {
|
||||
local domain="$1"
|
||||
local app_name="$2"
|
||||
local app_dir="$3"
|
||||
|
||||
write_loading_pages "$app_name" "$app_dir"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${domain} {
|
||||
root * ${app_dir}/.excloud
|
||||
rewrite * /initializing.html
|
||||
file_server
|
||||
}
|
||||
EOF
|
||||
|
||||
systemctl enable caddy
|
||||
systemctl reload caddy
|
||||
echo "Caddy serving initializing page for ${app_name}"
|
||||
}
|
||||
|
||||
# Phase 2 (runs in background): Poll the upstream port, then switch Caddy to reverse_proxy.
|
||||
# Accepts an optional 4th argument with custom Caddyfile content for apps like signoz.
|
||||
wait_and_switch_to_proxy() {
|
||||
local domain="$1"
|
||||
local port="$2"
|
||||
local app_dir="$3"
|
||||
local custom_caddyfile="${4:-}"
|
||||
|
||||
# Wait for the app to respond (up to 20 minutes)
|
||||
local start_time
|
||||
start_time="$(date +%s)"
|
||||
while ! curl -fsS -o /dev/null "http://127.0.0.1:${port}" 2>/dev/null; do
|
||||
if [ $(( $(date +%s) - start_time )) -ge 1200 ]; then
|
||||
echo "App did not become ready within 20 minutes" >&2
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# Switch to reverse proxy with handle_errors for unavailable page
|
||||
if [ -n "$custom_caddyfile" ]; then
|
||||
echo "$custom_caddyfile" > /etc/caddy/Caddyfile
|
||||
else
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${domain} {
|
||||
reverse_proxy 127.0.0.1:${port}
|
||||
handle_errors {
|
||||
root * ${app_dir}/.excloud
|
||||
rewrite * /unavailable.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
touch "${app_dir}/.excloud/.ready"
|
||||
systemctl reload caddy
|
||||
echo "App is ready — Caddy switched to reverse proxy"
|
||||
}
|
||||
|
||||
# Quick domain swap for an already-running app. Skips the initializing page
|
||||
# and background watcher — just updates the Caddyfile and reloads.
|
||||
# Accepts an optional 4th argument with custom Caddyfile content.
|
||||
switch_domain() {
|
||||
local domain="$1"
|
||||
local port="$2"
|
||||
local app_dir="$3"
|
||||
local custom_caddyfile="${4:-}"
|
||||
|
||||
write_loading_pages "$(basename "$app_dir")" "$app_dir"
|
||||
|
||||
if [ -n "$custom_caddyfile" ]; then
|
||||
echo "$custom_caddyfile" > /etc/caddy/Caddyfile
|
||||
else
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${domain} {
|
||||
reverse_proxy 127.0.0.1:${port}
|
||||
handle_errors {
|
||||
root * ${app_dir}/.excloud
|
||||
rewrite * /unavailable.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
systemctl reload caddy
|
||||
echo "Domain switched to ${domain}"
|
||||
}
|
||||
|
||||
# Returns 0 if the app has completed first-time setup, 1 otherwise.
|
||||
is_app_ready() {
|
||||
local app_dir="$1"
|
||||
[ -f "${app_dir}/.excloud/.ready" ]
|
||||
}
|
||||
228
_common/initializing.html
Normal file
228
_common/initializing.html
Normal file
@@ -0,0 +1,228 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="refresh" content="5">
|
||||
<title>APP_DISPLAY_NAME — Deploying</title>
|
||||
<style>
|
||||
/* --- Reset & Base --- */
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
:root {
|
||||
--surface: #0a0a0a;
|
||||
--surface-raised: #111111;
|
||||
--surface-border: #1a1a1a;
|
||||
--text-primary: #e8e4df;
|
||||
--text-secondary: #6b6560;
|
||||
--text-dim: #3a3632;
|
||||
--accent: #c4956a;
|
||||
--accent-glow: rgba(196, 149, 106, 0.08);
|
||||
--pulse-ring: rgba(196, 149, 106, 0.15);
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background: var(--surface);
|
||||
color: var(--text-primary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* --- Ambient grid texture --- */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(var(--surface-border) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--surface-border) 1px, transparent 1px);
|
||||
background-size: 60px 60px;
|
||||
opacity: 0.3;
|
||||
mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, black 20%, transparent 70%);
|
||||
-webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 50%, black 20%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* --- Main content --- */
|
||||
.deploy-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* --- Pulse beacon --- */
|
||||
.beacon {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.beacon::before,
|
||||
.beacon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid var(--accent);
|
||||
}
|
||||
|
||||
.beacon::before {
|
||||
animation: beacon-ping 2.4s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.beacon::after {
|
||||
animation: beacon-ping 2.4s cubic-bezier(0, 0, 0.2, 1) infinite 0.8s;
|
||||
}
|
||||
|
||||
.beacon-core {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 20px var(--pulse-ring), 0 0 40px var(--accent-glow);
|
||||
animation: core-breathe 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes beacon-ping {
|
||||
0% { transform: scale(0.3); opacity: 0.8; }
|
||||
100% { transform: scale(1.2); opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes core-breathe {
|
||||
0%, 100% { opacity: 0.7; transform: translate(-50%, -50%) scale(1); }
|
||||
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.15); }
|
||||
}
|
||||
|
||||
/* --- App label --- */
|
||||
.app-label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
margin-bottom: 0.75rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* --- Heading --- */
|
||||
.heading {
|
||||
font-size: clamp(1.4rem, 4vw, 1.8rem);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* --- Body copy --- */
|
||||
.body-text {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.7;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
/* --- Status bar --- */
|
||||
.status-bar {
|
||||
margin-top: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: status-blink 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes status-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* --- Footer --- */
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* --- Responsive --- */
|
||||
@media (max-width: 480px) {
|
||||
.deploy-container { padding: 1.5rem; }
|
||||
.beacon { width: 52px; height: 52px; margin-bottom: 2rem; }
|
||||
}
|
||||
|
||||
/* --- Reduced motion --- */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.beacon::before, .beacon::after, .beacon-core, .status-dot {
|
||||
animation: none;
|
||||
}
|
||||
.beacon::before { transform: scale(0.8); opacity: 0.3; }
|
||||
.beacon::after { transform: scale(1.1); opacity: 0.15; }
|
||||
.beacon-core { opacity: 0.9; }
|
||||
.status-dot { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="deploy-container">
|
||||
<div class="beacon">
|
||||
<div class="beacon-core"></div>
|
||||
</div>
|
||||
<div class="app-label">Deploying</div>
|
||||
<h1 class="heading">APP_DISPLAY_NAME is on its way</h1>
|
||||
<p class="body-text">Your app is being installed and configured. This usually takes a few minutes.</p>
|
||||
<div class="status-bar">
|
||||
<div class="status-dot"></div>
|
||||
<span class="status-text">Setting up — this page refreshes automatically</span>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<span class="footer-text">Powered by Excloud</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
222
_common/unavailable.html
Normal file
222
_common/unavailable.html
Normal file
@@ -0,0 +1,222 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="refresh" content="5">
|
||||
<title>APP_DISPLAY_NAME — Unavailable</title>
|
||||
<style>
|
||||
/* --- Reset & Base --- */
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
:root {
|
||||
--surface: #0a0a0a;
|
||||
--surface-raised: #111111;
|
||||
--surface-border: #1a1a1a;
|
||||
--text-primary: #e8e4df;
|
||||
--text-secondary: #6b6560;
|
||||
--text-dim: #3a3632;
|
||||
--caution: #b5874f;
|
||||
--caution-dim: rgba(181, 135, 79, 0.06);
|
||||
--caution-border: rgba(181, 135, 79, 0.12);
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background: var(--surface);
|
||||
color: var(--text-primary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* --- Subtle noise/grain feel via dots --- */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: radial-gradient(circle at center, var(--surface-border) 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
opacity: 0.25;
|
||||
mask-image: radial-gradient(ellipse 40% 40% at 50% 50%, black 10%, transparent 70%);
|
||||
-webkit-mask-image: radial-gradient(ellipse 40% 40% at 50% 50%, black 10%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* --- Main content --- */
|
||||
.unavail-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* --- Pause indicator (two vertical bars — universal "paused" symbol) --- */
|
||||
.pause-icon {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-bottom: 2.5rem;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.pause-bar {
|
||||
width: 6px;
|
||||
height: 32px;
|
||||
background: var(--caution);
|
||||
border-radius: 2px;
|
||||
animation: bar-fade 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.pause-bar:nth-child(2) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
@keyframes bar-fade {
|
||||
0%, 100% { opacity: 0.5; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* --- Caution banner --- */
|
||||
.caution-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.35rem 0.85rem;
|
||||
background: var(--caution-dim);
|
||||
border: 1px solid var(--caution-border);
|
||||
border-radius: 100px;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.caution-badge-dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: var(--caution);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.caution-badge-text {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--caution);
|
||||
}
|
||||
|
||||
/* --- Heading --- */
|
||||
.heading {
|
||||
font-size: clamp(1.4rem, 4vw, 1.8rem);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* --- Body copy --- */
|
||||
.body-text {
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.7;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
/* --- Status bar --- */
|
||||
.status-bar {
|
||||
margin-top: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--caution);
|
||||
border-radius: 50%;
|
||||
animation: status-blink 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes status-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* --- Footer --- */
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
/* --- Responsive --- */
|
||||
@media (max-width: 480px) {
|
||||
.unavail-container { padding: 1.5rem; }
|
||||
.pause-bar { height: 26px; width: 5px; }
|
||||
.pause-icon { margin-bottom: 2rem; }
|
||||
}
|
||||
|
||||
/* --- Reduced motion --- */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.pause-bar, .status-dot {
|
||||
animation: none;
|
||||
}
|
||||
.pause-bar { opacity: 0.8; }
|
||||
.status-dot { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="unavail-container">
|
||||
<div class="pause-icon">
|
||||
<div class="pause-bar"></div>
|
||||
<div class="pause-bar"></div>
|
||||
</div>
|
||||
<div class="caution-badge">
|
||||
<div class="caution-badge-dot"></div>
|
||||
<span class="caution-badge-text">Temporarily unavailable</span>
|
||||
</div>
|
||||
<h1 class="heading">APP_DISPLAY_NAME is taking a moment</h1>
|
||||
<p class="body-text">The app is restarting or undergoing brief maintenance. It should be back shortly.</p>
|
||||
<div class="status-bar">
|
||||
<div class="status-dot"></div>
|
||||
<span class="status-text">Recovering — this page refreshes automatically</span>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<span class="footer-text">Powered by Excloud</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
APP_NAME="erpnext"
|
||||
APP_DIR="/var/excloud/apps"
|
||||
APP_UPSTREAM_PORT="${EXC_APP_UPSTREAM_PORT:-8080}"
|
||||
|
||||
DOMAIN="${1}"
|
||||
|
||||
if [ -z "${DOMAIN}" ]; then
|
||||
@@ -9,13 +12,17 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${DOMAIN} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
ERPNEXT_DIR="${APP_DIR}/${APP_NAME}"
|
||||
COMPOSE_FILE="${ERPNEXT_DIR}/pwd.yml"
|
||||
|
||||
echo "Caddyfile updated"
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
systemctl enable caddy
|
||||
systemctl reload caddy
|
||||
if is_app_ready "$ERPNEXT_DIR"; then
|
||||
# Update ERPNext site host_name for correct URLs in emails/redirects
|
||||
docker compose -f "${COMPOSE_FILE}" exec -T backend bench --site frontend set-config host_name "https://${DOMAIN}"
|
||||
docker compose -f "${COMPOSE_FILE}" restart backend frontend websocket
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$ERPNEXT_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$ERPNEXT_DIR"
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$ERPNEXT_DIR" &
|
||||
fi
|
||||
|
||||
@@ -54,20 +54,6 @@ sed -i \
|
||||
"${COMPOSE_FILE}"
|
||||
sed -i "s/\"8080:8080\"/\"127.0.0.1:${APP_UPSTREAM_PORT}:8080\"/" "${COMPOSE_FILE}"
|
||||
|
||||
docker compose -f "${COMPOSE_FILE}" up -d
|
||||
|
||||
start_time="$(date +%s)"
|
||||
while true; do
|
||||
if curl -fsS "http://127.0.0.1:${APP_UPSTREAM_PORT}" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
|
||||
if [ $(( $(date +%s) - start_time )) -ge 900 ]; then
|
||||
echo "ERPNext did not become ready in time" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
done
|
||||
|
||||
bash "${SCRIPT_DIR}/domain.sh" "${DOMAIN}"
|
||||
|
||||
docker compose -f "${COMPOSE_FILE}" up -d
|
||||
|
||||
@@ -29,6 +29,7 @@ fi
|
||||
DATABASE_ROOT_PASSWORD="$(cat "${ROOT_PASSWORD_FILE}")"
|
||||
DATABASE_PASSWORD="$(cat "${DATABASE_PASSWORD_FILE}")"
|
||||
|
||||
# Always rewrite .env with current domain
|
||||
cat > "${ENV_FILE}" <<EOF
|
||||
DOMAIN=${DOMAIN}
|
||||
DATABASE_ROOT_PASSWORD=${DATABASE_ROOT_PASSWORD}
|
||||
@@ -44,14 +45,14 @@ services:
|
||||
- "127.0.0.1:${APP_UPSTREAM_PORT}:2368"
|
||||
EOF
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${DOMAIN} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$GHOST_DIR"; then
|
||||
# Restart to pick up new domain from .env
|
||||
docker compose -f "${GHOST_DIR}/compose.yml" -f "${OVERRIDE_FILE}" up -d db ghost
|
||||
systemctl reload caddy
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$GHOST_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$GHOST_DIR"
|
||||
docker compose -f "${GHOST_DIR}/compose.yml" -f "${OVERRIDE_FILE}" up -d db ghost
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$GHOST_DIR" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,22 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
GITEA_DIR="${APP_DIR}/${APP_NAME}"
|
||||
COMPOSE_FILE="${GITEA_DIR}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$GITEA_DIR"; then
|
||||
# Update domain in docker-compose env vars
|
||||
sed -i \
|
||||
-e "s|GITEA__server__ROOT_URL: .*|GITEA__server__ROOT_URL: https://${DOMAIN}/|" \
|
||||
-e "s|GITEA__server__DOMAIN: .*|GITEA__server__DOMAIN: ${DOMAIN}|" \
|
||||
-e "s|GITEA__server__SSH_DOMAIN: .*|GITEA__server__SSH_DOMAIN: ${DOMAIN}|" \
|
||||
"${COMPOSE_FILE}"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$GITEA_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$GITEA_DIR"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$GITEA_DIR" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,14 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$APP_DIR/$APP_NAME"; then
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,18 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
METABASE_DIR="${APP_DIR}/${APP_NAME}"
|
||||
COMPOSE_FILE="${METABASE_DIR}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$METABASE_DIR"; then
|
||||
# Update domain in docker-compose env vars
|
||||
sed -i "s|MB_SITE_URL: .*|MB_SITE_URL: https://${DOMAIN}|" "${COMPOSE_FILE}"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$METABASE_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$METABASE_DIR"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$METABASE_DIR" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,21 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
N8N_DIR="${APP_DIR}/${APP_NAME}"
|
||||
COMPOSE_FILE="${N8N_DIR}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$N8N_DIR"; then
|
||||
# Update domain in docker-compose env vars
|
||||
sed -i \
|
||||
-e "s|N8N_HOST: .*|N8N_HOST: ${DOMAIN}|" \
|
||||
-e "s|WEBHOOK_URL: .*|WEBHOOK_URL: https://${DOMAIN}/|" \
|
||||
"${COMPOSE_FILE}"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$N8N_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$N8N_DIR"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$N8N_DIR" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,14 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$APP_DIR/$APP_NAME"; then
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,14 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$APP_DIR/$APP_NAME"; then
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" &
|
||||
fi
|
||||
|
||||
@@ -44,20 +44,21 @@ OPENCLAW_GATEWAY_BIND=lan
|
||||
OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN}
|
||||
EOF
|
||||
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
cd "${OPENCLAW_DIR}"
|
||||
rm -f "${LEGACY_OVERRIDE_FILE}"
|
||||
|
||||
if is_app_ready "$OPENCLAW_DIR"; then
|
||||
# Update allowed origins for the new domain
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js config set gateway.controlUi.allowedOrigins "[\"https://${DOMAIN}\",\"http://localhost:${APP_UPSTREAM_PORT}\",\"http://127.0.0.1:${APP_UPSTREAM_PORT}\"]" --strict-json
|
||||
docker compose -f docker-compose.yml up -d openclaw-gateway
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$OPENCLAW_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$OPENCLAW_DIR"
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js config set gateway.mode local
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js config set gateway.bind lan
|
||||
docker compose run --rm --no-deps --entrypoint node openclaw-gateway dist/index.js config set gateway.controlUi.allowedOrigins "[\"https://${DOMAIN}\",\"http://localhost:${APP_UPSTREAM_PORT}\",\"http://127.0.0.1:${APP_UPSTREAM_PORT}\"]" --strict-json
|
||||
docker compose -f docker-compose.yml up -d openclaw-gateway
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${DOMAIN} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
systemctl reload caddy
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$OPENCLAW_DIR" &
|
||||
fi
|
||||
|
||||
@@ -32,16 +32,13 @@ services:
|
||||
- "127.0.0.1:${APP_BRIDGE_PORT}:18790"
|
||||
EOF
|
||||
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
if is_app_ready "$PICOCLAW_DIR"; then
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$PICOCLAW_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$PICOCLAW_DIR"
|
||||
cd "${PICOCLAW_DIR}"
|
||||
docker compose -f docker/docker-compose.yml -f docker/compose.override.yml --profile launcher up -d
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${DOMAIN} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
systemctl reload caddy
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$PICOCLAW_DIR" &
|
||||
fi
|
||||
|
||||
@@ -23,6 +23,7 @@ fi
|
||||
|
||||
SECRET_KEY_BASE="$(tr -d '\n' < "${SECRET_KEY_FILE}")"
|
||||
|
||||
# Always rewrite .env with current domain
|
||||
cat > "${ENV_FILE}" <<EOF
|
||||
BASE_URL=https://${DOMAIN}
|
||||
SECRET_KEY_BASE=${SECRET_KEY_BASE}
|
||||
@@ -36,14 +37,14 @@ services:
|
||||
- "127.0.0.1:${APP_UPSTREAM_PORT}:80"
|
||||
EOF
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
https://${DOMAIN} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$PLAUSIBLE_DIR"; then
|
||||
# Restart to pick up new domain from .env
|
||||
docker compose -f "${PLAUSIBLE_DIR}/compose.yml" -f "${OVERRIDE_FILE}" up -d
|
||||
systemctl reload caddy
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$PLAUSIBLE_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$PLAUSIBLE_DIR"
|
||||
docker compose -f "${PLAUSIBLE_DIR}/compose.yml" -f "${OVERRIDE_FILE}" up -d
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$PLAUSIBLE_DIR" &
|
||||
fi
|
||||
|
||||
@@ -14,7 +14,6 @@ if [ -z "$DOMAIN" ]; then
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
INTERNAL_URL="https://internal-${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/signoz/deploy/docker/docker-compose.yaml"
|
||||
|
||||
set_env() {
|
||||
@@ -32,13 +31,17 @@ set_env() {
|
||||
fi
|
||||
}
|
||||
|
||||
set_env "SIGNOZ_GLOBAL_EXTERNAL_URL" "${URL}" ".services.signoz.environment"
|
||||
set_env "SIGNOZ_GLOBAL_INGESTION_URL" "${URL}" ".services.signoz.environment"
|
||||
set_env "SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL_URL" "${URL}" ".services.signoz.environment"
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
cat > /etc/caddy/Caddyfile << EOF
|
||||
# Signoz needs a custom Caddyfile with multi-route config
|
||||
read -r -d '' SIGNOZ_CADDYFILE <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
handle_errors {
|
||||
root * ${APP_DIR}/${APP_NAME}/.excloud
|
||||
rewrite * /unavailable.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
|
||||
${URL}:4317 {
|
||||
@@ -48,20 +51,17 @@ ${URL}:4317 {
|
||||
${URL}:4318 {
|
||||
reverse_proxy 127.0.0.1:44318
|
||||
}
|
||||
# ${INTERNAL_URL} {
|
||||
# reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
# }
|
||||
#
|
||||
# ${INTERNAL_URL}:4317 {
|
||||
# reverse_proxy h2c://127.0.0.1:44317
|
||||
# }
|
||||
#
|
||||
# ${INTERNAL_URL}:4318 {
|
||||
# reverse_proxy 127.0.0.1:44318
|
||||
# }
|
||||
EOF
|
||||
|
||||
echo "Caddyfile updated"
|
||||
set_env "SIGNOZ_GLOBAL_EXTERNAL_URL" "${URL}" ".services.signoz.environment"
|
||||
set_env "SIGNOZ_GLOBAL_INGESTION_URL" "${URL}" ".services.signoz.environment"
|
||||
set_env "SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL_URL" "${URL}" ".services.signoz.environment"
|
||||
|
||||
if is_app_ready "$APP_DIR/$APP_NAME"; then
|
||||
docker compose -f $COMPOSE_FILE up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" "$SIGNOZ_CADDYFILE"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
docker compose -f $COMPOSE_FILE up -d --remove-orphans
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" "$SIGNOZ_CADDYFILE" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,14 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$APP_DIR/$APP_NAME"; then
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$APP_DIR/$APP_NAME"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$APP_DIR/$APP_NAME" &
|
||||
fi
|
||||
|
||||
@@ -12,17 +12,18 @@ if [ -z "${DOMAIN}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
URL="https://${DOMAIN}"
|
||||
COMPOSE_FILE="${APP_DIR}/${APP_NAME}/docker-compose.yml"
|
||||
VAULTWARDEN_DIR="${APP_DIR}/${APP_NAME}"
|
||||
COMPOSE_FILE="${VAULTWARDEN_DIR}/docker-compose.yml"
|
||||
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${URL} {
|
||||
reverse_proxy 127.0.0.1:${APP_UPSTREAM_PORT}
|
||||
}
|
||||
EOF
|
||||
source /var/excloud/scripts/caddy-setup.sh
|
||||
|
||||
echo "Caddyfile updated"
|
||||
|
||||
systemctl enable caddy
|
||||
if is_app_ready "$VAULTWARDEN_DIR"; then
|
||||
# Update domain in docker-compose env vars
|
||||
sed -i "s|DOMAIN: .*|DOMAIN: https://${DOMAIN}|" "${COMPOSE_FILE}"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
systemctl reload caddy
|
||||
switch_domain "$DOMAIN" "$APP_UPSTREAM_PORT" "$VAULTWARDEN_DIR"
|
||||
else
|
||||
setup_initializing_page "$DOMAIN" "$APP_NAME" "$VAULTWARDEN_DIR"
|
||||
docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans
|
||||
wait_and_switch_to_proxy "$DOMAIN" "$APP_UPSTREAM_PORT" "$VAULTWARDEN_DIR" &
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user