1
0
mirror of git://f0xx.org/ac/ac-deploy synced 2026-07-29 06:17:57 +03:00

deploy: favicon, nuevo-player, ac-ms-broadcast, nginx notify/monitor/broadcast

- favicon.ico (32px) + apple-touch-icon.ico (180px) added to composed backend seed
- layout.php: add <link rel="icon"> and <link rel="apple-touch-icon"> in <head>
- nuevo-player (Video.js + Nuevo licensed plugin) into assets/vendor/nuevo-player/
- THIRD_PARTY_LICENSES.md: add Nuevo Player (commercial) + TipTap (MIT) entries
- apps-port80.conf: full rewrite with all live fixes merged in:
    * map $http_upgrade $connection_upgrade (WebSocket upgrade)
    * 2FA location regex extended: api/two-factor
    * grafana-auth-check internal endpoint + @monitor_login_redirect
    * Grafana proxy (cast04:3000) + Alertmanager proxy (cast04:9093)
    * broadcast admin path /app/androidcast_project/broadcast/ (auth_request gated)
    * notify.f0xx.org server block: public /m/{slug} landing + API
    * favicon.ico at server root
- deploy-ac-broadcast.sh: new app-phase script (DB schema, user, worker, rsync)
- populate_lab_setup.sh: add deploy-ac-broadcast.sh to run_phase_app

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-06-28 22:55:44 +02:00
parent b9925a6c74
commit 98f4ecc2c5
13 changed files with 280 additions and 5 deletions

View File

