├── .github ├── FUNDING.yml └── workflows │ ├── deploy.yml │ ├── linting.yml │ └── tests.yml ├── Dockerfile ├── README.md └── rootfs ├── etc ├── apt │ └── preferences.d │ │ └── chromium.pref ├── cont-init.d │ └── 90-dbus ├── openbox │ └── main-window-selection.xml └── services.d │ └── dbus │ └── run └── startapp.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/mikenyegithub'] 13 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Docker Hub 2 | 3 | on: 4 | workflow_dispatch: 5 | # Build and deploy the image on pushes to master branch 6 | push: 7 | branches: 8 | - master 9 | - main 10 | 11 | # Build and deploy the image nightly (to ensure we pick up any security updates) 12 | schedule: 13 | - cron: "0 0 * * *" 14 | 15 | jobs: 16 | deploy_dockerhub_single_arch: 17 | name: Deploy to DockerHub 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | docker-platform: 22 | - linux/amd64 23 | 24 | # Set job-wide environment variables 25 | # - REPO: repo name on dockerhub 26 | # - IMAGE: image name on dockerhub 27 | env: 28 | REPO: mikenye 29 | IMAGE: picard 30 | steps: 31 | 32 | # Check out our code 33 | - 34 | name: Checkout 35 | uses: actions/checkout@v2 36 | 37 | # Hit an issue where arm builds would fail with cURL errors regarding intermediary certificates when downloading from github (ie: deploy-s6-overlay). 38 | # After many hours of troubleshooting, the workaround is to pre-load the image's rootfs with the CA certificates from the runner. 39 | # This problem may go away in future. 40 | - 41 | name: Copy CA Certificates from GitHub Runner to Image rootfs 42 | run: | 43 | ls -la /etc/ssl/certs/ 44 | mkdir -p ./rootfs/etc/ssl/certs 45 | mkdir -p ./rootfs/usr/share/ca-certificates/mozilla 46 | cp --no-dereference /etc/ssl/certs/*.crt ./rootfs/etc/ssl/certs 47 | cp --no-dereference /etc/ssl/certs/*.pem ./rootfs/etc/ssl/certs 48 | cp --no-dereference /usr/share/ca-certificates/mozilla/*.crt ./rootfs/usr/share/ca-certificates/mozilla 49 | 50 | # # Set up QEMU for multi-arch builds 51 | # - 52 | # name: Set up QEMU 53 | # uses: docker/setup-qemu-action@v1 54 | 55 | # Log into docker hub (so we can push images) 56 | - 57 | name: Login to DockerHub 58 | uses: docker/login-action@v1 59 | with: 60 | username: ${{ secrets.DOCKERHUB_USERNAME }} 61 | password: ${{ secrets.DOCKERHUB_TOKEN }} 62 | 63 | # Set up buildx 64 | - 65 | name: Set up Docker Buildx 66 | id: buildx 67 | uses: docker/setup-buildx-action@v1 68 | 69 | # Build "latest" 70 | - 71 | name: Build & Push - latest 72 | run: docker buildx build --no-cache --push --progress plain -t "${{ env.REPO }}/${{ env.IMAGE }}:latest" --compress --platform "${{ matrix.docker-platform }}" . 73 | 74 | # Get version from "latest" 75 | - 76 | name: Get latest image version 77 | run: | 78 | docker pull "${{ env.REPO }}/${{ env.IMAGE }}:latest" 79 | echo "VERSION_TAG=$(docker run --rm --entrypoint cat "${{ env.REPO }}/${{ env.IMAGE }}:latest" /CONTAINER_VERSION)" >> $GITHUB_ENV 80 | 81 | # Show version from "latest" 82 | - 83 | name: Show latest image version 84 | run: | 85 | echo "${{ env.REPO }}/${{ env.IMAGE }}:latest contains version: ${{ env.VERSION_TAG }}" 86 | 87 | # Build version specific 88 | - 89 | name: Build & Push - version specific 90 | run: docker buildx build --push --progress plain -t "${{ env.REPO }}/${{ env.IMAGE }}:${{ env.VERSION_TAG }}" --compress --platform "${{ matrix.docker-platform }}" . 91 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - main 8 | 9 | jobs: 10 | 11 | shellcheck: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Pull koalaman/shellcheck:stable Image 18 | run: docker pull koalaman/shellcheck:stable 19 | - name: Run Shellcheck against shell scripts 20 | run: docker run --rm -i -v "$PWD:/mnt" koalaman/shellcheck:stable $(find . -type f -exec grep -m1 -l -E '^#!.*sh.*' {} \; | grep -v '/.git/') 21 | # docker run --rm -i -v "$PWD:/mnt" koalaman/shellcheck:stable $(find . -type f -exec grep -m1 -l -E '^#!.*execline.*' {} \; | grep -v '/.git/') 22 | 23 | hadolint: 24 | 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Pull hadolint/hadolint:latest Image 30 | run: docker pull hadolint/hadolint:latest 31 | - name: Run hadolint against Dockerfiles 32 | run: docker run --rm -i -v "$PWD":/workdir --workdir /workdir --entrypoint hadolint hadolint/hadolint --ignore DL3008 --ignore SC2068 --ignore SC1091 --ignore DL3013 --ignore SC2046 $(find . -type f -iname "Dockerfile*") 33 | 34 | markdownlint: 35 | 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: Pull markdownlint/markdownlint:latest Image 41 | run: docker pull markdownlint/markdownlint:latest 42 | - name: Run markdownlint against *.md files 43 | run: docker run --rm -i -v "$(pwd)":/workdir --workdir /workdir markdownlint/markdownlint:latest --rules ~MD013,~MD033,~MD029 $(find . -type f -iname '*.md' | grep -v '/.git/') 44 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - main 8 | 9 | jobs: 10 | buildx: 11 | name: Test image build 12 | runs-on: ubuntu-latest 13 | 14 | # Set job-wide environment variables 15 | # - REPO: repo name on dockerhub 16 | # - IMAGE: image name on dockerhub 17 | env: 18 | REPO: mikenye 19 | IMAGE: picard 20 | 21 | strategy: 22 | matrix: 23 | docker-platform: 24 | - linux/amd64 25 | # - linux/arm64 26 | # - linux/arm/v6 27 | # - linux/arm/v7 28 | # - linux/i386 29 | steps: 30 | 31 | # Check out our code 32 | - 33 | name: Checkout 34 | uses: actions/checkout@v2 35 | 36 | # Hit an issue where arm builds would fail with cURL errors regarding intermediary certificates when downloading from github (ie: deploy-s6-overlay). 37 | # After many hours of troubleshooting, the workaround is to pre-load the image's rootfs with the CA certificates from the runner. 38 | # This problem may go away in future. 39 | - 40 | name: Copy CA Certificates from GitHub Runner to Image rootfs 41 | run: | 42 | ls -la /etc/ssl/certs/ 43 | mkdir -p ./rootfs/etc/ssl/certs 44 | mkdir -p ./rootfs/usr/share/ca-certificates/mozilla 45 | cp --no-dereference /etc/ssl/certs/*.crt ./rootfs/etc/ssl/certs 46 | cp --no-dereference /etc/ssl/certs/*.pem ./rootfs/etc/ssl/certs 47 | cp --no-dereference /usr/share/ca-certificates/mozilla/*.crt ./rootfs/usr/share/ca-certificates/mozilla 48 | 49 | # Set up QEMU for multi-arch builds 50 | - 51 | name: Set up QEMU 52 | uses: docker/setup-qemu-action@v1 53 | 54 | # Set up buildx for multi platform builds 55 | - 56 | name: Set up Docker Buildx 57 | id: buildx 58 | uses: docker/setup-buildx-action@v1 59 | 60 | # Get archictecture suffix 61 | - 62 | name: Get image architecture suffix 63 | run: | 64 | echo "ARCH_TAG=$(echo '${{ matrix.docker-platform }}' | cut -d '/' -f2- | tr -s '/' '_')" >> $GITHUB_ENV 65 | 66 | # Show archictecture suffix 67 | - 68 | name: Show image architecture suffix 69 | run: | 70 | echo "Architecture suffix: ${{ env.ARCH_TAG }}" 71 | 72 | # Test container build for all supported platforms (defined above) 73 | - 74 | name: Build ${{ matrix.docker-platform }} 75 | run: docker buildx build --no-cache --progress plain -t "${{ env.REPO }}/${{ env.IMAGE }}:testing_${{ env.ARCH_TAG }}" --platform "${{ matrix.docker-platform }}" . 76 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/golang:1.23.5 AS trivy_builder 2 | 3 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 4 | 5 | RUN set -x && \ 6 | git clone --depth=1 https://github.com/aquasecurity/trivy /src/trivy && \ 7 | pushd /src/trivy/cmd/trivy && \ 8 | go build 9 | 10 | FROM docker.io/jlesage/baseimage-gui:ubuntu-22.04-v4 11 | 12 | ENV CHROMIUM_FLAGS="--no-sandbox" \ 13 | URL_PICARD_REPO="https://github.com/metabrainz/picard.git" \ 14 | URL_CHROMAPRINT_REPO="https://github.com/acoustid/chromaprint.git" \ 15 | URL_GOOGLETEST_REPO="https://github.com/google/googletest.git" 16 | 17 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 18 | 19 | COPY rootfs/ / 20 | COPY --from=trivy_builder /src/trivy/cmd/trivy/trivy /src/trivy 21 | 22 | RUN set -x && \ 23 | # Define package arrays 24 | # TEMP_PACKAGES are packages that will only be present in the image during container build 25 | # KEPT_PACKAGES will remain in the image 26 | TEMP_PACKAGES=() && \ 27 | KEPT_PACKAGES=() && \ 28 | # Install software-properties-common so we can use add-apt-repository 29 | TEMP_PACKAGES+=(software-properties-common) && \ 30 | apt-get update && \ 31 | apt-get install -y --no-install-recommends \ 32 | ${KEPT_PACKAGES[@]} \ 33 | ${TEMP_PACKAGES[@]} \ 34 | && \ 35 | TEMP_PACKAGES+=(gnupg) && \ 36 | # Install pip to allow install of Picard dependencies 37 | TEMP_PACKAGES+=(python3-pip) && \ 38 | TEMP_PACKAGES+=(python3-setuptools) && \ 39 | TEMP_PACKAGES+=(python3-wheel) && \ 40 | # SSL Libs 41 | TEMP_PACKAGES+=(libssl-dev) && \ 42 | KEPT_PACKAGES+=(libssl3) && \ 43 | # Install git to allow clones of git repos 44 | TEMP_PACKAGES+=(git) && \ 45 | # Install build tools to allow building 46 | TEMP_PACKAGES+=(build-essential) && \ 47 | TEMP_PACKAGES+=(cmake) && \ 48 | TEMP_PACKAGES+=(pkg-config) && \ 49 | # Install Chromaprint dependencies 50 | KEPT_PACKAGES+=(ffmpeg) && \ 51 | TEMP_PACKAGES+=(libswresample-dev) && \ 52 | KEPT_PACKAGES+=(libswresample3) && \ 53 | TEMP_PACKAGES+=(libfftw3-dev) && \ 54 | KEPT_PACKAGES+=(libfftw3-3) && \ 55 | TEMP_PACKAGES+=(libavcodec-dev) && \ 56 | KEPT_PACKAGES+=(libavcodec58) && \ 57 | TEMP_PACKAGES+=(libavformat-dev) && \ 58 | KEPT_PACKAGES+=(libavformat58) && \ 59 | # Install Picard dependencies 60 | TEMP_PACKAGES+=(python3-dev) && \ 61 | KEPT_PACKAGES+=(python3-six) && \ 62 | TEMP_PACKAGES+=(libdiscid-dev) && \ 63 | KEPT_PACKAGES+=(libdiscid0) && \ 64 | KEPT_PACKAGES+=(libxcb-icccm4) && \ 65 | KEPT_PACKAGES+=(libxcb-keysyms1) && \ 66 | KEPT_PACKAGES+=(libxcb-randr0) && \ 67 | KEPT_PACKAGES+=(libxcb-render-util0) && \ 68 | KEPT_PACKAGES+=(libxcb-xinerama0) && \ 69 | KEPT_PACKAGES+=(libxcb-image0) && \ 70 | KEPT_PACKAGES+=(libxcb-xkb1) && \ 71 | KEPT_PACKAGES+=(libxkbcommon-x11-0) && \ 72 | KEPT_PACKAGES+=(gettext) && \ 73 | KEPT_PACKAGES+=(locales) && \ 74 | # Package below fixes: issue #77 75 | KEPT_PACKAGES+=(libhangul1) && \ 76 | # Package below fixes: issue #42 77 | KEPT_PACKAGES+=(libgtk-3-0) && \ 78 | KEPT_PACKAGES+=(fonts-takao) && \ 79 | KEPT_PACKAGES+=(fonts-takao-mincho) && \ 80 | KEPT_PACKAGES+=(wget) && \ 81 | KEPT_PACKAGES+=(ca-certificates) && \ 82 | # Install Picard optical drive dependencies 83 | KEPT_PACKAGES+=(lsscsi) && \ 84 | # Install Picard Media Player dependencies 85 | KEPT_PACKAGES+=(gstreamer1.0-plugins-good) && \ 86 | KEPT_PACKAGES+=(gstreamer1.0-libav) && \ 87 | KEPT_PACKAGES+=(libpulse-mainloop-glib0) && \ 88 | KEPT_PACKAGES+=(libqt5multimedia5-plugins) && \ 89 | KEPT_PACKATES+=(libavcodec57) && \ 90 | # Install Chrome dependencies 91 | KEPT_PACKAGES+=(dbus-x11) && \ 92 | KEPT_PACKAGES+=(uuid-runtime) && \ 93 | # Install Picard plugin dependencies 94 | KEPT_PACKAGES+=(python3-aubio) && \ 95 | KEPT_PACKAGES+=(aubio-tools) && \ 96 | KEPT_PACKAGES+=(flac) && \ 97 | KEPT_PACKAGES+=(vorbisgain) && \ 98 | KEPT_PACKAGES+=(wavpack) && \ 99 | KEPT_PACKAGES+=(mp3gain) && \ 100 | # Install window compositor 101 | KEPT_PACKAGES+=(openbox) && \ 102 | # Security updates / fix for issue #37 (https://github.com/mikenye/docker-picard/issues/37) 103 | TEMP_PACKAGES+=(jq) && \ 104 | # Install packages 105 | apt-get update && \ 106 | apt-get install -y --no-install-recommends \ 107 | ${KEPT_PACKAGES[@]} \ 108 | ${TEMP_PACKAGES[@]} \ 109 | && \ 110 | # Update ca certs 111 | update-ca-certificates -f && \ 112 | # Build & install OpenSSL v1.1.1 113 | wget \ 114 | -O /tmp/openssl-1.1.1w.tar.gz \ 115 | --progress=dot:giga \ 116 | https://www.openssl.org/source/openssl-1.1.1w.tar.gz \ 117 | && \ 118 | mkdir -p /src/openssl && \ 119 | tar \ 120 | xzvf \ 121 | /tmp/openssl-1.1.1w.tar.gz \ 122 | -C /src/openssl \ 123 | && \ 124 | pushd /src/openssl/openssl-* && \ 125 | ./config && \ 126 | make test && \ 127 | make && \ 128 | make install && \ 129 | popd && \ 130 | ldconfig && \ 131 | # Prevent annoying detached head warnings 132 | git config --global advice.detachedHead false && \ 133 | # Clone googletest (required for build of Chromaprint) 134 | git clone "$URL_GOOGLETEST_REPO" /src/googletest && \ 135 | pushd /src/googletest && \ 136 | BRANCH_GOOGLETEST=$(git tag --sort="-creatordate" | grep 'release-' | head -1) && \ 137 | git checkout "tags/${BRANCH_GOOGLETEST}" && \ 138 | echo "googletest $BRANCH_GOOGLETEST" >> /VERSIONS && \ 139 | popd && \ 140 | # Clone Chromaprint repo & checkout latest version 141 | git clone "$URL_CHROMAPRINT_REPO" /src/chromaprint && \ 142 | pushd /src/chromaprint && \ 143 | # Pin chromaprint version to v1.4.3 due to https://github.com/acoustid/chromaprint/issues/107 144 | # BRANCH_CHROMAPRINT=$(git tag --sort="-creatordate" | head -1) && \ 145 | BRANCH_CHROMAPRINT="v1.4.3" && \ 146 | git checkout "tags/${BRANCH_CHROMAPRINT}" && \ 147 | cmake \ 148 | -DCMAKE_BUILD_TYPE=Release \ 149 | -DBUILD_TOOLS=ON \ 150 | -DBUILD_TESTS=ON \ 151 | -DGTEST_SOURCE_DIR=/src/googletest/googletest \ 152 | -DGTEST_INCLUDE_DIR=/src/googletest/googletest/include . \ 153 | && \ 154 | make && \ 155 | make check && \ 156 | make install && \ 157 | echo "chromaprint $BRANCH_CHROMAPRINT" >> /VERSIONS && \ 158 | popd && \ 159 | ldconfig && \ 160 | # Install chromium browser - https://askubuntu.com/questions/1204571/how-to-install-chromium-without-snap 161 | bash -c " echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/debian-buster.gpg] http://deb.debian.org/debian buster main' > /etc/apt/sources.list.d/debian.list" && \ 162 | bash -c " echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/debian-buster-updates.gpg] http://deb.debian.org/debian buster-updates main' >> /etc/apt/sources.list.d/debian.list" && \ 163 | bash -c " echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/debian-security-buster.gpg] http://deb.debian.org/debian-security buster/updates main' >> /etc/apt/sources.list.d/debian.list" && \ 164 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys DCC9EFBF77E11517 && \ 165 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 648ACFD622F3D138 && \ 166 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 112695A0E562B32A && \ 167 | bash -c "apt-key export 77E11517 | gpg --dearmour -o /usr/share/keyrings/debian-buster.gpg" && \ 168 | bash -c "apt-key export 22F3D138 | gpg --dearmour -o /usr/share/keyrings/debian-buster-updates.gpg" && \ 169 | bash -c "apt-key export E562B32A | gpg --dearmour -o /usr/share/keyrings/debian-security-buster.gpg" && \ 170 | apt-get update && \ 171 | apt-get install --no-install-recommends -y chromium && \ 172 | # Clone Picard repo & checkout latest version 173 | git clone "$URL_PICARD_REPO" /src/picard && \ 174 | pushd /src/picard && \ 175 | BRANCH_PICARD=$(git tag --sort="-creatordate" | head -1) && \ 176 | git checkout "tags/${BRANCH_PICARD}" && \ 177 | # Install Picard requirements 178 | python3 -m pip install --no-cache-dir --upgrade pip && \ 179 | python3 -m pip install --no-cache-dir discid python-libdiscid && \ 180 | python3 -m pip install --no-cache-dir -r requirements.txt && \ 181 | locale-gen en_US.UTF-8 && \ 182 | export LC_ALL=C.UTF-8 && \ 183 | # Build & install Picard 184 | python3 setup.py test && \ 185 | python3 setup.py install && \ 186 | mkdir -p /tmp/run/user/app && \ 187 | chmod 0700 /tmp/run/user/app && \ 188 | bash -c "if picard -v 2>&1 | grep -c error; then exit 1; fi" && \ 189 | bash -c "picard -v | cut -d ' ' -f 2- >> /VERSIONS" && \ 190 | popd && \ 191 | # Symlink for fpcalc (issue #32) 192 | ln -s /usr/local/bin/fpcalc /usr/bin/fpcalc && \ 193 | # Add optical drive script from jlesage/docker-handbrake 194 | wget \ 195 | --progress=dot:giga \ 196 | https://raw.githubusercontent.com/jlesage/docker-handbrake/6eb5567bcc29c2441507cb8cbd276293ec1790c8/rootfs/etc/cont-init.d/54-check-optical-drive.sh \ 197 | -O /etc/cont-init.d/54-check-optical-drive.sh \ 198 | && \ 199 | chmod +x /etc/cont-init.d/54-check-optical-drive.sh && \ 200 | # Security updates / fix for issue #37 (https://github.com/mikenye/docker-picard/issues/37) 201 | /src/trivy --cache-dir /tmp/trivy fs --vuln-type os -f json --ignore-unfixed --no-progress -o /tmp/trivy.out / && \ 202 | apt-get install -y --no-install-recommends $(jq .[].Vulnerabilities < /tmp/trivy.out | grep '"PkgName":' | tr -s ' ' | cut -d ':' -f 2 | tr -d ' ",' | uniq) && \ 203 | # Install streaming_extractor_music 204 | wget \ 205 | -O /tmp/essentia-extractor-linux-x86_64.tar.gz \ 206 | --progress=dot:giga \ 207 | 'https://data.metabrainz.org/pub/musicbrainz/acousticbrainz/extractors/essentia-extractor-v2.1_beta2-linux-x86_64.tar.gz' \ 208 | && \ 209 | tar \ 210 | xzvf \ 211 | /tmp/essentia-extractor-linux-x86_64.tar.gz \ 212 | -C /usr/local/sbin \ 213 | && \ 214 | # Clean-up 215 | apt-get remove -y ${TEMP_PACKAGES[@]} && \ 216 | apt-get autoremove -y && \ 217 | rm -rf /src/* /tmp/* /var/lib/apt/lists/* && \ 218 | find /var/log -type f -exec truncate --size=0 {} \; && \ 219 | # Install Chinese Fonts 220 | wget \ 221 | --progress=dot:giga \ 222 | -O /usr/share/fonts/SimSun.ttf \ 223 | "https://github.com/micmro/Stylify-Me/blob/main/.fonts/SimSun.ttf?raw=true" && \ 224 | fc-cache && \ 225 | # Capture picard version 226 | mkdir -p /tmp/run/user/app && \ 227 | bash -c "picard -V | grep Picard | cut -d ',' -f 1 | cut -d ' ' -f 2 | tr -d ' ' > /CONTAINER_VERSION" 228 | 229 | ENV APP_NAME="MusicBrainz Picard" \ 230 | LC_ALL="en_US.UTF-8" \ 231 | LANG="en_US.UTF-8" \ 232 | LANGUAGE="en_US.UTF-8" 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mikenye/picard 2 | 3 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/mikenye/docker-picard/Deploy%20to%20Docker%20Hub)](https://github.com/mikenye/docker-picard/actions?query=workflow%3A%22Deploy+to+Docker+Hub%22) 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/mikenye/picard.svg)](https://hub.docker.com/r/mikenye/picard) 5 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/mikenye/picard/latest)](https://hub.docker.com/r/mikenye/picard) 6 | [![Discord](https://img.shields.io/discord/734090820684349521)](https://discord.gg/sTf9uYF) 7 | 8 | Docker container for MusicBrainz Picard 9 | 10 | The GUI of the application is accessed through a modern web browser (no installation or configuration needed on client side) or via any VNC client. 11 | 12 | --- 13 | 14 | [![Picard logo](https://picard.musicbrainz.org/static/img/picard-icon-large.svg)](https://picard.musicbrainz.org)[![MusicBrainz Picard](https://dummyimage.com/400x150/ffffff/575757&text=MusicBrainz%20Picard)](https://picard.musicbrainz.org) 15 | 16 | Picard is a cross-platform music tagger written in Python. 17 | 18 | --- 19 | 20 | This container is based on the absolutely fantastic [jlesage/baseimage-gui](https://hub.docker.com/r/jlesage/baseimage-gui). All the hard work has been done by them, and I shamelessly copied their README.md too. I've cut the README.md down quite a bit, for advanced usage I suggest you check out the [README](https://github.com/jlesage/docker-handbrake/blob/master/README.md) from [jlesage/baseimage-gui](https://hub.docker.com/r/jlesage/baseimage-gui). 21 | 22 | --- 23 | 24 | ## Quick Start 25 | 26 | **NOTE**: The Docker command provided in this quick start is given as an example 27 | and parameters should be adjusted to your need. 28 | 29 | Launch the Picard docker container with the following command: 30 | 31 | ```shell 32 | docker run -d \ 33 | --name=picard \ 34 | -p 5800:5800 \ 35 | -v /path/to/config:/config:rw \ 36 | -v /path/to/music:/storage:rw \ 37 | mikenye/picard 38 | ``` 39 | 40 | Where: 41 | 42 | * `/path/to/config`: This is where the application stores its configuration, log and any files needing persistency. 43 | * `/path/to/music`: This location contains music files for Picard to operate on. 44 | 45 | Browse to `http://your-host-ip:5800` to access the Picard GUI. Your music will be located under `/storage`. 46 | 47 | ## Usage 48 | 49 | ```shell 50 | docker run [-d] \ 51 | --name=picard \ 52 | [-e =]... \ 53 | [-v :[:PERMISSIONS]]... \ 54 | [-p :]... \ 55 | mikenye/picard 56 | ``` 57 | 58 | | Parameter | Description | 59 | |-----------|-------------| 60 | | -d | Run the container in background. If not set, the container runs in foreground. | 61 | | -e | Pass an environment variable to the container. See the [Environment Variables](#environment-variables) section for more details. | 62 | | -v | Set a volume mapping (allows to share a folder/file between the host and the container). See the [Data Volumes](#data-volumes) section for more details. | 63 | | -p | Set a network port mapping (exposes an internal container port to the host). See the [Ports](#ports) section for more details. | 64 | 65 | ### Environment Variables 66 | 67 | To customize some properties of the container, the following environment 68 | variables can be passed via the `-e` parameter (one for each variable). Value 69 | of this parameter has the format `=`. 70 | 71 | | Variable | Description | Default | 72 | |----------------|----------------------------------------------|---------| 73 | |`USER_ID`| ID of the user the application runs as. See [User/Group IDs](#usergroup-ids) to better understand when this should be set. | `1000` | 74 | |`GROUP_ID`| ID of the group the application runs as. See [User/Group IDs](#usergroup-ids) to better understand when this should be set. | `1000` | 75 | |`SUP_GROUP_IDS`| Comma-separated list of supplementary group IDs of the application. | (unset) | 76 | |`UMASK`| Mask that controls how file permissions are set for newly created files. The value of the mask is in octal notation. By default, this variable is not set and the default umask of `022` is used, meaning that newly created files are readable by everyone, but only writable by the owner. See the following online umask calculator: | (unset) | 77 | |`TZ`| [TimeZone] of the container. Timezone can also be set by mapping `/etc/localtime` between the host and the container. | `Etc/UTC` | 78 | |`KEEP_APP_RUNNING`| When set to `1`, the application will be automatically restarted if it crashes or if user quits it. | `0` | 79 | |`APP_NICENESS`| Priority at which the application should run. A niceness value of -20 is the highest priority and 19 is the lowest priority. By default, niceness is not set, meaning that the default niceness of 0 is used. **NOTE**: A negative niceness (priority increase) requires additional permissions. In this case, the container should be run with the docker option `--cap-add=SYS_NICE`. | (unset) | 80 | |`CLEAN_TMP_DIR`| When set to `1`, all files in the `/tmp` directory are delete during the container startup. | `1` | 81 | |`DISPLAY_WIDTH`| Width (in pixels) of the application's window. | `1280` | 82 | |`DISPLAY_HEIGHT`| Height (in pixels) of the application's window. | `768` | 83 | |`SECURE_CONNECTION`| When set to `1`, an encrypted connection is used to access the application's GUI (either via web browser or VNC client). See the [Security](#security) section for more details. | `0` | 84 | |`VNC_PASSWORD`| Password needed to connect to the application's GUI. See the [VNC Password](#vnc-password) section for more details. | (unset) | 85 | |`X11VNC_EXTRA_OPTS`| Extra options to pass to the x11vnc server running in the Docker container. **WARNING**: For advanced users. Do not use unless you know what you are doing. | (unset) | 86 | |`ENABLE_CJK_FONT`| When set to `1`, open source computer font `WenQuanYi Zen Hei` is installed. This font contains a large range of Chinese/Japanese/Korean characters. | `0` | 87 | 88 | ### Data Volumes 89 | 90 | The following table describes data volumes used by the container. The mappings 91 | are set via the `-v` parameter. Each mapping is specified with the following 92 | format: `:[:PERMISSIONS]`. 93 | 94 | | Container path | Permissions | Description | 95 | |-----------------|-------------|-------------| 96 | |`/config`| rw | This is where the application stores its configuration, log and any files needing persistency. | 97 | |`/storage`| ro/rw | This location contains files from your host that need to be accessible by the application. Should be 'rw' if you want Picard to re-tag/re-name your files.| 98 | 99 | ### Ports 100 | 101 | Here is the list of ports used by the container. They can be mapped to the host 102 | via the `-p` parameter (one per port mapping). Each mapping is defined in the 103 | following format: `:`. The port number inside the 104 | container cannot be changed, but you are free to use any port on the host side. 105 | 106 | | Port | Mapping to host | Description | 107 | |------|-----------------|-------------| 108 | | 5800 | Mandatory | Port used to access the application's GUI via the web interface. | 109 | | 5900 | Optional | Port used to access the application's GUI via the VNC protocol. Optional if no VNC client is used. | 110 | | 8000 | Optional | Port used to access Picard's "browser integration" feature. Must be enabled in Picard's options (advanced > network, check "browser integration" and uncheck "listen only on localhost). | 111 | 112 | ### Changing Parameters of a Running Container 113 | 114 | As seen, environment variables, volume mappings and port mappings are specified 115 | while creating the container. 116 | 117 | The following steps describe the method used to add, remove or update 118 | parameter(s) of an existing container. The generic idea is to destroy and 119 | re-create the container: 120 | 121 | 1. Stop the container (if it is running): 122 | 123 | ```shell 124 | docker stop picard 125 | ``` 126 | 127 | 2. Remove the container: 128 | 129 | ```shell 130 | docker rm picard 131 | ``` 132 | 133 | 3. Create/start the container using the `docker run` command, by adjusting 134 | parameters as needed. 135 | 136 | **NOTE**: Since all application's data is saved under the `/config` container 137 | folder, destroying and re-creating a container is not a problem: nothing is lost 138 | and the application comes back with the same state (as long as the mapping of 139 | the `/config` folder remains the same). 140 | 141 | ## Docker Compose File 142 | 143 | Here is an example of a `docker-compose.yml` file that can be used with 144 | [Docker Compose](https://docs.docker.com/compose/overview/). 145 | 146 | Make sure to adjust according to your needs. Note that only mandatory network 147 | ports are part of the example. 148 | 149 | ```yaml 150 | version: '3' 151 | services: 152 | picard: 153 | image: mikenye/picard:latest 154 | ports: 155 | - "5800:5800" 156 | volumes: 157 | - "/path/to/config:/config:rw" 158 | - "/path/to/music:/storage:rw" 159 | ``` 160 | 161 | ## Docker Image Update 162 | 163 | If the system on which the container runs doesn't provide a way to easily update 164 | the Docker image, the following steps can be followed: 165 | 166 | 1. Fetch the latest image: 167 | 168 | ```shell 169 | docker pull mikenye/picard 170 | ``` 171 | 172 | 2. Stop the container: 173 | 174 | ```shell 175 | docker stop picard 176 | ``` 177 | 178 | 3. Remove the container: 179 | 180 | ```shell 181 | docker rm picard 182 | ``` 183 | 184 | 4. Start the container using the `docker run` command. 185 | 186 | ## User/Group IDs 187 | 188 | When using data volumes (`-v` flags), permissions issues can occur between the 189 | host and the container. For example, the user within the container may not 190 | exists on the host. This could prevent the host from properly accessing files 191 | and folders on the shared volume. 192 | 193 | To avoid any problem, you can specify the user the application should run as. 194 | 195 | This is done by passing the user ID and group ID to the container via the 196 | `USER_ID` and `GROUP_ID` environment variables. 197 | 198 | To find the right IDs to use, issue the following command on the host, with the 199 | user owning the data volume on the host: 200 | 201 | ```shell 202 | id 203 | ``` 204 | 205 | Which gives an output like this one: 206 | 207 | ```text 208 | uid=1000(myuser) gid=1000(myuser) groups=1000(myuser),4(adm),24(cdrom),27(sudo),46(plugdev),113(lpadmin) 209 | ``` 210 | 211 | The value of `uid` (user ID) and `gid` (group ID) are the ones that you should 212 | be given the container. 213 | 214 | ## Accessing the GUI 215 | 216 | Assuming that container's ports are mapped to the same host's ports, the 217 | graphical interface of the application can be accessed via: 218 | 219 | * A web browser: 220 | 221 | ```text 222 | http://:5800 223 | ``` 224 | 225 | * Any VNC client: 226 | 227 | ```text 228 | :5900 229 | ``` 230 | 231 | ## Security 232 | 233 | By default, access to the application's GUI is done over an unencrypted 234 | connection (HTTP or VNC). 235 | 236 | Secure connection can be enabled via the `SECURE_CONNECTION` environment 237 | variable. See the [Environment Variables](#environment-variables) section for 238 | more details on how to set an environment variable. 239 | 240 | When enabled, application's GUI is performed over an HTTPs connection when 241 | accessed with a browser. All HTTP accesses are automatically redirected to 242 | HTTPs. 243 | 244 | When using a VNC client, the VNC connection is performed over SSL. Note that 245 | few VNC clients support this method. [SSVNC] is one of them. 246 | 247 | [SSVNC]: http://www.karlrunge.com/x11vnc/ssvnc.html 248 | 249 | ### Certificates 250 | 251 | Here are the certificate files needed by the container. By default, when they 252 | are missing, self-signed certificates are generated and used. All files have 253 | PEM encoded, x509 certificates. 254 | 255 | | Container Path | Purpose | Content | 256 | |---------------------------------|----------------------------|---------| 257 | |`/config/certs/vnc-server.pem` |VNC connection encryption. |VNC server's private key and certificate, bundled with any root and intermediate certificates.| 258 | |`/config/certs/web-privkey.pem` |HTTPs connection encryption.|Web server's private key.| 259 | |`/config/certs/web-fullchain.pem`|HTTPs connection encryption.|Web server's certificate, bundled with any root and intermediate certificates.| 260 | 261 | **NOTE**: To prevent any certificate validity warnings/errors from the browser 262 | or VNC client, make sure to supply your own valid certificates. 263 | 264 | **NOTE**: Certificate files are monitored and relevant daemons are automatically 265 | restarted when changes are detected. 266 | 267 | ### VNC Password 268 | 269 | To restrict access to your application, a password can be specified. This can 270 | be done via two methods: 271 | 272 | * By using the `VNC_PASSWORD` environment variable. 273 | * By creating a `.vncpass_clear` file at the root of the `/config` volume. 274 | This file should contains the password in clear-text. During the container 275 | startup, content of the file is obfuscated and moved to `.vncpass`. 276 | 277 | The level of security provided by the VNC password depends on two things: 278 | 279 | * The type of communication channel (encrypted/unencrypted). 280 | * How secure access to the host is. 281 | 282 | When using a VNC password, it is highly desirable to enable the secure 283 | connection to prevent sending the password in clear over an unencrypted channel. 284 | 285 | **ATTENTION**: Password is limited to 8 characters. This limitation comes from 286 | the Remote Framebuffer Protocol [RFC](https://tools.ietf.org/html/rfc6143) (see 287 | section [7.2.2](https://tools.ietf.org/html/rfc6143#section-7.2.2)). Any 288 | characters beyhond the limit are ignored. 289 | 290 | ## Shell Access 291 | 292 | To get shell access to a the running container, execute the following command: 293 | 294 | ```shell 295 | docker exec -ti picard bash 296 | ``` 297 | 298 | ## Optional: Access to Optical Drive(s) 299 | 300 | Picard can lookup CDs from an optical drive. 301 | 302 | By default, a Docker container doesn't have access to host's devices. However, 303 | access to one or more device can be granted with the `--device DEV` parameter. 304 | 305 | Optical drives usually have `/dev/srX` as device. For example, the first drive 306 | is `/dev/sr0`, the second `/dev/sr1`, and so on. To allow Picard to access 307 | the first drive, this parameter is needed: 308 | 309 | ```shell 310 | --device /dev/sr0 311 | ``` 312 | 313 | To easily find devices of optical drives, start the container and look at its 314 | log for messages similar to these ones: 315 | 316 | ```text 317 | ... 318 | [cont-init.d] 95-check-optical-drive.sh: executing... 319 | [cont-init.d] 95-check-optical-drive.sh: looking for usable optical drives... 320 | [cont-init.d] 95-check-optical-drive.sh: found optical drive /dev/sr0, but it is not usable because is not exposed to the container. 321 | [cont-init.d] 95-check-optical-drive.sh: no usable optical drive found. 322 | [cont-init.d] 95-check-optical-drive.sh: exited 0. 323 | ... 324 | ``` 325 | 326 | ## Getting Help 327 | 328 | Having troubles with the container or have questions? Please [create a new issue](https://github.com/mikenye/docker-picard/issues). 329 | 330 | I also have a [Discord channel](https://discord.gg/zpxkMrY), feel free to [join](https://discord.gg/zpxkMrY) and converse. 331 | -------------------------------------------------------------------------------- /rootfs/etc/apt/preferences.d/chromium.pref: -------------------------------------------------------------------------------- 1 | # Note: 2 blank lines are required between entries 2 | Package: * 3 | Pin: release a=eoan 4 | Pin-Priority: 500 5 | 6 | 7 | Package: * 8 | Pin: origin "deb.debian.org" 9 | Pin-Priority: 300 10 | 11 | 12 | # Pattern includes 'chromium', 'chromium-browser' and similarly 13 | # named dependencies: 14 | Package: chromium* 15 | Pin: origin "deb.debian.org" 16 | Pin-Priority: 700 17 | 18 | -------------------------------------------------------------------------------- /rootfs/etc/cont-init.d/90-dbus: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | # Generate machine-id 5 | rm /etc/machine-id > /dev/null 2>&1 || true 6 | dbus-uuidgen > /var/lib/dbus/machine-id 7 | ln -s /var/lib/dbus/machine-id /etc/machine-id 8 | 9 | # Ensure pid file is removed 10 | rm /var/run/dbus/pid > /dev/null 2>&1 || true 11 | 12 | # Ensure directory structure is present 13 | mkdir -p /var/run/dbus 14 | 15 | # Ensure messagebus user exists 16 | if ! id "messagebus" &>/dev/null; then 17 | useradd -r --no-create-home -U messagebus 18 | fi 19 | -------------------------------------------------------------------------------- /rootfs/etc/openbox/main-window-selection.xml: -------------------------------------------------------------------------------- 1 | normal 2 | MusicBrainz Picard -------------------------------------------------------------------------------- /rootfs/etc/services.d/dbus/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | # Start dbus 5 | dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address --nofork -------------------------------------------------------------------------------- /rootfs/startapp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | #shellcheck shell=sh 3 | 4 | set -xe 5 | 6 | # Set homedir to /config to capture all configuration 7 | HOME=/config 8 | export HOME 9 | 10 | # Unlock Chromium profile 11 | rm -rf /config/xdg/config/chromium/Singleton* 12 | 13 | # Launch picard 14 | /usr/local/bin/picard --------------------------------------------------------------------------------