From 4e2b4aba25809d21ad8f688d73d071e972c148ab Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Sun, 10 May 2026 12:51:16 +1000 Subject: [PATCH] Initial commit --- .dockerignore | 6 ++ .gitea/workflows/docker-publish.yml | 114 ++++++++++++++++++++++++++++ .gitignore | 20 +++++ Dockerfile | 42 ++++++++++ README.md | 87 ++++++++++++++++++++- compose.yaml | 17 +++++ docker-entrypoint.sh | 56 ++++++++++++++ 7 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/docker-publish.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 compose.yaml create mode 100644 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..480eb3a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +.upstream-open-stage-control +node_modules +dist +data +config diff --git a/.gitea/workflows/docker-publish.yml b/.gitea/workflows/docker-publish.yml new file mode 100644 index 0000000..99c61a2 --- /dev/null +++ b/.gitea/workflows/docker-publish.yml @@ -0,0 +1,114 @@ +name: Build & Push Docker + +on: + push: + branches: ["main"] + workflow_dispatch: + schedule: + - cron: "@weekly" + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Resolve latest Open Stage Control release + shell: bash + run: | + set -euo pipefail + + python3 <<'PY' + import json + import urllib.request + + url = "https://framagit.org/api/v4/projects/jean-emmanuel%2Fopen-stage-control/releases" + with urllib.request.urlopen(url) as response: + releases = json.load(response) + + if not releases: + raise SystemExit("No releases returned by upstream API") + + latest = releases[0] + tag = latest["tag_name"] + version = tag[1:] if tag.startswith("v") else tag + + with open("release.env", "w", encoding="utf-8") as f: + f.write(f"OSC_TAG={tag}\n") + f.write(f"OSC_VERSION={version}\n") + PY + + cat release.env >> "$GITHUB_ENV" + + - name: Set image names + shell: bash + env: + REPOSITORY: ${{ gitea.repository }} + run: | + set -euo pipefail + OWNER="${REPOSITORY%/*}" + REPO="${REPOSITORY#*/}" + echo "IMAGE_LATEST=git.f-40.com/${OWNER}/${REPO}:latest" >> "$GITHUB_ENV" + echo "IMAGE_VERSIONED=git.f-40.com/${OWNER}/${REPO}:${OSC_VERSION}" >> "$GITHUB_ENV" + + - name: Login to Gitea Container Registry + shell: bash + env: + REGISTRY_USER: ${{ secrets.USER }} + REGISTRY_TOKEN: ${{ secrets.TOKEN }} + run: | + set -euo pipefail + echo "$REGISTRY_TOKEN" | docker login git.f-40.com -u "$REGISTRY_USER" --password-stdin + + - name: Decide whether a build is needed + shell: bash + env: + EVENT_NAME: ${{ gitea.event_name }} + run: | + set -euo pipefail + + if [ "$EVENT_NAME" = "push" ] || [ "$EVENT_NAME" = "workflow_dispatch" ]; then + echo "SHOULD_BUILD=true" >> "$GITHUB_ENV" + echo "BUILD_REASON=repository change or manual run" >> "$GITHUB_ENV" + exit 0 + fi + + if docker manifest inspect "$IMAGE_VERSIONED" >/dev/null 2>&1; then + echo "SHOULD_BUILD=false" >> "$GITHUB_ENV" + echo "BUILD_REASON=version ${OSC_VERSION} already exists in registry" >> "$GITHUB_ENV" + else + echo "SHOULD_BUILD=true" >> "$GITHUB_ENV" + echo "BUILD_REASON=new upstream release ${OSC_TAG}" >> "$GITHUB_ENV" + fi + + - name: Build + shell: bash + run: | + set -euo pipefail + + if [ "${SHOULD_BUILD}" != "true" ]; then + echo "Skipping build: ${BUILD_REASON}" + exit 0 + fi + + echo "Building ${OSC_TAG} because ${BUILD_REASON}" + docker build \ + --build-arg OPEN_STAGE_CONTROL_VERSION="${OSC_VERSION}" \ + -t "${IMAGE_LATEST}" \ + -t "${IMAGE_VERSIONED}" \ + . + + - name: Push + shell: bash + run: | + set -euo pipefail + + if [ "${SHOULD_BUILD}" != "true" ]; then + echo "Skipping push: ${BUILD_REASON}" + exit 0 + fi + + docker push "${IMAGE_VERSIONED}" + docker push "${IMAGE_LATEST}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1504a16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# OS / editor +.DS_Store +Thumbs.db +.vscode/ +.idea/ + +# Temp / logs +*.log +*.tmp + +# Docker app data +config/ +data/ + +# Local upstream inspection clones +.upstream-open-stage-control/ + +# Node / build artifacts +node_modules/ +dist/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b80c52 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +FROM debian:bookworm-slim AS downloader + +ARG OPEN_STAGE_CONTROL_VERSION=1.30.3 +ARG OPEN_STAGE_CONTROL_BASE_URL=https://openstagecontrol.ammd.net/packages + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl unzip \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp + +RUN curl -fsSLo open-stage-control.zip \ + "${OPEN_STAGE_CONTROL_BASE_URL}/open-stage-control_${OPEN_STAGE_CONTROL_VERSION}_node.zip" \ + && unzip -q open-stage-control.zip -d extracted \ + && mv extracted/open-stage-control_${OPEN_STAGE_CONTROL_VERSION}_node /open-stage-control + +FROM node:20-bookworm-slim + +LABEL org.opencontainers.image.title="Open Stage Control" +LABEL org.opencontainers.image.description="Container image for the Open Stage Control node-only release" +LABEL org.opencontainers.image.source="https://framagit.org/jean-emmanuel/open-stage-control" + +ENV OSC_PORT=8080 +ENV OSC_OSC_PORT=8080 +ENV OSC_CACHE_DIR=/config +ENV OSC_REMOTE_ROOT=/data + +WORKDIR /opt + +COPY --from=downloader /open-stage-control /opt/open-stage-control +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh + +RUN chmod +x /usr/local/bin/docker-entrypoint.sh \ + && mkdir -p /config /data + +VOLUME ["/config", "/data"] + +EXPOSE 8080/tcp +EXPOSE 8080/udp + +ENTRYPOINT ["docker-entrypoint.sh"] +CMD [] diff --git a/README.md b/README.md index 45bfadd..cd4882b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,88 @@ # OSC-Docker -A docker image for Open Stage Control \ No newline at end of file +Docker image for the Open Stage Control node-only release. + +This image uses the upstream release artifact instead of building Electron from source. I verified on May 10, 2026 that Framagit's current release is `v1.30.3`, and that it publishes a `Node.js` package at: + +`https://openstagecontrol.ammd.net/packages/open-stage-control_1.30.3_node.zip` + +Source project: + +`https://framagit.org/jean-emmanuel/open-stage-control` + +## Build + +```bash +docker build -t open-stage-control . +``` + +To target a different upstream release: + +```bash +docker build \ + --build-arg OPEN_STAGE_CONTROL_VERSION=1.30.3 \ + -t open-stage-control . +``` + +## Run + +```bash +docker run --rm \ + -p 8080:8080/tcp \ + -p 8080:8080/udp \ + -v "${PWD}/config:/config" \ + -v "${PWD}/data:/data" \ + open-stage-control +``` + +Then open: + +`http://localhost:8080` + +## Docker Compose + +```bash +docker compose up --build +``` + +## Gitea Actions + +The repo includes a Gitea Actions workflow at `.gitea/workflows/docker-publish.yml`. + +It will: + +- build on pushes to `main` +- allow manual runs with `workflow_dispatch` +- poll the upstream Open Stage Control releases API once a week +- build and push a new image only when a new upstream release tag is found + +Required repository secrets: + +- `USER`: registry username +- `TOKEN`: registry token or PAT for `git.f-40.com` + +## Environment Variables + +- `OSC_PORT`: HTTP port. Default `8080`. +- `OSC_OSC_PORT`: OSC UDP input port. Default `8080`. +- `OSC_TCP_PORT`: Optional OSC TCP input port. +- `OSC_CACHE_DIR`: Config/cache path inside the container. Default `/config`. +- `OSC_REMOTE_ROOT`: File browser root inside the container. Default `/data`. +- `OSC_LOAD`: Optional session file to auto-load. +- `OSC_STATE`: Optional state file to auto-load. +- `OSC_CUSTOM_MODULE`: Optional custom module path. +- `OSC_THEME`: Optional theme path or theme name. +- `OSC_AUTHENTICATION`: Optional `user:password`. +- `OSC_CLIENT_OPTIONS`: Optional single `key=value` client option. For multiple client options, pass extra CLI args after the image name. +- `OSC_READ_ONLY`: Set to `true` to disable editing/saving. +- `OSC_USE_SSL`: Set to `true` to enable HTTPS. +- `OSC_DEBUG`: Set to `true` to log OSC traffic. +- `OSC_NO_QRCODE`: Set to `true` to suppress QR output. + +## Extra CLI Arguments + +You can still pass native Open Stage Control arguments after the image name: + +```bash +docker run --rm -p 8080:8080/tcp -p 8080:8080/udp open-stage-control --send 192.168.1.50:9000 +``` diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..ff82622 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,17 @@ +services: + open-stage-control: + build: + context: . + args: + OPEN_STAGE_CONTROL_VERSION: 1.30.3 + ports: + - "8080:8080/tcp" + - "8080:8080/udp" + environment: + OSC_PORT: 8080 + OSC_OSC_PORT: 8080 + OSC_REMOTE_ROOT: /data + OSC_CACHE_DIR: /config + volumes: + - ./config:/config + - ./data:/data diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..4468f26 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,56 @@ +#!/bin/sh +set -eu + +set -- \ + node /opt/open-stage-control \ + --no-gui \ + --cache-dir "${OSC_CACHE_DIR:-/config}" \ + --remote-root "${OSC_REMOTE_ROOT:-/data}" \ + --port "${OSC_PORT:-8080}" \ + --osc-port "${OSC_OSC_PORT:-${OSC_PORT:-8080}}" + +if [ -n "${OSC_TCP_PORT:-}" ]; then + set -- "$@" --tcp-port "${OSC_TCP_PORT}" +fi + +if [ -n "${OSC_LOAD:-}" ]; then + set -- "$@" --load "${OSC_LOAD}" +fi + +if [ -n "${OSC_STATE:-}" ]; then + set -- "$@" --state "${OSC_STATE}" +fi + +if [ -n "${OSC_CUSTOM_MODULE:-}" ]; then + set -- "$@" --custom-module "${OSC_CUSTOM_MODULE}" +fi + +if [ -n "${OSC_THEME:-}" ]; then + set -- "$@" --theme "${OSC_THEME}" +fi + +if [ -n "${OSC_CLIENT_OPTIONS:-}" ]; then + set -- "$@" --client-options "${OSC_CLIENT_OPTIONS}" +fi + +if [ -n "${OSC_AUTHENTICATION:-}" ]; then + set -- "$@" --authentication "${OSC_AUTHENTICATION}" +fi + +if [ "${OSC_READ_ONLY:-false}" = "true" ]; then + set -- "$@" --read-only +fi + +if [ "${OSC_USE_SSL:-false}" = "true" ]; then + set -- "$@" --use-ssl +fi + +if [ "${OSC_DEBUG:-false}" = "true" ]; then + set -- "$@" --debug +fi + +if [ "${OSC_NO_QRCODE:-false}" = "true" ]; then + set -- "$@" --no-qrcode +fi + +exec "$@"