fixing bugs
This commit is contained in:
parent
979d27ec7a
commit
274bdc8bac
8 changed files with 111 additions and 42 deletions
17
.env.example
17
.env.example
|
|
@ -100,17 +100,24 @@ TELESRV_SMTP_TIMEOUT=10s
|
||||||
## Public Links & Branding -- What clients show/open for links, and your product's name.
|
## Public Links & Branding -- What clients show/open for links, and your product's name.
|
||||||
|
|
||||||
# Public web address for links this server generates (invite links, sticker
|
# Public web address for links this server generates (invite links, sticker
|
||||||
# packs, etc). Use your real domain once you have one, e.g. https://example.com.
|
# packs, etc). :2401 matches TELESRV_PUBLIC_LINK_WEB_ADDR below (the Public
|
||||||
TELESRV_PUBLIC_BASE_URL=http://127.0.0.1
|
# Web Listener that actually serves those preview pages) so a link opened
|
||||||
|
# right after setup shows a real preview card instead of a dead connection.
|
||||||
|
# Use your real domain once you have one, e.g. https://example.com -- and
|
||||||
|
# put nginx (or similar) in front of the listener rather than pointing this
|
||||||
|
# at the raw port.
|
||||||
|
TELESRV_PUBLIC_BASE_URL=http://127.0.0.1:2401
|
||||||
# Custom URL scheme (like "owpg://") that public pages use to open your
|
# Custom URL scheme (like "owpg://") that public pages use to open your
|
||||||
# patched client. Must match what your client builds were compiled with.
|
# patched client. Must match what your client builds were compiled with.
|
||||||
TELESRV_PUBLIC_APP_SCHEME=owpg
|
TELESRV_PUBLIC_APP_SCHEME=owpg
|
||||||
# Optional: use "scheme://yourdomain.com/..." links instead of plain
|
# Optional: use "scheme://yourdomain.com/..." links instead of plain
|
||||||
# "scheme://...". Leave empty unless you specifically need this.
|
# "scheme://...". Leave empty unless you specifically need this.
|
||||||
TELESRV_PUBLIC_APP_LINK_BASE=
|
TELESRV_PUBLIC_APP_LINK_BASE=
|
||||||
# Address of your web client (if you have one) and the product name shown
|
# Address of your web client, if you have one -- shows an "Open in Web"
|
||||||
# on public landing pages.
|
# button on public profile/invite pages. Leave empty (the default) to hide
|
||||||
TELESRV_PUBLIC_WEB_BASE_URL=https://web.telesrv.net
|
# that button; there's no web client running unless you've deployed one.
|
||||||
|
TELESRV_PUBLIC_WEB_BASE_URL=
|
||||||
|
# Product name shown on public landing pages.
|
||||||
TELESRV_PUBLIC_APP_NAME=OwpenGram
|
TELESRV_PUBLIC_APP_NAME=OwpenGram
|
||||||
# Where the "Download" button on public pages links to.
|
# Where the "Download" button on public pages links to.
|
||||||
TELESRV_PUBLIC_DOWNLOAD_URL=https://owpengram.org
|
TELESRV_PUBLIC_DOWNLOAD_URL=https://owpengram.org
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
cmd/telesrv-admin/web/dist/index.html
vendored
4
cmd/telesrv-admin/web/dist/index.html
vendored
|
|
@ -23,8 +23,8 @@
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/index-B8l4nVOK.js"></script>
|
<script type="module" crossorigin src="/assets/index-B7vjbJOs.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DW3ERnl8.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-P_k7ini0.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ const NETWORK_FIELDS: { key: string; label: string; hint: string; placeholder: s
|
||||||
key: "TELESRV_PUBLIC_BASE_URL",
|
key: "TELESRV_PUBLIC_BASE_URL",
|
||||||
label: "Public base URL",
|
label: "Public base URL",
|
||||||
hint: "Used for links this server generates -- invites, sticker packs. e.g. https://example.com",
|
hint: "Used for links this server generates -- invites, sticker packs. e.g. https://example.com",
|
||||||
placeholder: "http://127.0.0.1"
|
placeholder: "http://127.0.0.1:2401"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "TELESRV_PUBLIC_APP_SCHEME",
|
key: "TELESRV_PUBLIC_APP_SCHEME",
|
||||||
|
|
@ -386,10 +386,10 @@ function DoneStep() {
|
||||||
</button>
|
</button>
|
||||||
</WizardActions>
|
</WizardActions>
|
||||||
{restartWatcher.waiting && (
|
{restartWatcher.waiting && (
|
||||||
<RestartOverlay label={"Restarting owpengram-server and the admin panel..."} timedOut={false} onDismiss={restartWatcher.dismiss} />
|
<RestartOverlay timedOut={false} onDismiss={restartWatcher.dismiss} />
|
||||||
)}
|
)}
|
||||||
{restartWatcher.timedOut && (
|
{restartWatcher.timedOut && (
|
||||||
<RestartOverlay label={"Restarting owpengram-server and the admin panel..."} timedOut={true} onDismiss={restartWatcher.dismiss} />
|
<RestartOverlay timedOut={true} onDismiss={restartWatcher.dismiss} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -551,12 +551,20 @@ export function useAdminRestartWatcher() {
|
||||||
return { waiting, timedOut, watch, dismiss };
|
return { waiting, timedOut, watch, dismiss };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RestartOverlay({ label, timedOut, onDismiss }: { label: string; timedOut: boolean; onDismiss: () => void }) {
|
// No detail line under the heading on purpose -- "restarting owpengram-server
|
||||||
|
// and the admin panel" (or Update's commit count) told the operator nothing
|
||||||
|
// they didn't already know from having just clicked Restart/Update/Finish
|
||||||
|
// setup, and this is meant to be glanced at for a few seconds, not read.
|
||||||
|
export function RestartOverlay({ timedOut, onDismiss }: { timedOut: boolean; onDismiss: () => void }) {
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div className="modal-backdrop" role="presentation">
|
<div className="modal-backdrop" role="presentation">
|
||||||
<section className="modal command-modal restart-overlay" role="dialog" aria-modal="true" aria-label={label}>
|
<section className="modal command-modal restart-overlay" role="dialog" aria-modal="true" aria-label={timedOut ? "Restart is taking longer than expected" : "Restarting"}>
|
||||||
{timedOut ? (
|
{timedOut ? (
|
||||||
<div className="command-body restart-overlay-body">
|
<div className="command-body restart-overlay-body">
|
||||||
|
<div className="restart-overlay-badge warn">
|
||||||
|
<RefreshCw size={26} />
|
||||||
|
</div>
|
||||||
|
<h2 className="restart-overlay-heading">{"Still restarting..."}</h2>
|
||||||
<Alert>{"The admin panel did not come back within the expected time. It may still be building/restarting -- reload manually in a bit, or check the server logs."}</Alert>
|
<Alert>{"The admin panel did not come back within the expected time. It may still be building/restarting -- reload manually in a bit, or check the server logs."}</Alert>
|
||||||
<div className="gift-table-actions restart-overlay-actions">
|
<div className="gift-table-actions restart-overlay-actions">
|
||||||
<button className="btn" type="button" onClick={onDismiss}>{"Dismiss"}</button>
|
<button className="btn" type="button" onClick={onDismiss}>{"Dismiss"}</button>
|
||||||
|
|
@ -565,8 +573,11 @@ export function RestartOverlay({ label, timedOut, onDismiss }: { label: string;
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="command-body restart-overlay-body">
|
<div className="command-body restart-overlay-body">
|
||||||
<Loader2 className="spin" size={28} />
|
<div className="restart-overlay-badge">
|
||||||
<p>{label}</p>
|
<RefreshCw size={26} className="restart-overlay-spin" />
|
||||||
|
</div>
|
||||||
|
<h2 className="restart-overlay-heading">{"Restarting"}</h2>
|
||||||
|
<div className="loader-bar restart-overlay-progress" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -655,7 +666,7 @@ const LIVE_POLL_MS = 4000;
|
||||||
// else's commits and relaunching the deployment deserves the same recorded
|
// else's commits and relaunching the deployment deserves the same recorded
|
||||||
// reason as every other action here. Auto-checks once on mount so the button
|
// reason as every other action here. Auto-checks once on mount so the button
|
||||||
// reflects reality without an operator having to click twice.
|
// reflects reality without an operator having to click twice.
|
||||||
function UpdateButton({ onUpdateStarted }: { onUpdateStarted: (overlayLabel: string) => void }) {
|
function UpdateButton({ onUpdateStarted }: { onUpdateStarted: () => void }) {
|
||||||
const [behind, setBehind] = useState<number | null>(null);
|
const [behind, setBehind] = useState<number | null>(null);
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
@ -692,9 +703,7 @@ function UpdateButton({ onUpdateStarted }: { onUpdateStarted: (overlayLabel: str
|
||||||
label={`Update (${commits})`}
|
label={`Update (${commits})`}
|
||||||
path="/api/actions/update-server"
|
path="/api/actions/update-server"
|
||||||
payload={() => ({})}
|
payload={() => ({})}
|
||||||
onDone={() => {
|
onDone={onUpdateStarted}
|
||||||
onUpdateStarted(`Pulled ${commits} commit${commits === 1 ? "" : "s"} -- rebuilding and restarting owpengram-server and the admin panel...`);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -718,7 +727,6 @@ function ServicesTab() {
|
||||||
const [statusError, setStatusError] = useState("");
|
const [statusError, setStatusError] = useState("");
|
||||||
const [docker, setDocker] = useState<DockerService[] | null>(null);
|
const [docker, setDocker] = useState<DockerService[] | null>(null);
|
||||||
const [dockerError, setDockerError] = useState("");
|
const [dockerError, setDockerError] = useState("");
|
||||||
const [overlayLabel, setOverlayLabel] = useState("");
|
|
||||||
const restartWatcher = useAdminRestartWatcher();
|
const restartWatcher = useAdminRestartWatcher();
|
||||||
const pausedRef = useRef(false);
|
const pausedRef = useRef(false);
|
||||||
pausedRef.current = restartWatcher.waiting;
|
pausedRef.current = restartWatcher.waiting;
|
||||||
|
|
@ -754,22 +762,14 @@ function ServicesTab() {
|
||||||
title={"Services"}
|
title={"Services"}
|
||||||
action={
|
action={
|
||||||
<div className="services-header-actions">
|
<div className="services-header-actions">
|
||||||
<UpdateButton
|
<UpdateButton onUpdateStarted={() => void restartWatcher.watch()} />
|
||||||
onUpdateStarted={(label) => {
|
|
||||||
setOverlayLabel(label);
|
|
||||||
void restartWatcher.watch();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
compact
|
compact
|
||||||
tone="primary"
|
tone="primary"
|
||||||
label={"Restart"}
|
label={"Restart"}
|
||||||
path="/api/actions/restart-server"
|
path="/api/actions/restart-server"
|
||||||
payload={() => ({})}
|
payload={() => ({})}
|
||||||
onDone={() => {
|
onDone={() => void restartWatcher.watch()}
|
||||||
setOverlayLabel("Restarting owpengram-server and the admin panel...");
|
|
||||||
void restartWatcher.watch();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
@ -811,8 +811,8 @@ function ServicesTab() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
{restartWatcher.waiting && <RestartOverlay label={overlayLabel} timedOut={false} onDismiss={restartWatcher.dismiss} />}
|
{restartWatcher.waiting && <RestartOverlay timedOut={false} onDismiss={restartWatcher.dismiss} />}
|
||||||
{restartWatcher.timedOut && <RestartOverlay label={overlayLabel} timedOut={true} onDismiss={restartWatcher.dismiss} />}
|
{restartWatcher.timedOut && <RestartOverlay timedOut={true} onDismiss={restartWatcher.dismiss} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1102,21 +1102,61 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.restart-overlay {
|
.restart-overlay {
|
||||||
width: min(440px, 100%);
|
width: min(340px, 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--panel);
|
||||||
|
background-image: radial-gradient(220px 140px at 50% 0%, var(--brand-tint) 0%, rgba(0, 0, 0, 0) 75%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.restart-overlay-body {
|
/* .restart-overlay-body qualified by its .restart-overlay parent so this
|
||||||
|
padding/gap wins over the bare .command-body rule in 04-modal-and-login
|
||||||
|
(equal specificity otherwise, and that file loads after this one). */
|
||||||
|
.restart-overlay .restart-overlay-body {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
gap: 12px;
|
gap: 6px;
|
||||||
padding: 28px 20px;
|
padding: 36px 24px 32px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.restart-overlay-badge {
|
||||||
|
display: grid;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
place-items: center;
|
||||||
|
color: var(--brand);
|
||||||
|
background: var(--brand-tint);
|
||||||
|
border: 1px solid var(--brand-tint-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow: 0 0 0 8px var(--brand-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.restart-overlay-badge.warn {
|
||||||
|
color: var(--warn);
|
||||||
|
background: var(--warn-tint);
|
||||||
|
border-color: var(--warn-border);
|
||||||
|
box-shadow: 0 0 0 8px var(--warn-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.restart-overlay-spin {
|
||||||
|
animation: spin 1.4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.restart-overlay-heading {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--heading);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.restart-overlay-body p {
|
.restart-overlay-body p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text-soft);
|
color: var(--muted);
|
||||||
font-weight: 700;
|
}
|
||||||
|
|
||||||
|
.restart-overlay-progress {
|
||||||
|
width: min(220px, 100%);
|
||||||
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.restart-overlay-actions {
|
.restart-overlay-actions {
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,16 @@ class Status:
|
||||||
admin_pid: int | None
|
admin_pid: int | None
|
||||||
admin_alive: bool
|
admin_alive: bool
|
||||||
containers: list[tuple[str, str | None]]
|
containers: list[tuple[str, str | None]]
|
||||||
|
# Whether Postgres and Redis -- the two containers nothing here works
|
||||||
|
# without -- both actually report "running", not just exist. Separate
|
||||||
|
# from server_alive/admin_alive on purpose: a Go process can stay alive
|
||||||
|
# as an OS process for a good while after losing its database, so PID
|
||||||
|
# aliveness alone was reporting a deployment as fine when Docker (e.g.
|
||||||
|
# Docker Desktop itself not running yet) had left it unable to serve
|
||||||
|
# anything. See quickstart()'s use of this -- server_alive/admin_alive
|
||||||
|
# and `running` itself are left exactly as every other caller already
|
||||||
|
# relies on them.
|
||||||
|
docker_healthy: bool
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def running(self) -> bool:
|
def running(self) -> bool:
|
||||||
|
|
@ -194,9 +204,11 @@ class ServerManager:
|
||||||
# default for a never-started, fresh install) so the container list
|
# default for a never-started, fresh install) so the container list
|
||||||
# shows something sensible even before Start has ever run.
|
# shows something sensible even before Start has ever run.
|
||||||
prefix = self.cached_docker_naming() or state.get("docker_prefix") or "owpengram"
|
prefix = self.cached_docker_naming() or state.get("docker_prefix") or "owpengram"
|
||||||
|
postgres_state = self.container_status(f"{prefix}-postgres")
|
||||||
|
redis_state = self.container_status(f"{prefix}-redis")
|
||||||
containers = [
|
containers = [
|
||||||
(f"{prefix}-{service}", self.container_status(f"{prefix}-{service}"))
|
(f"{prefix}-postgres", postgres_state),
|
||||||
for service in ("postgres", "redis")
|
(f"{prefix}-redis", redis_state),
|
||||||
]
|
]
|
||||||
# MinIO is optional (only relevant when TELESRV_BLOB_BACKEND=s3 points
|
# MinIO is optional (only relevant when TELESRV_BLOB_BACKEND=s3 points
|
||||||
# at the self-hosted container rather than AWS S3), so unlike
|
# at the self-hosted container rather than AWS S3), so unlike
|
||||||
|
|
@ -212,6 +224,7 @@ class ServerManager:
|
||||||
server_alive=pid_alive(server_pid),
|
server_alive=pid_alive(server_pid),
|
||||||
admin_pid=admin_pid,
|
admin_pid=admin_pid,
|
||||||
admin_alive=pid_alive(admin_pid),
|
admin_alive=pid_alive(admin_pid),
|
||||||
|
docker_healthy=(postgres_state == "running" and redis_state == "running"),
|
||||||
containers=containers,
|
containers=containers,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -760,9 +773,18 @@ def quickstart() -> int:
|
||||||
generated_password = bootstrap_env()
|
generated_password = bootstrap_env()
|
||||||
|
|
||||||
status = MANAGER.status()
|
status = MANAGER.status()
|
||||||
if status.running:
|
if status.running and status.docker_healthy:
|
||||||
print("[ok] Already running.")
|
print("[ok] Already running.")
|
||||||
else:
|
else:
|
||||||
|
if status.running:
|
||||||
|
# The binaries are alive but Postgres/Redis aren't -- Docker
|
||||||
|
# Desktop not up yet, a container that crashed, whatever the
|
||||||
|
# cause, the running processes have been failing every database
|
||||||
|
# call. Stop them first: launching the fresh ones below without
|
||||||
|
# this would try to bind the ports the stuck ones are still
|
||||||
|
# holding, rather than actually fixing anything.
|
||||||
|
print("[..] Processes are running but Docker isn't -- stopping before restarting cleanly...")
|
||||||
|
MANAGER.stop()
|
||||||
print("== Starting OwpenGram ==")
|
print("== Starting OwpenGram ==")
|
||||||
print()
|
print()
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue