-
Notifications
You must be signed in to change notification settings - Fork 0
add Dockerfile + compose + workflow -> mattfly/obby-api #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
236bb25
f4760b0
16b3fb9
829faae
65dc84c
fb5e73f
37b4cf1
166a187
7795279
d331468
8831eaf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| .git | ||
| .github | ||
| .vscode | ||
| .idea | ||
| .claude | ||
| *.md | ||
| LICENSE | ||
| *.db | ||
| *.db-journal | ||
| *.db-wal | ||
| *.db-shm | ||
| backend | ||
| hosted-backend | ||
| .env | ||
| .env.local |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,47 @@ | ||
| # Copy to .env for local dev. obby-api is built locally; obbyircd | ||
| # and the obby web client are pulled from Dockerhub. In Coolify, | ||
| # set these as application environment variables instead. | ||
|
|
||
| # --- DNS / Traefik routing (Coolify auto-issues LE) --- | ||
| WEB_FQDN=chat.example.com | ||
| API_FQDN=api.example.com | ||
| IRC_FQDN=irc.example.com | ||
|
|
||
| # --- backend tuning --- | ||
| PORT=8080 | ||
| DELETE_TIMEOUT_MINUTES=2 | ||
| MAX_UPLOAD_SIZE_MB=32 | ||
| JWT_SECRET=testsecret | ||
| IRC_SERVER_KEY=testkey | ||
| PASSWORD_SALT_SIZE=32 | ||
| UNREALIRCD_WS_URL=wss://127.0.0.1:8600/ | ||
| UNREALIRCD_API_USERNAME=adminpanel | ||
| UNREALIRCD_API_PASSWORD=password | ||
|
|
||
| # --- secrets (set per environment) --- | ||
| JWT_SECRET=testsecret # (secret in prod) openssl rand -hex 32 | ||
| IRC_SERVER_KEY=testkey # (secret in prod) shared with obbyircd | ||
| VOICE_TURN_SECRET= # (secret) openssl rand -hex 32 | ||
| RPC_PASSWORD=password # (secret in prod) obbyircd JSON-RPC password | ||
|
|
||
| # --- IRCd reachability --- | ||
| UNREALIRCD_WS_URL=ws://obbyircd:8080 | ||
| UNREALIRCD_API_USERNAME=admin | ||
| UNREALIRCD_API_PASSWORD=password # mirror of RPC_PASSWORD; unify in prod | ||
|
|
||
| # --- voice / TURN --- | ||
| VOICE_PUBLIC_IP= # public IP advertised in ICE candidates | ||
| TURN_PORT=3478 # UDP, open in host firewall | ||
| VOICE_TURN_REALM=obby | ||
| VOICE_MAX_ROOM=25 | ||
|
|
||
| # --- filehost --- | ||
| FILEHOST_PUBLIC_URL= # https://files.example.com | ||
|
|
||
| # --- image overrides (optional) --- | ||
| # OBBY_API_IMAGE=obby-api:local | ||
| # OBBYIRCD_IMAGE=obbyworld/obbyircd:latest | ||
| # OBBY_WEB_IMAGE=obbyworld/obby:latest | ||
|
|
||
| # --- frontend profile (opt-in) --- | ||
| # COMPOSE_PROFILES=frontend | ||
|
|
||
| # --- bind-mount overrides (optional) --- | ||
| # OBBY_API_DATA_BIND=/srv/obby/obby-api-data | ||
| # OBBY_API_IMAGES_BIND=/srv/obby/obby-api-images | ||
| # VOICE_BRIDGE_BIND=/srv/obby/voice-bridge | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| name: Reusable Docker Build and Push | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| image_name: | ||
| required: true | ||
| type: string | ||
| context: | ||
| required: true | ||
| type: string | ||
| dockerfile: | ||
| required: true | ||
| type: string | ||
| secrets: | ||
| DOCKERHUB_USERNAME: | ||
| required: true | ||
| DOCKERHUB_TOKEN: | ||
| required: true | ||
|
|
||
| jobs: | ||
| build: | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - arch: amd64 | ||
| runner: ubuntu-24.04 | ||
| - arch: arm64 | ||
| runner: ubuntu-24.04-arm | ||
| runs-on: ${{ matrix.runner }} | ||
| timeout-minutes: 30 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - uses: docker/login-action@v3 | ||
| if: ${{ github.event_name != 'pull_request' }} | ||
| with: | ||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
|
||
| - id: meta | ||
| run: echo "short_sha=$(git rev-parse --short=7 HEAD)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - uses: docker/build-push-action@v6 | ||
| with: | ||
| context: ${{ inputs.context }} | ||
| file: ${{ inputs.dockerfile }} | ||
| platforms: linux/${{ matrix.arch }} | ||
| push: ${{ github.event_name != 'pull_request' }} | ||
| provenance: false | ||
| tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ inputs.image_name }}:sha-${{ steps.meta.outputs.short_sha }}-${{ matrix.arch }} | ||
| cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/${{ inputs.image_name }}:cache-${{ matrix.arch }} | ||
| cache-to: ${{ github.event_name != 'pull_request' && format('type=registry,ref={0}/{1}:cache-{2},mode=max,image-manifest=true,oci-mediatypes=true', secrets.DOCKERHUB_USERNAME, inputs.image_name, matrix.arch) || '' }} | ||
|
|
||
| manifest: | ||
| needs: build | ||
| if: ${{ github.event_name != 'pull_request' }} | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 5 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: docker/login-action@v3 | ||
| with: | ||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
|
||
| - id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: ${{ secrets.DOCKERHUB_USERNAME }}/${{ inputs.image_name }} | ||
| tags: | | ||
| type=ref,event=branch | ||
| type=ref,event=pr | ||
| type=semver,pattern={{version}} | ||
| type=semver,pattern={{major}}.{{minor}} | ||
| type=semver,pattern={{major}} | ||
| type=sha,prefix=sha-,format=short | ||
| type=raw,value=latest,enable={{is_default_branch}} | ||
|
|
||
| - id: short | ||
| run: echo "sha=$(git rev-parse --short=7 HEAD)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Assemble multi-arch manifest | ||
| shell: bash | ||
| env: | ||
| IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/${{ inputs.image_name }} | ||
| SHORT_SHA: ${{ steps.short.outputs.sha }} | ||
| TAGS: ${{ steps.meta.outputs.tags }} | ||
| run: | | ||
| src_amd64="${IMAGE}:sha-${SHORT_SHA}-amd64" | ||
| src_arm64="${IMAGE}:sha-${SHORT_SHA}-arm64" | ||
| tag_args=() | ||
| while IFS= read -r t; do | ||
| [ -n "$t" ] && tag_args+=( --tag "$t" ) | ||
| done <<< "$TAGS" | ||
| docker buildx imagetools create "${tag_args[@]}" "$src_amd64" "$src_arm64" | ||
| docker buildx imagetools inspect "${IMAGE}:sha-${SHORT_SHA}" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| name: Build and Push obby-api Docker Image | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| tags: | ||
| - 'v*' | ||
| paths-ignore: | ||
| - '**.md' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix markdown ignore glob to match nested files.
Suggested patch- - '**.md'
+ - '**/*.md'
@@
- - '**.md'
+ - '**/*.md'Also applies to: 16-16 🤖 Prompt for AI Agents |
||
| - 'LICENSE' | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| paths-ignore: | ||
| - '**.md' | ||
| - 'LICENSE' | ||
| workflow_dispatch: | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build obby-api Image | ||
| uses: ./.github/workflows/docker-build-push.yml | ||
| with: | ||
| image_name: obby-api | ||
| context: . | ||
| dockerfile: ./Dockerfile | ||
| secrets: | ||
| DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # syntax=docker/dockerfile:1.6 | ||
| # | ||
| # obby-api / hosted-backend image -- REST + JWT + WebRTC SFU + TURN. | ||
| # CGO_ENABLED=1 is required because mattn/go-sqlite3 ships as cgo. | ||
| # | ||
| # Build: docker build -t obbyworld/obby-api . | ||
| # Compose: docker compose up -d | ||
|
|
||
| FROM golang:1.25-alpine AS builder | ||
| RUN apk add --no-cache build-base sqlite-dev opus-dev opusfile-dev pkgconfig | ||
| WORKDIR /src | ||
| COPY go.mod go.sum ./ | ||
| RUN go mod download | ||
| COPY . . | ||
| RUN CGO_ENABLED=1 go build -trimpath -ldflags="-s -w" -o /out/backend . | ||
|
|
||
| FROM alpine:3.20 | ||
| RUN apk add --no-cache ca-certificates sqlite-libs tzdata netcat-openbsd opus opusfile \ | ||
| && adduser -D -u 1000 backend \ | ||
| && mkdir -p /app/data /app/images /run/obbyirc \ | ||
| && chown -R backend:backend /app /run/obbyirc | ||
| WORKDIR /app | ||
| COPY --from=builder /out/backend /usr/local/bin/backend | ||
| USER backend | ||
| EXPOSE 8080 3478/udp | ||
| HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 \ | ||
| CMD nc -z 127.0.0.1 8080 || exit 1 | ||
| CMD ["backend"] |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,111 @@ | ||||||||||||
| # Local dev stack for obby-api. | ||||||||||||
| # | ||||||||||||
| # This service is built from the local checkout; obbyircd and the | ||||||||||||
| # web client are pulled from Dockerhub. For full-stack development | ||||||||||||
| # in one of the other repos, swap which service has `build:` and | ||||||||||||
| # which has only `image:`. | ||||||||||||
| # | ||||||||||||
| # docker compose up -d # api + obbyircd | ||||||||||||
| # docker compose --profile frontend up -d # + obby (web) | ||||||||||||
|
|
||||||||||||
| services: | ||||||||||||
| obby-api: | ||||||||||||
| build: | ||||||||||||
| context: . | ||||||||||||
| dockerfile: ./Dockerfile | ||||||||||||
| image: ${OBBY_API_IMAGE:-obby-api:local} | ||||||||||||
| container_name: obby-api | ||||||||||||
| restart: unless-stopped | ||||||||||||
| depends_on: | ||||||||||||
| obbyircd: | ||||||||||||
| condition: service_healthy | ||||||||||||
| expose: | ||||||||||||
| - "8080" | ||||||||||||
| ports: | ||||||||||||
| - "${TURN_PORT:-3478}:3478/udp" | ||||||||||||
|
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Publish the server WebRTC UDP range when using NAT1:1 host candidates. Only Suggested patch ports:
- "${TURN_PORT:-3478}:3478/udp"
+ - "${VOICE_WEBRTC_PORT_MIN:-40000}-${VOICE_WEBRTC_PORT_MAX:-40100}:${VOICE_WEBRTC_PORT_MIN:-40000}-${VOICE_WEBRTC_PORT_MAX:-40100}/udp"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
| volumes: | ||||||||||||
| - ${OBBY_API_DATA_BIND:-obby_api_data}:/app/data | ||||||||||||
| - ${OBBY_API_IMAGES_BIND:-obby_api_images}:/app/images | ||||||||||||
| - ${VOICE_BRIDGE_BIND:-obbyircd_voice_bridge}:/run/obbyirc | ||||||||||||
| environment: | ||||||||||||
| PORT: "8080" | ||||||||||||
| VOICE_BRIDGE_SOCKET: /run/obbyirc/voice.sock | ||||||||||||
| VOICE_TURN_SECRET: ${VOICE_TURN_SECRET} | ||||||||||||
| VOICE_PUBLIC_IP: ${VOICE_PUBLIC_IP} | ||||||||||||
| VOICE_TURN_PORT: "${TURN_PORT:-3478}" | ||||||||||||
| VOICE_TURN_REALM: ${VOICE_TURN_REALM:-obby} | ||||||||||||
| VOICE_MAX_ROOM: "${VOICE_MAX_ROOM:-25}" | ||||||||||||
| JWT_SECRET: ${JWT_SECRET} | ||||||||||||
| IRC_SERVER_KEY: ${IRC_SERVER_KEY} | ||||||||||||
| UNREALIRCD_WS_URL: ${UNREALIRCD_WS_URL:-ws://obbyircd:8080} | ||||||||||||
| UNREALIRCD_API_USERNAME: ${UNREALIRCD_API_USERNAME:-admin} | ||||||||||||
| UNREALIRCD_API_PASSWORD: ${RPC_PASSWORD} | ||||||||||||
| FILEHOST_PUBLIC_URL: ${FILEHOST_PUBLIC_URL} | ||||||||||||
| env_file: | ||||||||||||
| - path: .env | ||||||||||||
| required: false | ||||||||||||
| labels: | ||||||||||||
| - traefik.enable=true | ||||||||||||
| - traefik.http.routers.api.rule=Host(`${API_FQDN}`) | ||||||||||||
| - traefik.http.routers.api.tls=true | ||||||||||||
| - traefik.http.services.api.loadbalancer.server.port=8080 | ||||||||||||
|
|
||||||||||||
| obbyircd: | ||||||||||||
| image: ${OBBYIRCD_IMAGE:-obbyworld/obbyircd:latest} | ||||||||||||
| container_name: obbyircd | ||||||||||||
| restart: unless-stopped | ||||||||||||
| expose: | ||||||||||||
| - "8080" | ||||||||||||
| ports: | ||||||||||||
| - "${SSL_HOST_PORT:-6697}:${SSL_PORT:-6697}" | ||||||||||||
| volumes: | ||||||||||||
| - obbyircd_conf:/home/obbyircd/obby/conf | ||||||||||||
| - obbyircd_data:/home/obbyircd/obby/data | ||||||||||||
| - obbyircd_logs:/home/obbyircd/obby/logs | ||||||||||||
| - obbyircd_tls:/home/obbyircd/obby/tls | ||||||||||||
| - obbyircd_custom_modules:/home/obbyircd/obby/custom-modules | ||||||||||||
| - ${VOICE_BRIDGE_BIND:-obbyircd_voice_bridge}:/run/obbyirc | ||||||||||||
| environment: | ||||||||||||
| WS_PORT: "${WS_PORT:-8080}" | ||||||||||||
| SSL_PORT: "${SSL_PORT:-6697}" | ||||||||||||
| VOICE_BRIDGE_SOCKET: /run/obbyirc/voice.sock | ||||||||||||
| env_file: | ||||||||||||
| - path: .env | ||||||||||||
| required: false | ||||||||||||
| healthcheck: | ||||||||||||
| test: ["CMD-SHELL", "nc -z localhost ${WS_PORT:-8080}"] | ||||||||||||
| interval: 5s | ||||||||||||
| timeout: 5s | ||||||||||||
| retries: 6 | ||||||||||||
| start_period: 30s | ||||||||||||
| labels: | ||||||||||||
| - traefik.enable=true | ||||||||||||
| - traefik.http.routers.obbyircd.rule=Host(`${IRC_FQDN}`) | ||||||||||||
| - traefik.http.routers.obbyircd.tls=true | ||||||||||||
| - traefik.http.services.obbyircd.loadbalancer.server.port=8080 | ||||||||||||
|
|
||||||||||||
| obby: | ||||||||||||
| image: ${OBBY_WEB_IMAGE:-obbyworld/obby:latest} | ||||||||||||
| container_name: obby-web | ||||||||||||
| restart: unless-stopped | ||||||||||||
| profiles: ["frontend"] | ||||||||||||
| expose: | ||||||||||||
| - "80" | ||||||||||||
| env_file: | ||||||||||||
| - path: .env | ||||||||||||
| required: false | ||||||||||||
| labels: | ||||||||||||
| - traefik.enable=true | ||||||||||||
| - traefik.http.routers.web.rule=Host(`${WEB_FQDN}`) | ||||||||||||
| - traefik.http.routers.web.tls=true | ||||||||||||
| - traefik.http.services.web.loadbalancer.server.port=80 | ||||||||||||
|
|
||||||||||||
| volumes: | ||||||||||||
| obby_api_data: | ||||||||||||
| obby_api_images: | ||||||||||||
| obbyircd_conf: | ||||||||||||
| obbyircd_data: | ||||||||||||
| obbyircd_logs: | ||||||||||||
| obbyircd_tls: | ||||||||||||
| obbyircd_custom_modules: | ||||||||||||
| obbyircd_voice_bridge: | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document the new voice env vars consumed by
voice.go.VOICE_TURN_DISABLE_EMBEDDED,VOICE_WEBRTC_PORT_MIN, andVOICE_WEBRTC_PORT_MAXare read by runtime code but not surfaced in.env.example, which makes this feature hard to configure correctly in deployments.Suggested additions
📝 Committable suggestion
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 28-28: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
[warning] 28-28: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 29-29: [UnorderedKey] The TURN_PORT key should go before the VOICE_PUBLIC_IP key
(UnorderedKey)
[warning] 29-29: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 31-31: [UnorderedKey] The VOICE_MAX_ROOM key should go before the VOICE_PUBLIC_IP key
(UnorderedKey)
🤖 Prompt for AI Agents