App v0.2.26 and gateway 0.1.20

Client: seek controls, Bazarr subtitle download and cast panel in the
player; MDBList ratings strip; episode and schedule detail pages; series
pace estimate; what's new panel; install-permission onboarding step;
synced per-profile preferences; Emby outage banner.

Gateway: rebuilt admin console (one fragment per page), preference
history and restore, merged Continue Watching, Emby health probe,
subtitle selection and Bazarr download, structured request logging with
per-request identity, and embedded build version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ponzischeme89
2026-08-06 22:33:56 +12:00
co-authored by Claude Opus 5
parent 2675e6d82b
commit 4a4df7a73c
257 changed files with 24868 additions and 3108 deletions
+52 -9
View File
@@ -13,6 +13,8 @@ The remote deployment:
- installs .env from the local .env.example, overwriting the deployed
copy, and keeps the old one alongside as .env.previous;
- preserves the named database volume;
- tells every signed-in television a deployment is starting, through the
gateway that is still running, before the build begins;
- pulls Redis and PostgreSQL, then builds the Memby server;
- starts each service and waits for its health check;
- restores the previous application files if activation fails.
@@ -279,8 +281,17 @@ function New-DeploymentArchive {
[string] $ReleaseDirectory
)
# Everything under server/ is streamed over one SSH connection, so a build
# artefact left in the tree is paid for on the wire. The exclusions mirror
# server/.dockerignore: a GOCACHE pointed inside server/ (see the note there)
# is gitignored and therefore silent, and turned a 1.2MB source tree into a
# 774MB deployment. Both the directory entry and its contents are named
# because bsdtar — which is what tar.exe is on Windows — matches --exclude
# against each entry path rather than pruning the walk.
$arguments = @(
'-cf', $ArchivePath,
'--exclude', 'server/.tmp-go-cache', '--exclude', 'server/.tmp-go-cache/*',
'--exclude', 'server/bin', '--exclude', 'server/bin/*',
'-C', $RepositoryDirectory,
'server', 'docker-compose.yml', '.env.example'
)
@@ -622,7 +633,7 @@ wait_for_service() {
trap rollback EXIT
trap 'exit 130' INT TERM
step '[remote 1/9] Checking Docker'
step '[remote 1/10] Checking Docker'
if ! command -v docker >/dev/null 2>&1; then
failure 'Docker is not installed on the NAS'
exit 1
@@ -642,7 +653,7 @@ fi
success "$(docker --version)"
success "$(docker compose version)"
step '[remote 2/9] Extracting the release'
step '[remote 2/10] Extracting the release'
# Checked before anything is created: the staging directory, the swap and the backup all
# need write access to the parent, and "can't create directory" from BusyBox halfway
# through a deployment is a poor way to learn the account cannot write there.
@@ -670,13 +681,18 @@ if [ "$publish_release" -eq 1 ]; then
fi
success 'Release extracted'
step '[remote 3/9] Installing configuration from .env.example'
step '[remote 3/10] Installing configuration from .env.example'
# The local .env.example is the single source of truth for configuration and carries
# real values rather than placeholders. Every deployment overwrites the deployed .env
# with it, so neither a Git push nor an SSH edit is needed.
previous_password=''
previous_admin_token=''
if [ -f "$destination/.env" ]; then
previous_password=$(sed -n 's/^POSTGRES_PASSWORD=//p' "$destination/.env" | head -n 1 | tr -d '\r')
# The token of the gateway that is still running, which is the one that can still
# tell the televisions anything. It is usually the same as the incoming one, but
# reading it from the release being replaced is what makes that not a requirement.
previous_admin_token=$(sed -n 's/^MEMBY_ADMIN_TOKEN=//p' "$destination/.env" | head -n 1 | tr -d '\r')
# Kept beside the new one purely so a bad edit is recoverable by hand.
cp -- "$destination/.env" "$staging/.env.previous"
detail 'Previous .env saved as .env.previous'
@@ -724,21 +740,48 @@ fi
)
success 'Compose configuration is valid'
step '[remote 4/9] Pulling PostgreSQL and Redis'
step '[remote 4/10] Telling the televisions'
# Announced here rather than at the swap, and the position is the point of it: the
# gateway being replaced is still answering, and the build below takes minutes, which is
# the window in which every open television polls /v1/status and collects the notice.
# Announcing at the swap would be too late twice over — nothing is left to publish with
# once the stack is down, and Redis holds the alert list in memory with no volume, so the
# swap discards anything published but not yet collected.
#
# Entirely best-effort: a deployment must never fail over a banner, and a first
# deployment (or one onto a host with no curl) has nothing to announce with.
if [ -z "$previous_admin_token" ]; then
detail 'No previous release to announce from; skipping'
elif ! command -v curl >/dev/null 2>&1; then
detail 'curl is unavailable on the NAS; skipping the announcement'
else
announce_status=$(curl --silent --show-error --max-time 5 \
--output /dev/null \
--write-out '%{http_code}' \
-X POST \
-H "Authorization: Bearer $previous_admin_token" \
http://127.0.0.1:32768/admin/api/deployment-alert 2>/dev/null || true)
case "$announce_status" in
2??) success 'Signed-in televisions have been told a deployment is starting' ;;
*) detail "Could not announce the deployment (HTTP ${announce_status:-no response})" ;;
esac
fi
step '[remote 5/10] Pulling PostgreSQL and Redis'
(
cd "$staging"
docker compose pull postgres redis
)
success 'Dependency images are ready'
step '[remote 5/9] Building memby-server'
step '[remote 6/10] Building memby-server'
(
cd "$staging"
docker compose build --pull server
)
success 'Server image built'
step '[remote 6/9] Activating the release'
step '[remote 7/10] Activating the release'
rm -rf -- "$backup"
if [ -e "$destination" ] || [ -L "$destination" ]; then
if [ -f "$destination/docker-compose.yml" ]; then
@@ -760,12 +803,12 @@ mv -- "$staging" "$destination"
activated=1
success 'Release activated'
step '[remote 7/9] Starting the Compose stack'
step '[remote 8/10] Starting the Compose stack'
cd "$destination"
docker compose up -d --remove-orphans
success 'Compose start command completed'
step '[remote 8/9] Waiting for healthy services'
step '[remote 9/10] Waiting for healthy services'
wait_for_service postgres
wait_for_service redis
wait_for_service server
@@ -785,7 +828,7 @@ if ! docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$serve
fi
success 'Runtime configuration includes the admin token'
step '[remote 9/9] Publishing the signed Android update'
step '[remote 10/10] Publishing the signed Android update'
if [ "$publish_release" -eq 1 ]; then
release_version=$(tr -d '\r\n' < "$destination/release/version.txt")
release_sha256=$(tr -d '\r\n' < "$destination/release/sha256.txt")