diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..84f4a71 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.dockerignore +.git +.github +Dockerfile +LICENSE +README.md +tests/ diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 19d898d..040e8a7 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -1,4 +1,6 @@ -# Panubo build and push to Quay.io and ECR Public +# Source: https://github.com/panubo/reference-github-actions/blob/main/docker-images/build-push.yml +# +# Description: Panubo build and push to Quay.io and ECR Public # This GH Action is intended for public docker images that package upstream applications/services (ie not for projects of Panubo's). # For repos that build multiple repos use the multi-build-push.yml workflow. # @@ -9,6 +11,8 @@ # Automated testing is triggered by `make _ci_test`, if no test is required the Makefile target should just run `true`. # Before tests are run a Docker build is performed, the resulting image has a tag of "test" # BATS is installed since it is commonly required by the tests. +# +# LICENSE: MIT License, Copyright (c) 2021-2025 Volt Grid Pty Ltd t/a Panubo name: build and push on main and tags @@ -33,7 +37,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 + with: + submodules: true - name: Get repo name id: image_name @@ -42,7 +48,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: # list of Docker images to use as base name for tags images: | @@ -59,28 +65,28 @@ jobs: # type=sha - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 # The values provided to these two AWS steps are always the same for Panubo owned repos - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1-node16 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ env.GITHUB_ROLE_ARN }} aws-region: us-east-1 - name: Login to ECR if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: public.ecr.aws - name: Login to Quay.io if: github.event_name != 'pull_request' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: quay.io username: ${{ secrets.PANUBUILD_QUAYIO_USERNAME }} @@ -92,7 +98,7 @@ jobs: bats-version: 1.7.0 - name: Build and export to Docker - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: builder: ${{ steps.buildx.outputs.name }} cache-from: type=gha @@ -104,7 +110,7 @@ jobs: make _ci_test - name: Build and Push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: builder: ${{ steps.buildx.outputs.name }} push: ${{ github.event_name != 'pull_request' }} diff --git a/Dockerfile b/Dockerfile index 5b89f98..797c853 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM alpine:3.19 +# Does not build beyond alpine:3.20, may require upstream fixes for gcc15 and +# legacy C library functions eg fcvt, ecvt, and gcvt +FROM alpine:3.20 ENV XINETD_VERSION=2.3.15.4 @@ -11,7 +13,7 @@ RUN set -x \ && ./configure \ && make \ && make install \ - && install -m 0644 -D -t /etc/xinetd.d /tmp/xinetd-2.3.15.4/contrib/xinetd.d/* \ + && install -m 0644 -D -t /etc/xinetd.d /tmp/xinetd-${XINETD_VERSION}/contrib/xinetd.d/* \ && apk --no-cache del xz build-base \ && cd / \ && rm -rf /tmp/* \ @@ -21,8 +23,12 @@ COPY s6/ /etc/s6/ COPY xinetd.conf /etc/xinetd.conf RUN set -x \ - && sed -i 's/disable.*/disable\t\t= no/' /etc/xinetd.d/time \ - && sed -i 's/disable.*/disable\t\t= no/' /etc/xinetd.d/time-udp \ + # Enable the services + && sed -i 's/disable.*/disable\t\t= no/' /etc/xinetd.d/time /etc/xinetd.d/time-udp \ + # Use same underprivileged user as xinetd daemon + && sed -i 's/user.*/user\t\t= 1000/' /etc/xinetd.d/time /etc/xinetd.d/time-udp \ + # List the UDP service: "2 available services" + && sed -i 's/\ttype.*/\ttype\t\t= INTERNAL/' /etc/xinetd.d/time /etc/xinetd.d/time-udp \ ; EXPOSE 37/tcp diff --git a/Makefile b/Makefile index 8c6028d..76f1d98 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,19 @@ IMAGE_NAME := alpine-xinetd -build: +.PHONY: * +help: + @printf "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)\n" + +build: ## Build for publishing docker build -t panubo/$(IMAGE_NAME) . -run: +run: ## Run container normally docker run -p 37:37/udp -p 37:37/tcp --rm -it --name time-server panubo/$(IMAGE_NAME) # run-non-root: # docker run --cap-add CAP_NET_BIND_SERVICE --user 1000:1000 -p 37:37/udp -p 37:37/tcp --rm -it --name time-server panubo/$(IMAGE_NAME) -shell: +shell: ## Run sh docker run --rm -it panubo/$(IMAGE_NAME) sh _ci_test: diff --git a/README.md b/README.md index 7102346..8365019 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,65 @@ # Time Protocol Server (RFC 868) -This is an [RFC 868](https://datatracker.ietf.org/doc/html/rfc868/) [Time Protocol](https://en.wikipedia.org/wiki/Time_Protocol) Server implemented with [openSUSE/xinetd](https://github.com/openSUSE/xinetd). +This is an [RFC 868](https://datatracker.ietf.org/doc/html/rfc868/) [Time Protocol](https://en.wikipedia.org/wiki/Time_Protocol) Server implemented with [openSUSE/xinetd](https://github.com/openSUSE/xinetd) running on Alpine Linux. -## Client +The image is lightweight and suitable for production use. +## Building the image + +You can build the image using the provided `Makefile`: + +```bash +make build +``` + +This will build the Docker image with the name `panubo/alpine-xinetd`. + +## Running the server + +To run the time server, you can use the `run` target in the `Makefile`: + +```bash +make run ``` + +This will start the container and map the necessary ports (37/tcp and 37/udp). + +## Testing + +### Client Example + +You can test the server using tools like `socat` or `rdate`. + +```bash +# Test UDP socat -x - UDP4-DATAGRAM:127.0.0.1:37 + +# Test TCP socat -x - TCP-CONNECT:127.0.0.1:37 -# rdate is part of busybox +# Using rdate (part of busybox) rdate -p 127.0.0.1 ``` + +### JMeter Load Tests + +The project includes a JMeter test plan to verify the server's functionality. To run the tests, you need to have Docker installed. + +First, build the JMeter test image: + +```bash +cd tests/jmeter +make build +``` + +Then, run the tests: + +```bash +make run-test +``` + +This will execute the test plan `Test Plan.jmx` and connect to a local instance of time-server running on localhost / UDP port 37. + +## Status + +Stable and production ready. diff --git a/tests/jmeter/Dockerfile b/tests/jmeter/Dockerfile index 816141d..7278573 100644 --- a/tests/jmeter/Dockerfile +++ b/tests/jmeter/Dockerfile @@ -1,17 +1,18 @@ -FROM alpine:3.16 +FROM alpine:3.20 ENV \ - JMETER_URL=https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.5.tgz + JMETER_VER=5.6.3 \ + JMETER_URL=https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-5.6.3.tgz RUN set -x \ - && apk --no-cache add openjdk8 curl \ - && curl -sSf "${JMETER_URL}" -o /tmp/apache-jmeter-5.5.tgz \ + && apk --no-cache add openjdk8 curl socat \ + && curl -sSf "${JMETER_URL}" -o /tmp/apache-jmeter-${JMETER_VER}.tgz \ ; RUN set -x \ - && tar -C /opt -zxf /tmp/apache-jmeter-5.5.tgz \ + && tar -C /opt -zxf /tmp/apache-jmeter-${JMETER_VER}.tgz \ && curl -sSf https://jmeter-plugins.org/files/packages/jpgc-udp-0.4.zip -o /tmp/jpgc-udp-0.4.zip \ && curl -sSf https://jmeter-plugins.org/files/packages/jpgc-casutg-2.10.zip -o /tmp/jpgc-casutg-2.10.zip \ - && unzip -d /opt/apache-jmeter-5.5 /tmp/jpgc-udp-0.4.zip \ - && unzip -d /opt/apache-jmeter-5.5 /tmp/jpgc-casutg-2.10.zip \ + && unzip -d /opt/apache-jmeter-${JMETER_VER} /tmp/jpgc-udp-0.4.zip \ + && unzip -d /opt/apache-jmeter-${JMETER_VER} /tmp/jpgc-casutg-2.10.zip \ ; diff --git a/tests/jmeter/Makefile b/tests/jmeter/Makefile index dbdd270..74e1878 100644 --- a/tests/jmeter/Makefile +++ b/tests/jmeter/Makefile @@ -1,8 +1,12 @@ +JMETER_VER := 5.6.3 + +.PHONY: * + build: docker build -t panubo/time-server-jmeter . run: - docker run --rm -it panubo/time-server-jmeter sh + docker run --rm -it -v $(PWD):/workdir --workdir /workdir --user $(shell id -u) panubo/time-server-jmeter sh run-test: - docker run --rm -it -v $(PWD):/workdir --workdir /workdir --user $(shell id -u) panubo/time-server-jmeter /opt/apache-jmeter-5.5/bin/jmeter.sh -n -t "Test Plan.jmx" -l my_results.jtl + docker run --rm -it -v $(PWD):/workdir --workdir /workdir --user $(shell id -u) panubo/time-server-jmeter /opt/apache-jmeter-${JMETER_VER}/bin/jmeter.sh -n -t "Test Plan.jmx" -l my_results.jtl diff --git a/xinetd.conf b/xinetd.conf index aa0b9a3..b16e832 100644 --- a/xinetd.conf +++ b/xinetd.conf @@ -20,7 +20,7 @@ defaults log_type = FILE /tmp/xinetd.log # Define general logging characteristics. -# log_type = SYSLOG daemon info +# log_type = SYSLOG daemon info # log_on_failure = HOST ATTEMPT # log_on_success = HOST EXIT DURATION @@ -59,5 +59,5 @@ defaults # banner_success = } -includedir /etc/xinetd.d - +include /etc/xinetd.d/time +include /etc/xinetd.d/time-udp