Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
docker:
if: |
github.event_name == 'workflow_dispatch' ||
github.repository == 'rightup/pyMC_Repeater' ||
github.repository == 'pyMC-dev/pyMC_Repeater' ||
github.repository == 'yellowcooln/pyMC_Repeater'
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -77,7 +77,7 @@ jobs:
cache-to: type=gha,mode=max

- name: Notify Home Assistant add-on repository
if: github.repository == 'rightup/pyMC_Repeater'
if: github.repository == 'pyMC-dev/pyMC_Repeater'
env:
DISPATCH_TOKEN: ${{ secrets.HA_ADDON_REPO_DISPATCH_TOKEN }}
CHANNEL: ${{ github.ref_name }}
Expand Down
6 changes: 5 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
services:
pymc-repeater:
build: .
build:
context: .
args:
PUID: ${PUID:-1000}
PGID: ${PGID:-1000}
container_name: pymc-repeater
restart: unless-stopped
ports:
Expand Down
87 changes: 87 additions & 0 deletions docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/sh
set -eu

INSTALL_DIR="${INSTALL_DIR:-/opt/pymc_repeater}"
CONFIG_DIR="${CONFIG_DIR:-/etc/pymc_repeater}"
CONFIG_PATH="${PYMC_REPEATER_CONFIG:-${CONFIG_DIR}/config.yaml}"
EXAMPLE_PATH="${CONFIG_DIR}/config.yaml.example"
BUNDLED_EXAMPLE_PATH="${INSTALL_DIR}/config.yaml.example"
RUNTIME_USER="${USER:-repeater}"
RUNTIME_UID="${PUID:-unknown}"
RUNTIME_GID="${PGID:-unknown}"
YQ_CMD="${YQ_CMD:-/usr/local/bin/yq}"

mkdir -p "${CONFIG_DIR}"

copy_or_die() {
src="$1"
dest="$2"
if ! cp "${src}" "${dest}"; then
echo "Failed to initialize ${dest} from ${src}." >&2
echo "If you are bind-mounting ./config.yaml, ensure the host path is writable by ${RUNTIME_USER} (${RUNTIME_UID}:${RUNTIME_GID})." >&2
exit 1
fi
}

merge_config_from_example() {
config_path="$1"

if [ ! -f "${config_path}" ] || [ ! -f "${EXAMPLE_PATH}" ]; then
return 0
fi

if [ ! -x "${YQ_CMD}" ] || ! "${YQ_CMD}" --version 2>&1 | grep -q "mikefarah/yq"; then
echo "Skipping config merge: mikefarah yq is not available at ${YQ_CMD}." >&2
return 0
fi

tmpdir="$(mktemp -d)"
stripped_user="${tmpdir}/config.stripped.yaml"
merged_config="${tmpdir}/config.merged.yaml"

cleanup_merge() {
rm -rf "${tmpdir}"
}
trap cleanup_merge EXIT HUP INT TERM

# Keep only the example's comments to avoid comment duplication across upgrades.
"${YQ_CMD}" eval '... comments=""' "${config_path}" > "${stripped_user}" 2>/dev/null || cp "${config_path}" "${stripped_user}"

if ! "${YQ_CMD}" eval-all '. as $item ireduce ({}; . * $item)' "${EXAMPLE_PATH}" "${stripped_user}" > "${merged_config}" 2>/dev/null; then
echo "Failed to merge ${config_path} with ${EXAMPLE_PATH}; keeping the existing config." >&2
cleanup_merge
trap - EXIT HUP INT TERM
return 0
fi

if ! "${YQ_CMD}" eval '.' "${merged_config}" >/dev/null 2>&1; then
echo "Merged config for ${config_path} is invalid; keeping the existing config." >&2
cleanup_merge
trap - EXIT HUP INT TERM
return 0
fi

if ! cmp -s "${config_path}" "${merged_config}"; then
copy_or_die "${merged_config}" "${config_path}"
fi

cleanup_merge
trap - EXIT HUP INT TERM
}

