diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e9c11d1..4ab239c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,7 +43,7 @@ jobs: strategy: matrix: - service: [web, ingestor] + service: [web, ingestor, matrix-bridge] architecture: - { name: linux-amd64, platform: linux/amd64, label: "Linux x86_64", os: linux, architecture: amd64 } - { name: linux-arm64, platform: linux/arm64, label: "Linux ARM64", os: linux, architecture: arm64 } @@ -109,8 +109,8 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./${{ matrix.service == 'web' && 'web/Dockerfile' || 'data/Dockerfile' }} - target: production + file: ${{ matrix.service == 'web' && './web/Dockerfile' || matrix.service == 'ingestor' && './data/Dockerfile' || './matrix/Dockerfile' }} + target: ${{ matrix.service == 'matrix-bridge' && 'runtime' || 'production' }} platforms: ${{ matrix.architecture.platform }} push: true tags: | @@ -119,12 +119,12 @@ jobs: ${{ steps.tagging.outputs.include_latest == 'true' && format('{0}/{1}-{2}-{3}:latest', env.REGISTRY, env.IMAGE_PREFIX, matrix.service, matrix.architecture.name) || '' }} labels: | org.opencontainers.image.source=https://github.com/${{ github.repository }} - org.opencontainers.image.description=PotatoMesh ${{ matrix.service == 'web' && 'Web Application' || 'Python Ingestor' }} for ${{ matrix.architecture.label }} + org.opencontainers.image.description=PotatoMesh ${{ matrix.service == 'web' && 'Web Application' || matrix.service == 'ingestor' && 'Python Ingestor' || 'Matrix Bridge' }} for ${{ matrix.architecture.label }} org.opencontainers.image.licenses=Apache-2.0 org.opencontainers.image.version=${{ steps.version.outputs.version }} org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.title=PotatoMesh ${{ matrix.service == 'web' && 'Web' || 'Ingestor' }} (${{ matrix.architecture.label }}) + org.opencontainers.image.title=PotatoMesh ${{ matrix.service == 'web' && 'Web' || matrix.service == 'ingestor' && 'Ingestor' || 'Matrix Bridge' }} (${{ matrix.architecture.label }}) org.opencontainers.image.vendor=PotatoMesh org.opencontainers.image.architecture=${{ matrix.architecture.architecture }} org.opencontainers.image.os=${{ matrix.architecture.os }} @@ -208,6 +208,19 @@ jobs: VERSION=${GITHUB_REF#refs/tags/v} echo "version=$VERSION" >> $GITHUB_OUTPUT + - name: Determine tagging strategy + id: tagging + run: | + VERSION="${{ steps.version.outputs.version }}" + + if echo "$VERSION" | grep -E -- '-(rc|beta|alpha|dev)'; then + INCLUDE_LATEST=false + else + INCLUDE_LATEST=true + fi + + echo "include_latest=$INCLUDE_LATEST" >> $GITHUB_OUTPUT + - name: Publish release summary run: | echo "## 🚀 PotatoMesh Images Published to GHCR" >> $GITHUB_STEP_SUMMARY @@ -234,4 +247,13 @@ jobs: echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-armv7:latest\` - Linux ARMv7" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY fi + + # Matrix bridge images + echo "### 🧩 Matrix Bridge" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.tagging.outputs.include_latest }}" = "true" ]; then + echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-matrix-bridge-linux-amd64:latest\` - Linux x86_64" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-matrix-bridge-linux-arm64:latest\` - Linux ARM64" >> $GITHUB_STEP_SUMMARY + echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-matrix-bridge-linux-armv7:latest\` - Linux ARMv7" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi diff --git a/docker-compose.yml b/docker-compose.yml index 305e96e..8ab5a5b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,6 +76,21 @@ x-ingestor-base: &ingestor-base memory: 128M cpus: '0.1' +x-matrix-bridge-base: &matrix-bridge-base + image: ghcr.io/l5yth/potato-mesh-matrix-bridge-${POTATOMESH_IMAGE_ARCH:-linux-amd64}:${POTATOMESH_IMAGE_TAG:-latest} + volumes: + - potatomesh_matrix_bridge_state:/app + - ./matrix/Config.toml:/app/Config.toml:ro + restart: unless-stopped + deploy: + resources: + limits: + memory: 128M + cpus: '0.1' + reservations: + memory: 64M + cpus: '0.05' + services: web: <<: *web-base @@ -109,6 +124,24 @@ services: profiles: - bridge + matrix-bridge: + <<: *matrix-bridge-base + network_mode: host + depends_on: + - web + extra_hosts: + - "web:127.0.0.1" + + matrix-bridge-bridge: + <<: *matrix-bridge-base + container_name: potatomesh-matrix-bridge + networks: + - potatomesh-network + depends_on: + - web-bridge + profiles: + - bridge + volumes: potatomesh_data: driver: local @@ -116,6 +149,8 @@ volumes: driver: local potatomesh_logs: driver: local + potatomesh_matrix_bridge_state: + driver: local networks: potatomesh-network: diff --git a/matrix/Config.toml b/matrix/Config.toml index 8dbf907..0d34e40 100644 --- a/matrix/Config.toml +++ b/matrix/Config.toml @@ -1,19 +1,20 @@ [potatomesh] # Base domain (with or without trailing slash) -base_url = "https://potatomesh.net/" +base_url = "https://potatomesh.net" # Poll interval in seconds poll_interval_secs = 60 [matrix] # Homeserver base URL (client API) without trailing slash -homeserver = "https://matrix.example.org" +homeserver = "https://matrix.dod.ngo" # Appservice access token (from your registration.yaml) -as_token = "YOUR_APPSERVICE_AS_TOKEN" +as_token = "INVALID_TOKEN_NOT_WORKING" # Server name (domain) part of Matrix user IDs -server_name = "example.org" +server_name = "dod.ngo" # Room ID to send into (must be joined by the appservice / puppets) -room_id = "!yourroomid:example.org" +room_id = "!sXabOBXbVObAlZQEUs:c-base.org" # "#potato-bridge:c-base.org" [state] # Where to persist last seen message id (optional but recommended) state_file = "bridge_state.json" + diff --git a/matrix/Dockerfile b/matrix/Dockerfile new file mode 100644 index 0000000..ff15c3d --- /dev/null +++ b/matrix/Dockerfile @@ -0,0 +1,42 @@ +# Copyright © 2025-26 l5yth & contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM rust:1.79-bookworm AS builder + +WORKDIR /app + +COPY matrix/Cargo.toml matrix/Cargo.lock ./ +COPY matrix/src ./src + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + cargo build --release --locked + +FROM debian:bookworm-slim AS runtime + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates gosu \ + && rm -rf /var/lib/apt/lists/* + +RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin potatomesh + +WORKDIR /app + +COPY --from=builder /app/target/release/potatomesh-matrix-bridge /usr/local/bin/potatomesh-matrix-bridge +COPY matrix/Config.toml /app/Config.example.toml +COPY matrix/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh + +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] diff --git a/matrix/README.md b/matrix/README.md index 1e00fad..1ba7aae 100644 --- a/matrix/README.md +++ b/matrix/README.md @@ -170,6 +170,36 @@ target/release/potatomesh-matrix-bridge --- +## Docker + +Build the container from the repo root with the included `matrix/Dockerfile`: + +```bash +docker build -f matrix/Dockerfile -t potatomesh-matrix-bridge . +``` + +Provide your config at `/app/Config.toml` and persist the bridge state file by mounting volumes. Minimal example: + +```bash +docker run --rm \ + -v bridge_state:/app \ + -v "$(pwd)/matrix/Config.toml:/app/Config.toml:ro" \ + potatomesh-matrix-bridge +``` + +If you prefer to isolate the state file from the config, mount it directly instead of the whole `/app` directory: + +```bash +docker run --rm \ + -v bridge_state:/app \ + -v "$(pwd)/matrix/Config.toml:/app/Config.toml:ro" \ + potatomesh-matrix-bridge +``` + +The image ships `Config.example.toml` for reference, but the bridge will exit if `/app/Config.toml` is not provided. + +--- + ## Run Ensure `Config.toml` is present and valid, then: diff --git a/matrix/docker-entrypoint.sh b/matrix/docker-entrypoint.sh new file mode 100644 index 0000000..deec1ec --- /dev/null +++ b/matrix/docker-entrypoint.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Copyright © 2025-26 l5yth & contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +# Default state file path from Config.toml unless overridden. +STATE_FILE="${STATE_FILE:-/app/bridge_state.json}" +STATE_DIR="$(dirname "$STATE_FILE")" + +# Ensure state directory exists and is writable by the non-root user without +# touching the read-only config bind mount. +if [ ! -d "$STATE_DIR" ]; then + mkdir -p "$STATE_DIR" +fi + +# Best-effort ownership fix; ignore if the underlying volume is read-only. +chown potatomesh:potatomesh "$STATE_DIR" 2>/dev/null || true +touch "$STATE_FILE" 2>/dev/null || true +chown potatomesh:potatomesh "$STATE_FILE" 2>/dev/null || true + +exec gosu potatomesh potatomesh-matrix-bridge "$@"