@@ -44,3 +44,26 @@ Suggested public URL after deploy sync:
`https://apps.f0xx.org/app/androidcast_project/crashes/THIRD_PARTY_LICENSES.md` `https://apps.f0xx.org/app/androidcast_project/crashes/THIRD_PARTY_LICENSES.md`
(or render an HTML excerpt on the hub legal page linking mobile + BE lists). (or render an HTML excerpt on the hub legal page linking mobile + BE lists).
## Nuevo Player (Video.js + Nuevo Plugin)
**Component:** HTML5 video player embedded in VOD/restreaming service (DR: `ac-docs/drafts/20260616_VOD_and_restreaming_service.txt`)
**Files:** `public/assets/vendor/nuevo-player/js/` and `public/assets/vendor/nuevo-player/vdjs/`
| File | Library | License |
|------|---------|---------|
| `video.min.js` | [Video.js](https://videojs.com) | Apache-2.0 |
| `videojs-contrib-hls.min.js` | videojs-contrib-hls | Apache-2.0 |
| `videojs-landscape-fullscreen.min.js` | videojs-landscape-fullscreen | MIT |
| `nuevo.min.js` | [Nuevo Player](https://www.nuevodevel.com) | **Commercial license** — purchased; see `tmp/nuevo-player.tar.xz` (not in repo) |
| `nsvideo.min.js` | Nuevo Player (nsVideo module) | **Commercial license** (same purchase) |
| `vdjs/v10.0.0/skins/treso/videojs.min.css` | Treso skin (Nuevo) | **Commercial license** (same purchase) |
**Note:** `nuevo.min.js` and `nsvideo.min.js` are proprietary (Nuevo Player commercial license). Binary distribution requires a valid Nuevo Player license. These files are included in the repo under the purchased license and must **not** be redistributed or open-sourced. The `video.min.js` (Video.js core) and HLS plugin are separately Apache-2.0 licensed and may be replaced with upstream npm builds.
## TipTap Editor
**Component:** Rich text editor in Broadcast admin compose view (`ac-ms-broadcast/views/admin/compose.php`)
**License:** MIT
**URL:** https://tiptap.dev | https://github.com/ueberdosis/tiptap
**Note:** Only open-source core extensions used. No Pro cloud extensions. Loaded from unpkg CDN in current implementation — for production, pin version and self-host bundle.

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
/*! @name videojs-landscape-fullscreen @version 11.1111.0 @license ISC */
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("video.js"),require("global/window")):"function"==typeof define&&define.amd?define(["video.js","global/window"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).videojsLandscapeFullscreen=n(e.videojs,e.window)}(this,(function(e,n){"use strict";function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var o=t(e),i=t(n),r={fullscreen:{enterOnRotate:!0,exitOnRotate:!0,alwaysInLandscapeMode:!0,iOS:!0}},l=i.default.screen;l.lockOrientationUniversal=function(e){return l.orientation&&l.orientation.lock(e).then((function(){}),(function(e){return console.log(e)}))||l.mozLockOrientation&&l.mozLockOrientation(e)||l.msLockOrientation&&l.msLockOrientation(e)};var a=function(){return"number"==typeof i.default.orientation||l&&l.orientation&&l.orientation.angle?i.default.orientation:(o.default.log("angle unknown"),0)},u=o.default.registerPlugin||o.default.plugin,s=function(e){var n=this;(o.default.browser.IS_ANDROID||o.default.browser.IS_IOS)&&this.ready((function(){!function(e,n){e.addClass("vjs-landscape-fullscreen"),n.fullscreen.iOS&&o.default.browser.IS_IOS&&o.default.browser.IOS_VERSION>9&&!e.el_.ownerDocument.querySelector(".bc-iframe")&&(e.tech_.el_.setAttribute("playsinline","playsinline"),e.tech_.supportsFullScreen=function(){return!1});var t=function(){var t=a();90!==t&&270!==t&&-90!==t||n.fullscreen.enterOnRotate&&!1===e.paused()&&(e.requestFullscreen(),l.lockOrientationUniversal("landscape")),0!==t&&180!==t||n.fullscreen.exitOnRotate&&e.isFullscreen()&&e.exitFullscreen()};o.default.browser.IS_IOS?i.default.addEventListener("orientationchange",t):l&&l.orientation&&(l.orientation.onchange=t),e.on("fullscreenchange",(function(t){(o.default.browser.IS_ANDROID||o.default.browser.IS_IOS)&&!a()&&e.isFullscreen()&&n.fullscreen.alwaysInLandscapeMode&&l.lockOrientationUniversal("landscape")})),e.on("dispose",(function(){i.default.removeEventListener("orientationchange",t)}))}(n,o.default.mergeOptions(r,e))}))};return u("landscapeFullscreen",s),s.VERSION="11.1111.0",s}));

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,8 @@
if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l); if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l);
})(); })();
</script> </script>
<link rel="icon" type="image/x-icon" href="<?= h(Auth::basePath()) ?>/assets/favicon.ico">
<link rel="apple-touch-icon" href="<?= h(Auth::basePath()) ?>/assets/apple-touch-icon.ico">
<link rel="stylesheet" href="<?= h(Auth::basePath()) ?>/assets/css/app.css"> <link rel="stylesheet" href="<?= h(Auth::basePath()) ?>/assets/css/app.css">
<script src="<?= h(Auth::basePath()) ?>/assets/js/nav_shell.js" defer></script> <script src="<?= h(Auth::basePath()) ?>/assets/js/nav_shell.js" defer></script>
<script src="<?= h(Auth::basePath()) ?>/assets/js/i18n.js" defer></script> <script src="<?= h(Auth::basePath()) ?>/assets/js/i18n.js" defer></script>

View File

