diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f80ab6d..871b391 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,13 +1,16 @@ name: Build Reticulum +env: + REGISTRY_IMAGE: ghcr.io/${{ github.repository_owner }}/reticulum + on: push: - branches: + branches: - '*' tags: - "[0-9]+.[0-9]+.[0-9]+*" pull_request: - branches: + branches: - master paths-ignore: - .gitignore @@ -16,7 +19,7 @@ on: permissions: contents: write -concurrency: +concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -35,7 +38,6 @@ jobs: package: needs: test - if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest environment: ${{ contains(github.ref, '-') && 'development' || 'production' }} steps: @@ -96,3 +98,114 @@ jobs: generate_release_notes: true prerelease: ${{ contains(github.ref, '-') }} fail_on_unmatched_files: true + + build-containers-release: + needs: package + runs-on: ubuntu-latest + permissions: + packages: write + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + - linux/arm/v7 + - linux/arm/v6 + environment: ${{ contains(github.ref, '-') && 'development' || 'production' }} + steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY_IMAGE }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # # Uncomment to use QEMU emulation, if niche architectures are needed + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + path: .artifacts + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile.release + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ env.REGISTRY_IMAGE }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + container-manifest-merge: + runs-on: ubuntu-latest + permissions: + packages: write + needs: + - build-containers-release + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY_IMAGE }} + tags: | + type=ref,event=tag + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/.gitignore b/.gitignore index 0b6903d..da30404 100755 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ tests/rnsconfig/storage tests/rnsconfig/logfile* *.data *.result +.vscode diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..cb61452 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,30 @@ +ARG python_version=3.13 + +FROM python:${python_version}-alpine AS build + +#RUN apk add --no-cache build-base linux-headers libffi-dev libressl-dev cargo + +ENV PIP_ROOT_USER_ACTION=ignore +ENV PIP_DISABLE_PIP_VERSION_CHECK=1 +ENV PIP_NO_CACHE_DIR=1 +RUN pip install rns + +#FROM python:${python_version}-alpine +#ARG python_version + +# Only copy the necessary files from the build stage, to improve layer efficiency +#COPY --from=build /usr/local/bin/rn* /usr/local/bin/ +#COPY --from=build /usr/local/lib/python${python_version}/site-packages/ /usr/local/lib/python${python_version}/site-packages/ + +RUN mkdir /config + +RUN addgroup -S rns --gid 1000 && adduser -S rns --uid 1000 -G rns +RUN chown rns:rns /config + +USER rns:rns + +VOLUME ["/config"] + +ENV PYTHONUNBUFFERED=1 + +ENTRYPOINT ["/usr/local/bin/rnsd", "--config", "/config"] diff --git a/docker/Dockerfile.release b/docker/Dockerfile.release new file mode 100644 index 0000000..184ab02 --- /dev/null +++ b/docker/Dockerfile.release @@ -0,0 +1,32 @@ +ARG python_version=3.13 + +FROM python:${python_version}-alpine AS build + +RUN apk add --no-cache build-base linux-headers libffi-dev libressl-dev cargo + +ADD .artifacts/package/rns-*.whl /tmp/ + +ENV PIP_ROOT_USER_ACTION=ignore +ENV PIP_DISABLE_PIP_VERSION_CHECK=1 +ENV PIP_NO_CACHE_DIR=1 +RUN pip install /tmp/rns-*.whl + +FROM python:${python_version}-alpine +ARG python_version + +# Only copy the necessary files from the build stage, to improve layer efficiency +COPY --from=build /usr/local/bin/rn* /usr/local/bin/ +COPY --from=build /usr/local/lib/python${python_version}/site-packages/ /usr/local/lib/python${python_version}/site-packages/ + +RUN mkdir /config + +RUN addgroup -S rns --gid 1000 && adduser -S rns --uid 1000 -G rns +RUN chown rns:rns /config + +USER rns:rns + +VOLUME ["/config"] + +ENV PYTHONUNBUFFERED=1 + +ENTRYPOINT ["/usr/local/bin/rnsd", "--config", "/config"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..99b4ae1 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,50 @@ +# Docker Images + +Docker resources Reticulum service and tooling + +## End-user + +As an end-user you can either: + +- grab prebuilt docker images from the github container registry @ `ghcr.io/markqvist/reticulum:latest` +- use of the `Dockerfile` to create a simple docker image based on the latest `rns` package available at [PyPi](https://pypi.org/project/rns/) + +### Building from `Dockerfile` + +To build the image, choose one: + +- Copy the `Dockerfile` to a directory and in that directory run: + - `docker build -t reticulum:latest .` + +- From the root of this repository run: + - `docker build -t reticulum:latest -f docker/Dockerfile .` + +### Running + +#### Docker Run +You can run the container in various ways, a quick way to test would be interactively: + +- Create a directory to hold the configuration and other files - `mkdir config` +- Start the container - `docker run --rm --name reticulum -v ./config:/config -it reticulum:latest` + +Replace the image name to match either the one you built or pre-built github versions. + +This will create a container named `reticulum`, mount the config directory to the directory you created above in your current working directory (`./config`) and automatically delete que container (`--rm`) when you detach from the session (files in the config directory will be retained) + +You can edit the config file at `./config/config` to configure rns as usual + +Once the container is running, you can use other rns tools via `docker exec`: + +`docker exec -it reticulum rnpath` + + +#### Docker Compose + +You can also use the included example `docker-compose.yml` file to manage the container in a more automated way. It has some comments but if you are not familiar with it, it is probably a good idea to read the [official `docker compose` docs](https://docs.docker.com/compose/) + + +## Developer + +The file `Dockerfile.release` is meant to be used for CI, its similar to the end-user Dockerfile except that it will grab and install wheel files from the artifacts generated by the `package` job in the build workflow. + +There are image builds available for both releases and pushes to branches to facilitate quick testing. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..b267631 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,35 @@ +services: + reticulum: + container_name: reticulum + image: ghcr.io/markqvist/reticulum:latest + restart: unless-stopped + # You can mount devices, make sure to add the user to the group id + # which has rw access to the device on the host + devices: + - /dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0:/dev/ttyACM0 + group_add: + - 986 + # Mount the config directory on the host in the same location as the docker-compose.yml + # to allow data persistency + volumes: + - ./config:/config:rw + # Define ports to expose, for example a default TCP Listener + ports: + - "4242:4242/tcp" + networks: + - reticulum + # Define resource limits, useful if more services exist on the same host + # to avoid accidental resource contention, monitor and adjust as needed + deploy: + resources: + limits: + memory: "200M" + +# We define a custom network to allow easy communication between containers, +# for example if you want to run a nomadnet node and make use of the reticulum +# running in this container, you can add nomadnet container to this same network +# and use the service name as the hostname (eg: "reticulum") +# see: https://docs.docker.com/compose/how-tos/networking/#use-a-pre-existing-network +networks: + reticulum: + name: reticulum