Auth-before-mic connect flow and generic deploy docs
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,3 +19,6 @@ client/dist/
|
|||||||
|
|
||||||
# Local planning scratch
|
# Local planning scratch
|
||||||
plans/
|
plans/
|
||||||
|
|
||||||
|
# Host-local notes
|
||||||
|
local/
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ Common server overrides:
|
|||||||
Use `deploy/README.md`.
|
Use `deploy/README.md`.
|
||||||
|
|
||||||
Summary:
|
Summary:
|
||||||
1. Copy repo to `/home/<user>/chgrid`.
|
1. Copy repo to your server.
|
||||||
2. Build client and publish `client/dist/` to `/home/<user>/public_html/chgrid/`.
|
2. Build client and publish `client/dist/` to your web root/subdirectory.
|
||||||
3. Configure server `config.toml` and run it via `systemd`.
|
3. Configure server `config.toml` and run it via `systemd`.
|
||||||
4. Add Apache `/ws` websocket proxy from `deploy/apache/chgrid-vhost-snippet.conf`.
|
4. Add Apache `/ws` websocket proxy from `deploy/apache/chgrid-vhost-snippet.conf`.
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
</section>
|
</section>
|
||||||
<div class="controls" id="button-container">
|
<div class="controls" id="button-container">
|
||||||
<button id="connectButton">Connect</button>
|
<button id="connectButton">Connect</button>
|
||||||
|
<span id="authModeSeparator" aria-hidden="true">or</span>
|
||||||
<button id="showRegisterButton" type="button">Register</button>
|
<button id="showRegisterButton" type="button">Register</button>
|
||||||
<button id="logoutButton" class="hidden">Log out</button>
|
<button id="logoutButton" class="hidden">Log out</button>
|
||||||
<button id="settingsButton">Audio setup</button>
|
<button id="settingsButton">Audio setup</button>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Maintainer-controlled web client version.
|
// Maintainer-controlled web client version.
|
||||||
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
// Format: YYYY.MM.DD Rn (example: 2026.02.20 R2)
|
||||||
window.CHGRID_WEB_VERSION = "2026.02.25 R255";
|
window.CHGRID_WEB_VERSION = "2026.02.25 R256";
|
||||||
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
// Optional display timezone for timestamps. Falls back to America/Detroit if unset/invalid.
|
||||||
window.CHGRID_TIME_ZONE = "America/Detroit";
|
window.CHGRID_TIME_ZONE = "America/Detroit";
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ type Dom = {
|
|||||||
authPolicyHintRegister: HTMLParagraphElement;
|
authPolicyHintRegister: HTMLParagraphElement;
|
||||||
authSessionView: HTMLElement;
|
authSessionView: HTMLElement;
|
||||||
authSessionText: HTMLParagraphElement;
|
authSessionText: HTMLParagraphElement;
|
||||||
|
authModeSeparator: HTMLElement;
|
||||||
showRegisterButton: HTMLButtonElement;
|
showRegisterButton: HTMLButtonElement;
|
||||||
updatesSection: HTMLElement;
|
updatesSection: HTMLElement;
|
||||||
updatesToggle: HTMLButtonElement;
|
updatesToggle: HTMLButtonElement;
|
||||||
@@ -136,6 +137,7 @@ const dom: Dom = {
|
|||||||
authPolicyHintRegister: requiredById('authPolicyHintRegister'),
|
authPolicyHintRegister: requiredById('authPolicyHintRegister'),
|
||||||
authSessionView: requiredById('authSessionView'),
|
authSessionView: requiredById('authSessionView'),
|
||||||
authSessionText: requiredById('authSessionText'),
|
authSessionText: requiredById('authSessionText'),
|
||||||
|
authModeSeparator: requiredById('authModeSeparator'),
|
||||||
showRegisterButton: requiredById('showRegisterButton'),
|
showRegisterButton: requiredById('showRegisterButton'),
|
||||||
updatesSection: requiredById('updatesSection'),
|
updatesSection: requiredById('updatesSection'),
|
||||||
updatesToggle: requiredById('updatesToggle'),
|
updatesToggle: requiredById('updatesToggle'),
|
||||||
@@ -599,11 +601,13 @@ function updateConnectAvailability(): void {
|
|||||||
const label = sanitizeAuthUsername(authUsername) || 'current account';
|
const label = sanitizeAuthUsername(authUsername) || 'current account';
|
||||||
dom.authSessionText.textContent = `Logged in as ${label}.`;
|
dom.authSessionText.textContent = `Logged in as ${label}.`;
|
||||||
dom.showRegisterButton.classList.add('hidden');
|
dom.showRegisterButton.classList.add('hidden');
|
||||||
|
dom.authModeSeparator.classList.add('hidden');
|
||||||
dom.loginView.classList.add('hidden');
|
dom.loginView.classList.add('hidden');
|
||||||
dom.registerView.classList.add('hidden');
|
dom.registerView.classList.add('hidden');
|
||||||
dom.authSessionView.classList.remove('hidden');
|
dom.authSessionView.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
dom.showRegisterButton.classList.remove('hidden');
|
dom.showRegisterButton.classList.remove('hidden');
|
||||||
|
dom.authModeSeparator.classList.remove('hidden');
|
||||||
dom.showRegisterButton.textContent = authMode === 'login' ? 'Register' : 'Login';
|
dom.showRegisterButton.textContent = authMode === 'login' ? 'Register' : 'Login';
|
||||||
dom.loginView.classList.toggle('hidden', authMode !== 'login');
|
dom.loginView.classList.toggle('hidden', authMode !== 'login');
|
||||||
dom.registerView.classList.toggle('hidden', authMode !== 'register');
|
dom.registerView.classList.toggle('hidden', authMode !== 'register');
|
||||||
@@ -1525,11 +1529,6 @@ function getConnectionFlowDeps(): ConnectFlowDeps {
|
|||||||
updateConnectAvailability,
|
updateConnectAvailability,
|
||||||
mediaIsConnecting: () => mediaSession.isConnecting(),
|
mediaIsConnecting: () => mediaSession.isConnecting(),
|
||||||
mediaSetConnecting: (value) => mediaSession.setConnecting(value),
|
mediaSetConnecting: (value) => mediaSession.setConnecting(value),
|
||||||
mediaCheckMicPermission: () => checkMicPermission(),
|
|
||||||
mediaPopulateAudioDevices: () => populateAudioDevices(),
|
|
||||||
mediaGetPreferredInputDeviceId: () => mediaSession.getPreferredInputDeviceId(),
|
|
||||||
mediaSetupLocalMedia: (audioDeviceId) => setupLocalMedia(audioDeviceId),
|
|
||||||
mediaDescribeError: (error) => describeMediaError(error),
|
|
||||||
mediaStopLocalMedia: () => stopLocalMedia(),
|
mediaStopLocalMedia: () => stopLocalMedia(),
|
||||||
signalingConnect: (handler) => signaling.connect(handler as (message: IncomingMessage) => Promise<void>),
|
signalingConnect: (handler) => signaling.connect(handler as (message: IncomingMessage) => Promise<void>),
|
||||||
signalingSendAuth: () => sendAuthRequest(),
|
signalingSendAuth: () => sendAuthRequest(),
|
||||||
@@ -1671,6 +1670,9 @@ async function onSignalingMessage(message: IncomingMessage): Promise<void> {
|
|||||||
startHeartbeat();
|
startHeartbeat();
|
||||||
}
|
}
|
||||||
await onAppMessage(message);
|
await onAppMessage(message);
|
||||||
|
if (message.type === 'welcome') {
|
||||||
|
void setupMediaAfterAuth();
|
||||||
|
}
|
||||||
itemBehaviorRegistry.onUseResultMessage(message);
|
itemBehaviorRegistry.onUseResultMessage(message);
|
||||||
itemBehaviorRegistry.onWorldUpdate();
|
itemBehaviorRegistry.onWorldUpdate();
|
||||||
applyConfiguredPeerListenGains();
|
applyConfiguredPeerListenGains();
|
||||||
@@ -1685,6 +1687,28 @@ async function onSignalingMessage(message: IncomingMessage): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Requests microphone access and initializes local media after successful auth/welcome. */
|
||||||
|
async function setupMediaAfterAuth(): Promise<void> {
|
||||||
|
if (!state.running) return;
|
||||||
|
const canProceed = await checkMicPermission();
|
||||||
|
if (!canProceed) {
|
||||||
|
setConnectionStatus('Microphone access is required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await populateAudioDevices();
|
||||||
|
if (dom.audioInputSelect.options.length === 0) {
|
||||||
|
setConnectionStatus('No audio input device found. Open Audio setup or connect a microphone.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inputDeviceId = dom.audioInputSelect.value || mediaSession.getPreferredInputDeviceId();
|
||||||
|
await setupLocalMedia(inputDeviceId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
setConnectionStatus(describeMediaError(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Toggles local microphone track mute state. */
|
/** Toggles local microphone track mute state. */
|
||||||
function toggleMute(): void {
|
function toggleMute(): void {
|
||||||
state.isMuted = !state.isMuted;
|
state.isMuted = !state.isMuted;
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ export type ConnectFlowDeps = {
|
|||||||
updateConnectAvailability: () => void;
|
updateConnectAvailability: () => void;
|
||||||
mediaIsConnecting: () => boolean;
|
mediaIsConnecting: () => boolean;
|
||||||
mediaSetConnecting: (value: boolean) => void;
|
mediaSetConnecting: (value: boolean) => void;
|
||||||
mediaCheckMicPermission: () => Promise<boolean>;
|
|
||||||
mediaPopulateAudioDevices: () => Promise<void>;
|
|
||||||
mediaGetPreferredInputDeviceId: () => string;
|
|
||||||
mediaSetupLocalMedia: (audioDeviceId: string) => Promise<void>;
|
|
||||||
mediaDescribeError: (error: unknown) => string;
|
|
||||||
mediaStopLocalMedia: () => void;
|
mediaStopLocalMedia: () => void;
|
||||||
signalingConnect: (onMessage: (message: unknown) => Promise<void>) => Promise<void>;
|
signalingConnect: (onMessage: (message: unknown) => Promise<void>) => Promise<void>;
|
||||||
signalingSendAuth: () => void;
|
signalingSendAuth: () => void;
|
||||||
@@ -36,7 +31,7 @@ export type ConnectFlowDeps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs connect flow: preflight media setup, then signaling connect/auth.
|
* Runs connect flow: signaling connect/auth first, media setup after auth/welcome.
|
||||||
*/
|
*/
|
||||||
export async function runConnectFlow(deps: ConnectFlowDeps): Promise<void> {
|
export async function runConnectFlow(deps: ConnectFlowDeps): Promise<void> {
|
||||||
if (deps.mediaIsConnecting() || deps.state.running) {
|
if (deps.mediaIsConnecting() || deps.state.running) {
|
||||||
@@ -47,32 +42,6 @@ export async function runConnectFlow(deps: ConnectFlowDeps): Promise<void> {
|
|||||||
deps.mediaSetConnecting(true);
|
deps.mediaSetConnecting(true);
|
||||||
deps.updateConnectAvailability();
|
deps.updateConnectAvailability();
|
||||||
|
|
||||||
const canProceed = await deps.mediaCheckMicPermission();
|
|
||||||
if (!canProceed) {
|
|
||||||
deps.updateStatus('Microphone access is required.');
|
|
||||||
deps.mediaSetConnecting(false);
|
|
||||||
deps.updateConnectAvailability();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await deps.mediaPopulateAudioDevices();
|
|
||||||
if (deps.dom.audioInputSelect.options.length === 0) {
|
|
||||||
deps.updateStatus('No audio input device found. Open Settings or connect a microphone.');
|
|
||||||
deps.mediaSetConnecting(false);
|
|
||||||
deps.updateConnectAvailability();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const inputDeviceId = deps.dom.audioInputSelect.value || deps.mediaGetPreferredInputDeviceId();
|
|
||||||
await deps.mediaSetupLocalMedia(inputDeviceId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
deps.updateStatus(deps.mediaDescribeError(error));
|
|
||||||
deps.mediaSetConnecting(false);
|
|
||||||
deps.updateConnectAvailability();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deps.signalingConnect(deps.onMessage);
|
await deps.signalingConnect(deps.onMessage);
|
||||||
deps.signalingSendAuth();
|
deps.signalingSendAuth();
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#authModeSeparator {
|
||||||
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-panel {
|
.auth-panel {
|
||||||
|
|||||||
248
deploy/README.md
248
deploy/README.md
@@ -1,256 +1,118 @@
|
|||||||
# Deployment Guide
|
# Deployment Guide
|
||||||
|
|
||||||
Target example: AlmaLinux/cPanel host with files under `/home/<user>`.
|
This guide is intentionally host-agnostic.
|
||||||
|
|
||||||
## 1) Place project files
|
## 1) Choose Your Paths Once
|
||||||
- Repo root: `/home/<user>/chgrid`
|
|
||||||
|
|
||||||
## 2) Make deploy scripts executable (once)
|
Pick your own repo path, publish path, base URL path, and service name.
|
||||||
|
|
||||||
|
Example values:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/<user>/chgrid
|
REPO_ROOT=/srv/chgrid
|
||||||
chmod +x deploy/scripts/*.sh
|
PUBLISH_DIR=/var/www/html/chgrid
|
||||||
|
BASE_PATH=/chgrid/
|
||||||
|
UNIT_NAME=chat-grid.service
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3) Install server (uv)
|
Use your own paths for your host.
|
||||||
|
|
||||||
Verify server files first:
|
## 2) Install Server Runtime
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ls -l /home/<user>/chgrid/server/pyproject.toml
|
cd "$REPO_ROOT"
|
||||||
|
./deploy/scripts/install_server.sh "$REPO_ROOT"
|
||||||
```
|
```
|
||||||
|
|
||||||
Run install scripts from repo root (`/home/<user>/chgrid`), not from `server/`.
|
What this sets up:
|
||||||
|
- Python venv under `server/.venv`
|
||||||
|
- `server/config.toml` (if missing)
|
||||||
|
- `server/.env` with `CHGRID_AUTH_SECRET` (if missing)
|
||||||
|
- `server/run_server.sh` (loads `.env` and starts server)
|
||||||
|
- first-run admin bootstrap prompt (if no admin exists)
|
||||||
|
|
||||||
|
## 3) Publish Client
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/<user>/chgrid
|
cd "$REPO_ROOT"
|
||||||
./deploy/scripts/install_server.sh /home/<user>/chgrid
|
./deploy/scripts/deploy_client.sh "$REPO_ROOT" "$PUBLISH_DIR" "$BASE_PATH"
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
## 4) Install/Reload Service Unit
|
||||||
- Script defaults to Python `3.13` (`PYTHON_SPEC=3.13`).
|
|
||||||
- It reuses existing `.venv` instead of replacing it interactively.
|
|
||||||
- If you need to force a fresh 3.13 env:
|
|
||||||
- `rm -rf /home/<user>/chgrid/server/.venv`
|
|
||||||
- rerun `./deploy/scripts/install_server.sh /home/<user>/chgrid`
|
|
||||||
|
|
||||||
This creates:
|
|
||||||
- `/home/<user>/chgrid/server/.venv`
|
|
||||||
- `/home/<user>/chgrid/server/config.toml` (if missing)
|
|
||||||
- `/home/<user>/chgrid/server/.env` with `CHGRID_AUTH_SECRET` (if missing)
|
|
||||||
- `/home/<user>/chgrid/server/run_server.sh` (loads `.env` then starts server)
|
|
||||||
- On first run only, if no admin exists, it prompts to create one immediately.
|
|
||||||
|
|
||||||
Edit `/home/<user>/chgrid/server/config.toml`:
|
|
||||||
- `server.bind_ip = "127.0.0.1"`
|
|
||||||
- `server.port = 8765`
|
|
||||||
- `network.allow_insecure_ws = true`
|
|
||||||
- `tls.cert_file = ""`
|
|
||||||
- `tls.key_file = ""`
|
|
||||||
- `storage.state_file = "runtime/items.json"`
|
|
||||||
- `auth.db_file = "runtime/chatgrid.db"`
|
|
||||||
|
|
||||||
If you skip first-run admin creation, run later:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/<user>/chgrid/server
|
cd "$REPO_ROOT"
|
||||||
source .env
|
./deploy/scripts/install_service.sh "$REPO_ROOT" "$UNIT_NAME"
|
||||||
.venv/bin/python main.py --config config.toml --bootstrap-admin
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4) Build and publish client
|
Service logs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/<user>/chgrid
|
journalctl -u "$UNIT_NAME" -f
|
||||||
./deploy/scripts/deploy_client.sh /home/<user>/chgrid /home/<user>/public_html/chgrid /chgrid/
|
tail -f "$REPO_ROOT/server/runtime/server.log"
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
## 5) One-Command Update
|
||||||
- Third arg is Vite base path for production assets.
|
|
||||||
- For `https://example.com/chgrid/`, use `/chgrid/`.
|
|
||||||
- For site root deploy (`https://example.com/`), use `/`.
|
|
||||||
- Deploy script normalizes publish permissions to avoid shared-host PHP soft exceptions:
|
|
||||||
- directories `755`
|
|
||||||
- files `644`
|
|
||||||
|
|
||||||
Shortcut (client deploy + service restart):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/<user>/chgrid
|
cd "$REPO_ROOT"
|
||||||
./deploy/scripts/up.sh /home/<user>/chgrid /home/<user>/public_html/chgrid /chgrid/
|
./deploy/scripts/up.sh "$REPO_ROOT" "$PUBLISH_DIR" "$BASE_PATH" "$UNIT_NAME"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5) Install/restart signaling service (systemd)
|
## 6) Apache Websocket Proxy
|
||||||
|
|
||||||
|
Install your vhost include from the provided snippet:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /home/<user>/chgrid
|
cd "$REPO_ROOT"
|
||||||
./deploy/scripts/install_service.sh /home/<user>/chgrid
|
./deploy/scripts/install_apache.sh "$REPO_ROOT" /path/to/apache/include/chgrid.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Expected proxy endpoint:
|
||||||
- Service startup uses `/home/<user>/chgrid/server/run_server.sh`, which sources local
|
|
||||||
`/home/<user>/chgrid/server/.env` before launching Python.
|
|
||||||
|
|
||||||
Logs:
|
```apache
|
||||||
|
ProxyPass /ws ws://127.0.0.1:8765
|
||||||
```bash
|
ProxyPassReverse /ws ws://127.0.0.1:8765
|
||||||
journalctl -u chat-grid.service -f
|
|
||||||
tail -f /home/<user>/chgrid/server/runtime/server.log
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you previously used `chgrid-signaling.service`, migrate once:
|
After Apache changes, reload Apache using your host's command.
|
||||||
|
|
||||||
```bash
|
## 7) Optional HTTP Stream Relay
|
||||||
sudo systemctl disable --now chgrid-signaling.service
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6) Apache websocket proxy
|
If you need HTTPS relays for plain HTTP streams, add vhost relays such as:
|
||||||
|
|
||||||
Install using script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/<user>/chgrid
|
|
||||||
./deploy/scripts/install_apache.sh \
|
|
||||||
/home/<user>/chgrid \
|
|
||||||
/etc/apache2/conf.d/userdata/ssl/2_4/<cpanel-user>/yourdomain.com/chgrid.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Replace `yourdomain.com` with your real domain.
|
|
||||||
- Script copies `deploy/apache/chgrid-vhost-snippet.conf`, runs `rebuildhttpdconf`, then restarts Apache via WHM restart command.
|
|
||||||
- Snippet now includes no-cache headers for `/chgrid/` and `/chgrid/index.html` so client updates are not stuck on stale HTML.
|
|
||||||
- `deploy_client.sh` also installs `/chgrid/.htaccess` from `deploy/apache/chgrid-public-htaccess`
|
|
||||||
to force no-cache on `index.html` and `version.js` in shared-host setups.
|
|
||||||
|
|
||||||
## 7) Optional HTTPS relay for HTTP radio streams
|
|
||||||
|
|
||||||
If stream sources are plain HTTP (for example ports `8000`, `8010`, `8020`, `8030`), add relays in:
|
|
||||||
|
|
||||||
`/etc/apache2/conf.d/userdata/ssl/2_4/<cpanel-user>/example.com/chgrid.conf`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```apache
|
```apache
|
||||||
ProxyPass /listen/8000/ http://127.0.0.1:8000/
|
ProxyPass /listen/8000/ http://127.0.0.1:8000/
|
||||||
ProxyPassReverse /listen/8000/ http://127.0.0.1:8000/
|
ProxyPassReverse /listen/8000/ http://127.0.0.1:8000/
|
||||||
ProxyPass /listen/8010/ http://127.0.0.1:8010/
|
|
||||||
ProxyPassReverse /listen/8010/ http://127.0.0.1:8010/
|
|
||||||
ProxyPass /listen/8020/ http://127.0.0.1:8020/
|
|
||||||
ProxyPassReverse /listen/8020/ http://127.0.0.1:8020/
|
|
||||||
ProxyPass /listen/8030/ http://127.0.0.1:8030/
|
|
||||||
ProxyPassReverse /listen/8030/ http://127.0.0.1:8030/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Apply changes:
|
## 8) PHP Media Proxy
|
||||||
|
|
||||||
```bash
|
`deploy/php/media_proxy.php` is copied into your publish directory by `deploy_client.sh`.
|
||||||
sudo /usr/local/cpanel/scripts/rebuildhttpdconf
|
|
||||||
sudo /usr/local/cpanel/scripts/restartsrv_httpd
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage example in Chat Grid:
|
Use:
|
||||||
- `https://example.com/listen/8000/stream`
|
|
||||||
|
|
||||||
## 8) PHP media proxy (Dropbox + HTTP stream passthrough)
|
|
||||||
|
|
||||||
`deploy/php/media_proxy.php` is a lightweight same-origin proxy for stream URLs.
|
|
||||||
|
|
||||||
It is auto-copied to your publish dir by `deploy_client.sh` (and `up.sh`), so after deploy it should be available at:
|
|
||||||
|
|
||||||
- `https://example.com/chgrid/media_proxy.php`
|
|
||||||
|
|
||||||
Use in Chat Grid `streamUrl`:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
https://example.com/chgrid/media_proxy.php?url=<urlencoded-upstream-url>
|
https://example.com/chgrid/media_proxy.php?url=urlencoded_upstream_url
|
||||||
```
|
```
|
||||||
|
|
||||||
Examples:
|
## 9) Git Update Flow
|
||||||
|
|
||||||
- Dropbox:
|
|
||||||
`https://example.com/chgrid/media_proxy.php?url=https%3A%2F%2Fwww.dropbox.com%2Fscl%2Ffi%2Fa7s3n15bgj043rr54k3n9%2FMario-Hold-Music.mp3%3Frlkey%3Ddfr3dybr7s7nndudag0k8xflc%26dl%3D1`
|
|
||||||
- HTTP stream:
|
|
||||||
`https://example.com/chgrid/media_proxy.php?url=http%3A%2F%2Fstream.rpgamers.net%3A8000%2Frpgn`
|
|
||||||
|
|
||||||
Troubleshooting checks:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -I "https://example.com/chgrid/media_proxy.php?url=https%3A%2F%2Fwww.dropbox.com%2Fscl%2Ffi%2Fa7s3n15bgj043rr54k3n9%2FMario-Hold-Music.mp3%3Frlkey%3Ddfr3dybr7s7nndudag0k8xflc%26dl%3D1"
|
cd "$REPO_ROOT"
|
||||||
curl -I "https://example.com/chgrid/media_proxy.php?url=http%3A%2F%2Fstream.rpgamers.net%3A8000%2Frpgn"
|
|
||||||
```
|
|
||||||
|
|
||||||
Optional hardening:
|
|
||||||
|
|
||||||
- Set env var `CHGRID_MEDIA_PROXY_ALLOWLIST` (comma-separated hosts/suffixes) in Apache/PHP-FPM.
|
|
||||||
- Example: `dropbox.com,dropboxusercontent.com,stream.rpgamers.net`
|
|
||||||
|
|
||||||
## 9) GitHub-based update flow
|
|
||||||
|
|
||||||
Initial clone (one time):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/<user>
|
|
||||||
git clone https://github.com/jage9/chat_grid.git chgrid
|
|
||||||
```
|
|
||||||
|
|
||||||
Update and redeploy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/<user>/chgrid
|
|
||||||
git fetch origin
|
git fetch origin
|
||||||
git switch main
|
git switch main
|
||||||
git pull --ff-only origin main
|
git pull --ff-only origin main
|
||||||
|
./deploy/scripts/install_server.sh "$REPO_ROOT"
|
||||||
# Rebuild/publish web client
|
./deploy/scripts/up.sh "$REPO_ROOT" "$PUBLISH_DIR" "$BASE_PATH" "$UNIT_NAME"
|
||||||
./deploy/scripts/deploy_client.sh /home/<user>/chgrid /home/<user>/public_html/chgrid /chgrid/
|
|
||||||
|
|
||||||
# Reconcile server env/deps (safe to rerun on updates)
|
|
||||||
./deploy/scripts/install_server.sh /home/<user>/chgrid
|
|
||||||
|
|
||||||
# Restart signaling service
|
|
||||||
sudo systemctl restart chat-grid.service
|
|
||||||
journalctl -u chat-grid.service -n 50 --no-pager
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Typical quick update:
|
## 10) HTTPS Git Auth (PAT)
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/<user>/chgrid
|
|
||||||
./deploy/scripts/up.sh /home/<user>/chgrid /home/<user>/public_html/chgrid /chgrid/
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Run Apache install/reload steps again only if proxy config changed.
|
|
||||||
- If your checkout has local changes, stash or commit before `git pull`.
|
|
||||||
- For HTTPS GitHub auth, use your GitHub username plus a Personal Access Token (PAT) as the password.
|
|
||||||
- SSH key passphrases are only used for `git@github.com:` remotes, not `https://` remotes.
|
|
||||||
|
|
||||||
## 10) Save GitHub PAT for HTTPS pulls/pushes
|
|
||||||
|
|
||||||
Persistent storage (simple, plaintext in `~/.git-credentials`):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git config --global credential.helper store
|
git config --global credential.helper store
|
||||||
```
|
```
|
||||||
|
|
||||||
Memory cache only (not persisted across reboot):
|
Then run one authenticated pull/push and enter:
|
||||||
|
- Username: your GitHub username
|
||||||
```bash
|
|
||||||
git config --global credential.helper "cache --timeout=28800"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run one authenticated command and enter:
|
|
||||||
- Username: `jage9`
|
|
||||||
- Password: your GitHub PAT
|
- Password: your GitHub PAT
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/<user>/chgrid
|
|
||||||
git pull --ff-only origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
If you saved the wrong token and need to re-enter it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
printf "protocol=https\nhost=github.com\n" | git credential reject
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,256 +0,0 @@
|
|||||||
# Deployment Guide
|
|
||||||
|
|
||||||
Target example: AlmaLinux/cPanel host with files under `/home/bestmidi`.
|
|
||||||
|
|
||||||
## 1) Place project files
|
|
||||||
- Repo root: `/home/bestmidi/chgrid`
|
|
||||||
|
|
||||||
## 2) Make deploy scripts executable (once)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
chmod +x deploy/scripts/*.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3) Install server (uv)
|
|
||||||
|
|
||||||
Verify server files first:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls -l /home/bestmidi/chgrid/server/pyproject.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Run install scripts from repo root (`/home/bestmidi/chgrid`), not from `server/`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
./deploy/scripts/install_server.sh /home/bestmidi/chgrid
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Script defaults to Python `3.13` (`PYTHON_SPEC=3.13`).
|
|
||||||
- It reuses existing `.venv` instead of replacing it interactively.
|
|
||||||
- If you need to force a fresh 3.13 env:
|
|
||||||
- `rm -rf /home/bestmidi/chgrid/server/.venv`
|
|
||||||
- rerun `./deploy/scripts/install_server.sh /home/bestmidi/chgrid`
|
|
||||||
|
|
||||||
This creates:
|
|
||||||
- `/home/bestmidi/chgrid/server/.venv`
|
|
||||||
- `/home/bestmidi/chgrid/server/config.toml` (if missing)
|
|
||||||
- `/home/bestmidi/chgrid/server/.env` with `CHGRID_AUTH_SECRET` (if missing)
|
|
||||||
- `/home/bestmidi/chgrid/server/run_server.sh` (loads `.env` then starts server)
|
|
||||||
- On first run only, if no admin exists, it prompts to create one immediately.
|
|
||||||
|
|
||||||
Edit `/home/bestmidi/chgrid/server/config.toml`:
|
|
||||||
- `server.bind_ip = "127.0.0.1"`
|
|
||||||
- `server.port = 8765`
|
|
||||||
- `network.allow_insecure_ws = true`
|
|
||||||
- `tls.cert_file = ""`
|
|
||||||
- `tls.key_file = ""`
|
|
||||||
- `storage.state_file = "runtime/items.json"`
|
|
||||||
- `auth.db_file = "runtime/chatgrid.db"`
|
|
||||||
|
|
||||||
If you skip first-run admin creation, run later:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid/server
|
|
||||||
source .env
|
|
||||||
.venv/bin/python main.py --config config.toml --bootstrap-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4) Build and publish client
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
./deploy/scripts/deploy_client.sh /home/bestmidi/chgrid /home/bestmidi/public_html/chgrid /chgrid/
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Third arg is Vite base path for production assets.
|
|
||||||
- For `https://bestmidi.com/chgrid/`, use `/chgrid/`.
|
|
||||||
- For site root deploy (`https://bestmidi.com/`), use `/`.
|
|
||||||
- Deploy script normalizes publish permissions to avoid shared-host PHP soft exceptions:
|
|
||||||
- directories `755`
|
|
||||||
- files `644`
|
|
||||||
|
|
||||||
Shortcut (client deploy + service restart):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
./deploy/scripts/up.sh /home/bestmidi/chgrid /home/bestmidi/public_html/chgrid /chgrid/
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5) Install/restart signaling service (systemd)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
./deploy/scripts/install_service.sh /home/bestmidi/chgrid
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Service startup uses `/home/bestmidi/chgrid/server/run_server.sh`, which sources local
|
|
||||||
`/home/bestmidi/chgrid/server/.env` before launching Python.
|
|
||||||
|
|
||||||
Logs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
journalctl -u chat-grid.service -f
|
|
||||||
tail -f /home/bestmidi/chgrid/server/runtime/server.log
|
|
||||||
```
|
|
||||||
|
|
||||||
If you previously used `chgrid-signaling.service`, migrate once:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl disable --now chgrid-signaling.service
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6) Apache websocket proxy
|
|
||||||
|
|
||||||
Install using script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
./deploy/scripts/install_apache.sh \
|
|
||||||
/home/bestmidi/chgrid \
|
|
||||||
/etc/apache2/conf.d/userdata/ssl/2_4/bestmidi/yourdomain.com/chgrid.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Replace `yourdomain.com` with your real domain.
|
|
||||||
- Script copies `deploy/apache/chgrid-vhost-snippet.conf`, runs `rebuildhttpdconf`, then restarts Apache via WHM restart command.
|
|
||||||
- Snippet now includes no-cache headers for `/chgrid/` and `/chgrid/index.html` so client updates are not stuck on stale HTML.
|
|
||||||
- `deploy_client.sh` also installs `/chgrid/.htaccess` from `deploy/apache/chgrid-public-htaccess`
|
|
||||||
to force no-cache on `index.html` and `version.js` in shared-host setups.
|
|
||||||
|
|
||||||
## 7) Optional HTTPS relay for HTTP radio streams
|
|
||||||
|
|
||||||
If stream sources are plain HTTP (for example ports `8000`, `8010`, `8020`, `8030`), add relays in:
|
|
||||||
|
|
||||||
`/etc/apache2/conf.d/userdata/ssl/2_4/bestmidi/bestmidi.com/chgrid.conf`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```apache
|
|
||||||
ProxyPass /listen/8000/ http://127.0.0.1:8000/
|
|
||||||
ProxyPassReverse /listen/8000/ http://127.0.0.1:8000/
|
|
||||||
ProxyPass /listen/8010/ http://127.0.0.1:8010/
|
|
||||||
ProxyPassReverse /listen/8010/ http://127.0.0.1:8010/
|
|
||||||
ProxyPass /listen/8020/ http://127.0.0.1:8020/
|
|
||||||
ProxyPassReverse /listen/8020/ http://127.0.0.1:8020/
|
|
||||||
ProxyPass /listen/8030/ http://127.0.0.1:8030/
|
|
||||||
ProxyPassReverse /listen/8030/ http://127.0.0.1:8030/
|
|
||||||
```
|
|
||||||
|
|
||||||
Apply changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo /usr/local/cpanel/scripts/rebuildhttpdconf
|
|
||||||
sudo /usr/local/cpanel/scripts/restartsrv_httpd
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage example in Chat Grid:
|
|
||||||
- `https://bestmidi.com/listen/8000/stream`
|
|
||||||
|
|
||||||
## 8) PHP media proxy (Dropbox + HTTP stream passthrough)
|
|
||||||
|
|
||||||
`deploy/php/media_proxy.php` is a lightweight same-origin proxy for stream URLs.
|
|
||||||
|
|
||||||
It is auto-copied to your publish dir by `deploy_client.sh` (and `up.sh`), so after deploy it should be available at:
|
|
||||||
|
|
||||||
- `https://bestmidi.com/chgrid/media_proxy.php`
|
|
||||||
|
|
||||||
Use in Chat Grid `streamUrl`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
https://bestmidi.com/chgrid/media_proxy.php?url=<urlencoded-upstream-url>
|
|
||||||
```
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- Dropbox:
|
|
||||||
`https://bestmidi.com/chgrid/media_proxy.php?url=https%3A%2F%2Fwww.dropbox.com%2Fscl%2Ffi%2Fa7s3n15bgj043rr54k3n9%2FMario-Hold-Music.mp3%3Frlkey%3Ddfr3dybr7s7nndudag0k8xflc%26dl%3D1`
|
|
||||||
- HTTP stream:
|
|
||||||
`https://bestmidi.com/chgrid/media_proxy.php?url=http%3A%2F%2Fstream.rpgamers.net%3A8000%2Frpgn`
|
|
||||||
|
|
||||||
Troubleshooting checks:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -I "https://bestmidi.com/chgrid/media_proxy.php?url=https%3A%2F%2Fwww.dropbox.com%2Fscl%2Ffi%2Fa7s3n15bgj043rr54k3n9%2FMario-Hold-Music.mp3%3Frlkey%3Ddfr3dybr7s7nndudag0k8xflc%26dl%3D1"
|
|
||||||
curl -I "https://bestmidi.com/chgrid/media_proxy.php?url=http%3A%2F%2Fstream.rpgamers.net%3A8000%2Frpgn"
|
|
||||||
```
|
|
||||||
|
|
||||||
Optional hardening:
|
|
||||||
|
|
||||||
- Set env var `CHGRID_MEDIA_PROXY_ALLOWLIST` (comma-separated hosts/suffixes) in Apache/PHP-FPM.
|
|
||||||
- Example: `dropbox.com,dropboxusercontent.com,stream.rpgamers.net`
|
|
||||||
|
|
||||||
## 9) GitHub-based update flow (`bestmidi`)
|
|
||||||
|
|
||||||
Initial clone (one time):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi
|
|
||||||
git clone https://github.com/jage9/chat_grid.git chgrid
|
|
||||||
```
|
|
||||||
|
|
||||||
Update and redeploy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
git fetch origin
|
|
||||||
git switch main
|
|
||||||
git pull --ff-only origin main
|
|
||||||
|
|
||||||
# Rebuild/publish web client
|
|
||||||
./deploy/scripts/deploy_client.sh /home/bestmidi/chgrid /home/bestmidi/public_html/chgrid /chgrid/
|
|
||||||
|
|
||||||
# Reconcile server env/deps (safe to rerun on updates)
|
|
||||||
./deploy/scripts/install_server.sh /home/bestmidi/chgrid
|
|
||||||
|
|
||||||
# Restart signaling service
|
|
||||||
sudo systemctl restart chat-grid.service
|
|
||||||
journalctl -u chat-grid.service -n 50 --no-pager
|
|
||||||
```
|
|
||||||
|
|
||||||
Typical quick update:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
./deploy/scripts/up.sh /home/bestmidi/chgrid /home/bestmidi/public_html/chgrid /chgrid/
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Run Apache install/reload steps again only if proxy config changed.
|
|
||||||
- If your checkout has local changes, stash or commit before `git pull`.
|
|
||||||
- For HTTPS GitHub auth, use your GitHub username plus a Personal Access Token (PAT) as the password.
|
|
||||||
- SSH key passphrases are only used for `git@github.com:` remotes, not `https://` remotes.
|
|
||||||
|
|
||||||
## 10) Save GitHub PAT for HTTPS pulls/pushes
|
|
||||||
|
|
||||||
Persistent storage (simple, plaintext in `~/.git-credentials`):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git config --global credential.helper store
|
|
||||||
```
|
|
||||||
|
|
||||||
Memory cache only (not persisted across reboot):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git config --global credential.helper "cache --timeout=28800"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run one authenticated command and enter:
|
|
||||||
- Username: `jage9`
|
|
||||||
- Password: your GitHub PAT
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /home/bestmidi/chgrid
|
|
||||||
git pull --ff-only origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
If you saved the wrong token and need to re-enter it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
printf "protocol=https\nhost=github.com\n" | git credential reject
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user