@@ -1,18 +1,34 @@
# Full androidcast vhost for Alpine BE — listen 80 only. # Full androidcast vhost for Alpine BE — listen 80 only.
# FE (apps.f0xx.org / s.f0xx.org) proxies here: cast0103 cluster :80 # FE (apps.f0xx.org / s.f0xx.org / notify.f0xx.org) proxies here: cast0103 cluster :80
# Port 8089 is a different service (e.g. Janus) — not this vhost. # Port 8089 is a different service (e.g. Janus) — not this vhost.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server { server {
listen 80 default_server; listen 80 default_server;
listen [::]:80 default_server; listen [::]:80 default_server;
# OTA v0 static tree — devices fetch https://apps.f0xx.org/v0/ota/channel/stable.json # OTA v0 static tree — devices fetch https://apps.f0xx.org/v0/ota/channel/stable.json
# Populate: builder auto_deploy, or rsync out/ota/v0/ → .../ota-artifacts/v0/
location ^~ /v0/ota/ { location ^~ /v0/ota/ {
alias /var/www/localhost/htdocs/apps/app/androidcast_project/ota-artifacts/v0/ota/; alias /var/www/localhost/htdocs/apps/app/androidcast_project/ota-artifacts/v0/ota/;
add_header Cache-Control "public, max-age=60"; add_header Cache-Control "public, max-age=60";
} }
# Favicon — served from hub root (shared by all sub-apps via explicit <link> tags)
location = /favicon.ico {
alias /var/www/localhost/htdocs/apps/app/androidcast_project/favicon.ico;
access_log off;
expires 7d;
}
location = /apple-touch-icon.ico {
alias /var/www/localhost/htdocs/apps/app/androidcast_project/apple-touch-icon.ico;
access_log off;
expires 7d;
}
location = /app/androidcast_project { location = /app/androidcast_project {
return 301 /app/androidcast_project/; return 301 /app/androidcast_project/;
} }
@@ -28,8 +44,9 @@ server {
rewrite ^ /app/androidcast_project/index.php last; rewrite ^ /app/androidcast_project/index.php last;
} }
# Shared session auth at project root (not under /issues/) — composed ac/* backend # Shared session auth at project root — composed ac/* backend
location ~ ^/app/androidcast_project/(login|logout|register|two-factor|verify-email)(/|$) { # Includes 2FA cross-device and polling endpoints
location ~ ^/app/androidcast_project/(login|logout|register|two-factor|verify-email|api/two-factor)(/|$) {
include fastcgi_params; include fastcgi_params;
fastcgi_pass 127.0.0.1:9000; fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME /var/www/ac/composed/backend/public/index.php; fastcgi_param SCRIPT_FILENAME /var/www/ac/composed/backend/public/index.php;
@@ -48,7 +65,7 @@ server {
return 301 /app/androidcast_project/graphs/; return 301 /app/androidcast_project/graphs/;
} }
# --- Target paths (SPEC): /issues/ served from composed ac/* backend --- # --- /issues/ served from composed ac/* backend ---
location = /app/androidcast_project/issues { location = /app/androidcast_project/issues {
return 301 /app/androidcast_project/issues/; return 301 /app/androidcast_project/issues/;
} }
@@ -140,6 +157,77 @@ server {
fastcgi_read_timeout 1800s; fastcgi_read_timeout 1800s;
} }
# ── grafana-auth-check: internal auth_request endpoint ───────────────────
location = /app/androidcast_project/api/grafana-auth-check.php {
internal;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME /var/www/ac/composed/backend/public/api/grafana-auth-check.php;
fastcgi_param SCRIPT_NAME /app/androidcast_project/api/grafana-auth-check.php;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param HTTP_COOKIE $http_cookie;
}
location @monitor_login_redirect {
return 302 /app/androidcast_project/login?redirect=$request_uri;
}
# ── Grafana at cast04:3000 ────────────────────────────────────────────────
location = /app/androidcast_project/monitor {
return 301 /app/androidcast_project/monitor/;
}
location /app/androidcast_project/monitor/ {
auth_request /app/androidcast_project/api/grafana-auth-check.php;
auth_request_set $grafana_user $upstream_http_x_webauth_user;
error_page 401 = @monitor_login_redirect;
proxy_pass http://10.7.16.239:3000/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-WEBAUTH-USER $grafana_user;
proxy_http_version 1.1;
proxy_read_timeout 300;
proxy_send_timeout 300;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
# ── Alertmanager at cast04:9093 ───────────────────────────────────────────
location = /app/androidcast_project/alertmanager {
return 301 /app/androidcast_project/alertmanager/;
}
location /app/androidcast_project/alertmanager/ {
auth_request /app/androidcast_project/api/grafana-auth-check.php;
auth_request_set $grafana_user $upstream_http_x_webauth_user;
error_page 401 = @monitor_login_redirect;
proxy_pass http://10.7.16.239:9093/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_read_timeout 120;
proxy_send_timeout 120;
}
# ── Broadcast admin dashboard (/app/androidcast_project/broadcast/) ───────
location = /app/androidcast_project/broadcast {
return 301 /app/androidcast_project/broadcast/;
}
location /app/androidcast_project/broadcast/ {
auth_request /app/androidcast_project/api/grafana-auth-check.php;
auth_request_set $auth_user $upstream_http_x_webauth_user;
error_page 401 = @monitor_login_redirect;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME /var/www/ac/broadcast/public/index.php;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param HTTP_COOKIE $http_cookie;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
# --- Gitea web UI (lab browse; mirrors configured separately) --- # --- Gitea web UI (lab browse; mirrors configured separately) ---
location = /app/androidcast_project/git { location = /app/androidcast_project/git {
return 301 /app/androidcast_project/git/; return 301 /app/androidcast_project/git/;
@@ -164,3 +252,39 @@ server {
fastcgi_pass 127.0.0.1:9000; fastcgi_pass 127.0.0.1:9000;
} }
} }
# ── notify.f0xx.org — Broadcast public landing pages ─────────────────────────
# CNAME: notify.f0xx.org → apps.f0xx.org → cluster (cast0103)
server {
listen 80;
listen [::]:80;
server_name notify.f0xx.org;
root /var/www/ac/broadcast/public;
index index.php;
location = /favicon.ico {
alias /var/www/localhost/htdocs/apps/app/androidcast_project/favicon.ico;
access_log off;
expires 7d;
}
location / {
try_files $uri /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME /var/www/ac/broadcast/public/index.php;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param HTTP_COOKIE $http_cookie;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
location ~* \.(js|css|png|jpg|ico|woff2?)$ {
expires 7d;
add_header Cache-Control "public";
}
}

View File

@@ -71,6 +71,7 @@ run_phase_app() {
run_script "$ROOT/scripts/deploy-ac-workspace.sh" run_script "$ROOT/scripts/deploy-ac-workspace.sh"
run_script "$ROOT/scripts/purge-legacy-monolith.sh" run_script "$ROOT/scripts/purge-legacy-monolith.sh"
run_script "$ROOT/scripts/deploy-ac-url-shortener.sh" run_script "$ROOT/scripts/deploy-ac-url-shortener.sh"
run_script "$ROOT/scripts/deploy-ac-broadcast.sh"
if [ "${COMPOSE_RUNTIME:-0}" = "1" ]; then if [ "${COMPOSE_RUNTIME:-0}" = "1" ]; then
run_script "$ROOT/scripts/compose-lab-backend.sh" run_script "$ROOT/scripts/compose-lab-backend.sh"
run_script "$ROOT/scripts/deploy-ac-hub.sh" run_script "$ROOT/scripts/deploy-ac-hub.sh"

View File

@@ -0,0 +1,89 @@
#!/bin/sh
# Deploy ac-ms-broadcast service to /var/www/ac/broadcast/
# Creates DB schema on primary node; copies files on all nodes.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
# shellcheck source=/dev/null
. "$ROOT/scripts/lib/common.sh"
load_cluster_env
WS="${AC_WORKSPACE_ROOT:-/var/www/ac/workspace}"
BROADCAST_SRC="${WS}/ac-ms-broadcast"
BROADCAST_DEST="/var/www/ac/broadcast"
[ -d "$BROADCAST_SRC" ] || { log "skip deploy-ac-broadcast: ac-ms-broadcast not in workspace"; return 0; }
log "deploy ac-ms-broadcast → ${BROADCAST_DEST}"
mkdir -p "$BROADCAST_DEST"
rsync -a --delete \
--exclude='.git' \
--exclude='config/config.php' \
"${BROADCAST_SRC}/" "${BROADCAST_DEST}/"
# Config from cluster env / secrets
DB_PASS="$(read_cred mariadb_app password 2>/dev/null || echo 'CHANGE_ME')"
SHORTENER_BEARER="$(read_cred shortener_bearer token 2>/dev/null || echo 'CHANGE_ME')"
TELEGRAM_TOKEN="$(read_cred telegram_bot token 2>/dev/null || echo '')"
TELEGRAM_CHAT="$(read_cred telegram_chat_id value 2>/dev/null || echo '')"
cat > "${BROADCAST_DEST}/config/config.php" <<PHPEOF
<?php
return [
'db' => [
'host' => '${PRIMARY_DB_HOST:-127.0.0.1}',
'port' => 3306,
'name' => 'broadcast',
'user' => 'broadcast_svc',
'password' => '${DB_PASS}',
],
'public_base' => 'https://notify.f0xx.org',
'shortener_url' => 'http://10.7.16.236/api/v1/shorten',
'shortener_bearer' => '${SHORTENER_BEARER}',
'telegram_token' => '${TELEGRAM_TOKEN}',
'telegram_chat_id' => '${TELEGRAM_CHAT}',
'smtp_host' => 'smtp.gmail.com',
'smtp_port' => 587,
'smtp_user' => 'bestcastr@gmail.com',
'smtp_password' => 'wnxv caln tcmt ldpn',
'smtp_from' => 'bestcastr@gmail.com',
'smtp_from_name' => 'AndroidCast',
'vapid_public' => 'BNncoy2XgjxGUdvmXtqtaOIM16k0MpA31KKm2C8N3I7HK56NXgj69etBoseASxibS_ypPNHJ38tol8mL3AZNx3E',
'vapid_private' => 'sgNXNbD29znCJfAEFI5kHb4IzrbkpYJxgroKCx8YJjk',
'vapid_subject' => 'mailto:a.afanasieff@gmail.com',
'worker_interval' => 30,
'push_batch_size' => 100,
'max_retries' => 3,
'port' => 8201,
];
PHPEOF
chown -R nginx:nginx "$BROADCAST_DEST" 2>/dev/null || true
# DB schema + user — primary node only
if is_primary_node; then
log "broadcast: create DB + user on primary"
DB_SCHEMA="${BROADCAST_SRC}/sql/schema.mariadb.sql"
if [ -f "$DB_SCHEMA" ]; then
sudo -u mysql mariadb --socket=/run/mysqld/mysqld.sock < "$DB_SCHEMA" || true
sudo -u mysql mariadb --socket=/run/mysqld/mysqld.sock <<SQLEOF
CREATE USER IF NOT EXISTS 'broadcast_svc'@'%' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON broadcast.* TO 'broadcast_svc'@'%';
FLUSH PRIVILEGES;
SQLEOF
log "broadcast: DB schema applied"
fi
# Start / restart delivery worker
if [ -f /etc/init.d/broadcast-worker ]; then
rc-service broadcast-worker restart 2>/dev/null || true
else
install -m 755 "${BROADCAST_DEST}/deploy/openrc/broadcast-worker" /etc/init.d/broadcast-worker
# Point worker to correct PHP binary
sed -i 's|/usr/bin/php83|'"$(which php83 2>/dev/null || echo /usr/bin/php83)"'|g' /etc/init.d/broadcast-worker
rc-service broadcast-worker start 2>/dev/null || true
rc-update add broadcast-worker default 2>/dev/null || true
fi
log "broadcast: worker (re)started"
fi
log "deploy-ac-broadcast_ok $(host_short)"