webgate 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {webgate-0.2.1 → webgate-0.2.2}/CHANGELOG.md +17 -0
- webgate-0.2.2/Dockerfile.demo +63 -0
- {webgate-0.2.1 → webgate-0.2.2}/PKG-INFO +4 -1
- {webgate-0.2.1 → webgate-0.2.2}/README.md +3 -0
- webgate-0.2.2/fly.toml +48 -0
- {webgate-0.2.1 → webgate-0.2.2}/pyproject.toml +1 -1
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/app.py +31 -1
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/config.py +1 -0
- webgate-0.2.2/src/webgate/demo.py +55 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/static/index.html +11 -0
- {webgate-0.2.1 → webgate-0.2.2}/.dockerignore +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/.github/workflows/docs.yml +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/.gitignore +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/Dockerfile +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/Dockerfile.ssh-demo +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/LICENSE +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/ROADMAP.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/VERSION +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/compose.dev.yml +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/compose.yml +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/api/auth.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/api/files.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/api/servers.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/api/terminal.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/changelog.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/getting-started/installation.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/getting-started/quickstart.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/guide/files.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/guide/servers.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/guide/split.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/guide/terminal.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/guide/users.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/index.md +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/access-control.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/audit.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/edit-access-control.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/editor.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/light-theme.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/login.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/new-server-form.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/sftp-restricted.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/sftp.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/site-manager.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/split-view.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/ssh-disabled.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/terminal.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/docs/screenshots/users.png +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/mkdocs.yml +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/__main__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/audit/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/audit/models.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/audit/service.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/auth/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/auth/models.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/auth/routes.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/auth/service.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/db/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/db/engine.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/files/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/files/models.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/files/pool.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/files/routes.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/files/sftp_service.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/servers/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/servers/crypto.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/servers/models.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/servers/monitor.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/servers/routes.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/servers/service.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/terminal/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/terminal/routes.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/terminal/ssh_session.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/src/webgate/terminal/ws_handler.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/tests/__init__.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/tests/conftest.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/tests/test_auth.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/tests/test_files.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/tests/test_servers.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/tests/test_terminal.py +0 -0
- {webgate-0.2.1 → webgate-0.2.2}/uv.lock +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.2.2 (2026-04-15)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- **Demo mode** (`WEBGATE_DEMO_MODE=true`) -- read-only public demo deployments. Blocks every write request on `/api/*` (except login), disables the WebSocket quick-connect endpoint, seeds a `demo`/`demo` user with a sample server, and shows a top banner in the UI.
|
|
8
|
+
- **`Dockerfile.demo`** -- single-container image bundling webgate + a sandboxed `sshd` target via supervisord. Ready for free hosting tiers.
|
|
9
|
+
- **`fly.toml`** -- Fly.io configuration for one-command demo deployments (`flyctl deploy`).
|
|
10
|
+
|
|
11
|
+
### Details
|
|
12
|
+
|
|
13
|
+
- New public endpoint `GET /api/config` exposes `{"demo_mode": bool}` for the frontend to render the banner before login
|
|
14
|
+
- `WEBGATE_DEMO_MODE=true` adds an HTTP middleware that returns `403` for any `POST/PUT/PATCH/DELETE` on `/api/*` (allowlist: `/api/auth/login`, `/api/auth/totp/verify`)
|
|
15
|
+
- Demo seed (`webgate.demo`) is idempotent and only runs when the flag is on
|
|
16
|
+
- Hourly state reset for the public demo can be done with a cron pinging the container restart, so DB returns to seed state
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
3
20
|
## v0.2.1 (2026-04-15)
|
|
4
21
|
|
|
5
22
|
### Features
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Single-container demo image: webgate + a sandboxed SSH/SFTP target
|
|
2
|
+
# managed by supervisord. Suitable for free tiers (Fly.io, Render) where
|
|
3
|
+
# spinning a sidecar VM isn't ideal.
|
|
4
|
+
#
|
|
5
|
+
# Build: docker build -f Dockerfile.demo -t webgate-demo .
|
|
6
|
+
# Run: docker run -p 8443:8443 -e WEBGATE_SECRET_KEY=demo webgate-demo
|
|
7
|
+
|
|
8
|
+
# --- Stage 1: build webgate ---
|
|
9
|
+
FROM python:3.13-slim AS builder
|
|
10
|
+
WORKDIR /app
|
|
11
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
12
|
+
COPY pyproject.toml uv.lock README.md ./
|
|
13
|
+
RUN uv sync --frozen --no-dev --no-editable
|
|
14
|
+
COPY src/ src/
|
|
15
|
+
|
|
16
|
+
# --- Stage 2: runtime with webgate + sshd + supervisord ---
|
|
17
|
+
FROM python:3.13-slim
|
|
18
|
+
|
|
19
|
+
RUN apt-get update && \
|
|
20
|
+
apt-get install -y --no-install-recommends \
|
|
21
|
+
openssh-server supervisor procps curl nano htop tree && \
|
|
22
|
+
rm -rf /var/lib/apt/lists/*
|
|
23
|
+
|
|
24
|
+
# sshd config: only the demo user, password auth allowed for the demo
|
|
25
|
+
RUN mkdir -p /run/sshd && \
|
|
26
|
+
sed -i 's/^#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config && \
|
|
27
|
+
sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
|
|
28
|
+
useradd -m -s /bin/bash demo && \
|
|
29
|
+
echo 'demo:demo' | chpasswd
|
|
30
|
+
|
|
31
|
+
# Sample tree so SFTP browsing has something to show
|
|
32
|
+
RUN mkdir -p /home/demo/projects/webgate /home/demo/projects/api-server \
|
|
33
|
+
/home/demo/documents /home/demo/logs /home/demo/scripts && \
|
|
34
|
+
printf '# webgate\nSelf-hosted SSH + SFTP manager\n' > /home/demo/projects/webgate/README.md && \
|
|
35
|
+
printf '{"name":"api-server","version":"2.1.0"}\n' > /home/demo/projects/api-server/package.json && \
|
|
36
|
+
printf 'Hello from the webgate demo!\n' > /home/demo/documents/welcome.txt && \
|
|
37
|
+
printf '2026-04-08 10:00:00 INFO Server started on port 8080\n' > /home/demo/logs/app.log && \
|
|
38
|
+
printf '#!/bin/bash\necho "Deploy complete"\n' > /home/demo/scripts/deploy.sh && \
|
|
39
|
+
chmod +x /home/demo/scripts/deploy.sh && \
|
|
40
|
+
chown -R demo:demo /home/demo
|
|
41
|
+
|
|
42
|
+
# webgate
|
|
43
|
+
WORKDIR /app
|
|
44
|
+
COPY --from=builder /app/.venv /app/.venv
|
|
45
|
+
COPY --from=builder /app/src /app/src
|
|
46
|
+
RUN mkdir -p /data
|
|
47
|
+
ENV PATH="/app/.venv/bin:$PATH" \
|
|
48
|
+
PYTHONPATH="/app/src" \
|
|
49
|
+
WEBGATE_HOST=0.0.0.0 \
|
|
50
|
+
WEBGATE_PORT=8443 \
|
|
51
|
+
WEBGATE_DB_URL=sqlite+aiosqlite:////data/webgate.db \
|
|
52
|
+
WEBGATE_LOG_LEVEL=info \
|
|
53
|
+
WEBGATE_DEMO_MODE=true
|
|
54
|
+
|
|
55
|
+
# supervisord config
|
|
56
|
+
RUN printf '[supervisord]\nnodaemon=true\nuser=root\nlogfile=/dev/stdout\nlogfile_maxbytes=0\n\n[program:sshd]\ncommand=/usr/sbin/sshd -D -e\nautorestart=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n\n[program:webgate]\ncommand=python -m webgate\nautorestart=true\nstdout_logfile=/dev/stdout\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/stderr\nstderr_logfile_maxbytes=0\n' > /etc/supervisor/conf.d/webgate.conf
|
|
57
|
+
|
|
58
|
+
EXPOSE 8443
|
|
59
|
+
|
|
60
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
|
61
|
+
CMD curl -fsS http://localhost:8443/api/health || exit 1
|
|
62
|
+
|
|
63
|
+
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/webgate.conf"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webgate
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Self-hosted web application for remote server management via SSH terminal and SFTP file browser
|
|
5
5
|
Project-URL: Homepage, https://github.com/kalexnolasco/webgate
|
|
6
6
|
Project-URL: Documentation, https://kalexnolasco.github.io/webgate/
|
|
@@ -58,6 +58,8 @@ Description-Content-Type: text/markdown
|
|
|
58
58
|
Self-hosted web application for remote server management via **SSH terminal** and **SFTP file browser**. A modern Python replacement combining the best of [webssh](https://github.com/huashengdun/webssh) and [filebrowser](https://github.com/filebrowser/filebrowser) into a single unified tool with a FileZilla-inspired interface.
|
|
59
59
|
|
|
60
60
|
> **Documentation: [kalexnolasco.github.io/webgate](https://kalexnolasco.github.io/webgate/)**
|
|
61
|
+
>
|
|
62
|
+
> **🎮 Live demo: [webgate-demo.fly.dev](https://webgate-demo.fly.dev/)** — login `demo` / `demo` (read-only, resets hourly)
|
|
61
63
|
|
|
62
64
|
---
|
|
63
65
|
|
|
@@ -449,6 +451,7 @@ All settings are configurable via environment variables with the `WEBGATE_` pref
|
|
|
449
451
|
| `WEBGATE_DB_URL` | `sqlite+aiosqlite:///./webgate.db` | Database URL |
|
|
450
452
|
| `WEBGATE_ALLOWED_ORIGINS` | `*` | CORS origins (comma-separated) |
|
|
451
453
|
| `WEBGATE_ROOT_PATH` | `` (empty) | URL prefix when served behind a reverse proxy at a sub-path (e.g. `/webgate`) |
|
|
454
|
+
| `WEBGATE_DEMO_MODE` | `false` | Read-only public demo: blocks all writes, seeds a `demo`/`demo` user and shows a banner |
|
|
452
455
|
| `WEBGATE_LOG_LEVEL` | `info` | Log level |
|
|
453
456
|
| `WEBGATE_SESSION_TIMEOUT` | `3600` | SSH session timeout (seconds) |
|
|
454
457
|
| `WEBGATE_MAX_UPLOAD_SIZE` | `104857600` | Max upload size (100 MB) |
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
Self-hosted web application for remote server management via **SSH terminal** and **SFTP file browser**. A modern Python replacement combining the best of [webssh](https://github.com/huashengdun/webssh) and [filebrowser](https://github.com/filebrowser/filebrowser) into a single unified tool with a FileZilla-inspired interface.
|
|
13
13
|
|
|
14
14
|
> **Documentation: [kalexnolasco.github.io/webgate](https://kalexnolasco.github.io/webgate/)**
|
|
15
|
+
>
|
|
16
|
+
> **🎮 Live demo: [webgate-demo.fly.dev](https://webgate-demo.fly.dev/)** — login `demo` / `demo` (read-only, resets hourly)
|
|
15
17
|
|
|
16
18
|
---
|
|
17
19
|
|
|
@@ -403,6 +405,7 @@ All settings are configurable via environment variables with the `WEBGATE_` pref
|
|
|
403
405
|
| `WEBGATE_DB_URL` | `sqlite+aiosqlite:///./webgate.db` | Database URL |
|
|
404
406
|
| `WEBGATE_ALLOWED_ORIGINS` | `*` | CORS origins (comma-separated) |
|
|
405
407
|
| `WEBGATE_ROOT_PATH` | `` (empty) | URL prefix when served behind a reverse proxy at a sub-path (e.g. `/webgate`) |
|
|
408
|
+
| `WEBGATE_DEMO_MODE` | `false` | Read-only public demo: blocks all writes, seeds a `demo`/`demo` user and shows a banner |
|
|
406
409
|
| `WEBGATE_LOG_LEVEL` | `info` | Log level |
|
|
407
410
|
| `WEBGATE_SESSION_TIMEOUT` | `3600` | SSH session timeout (seconds) |
|
|
408
411
|
| `WEBGATE_MAX_UPLOAD_SIZE` | `104857600` | Max upload size (100 MB) |
|
webgate-0.2.2/fly.toml
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Fly.io deployment for the public webgate demo.
|
|
2
|
+
# Edit `app` to your fly app name, then:
|
|
3
|
+
# flyctl launch --no-deploy --copy-config
|
|
4
|
+
# flyctl secrets set WEBGATE_SECRET_KEY=$(openssl rand -hex 32)
|
|
5
|
+
# flyctl deploy
|
|
6
|
+
#
|
|
7
|
+
# Free tier (1x shared-cpu, 256-512 MB) is enough for light demo traffic.
|
|
8
|
+
|
|
9
|
+
app = "webgate-demo"
|
|
10
|
+
primary_region = "cdg" # Paris; closest EU region to Spain
|
|
11
|
+
|
|
12
|
+
[build]
|
|
13
|
+
dockerfile = "Dockerfile.demo"
|
|
14
|
+
|
|
15
|
+
[env]
|
|
16
|
+
WEBGATE_DEMO_MODE = "true"
|
|
17
|
+
WEBGATE_LOG_LEVEL = "info"
|
|
18
|
+
|
|
19
|
+
[http_service]
|
|
20
|
+
internal_port = 8443
|
|
21
|
+
force_https = true
|
|
22
|
+
auto_stop_machines = "stop" # save free-tier hours when idle
|
|
23
|
+
auto_start_machines = true
|
|
24
|
+
min_machines_running = 0
|
|
25
|
+
|
|
26
|
+
[http_service.concurrency]
|
|
27
|
+
type = "requests"
|
|
28
|
+
soft_limit = 50
|
|
29
|
+
hard_limit = 100
|
|
30
|
+
|
|
31
|
+
[[http_service.checks]]
|
|
32
|
+
interval = "30s"
|
|
33
|
+
timeout = "5s"
|
|
34
|
+
grace_period = "15s"
|
|
35
|
+
method = "get"
|
|
36
|
+
path = "/api/health"
|
|
37
|
+
|
|
38
|
+
[[vm]]
|
|
39
|
+
cpu_kind = "shared"
|
|
40
|
+
cpus = 1
|
|
41
|
+
memory_mb = 512
|
|
42
|
+
|
|
43
|
+
# Persistent volume so the demo seed survives restarts. The hourly reset
|
|
44
|
+
# (see scripts/demo-reset.sh) wipes the DB inside the container, not the
|
|
45
|
+
# whole volume, so this stays cheap.
|
|
46
|
+
[mounts]
|
|
47
|
+
source = "webgate_demo_data"
|
|
48
|
+
destination = "/data"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from collections.abc import AsyncGenerator
|
|
2
2
|
from contextlib import asynccontextmanager
|
|
3
3
|
|
|
4
|
-
from fastapi import FastAPI
|
|
4
|
+
from fastapi import FastAPI, Request
|
|
5
5
|
from fastapi.middleware.cors import CORSMiddleware
|
|
6
|
+
from fastapi.responses import JSONResponse
|
|
6
7
|
from fastapi.staticfiles import StaticFiles
|
|
7
8
|
from slowapi import _rate_limit_exceeded_handler
|
|
8
9
|
from slowapi.errors import RateLimitExceeded
|
|
@@ -12,6 +13,7 @@ from webgate.auth.routes import router as auth_router
|
|
|
12
13
|
from webgate.auth.service import seed_admin
|
|
13
14
|
from webgate.config import settings
|
|
14
15
|
from webgate.db.engine import async_session_factory, close_db, init_db
|
|
16
|
+
from webgate.demo import seed_demo
|
|
15
17
|
from webgate.files.pool import sftp_pool
|
|
16
18
|
from webgate.files.routes import router as files_router
|
|
17
19
|
from webgate.servers.monitor import server_monitor
|
|
@@ -24,6 +26,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
|
|
24
26
|
await init_db()
|
|
25
27
|
async with async_session_factory() as session:
|
|
26
28
|
await seed_admin(session)
|
|
29
|
+
if settings.demo_mode:
|
|
30
|
+
await seed_demo(session)
|
|
27
31
|
await sftp_pool.start()
|
|
28
32
|
await server_monitor.start()
|
|
29
33
|
yield
|
|
@@ -55,6 +59,32 @@ def create_app() -> FastAPI:
|
|
|
55
59
|
async def health() -> dict[str, str]: # pyright: ignore[reportUnusedFunction]
|
|
56
60
|
return {"status": "ok"}
|
|
57
61
|
|
|
62
|
+
@app.get("/api/config")
|
|
63
|
+
async def public_config() -> dict[str, object]: # pyright: ignore[reportUnusedFunction]
|
|
64
|
+
# Public flags consumed by the frontend before login.
|
|
65
|
+
return {"demo_mode": settings.demo_mode}
|
|
66
|
+
|
|
67
|
+
if settings.demo_mode:
|
|
68
|
+
# In demo mode block any state-changing request on /api/* except login
|
|
69
|
+
# and the WS quick-connect endpoint (no arbitrary SSH targets allowed).
|
|
70
|
+
WRITE_METHODS = {"POST", "PUT", "PATCH", "DELETE"}
|
|
71
|
+
WRITE_ALLOWLIST = {"/api/auth/login", "/api/auth/totp/verify"}
|
|
72
|
+
|
|
73
|
+
@app.middleware("http")
|
|
74
|
+
async def _demo_readonly(request: Request, call_next): # pyright: ignore[reportUnusedFunction]
|
|
75
|
+
path = request.url.path
|
|
76
|
+
if request.method in WRITE_METHODS and path.startswith("/api/") and path not in WRITE_ALLOWLIST:
|
|
77
|
+
return JSONResponse(
|
|
78
|
+
status_code=403,
|
|
79
|
+
content={"detail": "Demo mode: write operations are disabled"},
|
|
80
|
+
)
|
|
81
|
+
if path.endswith("/api/ws/terminal/quick"):
|
|
82
|
+
return JSONResponse(
|
|
83
|
+
status_code=403,
|
|
84
|
+
content={"detail": "Demo mode: quick-connect is disabled"},
|
|
85
|
+
)
|
|
86
|
+
return await call_next(request)
|
|
87
|
+
|
|
58
88
|
app.include_router(auth_router)
|
|
59
89
|
app.include_router(servers_router)
|
|
60
90
|
app.include_router(terminal_router)
|
|
@@ -12,6 +12,7 @@ class Settings(BaseSettings):
|
|
|
12
12
|
db_url: str = "sqlite+aiosqlite:///./webgate.db"
|
|
13
13
|
allowed_origins: str = "*"
|
|
14
14
|
root_path: str = "" # URL prefix when served behind a reverse proxy (e.g. "/webgate")
|
|
15
|
+
demo_mode: bool = False # Read-only public demo: blocks writes, hides admin UI
|
|
15
16
|
log_level: str = "info"
|
|
16
17
|
session_timeout: int = 3600
|
|
17
18
|
max_upload_size: int = 104857600 # 100MB
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Demo mode seeding: pre-populates the demo user and showcase servers.
|
|
2
|
+
|
|
3
|
+
Activated when WEBGATE_DEMO_MODE=true. Idempotent — safe to call on every startup.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
|
+
|
|
13
|
+
from webgate.auth.service import create_user, get_user_by_username
|
|
14
|
+
from webgate.servers.models import ServerCreate
|
|
15
|
+
from webgate.servers.service import create_server, list_servers
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
DEMO_USERNAME = "demo"
|
|
21
|
+
DEMO_PASSWORD = "demo"
|
|
22
|
+
|
|
23
|
+
# Servers pre-registered for the demo. Hostnames must be reachable from the
|
|
24
|
+
# webgate container. In Fly.io they resolve via *.internal DNS.
|
|
25
|
+
DEMO_SERVERS: list[ServerCreate] = [
|
|
26
|
+
ServerCreate(
|
|
27
|
+
name="demo-server",
|
|
28
|
+
hostname="127.0.0.1",
|
|
29
|
+
port=22,
|
|
30
|
+
username="demo",
|
|
31
|
+
password="demo",
|
|
32
|
+
group="demo",
|
|
33
|
+
tags=["demo", "debian"],
|
|
34
|
+
description="Sandbox SSH/SFTP server (debian). Try `ls`, `htop`, browse /home/demo.",
|
|
35
|
+
sftp_read_only=True,
|
|
36
|
+
),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def seed_demo(session: AsyncSession) -> None:
|
|
41
|
+
user = await get_user_by_username(session, DEMO_USERNAME)
|
|
42
|
+
if user is None:
|
|
43
|
+
user = await create_user(session, DEMO_USERNAME, DEMO_PASSWORD, is_admin=False)
|
|
44
|
+
user.must_change_password = False
|
|
45
|
+
user.allowed_groups = json.dumps(["demo"])
|
|
46
|
+
await session.commit()
|
|
47
|
+
logger.info("Created demo user (%s/%s)", DEMO_USERNAME, DEMO_PASSWORD)
|
|
48
|
+
|
|
49
|
+
existing = await list_servers(session, user.id, is_admin=True)
|
|
50
|
+
existing_names = {s.name for s in existing}
|
|
51
|
+
for srv in DEMO_SERVERS:
|
|
52
|
+
if srv.name in existing_names:
|
|
53
|
+
continue
|
|
54
|
+
await create_server(session, srv, user.id)
|
|
55
|
+
logger.info("Seeded demo server: %s -> %s:%s", srv.name, srv.hostname, srv.port)
|
|
@@ -118,6 +118,9 @@
|
|
|
118
118
|
.cm-editor .cm-scroller { overflow: auto; }
|
|
119
119
|
|
|
120
120
|
/* Login overlay */
|
|
121
|
+
.fz-demo-banner { position: fixed; top: 0; left: 0; right: 0; z-index: 200; background: linear-gradient(90deg, #f59e0b, #ef4444); color: #fff; text-align: center; font-size: 12px; padding: 6px 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); }
|
|
122
|
+
.fz-demo-banner code { background: rgba(255,255,255,0.25); padding: 1px 5px; border-radius: 3px; font-family: monospace; }
|
|
123
|
+
.fz-demo-banner a { color: #fff; text-decoration: underline; }
|
|
121
124
|
.fz-login-overlay { position: fixed; inset: 0; background: var(--bg-tertiary); display: flex; align-items: center; justify-content: center; z-index: 100; }
|
|
122
125
|
.fz-login-box { background: var(--modal-bg); border: 1px solid var(--border); border-radius: 12px; padding: 32px; width: 380px; box-shadow: 0 16px 48px rgba(0,0,0,0.4); }
|
|
123
126
|
.fz-login-box h1 { text-align: center; font-size: 24px; margin-bottom: 4px; color: var(--text-primary); font-weight: 700; }
|
|
@@ -191,6 +194,12 @@ window._showToastImpl = function(message, type) {
|
|
|
191
194
|
|
|
192
195
|
<div x-data="app()" x-cloak @keydown.window="handleKeyboard($event)">
|
|
193
196
|
|
|
197
|
+
<!-- Demo banner (shown when WEBGATE_DEMO_MODE=true) -->
|
|
198
|
+
<div x-show="demoMode" class="fz-demo-banner">
|
|
199
|
+
🎮 <strong>Demo mode</strong> — login with <code>demo</code> / <code>demo</code> · read-only (servers cannot be added or modified) · state resets hourly ·
|
|
200
|
+
<a href="https://github.com/kalexnolasco/webgate" target="_blank" rel="noopener">GitHub</a>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
194
203
|
<!-- ======================== LOGIN ======================== -->
|
|
195
204
|
<div x-show="!token" class="fz-login-overlay">
|
|
196
205
|
<div class="fz-login-box">
|
|
@@ -807,6 +816,7 @@ const BASE = location.pathname.replace(/\/(index\.html)?$/, '');
|
|
|
807
816
|
function app() {
|
|
808
817
|
return {
|
|
809
818
|
theme: localStorage.getItem('webgate_theme') || 'dark',
|
|
819
|
+
demoMode: false,
|
|
810
820
|
uploadProgress: { active: false, percent: 0, fileName: '', total: 0, loaded: 0 },
|
|
811
821
|
token: localStorage.getItem('webgate_token') || '',
|
|
812
822
|
currentUser: null, username: '', password: '',
|
|
@@ -879,6 +889,7 @@ function app() {
|
|
|
879
889
|
|
|
880
890
|
async init() {
|
|
881
891
|
document.documentElement.dataset.theme = this.theme;
|
|
892
|
+
try { const cfg = await this.apiCall('/api/config'); this.demoMode = !!cfg.demo_mode; } catch {}
|
|
882
893
|
if (this.token) {
|
|
883
894
|
await this.fetchMe();
|
|
884
895
|
if (this.currentUser?.must_change_password) return;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|