if [ ! -f "${EXAMPLE_PATH}" ] && [ -f "${BUNDLED_EXAMPLE_PATH}" ]; then
copy_or_die "${BUNDLED_EXAMPLE_PATH}" "${EXAMPLE_PATH}"
fi

if [ -d "${CONFIG_PATH}" ]; then
if [ ! -s "${CONFIG_PATH}/config.yaml" ] && [ -f "${EXAMPLE_PATH}" ]; then
copy_or_die "${EXAMPLE_PATH}" "${CONFIG_PATH}/config.yaml"
fi
CONFIG_PATH="${CONFIG_PATH}/config.yaml"
elif [ ! -s "${CONFIG_PATH}" ] && [ -f "${EXAMPLE_PATH}" ]; then
copy_or_die "${EXAMPLE_PATH}" "${CONFIG_PATH}"
fi

merge_config_from_example "${CONFIG_PATH}"

exec python3 -m repeater.main --config "${CONFIG_PATH}"
45 changes: 41 additions & 4 deletions dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
FROM python:3.12-slim-bookworm

ARG PACKAGE_VERSION=1.0.5
ARG USER=repeater
ARG GROUP=repeater
ARG PUID=15888
ARG PGID=15888
ARG TARGETARCH
ARG YQ_VERSION=v4.40.5

ENV INSTALL_DIR=/opt/pymc_repeater \
CONFIG_DIR=/etc/pymc_repeater \
DATA_DIR=/var/lib/pymc_repeater \
HOME_DIR=/home/${USER} \
PATH=/home/${USER}/.local/bin:${PATH} \
PYTHONUNBUFFERED=1 \
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYMC_REPEATER=${PACKAGE_VERSION}
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYMC_REPEATER=${PACKAGE_VERSION} \
PUID=${PUID} \
PGID=${PGID}

# Install runtime dependencies only
RUN apt-get update && apt-get install -y \
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
libffi-dev \
python3-rrdtool \
jq \
Expand All @@ -21,20 +31,47 @@ RUN apt-get update && apt-get install -y \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

RUN arch="${TARGETARCH:-}" \
&& if [ -z "${arch}" ]; then arch="$(uname -m)"; fi \
&& case "${arch}" in \
amd64|x86_64) YQ_BINARY="yq_linux_amd64" ;; \
arm64|aarch64) YQ_BINARY="yq_linux_arm64" ;; \
arm|armv7|armv7l) YQ_BINARY="yq_linux_arm" ;; \
*) echo "Unsupported architecture for yq: ${arch}" >&2; exit 1 ;; \
esac \
&& wget -qO /usr/local/bin/yq "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/${YQ_BINARY}" \
&& chmod +x /usr/local/bin/yq

# Create the group and user in order to run without root privileges
RUN groupadd --gid "$PGID" "$GROUP" \
&& useradd --uid "$PUID" --gid "$PGID" --home-dir "$HOME_DIR" --create-home --shell /usr/bin/bash "$USER"

# Create runtime directories
RUN mkdir -p ${INSTALL_DIR} ${CONFIG_DIR} ${DATA_DIR}
RUN mkdir -p ${INSTALL_DIR} ${CONFIG_DIR} ${DATA_DIR} \
&& chown -R "$USER":"$GROUP" ${INSTALL_DIR} ${CONFIG_DIR} ${DATA_DIR} ${HOME_DIR}

WORKDIR ${INSTALL_DIR}

# Copy source
COPY repeater ./repeater
COPY pyproject.toml .
COPY config.yaml.example .
COPY radio-presets.json .
COPY radio-settings.json .
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

# Switch to the unprivileged runtime user
USER ${USER}

# Install package
RUN pip install --no-cache-dir .

USER root

RUN chmod +x /usr/local/bin/docker-entrypoint.sh

USER ${USER}

EXPOSE 8000

ENTRYPOINT ["python3", "-m", "repeater.main", "--config", "/etc/pymc_repeater/config.yaml"]
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]