From: Olliver Schinagl Date: Wed, 1 Mar 2023 18:08:01 +0000 (+0100) Subject: container: Add container support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ce429efe9bc48acd31cfb9f2e971fa3094a7f147;p=thirdparty%2Ftvheadend.git container: Add container support This commit adds support for containizersation of TVHeadend. It adds the actual technology agnostic container file, an entry point and healthcheck for it and a github workflow component to publish it. TODO: Healthcheck script is not yet working. TODO: Add decent documetnation Signed-off-by: Olliver Schinagl --- diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..3baed2ffe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.config.mk +build.*/ diff --git a/.github/workflows/container-build.yaml b/.github/workflows/container-build.yaml new file mode 100644 index 000000000..a32220dc3 --- /dev/null +++ b/.github/workflows/container-build.yaml @@ -0,0 +1,74 @@ +name: Create and publish Container image + +on: + push: + branches: + - master + tags: + - 'v*' + pull_request: + branches: + - master + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + strategy: + matrix: + include: + - container: Containerfile.debian + autotag: false + suffix: -debian + - container: Containerfile.alpine + autotag: auto + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=edge + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + flavor: | + latest=${{ matrix.autotag }} + suffix=${{ matrix.suffix }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 + context: . + file: ${{ matrix.container }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Containerfile b/Containerfile new file mode 120000 index 000000000..83ac7f483 --- /dev/null +++ b/Containerfile @@ -0,0 +1 @@ +Containerfile.alpine \ No newline at end of file diff --git a/Containerfile.alpine b/Containerfile.alpine new file mode 100644 index 000000000..0d501df9d --- /dev/null +++ b/Containerfile.alpine @@ -0,0 +1,132 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023 Olliver Schinagl + +ARG ALPINE_VERSION="latest" +ARG TARGET_ARCH="library" + +FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} AS builder + +WORKDIR /src + +COPY . /src/ + +RUN apk add --no-cache \ + 'avahi-dev' \ + 'bash' \ + 'bsd-compat-headers' \ + 'build-base' \ + 'cmake' \ + 'coreutils' \ + 'dbus-dev' \ + 'ffmpeg4-dev' \ + 'findutils' \ + 'gettext-dev' \ + 'git' \ + 'gnu-libiconv-dev' \ + 'libdvbcsa-dev' \ + 'libhdhomerun-dev' \ + 'libva-dev' \ + 'libvpx-dev' \ + 'linux-headers' \ + 'musl-dev' \ + 'openssl-dev>3' \ + 'opus-dev' \ + 'pngquant' \ + 'python3' \ + 'uriparser-dev' \ + 'wget' \ + 'x264-dev' \ + 'x265-dev' \ + 'zlib-dev' \ + && \ + git config --global --add safe.directory '/src/data/dvb-scan' && \ + ./configure \ + --prefix='/usr/local' \ + --disable-doc \ + --disable-execinfo \ + --disable-ffmpeg_static \ + --disable-hdhomerun_static \ + --disable-libfdkaac_static \ + --disable-libmfx_static \ + --disable-libopus_static \ + --disable-libtheora_static \ + --disable-libvorbis_static \ + --disable-libvpx_static \ + --disable-libx264_static \ + --disable-libx265_static \ + --enable-bundle \ + --enable-dvbcsa \ + --enable-hdhomerun_client \ + --enable-kqueue \ + --enable-libav \ + --enable-nvenc \ + --enable-pngquant \ + --enable-qsv \ + --python=python3 \ + && \ + make DESTDIR='/tvheadend' -j$(($(nproc) - 1)) install + +FROM docker.io/${TARGET_ARCH}/alpine:${ALPINE_VERSION} + +LABEL maintainer="Olliver Schinagl " + +EXPOSE 9981 \ + 9982 \ + 9983 + +RUN apk add --no-cache \ + 'avahi' \ + 'dbus-libs' \ + 'ffmpeg4' \ + 'ffmpeg4-libavcodec' \ + 'ffmpeg4-libavdevice' \ + 'ffmpeg4-libavfilter' \ + 'ffmpeg4-libavfilter' \ + 'ffmpeg4-libavformat' \ + 'ffmpeg4-libavutil' \ + 'ffmpeg4-libpostproc' \ + 'ffmpeg4-libswresample' \ + 'ffmpeg4-libswscale' \ + 'gnu-libiconv-libs' \ + 'libcrypto3' \ + 'libdvbcsa' \ + 'libhdhomerun-libs' \ + 'libssl3' \ + 'liburiparser' \ + 'libva' \ + 'libvpx' \ + 'mesa' \ + 'opus' \ + 'perl-http-entity-parser' \ + 'pngquant' \ + 'python3' \ + 'tini' \ + 'x264-libs' \ + 'x265-libs' \ + 'xmltv' \ + 'zlib' \ + && \ + [ "$(uname -m)" = 'x86'* ] && apk add --no-cache \ + 'libva-intel-driver' \ + ; \ + [ "$(uname -m)" = 'x86_64' ] && apk add --no-cache \ + 'intel-media-driver' \ + ; \ + addgroup -S 'tvheadend' && \ + adduser -D -G 'tvheadend' -h '/var/lib/tvheadend' -s '/bin/nologin' -S 'tvheadend' && \ + adduser 'tvheadend' 'audio' && \ + adduser 'tvheadend' 'usb' && \ + adduser 'tvheadend' 'video' && \ + install -d -m 775 -g 'tvheadend' -o 'tvheadend' '/var/lib/tvheadend/recordings' && \ + install -d -m 775 -g 'tvheadend' -o 'tvheadend' '/var/log/tvheadend' + +COPY --from=builder "/tvheadend" "/" +COPY "./support/container-entrypoint.sh" "/init" + +VOLUME /var/lib/tvheadend +VOLUME /var/lib/tvheadend/recordings +WORKDIR /var/lib/tvheadend/ +USER tvheadend + +ENTRYPOINT [ "/sbin/tini", "--", "/init" ] diff --git a/Containerfile.debian b/Containerfile.debian new file mode 100644 index 000000000..dadbedd59 --- /dev/null +++ b/Containerfile.debian @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2023 Olliver Schinagl + +ARG DEBIAN_VERSION="stable-slim" +ARG TARGET_ARCH="library" + +FROM docker.io/${TARGET_ARCH}/debian:${DEBIAN_VERSION} AS builder + +ENV DEBIAN_FRONTEND noninteractive + +WORKDIR /src + +COPY . /src/ + +RUN apt-get update --yes && apt-get install --yes \ + 'build-essential' \ + 'bzip2' \ + 'cmake' \ + 'debhelper' \ + 'gettext' \ + 'git-core' \ + 'libavahi-client-dev' \ + 'libavdevice-dev' \ + 'libdbus-1-dev' \ + 'libdvbcsa-dev' \ + 'libhdhomerun-dev' \ + 'libopus-dev' \ + 'libpcre2-dev' \ + 'libssl-dev' \ + 'liburiparser-dev' \ + 'libva-dev' \ + 'libvpx-dev' \ + 'libx264-dev' \ + 'libx265-dev' \ + 'lsb-release' \ + 'pkg-config' \ + 'pngquant' \ + 'python3' \ + 'python3-distutils-extra' \ + 'python3-requests' \ + 'wget' \ + 'zlib1g-dev' \ + && \ + git config --global --add safe.directory '/src/data/dvb-scan' && \ + ./configure \ + --prefix='/usr/local' \ + --disable-doc \ + --disable-execinfo \ + --disable-ffmpeg_static \ + --disable-hdhomerun_static \ + --disable-libfdkaac_static \ + --disable-libmfx_static \ + --disable-libopus_static \ + --disable-libtheora_static \ + --disable-libvorbis_static \ + --disable-libvpx_static \ + --disable-libx264_static \ + --disable-libx265_static \ + --enable-bundle \ + --enable-dvbcsa \ + --enable-hdhomerun_client \ + --enable-kqueue \ + --enable-libav \ + --enable-nvenc \ + --enable-pngquant \ + --enable-qsv \ + --python=python3 \ + && \ + make DESTDIR='/tvheadend' -j$(($(nproc) - 1)) install + +FROM docker.io/${TARGET_ARCH}/debian:${DEBIAN_VERSION} + +ENV DEBIAN_FRONTEND noninteractive + +LABEL maintainer="Olliver Schinagl " + +EXPOSE 9981 \ + 9982 \ + 9983 + +RUN apt-get update --yes && apt-get install --yes \ + 'libavahi-client3' \ + 'libavcodec59' \ + 'libavdevice59' \ + 'libavfilter8' \ + 'libavfilter8' \ + 'libavutil57' \ + 'libdbus-1-3' \ + 'libdvbcsa1' \ + 'libhdhomerun4' \ + 'libpostproc56' \ + 'libswscale6' \ + 'libopus0' \ + 'libpcre2-8-0' \ + 'libssl3' \ + 'liburiparser1' \ + 'libva2' \ + 'libvpx7' \ + 'libx264-164' \ + 'libx265-199' \ + 'pngquant' \ + 'python3' \ + 'mesa-va-drivers' \ + 'mesa-vdpau-drivers' \ + 'zlib1g' \ + 'xmltv' \ + 'tini' \ + && \ + useradd -c 'TVHeadend' -d '/var/lib/tvheadend' -G 'audio','video' -m -r -s '/bin/false' -U 'tvheadend' && \ + install -d -m 775 -g 'tvheadend' -o 'tvheadend' '/var/lib/tvheadend/recordings' && \ + install -d -m 775 -g 'tvheadend' -o 'tvheadend' '/var/log/tvheadend' + +COPY --from=builder "/tvheadend" "/" +COPY "./support/container-entrypoint.sh" "/init" + +VOLUME /var/lib/tvheadend +VOLUME /var/lib/tvheadend/recordings +WORKDIR /var/lib/tvheadend/ +USER tvheadend + +ENTRYPOINT [ "/usr/bin/tini", "--", "/init" ] diff --git a/Dockerfile b/Dockerfile new file mode 120000 index 000000000..5240dc01e --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +Containerfile \ No newline at end of file diff --git a/README.Docker.md b/README.Docker.md new file mode 100644 index 000000000..28bc20d50 --- /dev/null +++ b/README.Docker.md @@ -0,0 +1,490 @@ +# TVHeadend in Docker +TVHeadend can be run within a Docker container. This provides isolation from +other processes by running it in a containerized environment. As this is not +and in-depth tutorial on docker, those with Docker, containers or cgroups see +[docker.com][docker]. + + +## Building the TVHeadend image +While it is recommended to pull the image from the [GHCR][ghcr] (GitHub +Container Registry), it is certainly possible to build the image locally. + +To do so, clone this repository: + +$ git clone https://github.com/tvheadend/tvheadend + +Then, from within the repository + +```sh +docker image build + --rm \ + --tag 'tvheadend:issue-123' \ + './' +``` + +The tag 'issue-123' in the example, is just that, an example, anything can be +used for the tag. + +> _Note_: Omitting the tag, will use `latest` by default. + + +## Running the TVHeadend image +If the container wasn't built with the above instructions, it can be optionally +be pulled from the [GHCR][ghcr] first instead. + +```sh +docker image pull ghcr.io/tvheadend/tvheadend:latest +``` + +> _Note_: The `latest` tag can be replaced with any desired tag, including +> `master` for the git development version. + +Running the TVHeadend container is then done as follows. + +```sh +docker container run \ + --interactive \ + --name 'TVHeadend_container_01' \ + --rm \ + --tty \ + 'ghcr.io/tvheadend/tvheadend:issue-123' \ + --firstrun +``` + +> _Note_: Docker will try to pull a container it doesn't know about yet. So if +> the container was not previously built or pulled, the `run` sub command +> will try to pull it instead. Likewise, `--pull always` can be used to force +> a pull. See the docker documentation for more details. + +The above will now run TVHeadend and the log output should be visible. + +In the snippet above, the `--firstrun` flag was used. This flag is of course +optional. Please do read the remainder of the next chapter +[Persistently storing configuration](#Persistently-storing-configuration) +to learn more about where configuration is stored. + + +## Persistently storing configuration +Containers do not store files persistently, they are ephemeral by design. +Obviously, storing of configuration (or video) data is desirable and of course +is there a solution for this. There are three (probably more) potential options. + +It is also important be aware that the configuration directory can be different +depending on how TVHeadend is started. If tvheadend is started, and a directory +exists in `/var/lib/tvheadend` or `/etc/tvheadend` which matches the UID of the +user executing TVHeadend, those locations will be preferred. Since the container +will always run as the user `tvheadend` and these locations are always created, +TVHeadend in the container will always use `/var/lib/tvheadend` as its +configuration directory. + +Before diving more deeply into things, the current container has two default +volumes defined, `/var/lib/tvheadend` and `/var/lib/tvheadend/recordings`. These +can also be defined as named/external volumes as well as more that can be added. + + +### Secrets +Docker secrets are files that can be mounted in a container. They are not +ever written to disk (they live in `/run/secrets/`). While useful, not +applicable, as they can't be controlled runtime, and store one file per secret. + + +### Config +Very similar to Secrets, but stored on disk within the container. + + +### Volumes +Volumes come in a few flavors actually, but the basics are the same. A local +directory is mounted inside the container. The flavors come in the form of +where to get the source directory from. + + +#### Local directory +Lets say the docker container should share the configuration files with the +local user. In such case, the local `.config/hts` directory is needed within +the container, can be easily mounted with the following example. Note that +the `--volume` flag requires a absolute path. + +```sh +docker container run \ + --interactive \ + --name 'TVHeadend_container_01' \ + --rm \ + --tty \ + --user "$(id -u):$(id -g)" \ + --volume '/home/user/.config/hts:/var/lib/tvheadend:rw' \ + --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \ + 'ghcr.io/tvheadend/tvheadend:issue-123' +``` + +> _Note_: The `--user` flag is also used here, to ensure file-ownership does +> not change. By default the TVHeadend container runs as user `tvheadend` +> which may not have the same UID or GID as the local user. If the `id` command +> is not available, `"1000:1000"` could be used instead, where `1000` would be +> the actual UID and GID for the current user. The `rw` (read-write) or `ro` +> (read-only) flags can set the access mode, but are optional. + + +#### Anonymous Volumes +Docker manage volumes also internally. This is actually very common when using +docker containers, as the volumes are fully docker managed. It is even possible +to share volumes between containers, e.g. have multiple TVHeadend instances +running, with their own configuration, but sharing the recordings volume. + +The TVHeadend container already creates an anonymous volume by default, so that +configuration is stored/re-used. Anonymous volumes are not 'forever persistent' +and are removed by regular cleanup actions (`docker system prune` for example). + + +#### Named Volumes +Docker named volumes are manually created and persistently stored. For long +term use (using a server for example), they are the preferred way of handling +data. Docker compose can create them automatically (more on that later) but +generally, a volume is created beforehand as such. + +$ docker volume create 'hts_config' + +and mounted as: + +```sh +docker container run \ + --interactive \ + --name 'TVHeadend_container_01' \ + --rm \ + --tty \ + --user "$(id -u):$(id -g)" \ + --volume 'hts_config:/var/lib/tvheadend:rw' \ + --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \ + 'ghcr.io/tvheadend/tvheadend:issue-123' +``` + + +### Additional files +It is possible to use the volumes concept to add additional files to an +dockerized setup. For example if there is a volume holding various picons, +which are created and managed/updated through a different container. The volume +can be simply added in the same way. + + +```sh +docker container run \ + --interactive \ + --name 'TVHeadend_container_01' \ + --rm \ + --tty \ + --user "$(id -u):$(id -g)" \ + --volume 'hts_config:/var/lib/tvheadend:rw' \ + --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \ + --volume 'picons_volume:/usr/share/picons' \ + 'ghcr.io/tvheadend/tvheadend:issue-123' +``` + +> _Note_: The same can be done for directories holding binaries or scripts +> but beware that this may not always work as expected. For example if a +> python script is added, and requires the python interpreter, which is not +> available in the TVHeadend container, the binary will be there, but it cannot +> run. The same is true for an executable, if the libraries it depends on are +> not there, it will fail to run. If in doubt, entering the container by +> appending `/bin/sh` and running the binary or `ldd`ing may give a clue. + + +### Environment variables +TVHeadend can also consume environment variables for additional configuration. +Most notably being the timezone. Environment variables can be easily added from +the commandline. + +```sh +docker container run \ + --env 'TZ="Etc/UTC"' \ + --interactive \ + --name 'TVHeadend_container_01' \ + --rm \ + --tty \ + --user "$(id -u):$(id -g)" \ + --volume 'hts_config:/var/lib/tvheadend:rw' \ + --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \ + --volume 'picons_volume:/usr/share/picons' \ + 'ghcr.io/tvheadend/tvheadend:issue-123' +``` + + +## Configuring devices +TVHeadend gets most of its use via external devices, DVB tuners and the like. +The following section is completely optional however if no devices need to be +mapped to the container however. + +There are a some caveats depending on static or dynamic devices. + +In both cases, the device needs to exist however when starting docker and +the permissions to access the device need to be correct. The TVHeadend +container is by default part of the `video`, `audio` and `usb` groups, +which seems to use the same UID on at least Gentoo and Alpine, but Arch has +different GID's. + +> _Note_: If there is no shared GID, and no desire to change the GID of the +> host, it is also possible to give **HIGHLY UNRECOMMENDED** 666 permissions to +> the device. + + +### Static devices +For static devices, that are not added or removed while the container is +running, this is easy enough with the add the `--device` flag. Assuming +TVHeadend is to take care of all devices, the entire dvb directory can be +shared. + +```sh +docker container run \ + --env 'TZ="Etc/UTC"' \ + --device '/dev/dvb' \ + --interactive \ + --name 'TVHeadend_container_01' \ + --rm \ + --tty \ + --user "$(id -u):$(id -g)" \ + --volume 'hts_config:/var/lib/tvheadend:rw' \ + --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \ + --volume 'picons_volume:/var/lib/picons' \ + 'ghcr.io/tvheadend/tvheadend:issue-123' +``` + + +### Dynamic devices +Dynamic devices are a lot harder to deal with, due to their dynamic nature. +For DVB devices, that only create and remove themselves from `/dev/dvb` this +should work. Devices however that get exposed by their exact path, things +are less easy. + +One thing that seems to work quite well, is to create a specific udev rule, +that creates a symlink the device seems to work well. The following example +exposes a USB serial converter as `/dev/myserialdevice`. + +``` +ACTION=="add", +SUBSYSTEM=="tty", +ATTRS{idVendor}=="1236", +ATTRS{idProduct}=="5678", +ATTRS{serial}=="12345678", +MODE="660", +TAG+="uaccess", +SYMLINK+="myserialdevice" +``` + +> _Note_: Worse comes to worst, the entire `/dev` directory could be device +> mounted or the `--privileged` flag, but are **HIGHLY UNRECOMMENDED** from +> a security (and isolation) perspective. +> Also `udevadm info -q all -n '/dev/mydevice'` can be used to inspect a device +> and also show any currently installed symlinks. +> Finally, if permission access is not working, the `MODE` could be set to an +> insecure `666`. + +### Other devices +The above section spoke mostly of adding DVB devices to the container, but +other devices can be added as well, for example `/dev/dri` to map a GPU for +encoding acceleration, assuming the needed tools are available to do so. + +> _Tip_: It is actually better to have a dedicated container running conversion +> and share storage locations via volumes instead. + +## Network access +TVHeadend is a network connected device, but by default, docker will not map +any ports that a service listen to the host. Full network isolation. As such +network ports need to be published to the host. The `--publish` flag does so. +In the example below, a range of ports gets published to all devices via the +IP address `0.0.0.0`. This can be restricted to a specific interface via its +specific IP address. + +```sh +docker container run \ + --device '/dev/dvb' \ + --env 'TZ="Etc/UTC"' \ + --interactive \ + --name 'TVHeadend_container_01' \ + --publish "0.0.0.0:9981-9982:9981-9982/tcp" \ + --rm \ + --tty \ + --user "$(id -u):$(id -g)" \ + --volume 'hts_config:/var/lib/tvheadend:rw' \ + --volume '/home/user/Videos:/var/lib/tvheadend/recordings:rw' \ + --volume 'picons_volume:/var/lib/picons' \ + 'ghcr.io/tvheadend/tvheadend:issue-123' +``` + + +> _Note_: A proper docker setup isolates container's networking completely with +> unique interfaces per container. The default `run` command as used throughout +> this guide however, uses the default docker network, which means that +> individual containers are actually on the same network internally. Think of +> it as a networking router controlled by docker, where all containers are +> plugged into. A proper setup can be done with the normal `docker run`, but +> is out of scope, as it is better to use `docker compose` for that. More on +> this later. + + +### CAClient access +Using a CAClient over the network, requires that both containers are on the +same network, as they have to be to reach each other. With the default +network setup of `docker run`, this shouldn't be an issue generally. + + +### Firewall +There currently exists a bug with docker, in that a default firewall policy of +`DROP` on the `INPUT` chain breaks certain container to container traffic. + +While it is possible to craft `iptables` rules to fix this, the dynamic nature +of everything makes this very tricky in general. The only work around is +to set the default policy to `ACCEPT` and ensure the chain `DROPS` all packets. + +Assuming `ppp0` is the internet connection that is a monitored interface and +everything else is `okay`. On an empty/firewall; + +$ iptables -A -i ppp0 -j DROP +$ iptables -P INPUT DROP + + +## Troubleshooting +To trouble shoot device pass through, networking, or other permission related +ales, it is possible to start the container in privileged mode, connect directly +to the host network and run TVHeadend as root. + +All are **NOT** recommended for normal use, but can help isolate issues. + +$ docker container run --privileged --user 'root:root' --network 'host' ... + +> _Tip_: After running the container as root, make sure to change all +> ownership back to `tvheadend:tvheadend`, most likely the configuration files. +> This is best done from within the container using +> +> $ chmod -R 'tvheadend:tvheadend' '/home/tvheadend' +> + + +## Compose +Docker compose can be used to define whole cluster of services. While this entry +will not be an expert piece on how to properly define compose services and setup +whole service clusters, here is an example that puts all of the above in a +single file. It is here for reference only and users are expected to know what +they are doing when using this. + +```yaml +networks: + tvheadend: {} + oscam: {} + +volumes: + tvheadend: + oscam: + +services: + oscam: + image: docker.io/olliver/oscam:latest # TODO + cap_drop: + - all + ulimits: + nproc: 64 + nofile: + soft: 1024 + hard: 65535 + devices: + - /dev/cardreader + env_file: + - common.env + volumes: + - oscam:/etc/oscam:rw + networks: + - oscam + expose: + - "1988/udp" + - "15000/tcp" + ports: + - "8888:8888/tcp" + command: -d 64 + restart: unless-stopped + healthcheck: + test: wget localhost:8888 2>&1 | grep -q 'connected' + + +services: + tvheadend: + image: ghcr.io/tvheadend/tvheadend:latest + cap_drop: + - all + ulimits: + nproc: 256 + nofile: + soft: 8192 + hard: 65535 + devices: + - /dev/dvb/ + environment: + - TZ: 'Europe/Amsterdam' + volumes: + - tvheadend:/var/lib/tvheadend:rw + - /export/recordings:/var/lib/tvheadend/recordings:rw + networks: + - tvheadend + - oscam + ports: + - "9981-9982:9981-9982/tcp" + command: --config '/var/lib/tvheadend' --nosatip + restart: unless-stopped + healthcheck: + test: wget -O - -q 'http://localhost:9981/ping' | grep -q 'PONG' +``` + +> _Tip_: The `ulimits` are optional but recommended. Oscam uses a `common.env` +> file holding the Timezone, TVHeadend uses a variable, both achieve the same. + +> _Note_: TVHeadend automatically detects the location for storage, the above +> is just an example, if the location is changed here however, make sure to +> update the configuration in the UI to match. + + +## Developing using a container +While it is certainly easy and possible to develop using `docker image build`, +it is quite slow, as some setup and teardown work needs to be done before +compilation can be done, also already compiled objects get discarded. + +Instead, the `builder` container, an intermediate step, can be used instead +and the source directory volume mounted herein. + +```sh +docker image build + --rm \ + --tag 'tvheadend:builder' \ + --target 'builder' \ + './' +``` + +> _Note_: Because the way the builder is set up, it will build everything +> once regardless. This is to keep the current `Containerfile` simple. +> Other then the cost of some time, it is harmless. + +With the built `builder` image, the container can be entered, and make can be +issued as normal. + +```sh +docker container run \ + --interactive \ + --rm \ + --tty \ + --user "$(id -u):$(id -g)" \ + --volume "$(pwd):/workdir" \ + --workdir '/workdir' \ + 'tvheadend:builder' '/bin/sh' +./configure +make +``` + +> _Tip_: It might be tempting to short-circuit the command `/bin/sh` by +> replacing it with `make`. However it will still be slower due to the +> container creation/teardown, but otherwise functionally identical. + +> _Note_: As the current working dir is volume mounted into the container, +> all changes done by the container will be made on the regular source code +> directory. As such, exiting and re-starting the container won't remove any +> intermediate object files etc. As the build-container runs normally runs as +> root, the `--user` flag has to be set as otherwise files created will be +> owned by root when exiting the container. + + +[docker]: https://www.docker.com +[ghcr]: https://github.com/tvheadend/tvheadend/pkgs/container/tvheadend diff --git a/README.md b/README.md index f54059bba..de75d3560 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,14 @@ It supports the following outputs: * HTSP (own protocol) * SAT>IP +Running in docker +----------------- +Running in docker can be as simple as + + $ docker run --rm ghcr.io/tvheadend/tvheadend:latest + +See [README.Docker.md](README.Docker.md) in this repository for more details. + How to build for Linux ---------------------- diff --git a/configure b/configure index 0d729023c..970404d2e 100755 --- a/configure +++ b/configure @@ -283,7 +283,6 @@ int test(void) check_cc_snippet time_ld ' #define _FILE_OFFSET_BITS 64 #define _TIME_BITS 64 -#include #include #include #define TEST test @@ -297,7 +296,6 @@ int test(void) check_cc_snippet time_lld ' #define _FILE_OFFSET_BITS 64 #define _TIME_BITS 64 -#include #include #include #define TEST test diff --git a/support/container-entrypoint.sh b/support/container-entrypoint.sh new file mode 100755 index 000000000..7a506b192 --- /dev/null +++ b/support/container-entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2023 Olliver Schinagl +# +# A beginning user should be able to docker run image bash (or sh) without +# needing to learn about --entrypoint +# https://github.com/docker-library/official-images#consistency + +set -eu + +bin='tvheadend' + +# run command if it is not starting with a "-" and is an executable in PATH +if [ "${#}" -le 0 ] || \ + [ "${1#-}" != "${1}" ] || \ + [ -d "${1}" ] || \ + ! command -v "${1}" > '/dev/null' 2>&1; then + entrypoint='true' +fi + +exec ${entrypoint:+${bin:?}} "${@}" + +exit 0