├── .flake8 ├── .github ├── CODEOWNERS ├── renovate.json └── workflows │ ├── preview-build-winonly.yaml │ ├── preview-build.yaml │ ├── release-build.yaml │ └── unstable-build.yaml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.py ├── build.yaml ├── checkout.py ├── debian ├── changelog.in ├── compat ├── conf │ ├── jellyfin │ ├── jellyfin.init │ ├── jellyfin.service │ ├── jellyfin.service.conf │ ├── jellyfin.upstart │ └── logging.json ├── control ├── copyright ├── docker │ ├── Dockerfile │ └── build.sh ├── gbp.conf ├── jellyfin-server.conffiles ├── jellyfin-server.install ├── jellyfin-server.postinst ├── jellyfin-server.postrm ├── jellyfin-server.preinst ├── jellyfin-server.prerm ├── jellyfin-web.conffiles ├── jellyfin-web.install ├── po │ ├── POTFILES.in │ └── templates.pot ├── rules └── source │ ├── format │ └── options ├── docker └── Dockerfile ├── portable ├── Dockerfile └── build.sh └── requirements.txt /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # We ignore the following errors: 3 | # * W503 (line break before binary operator): Black moves these to new lines 4 | # * E501 (line too long): Long lines are a fact of life in comment blocks; Black handles active instances of this 5 | # * E203 (whitespace before ':'): Black recommends this as disabled 6 | ignore = W503, E501 7 | extend-ignore = E203 8 | # Set the max line length to 88 for Black 9 | max-line-length = 88 10 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Joshua must review all changes to deployment and packaging 2 | * @joshuaboniface 3 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>jellyfin/.github//renovate-presets/default"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/preview-build-winonly.yaml: -------------------------------------------------------------------------------- 1 | name: "Preview Build Windows-Only" 2 | 3 | on: 4 | workflow_dispatch: 5 | # Manual trigger (from bot) 6 | inputs: 7 | version: 8 | required: true 9 | type: string 10 | description: 'The server and web stable release tag (i.e. "vX.Y.Z")' 11 | preview_id: 12 | required: true 13 | type: string 14 | description: 'The numerical preview ID (e.g. 1 for `rc1`)' 15 | 16 | env: 17 | SDK_VERSION: "9.0.x" 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | Windows: 24 | runs-on: ubuntu-24.04 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | arch: 29 | - amd64 30 | - arm64 31 | outputs: 32 | JELLYFIN_VERSION: ${{ steps.version.outputs.JELLYFIN_VERSION }} 33 | JELLYFIN_RELEASE_TYPE: ${{ steps.version.outputs.JELLYFIN_RELEASE_TYPE }} 34 | continue-on-error: false # true in prod, false for testing 35 | steps: 36 | - name: "Set dated version for unstable builds" 37 | id: version 38 | run: |- 39 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 40 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_OUTPUT 41 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 42 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_OUTPUT 43 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 44 | echo "DEBUG_FLAG=--debug" >>$GITHUB_OUTPUT 45 | 46 | - name: "Install dependencies" 47 | run: |- 48 | sudo apt-get update 49 | sudo apt-get install --yes python3-git python3-yaml 50 | 51 | - name: "Checkout repository" 52 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 53 | 54 | - name: "Prepare repository" 55 | run: |- 56 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 57 | 58 | - name: "Run builder for ${{ matrix.arch }}" 59 | run: |- 60 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} windows ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 61 | 62 | - name: "Upload artifacts to repository server" 63 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 64 | with: 65 | host: "${{ secrets.REPO_HOST }}" 66 | username: "${{ secrets.REPO_USER }}" 67 | key: "${{ secrets.REPO_KEY }}" 68 | source: "out/windows/*" 69 | strip_components: 2 70 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 71 | 72 | - name: "Move artifacts into repository" 73 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 74 | with: 75 | host: "${{ secrets.REPO_HOST }}" 76 | username: "${{ secrets.REPO_USER }}" 77 | key: "${{ secrets.REPO_KEY }}" 78 | debug: false 79 | script: | 80 | set -e 81 | export BASEDIR="/srv/repository/main/server/windows" 82 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 83 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 84 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 85 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 86 | 87 | - name: "Store artifacts for next stage" 88 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 89 | with: 90 | name: windows-artifacts 91 | retention-days: 1 92 | if-no-files-found: ignore # Ignore for arm64 build, as no file will be found 93 | path: out/windows/jellyfin_*-amd64.zip 94 | 95 | WindowsInstaller: 96 | needs: 97 | - Windows 98 | runs-on: windows-latest 99 | strategy: 100 | fail-fast: false 101 | matrix: 102 | arch: 103 | - amd64 104 | continue-on-error: false # true in prod, false for testing 105 | env: 106 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 107 | steps: 108 | - name: "Set dated version for unstable builds" 109 | id: version 110 | shell: pwsh 111 | run: | 112 | $version = "${{ needs.Windows.outputs.JELLYFIN_VERSION }}" 113 | $cleanVersion = $version.Substring(1) 114 | echo "JELLYFIN_VERSION=$cleanVersion" | Out-File -FilePath $env:GITHUB_ENV -Append 115 | 116 | - name: "Checkout repository" 117 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 118 | 119 | - name: "Install dependencies" 120 | run: |- 121 | python -m pip install --upgrade gitpython 122 | 123 | - name: "Install NSIS" 124 | uses: negrutiu/nsis-install@f3339c88dba6fd08910d5275a943f8f746d94876 # v2 125 | with: 126 | distro: official 127 | 128 | - name: "Prepare repository" 129 | run: |- 130 | python ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 131 | 132 | - name: "Fetch artifacts from previous stage" 133 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 134 | with: 135 | name: windows-artifacts 136 | path: ./jellyfin-server-windows 137 | 138 | - name: "Clone UX repository" 139 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 140 | with: 141 | repository: jellyfin/jellyfin-ux 142 | path: .\jellyfin-server-windows\jellyfin-ux 143 | 144 | - name: "Extract Jellyfin server archive" 145 | working-directory: ./jellyfin-server-windows 146 | run: | 147 | New-Item -ItemType Directory -Path jellyfin 148 | Expand-Archive -Path 'jellyfin_*-amd64.zip' -DestinationPath jellyfin -Force 149 | Copy-Item ".\Support Files\LICENSE" -Destination $(Resolve-Path .\jellyfin\jellyfin) 150 | 151 | - name: "Add NSSM" 152 | working-directory: ./jellyfin-server-windows 153 | run: | 154 | Invoke-WebRequest 'https://repo.jellyfin.org/files/other/nssm.zip' -OutFile 'nssm.zip' 155 | Expand-Archive 'nssm.zip' 156 | Copy-Item ".\nssm\nssm.exe" -Destination $(Resolve-Path .\jellyfin\jellyfin) 157 | 158 | - name: "Publish tray" 159 | working-directory: ./jellyfin-server-windows 160 | run: | 161 | New-Item -Path .\jellyfin\jellyfin\jellyfin-windows-tray -ItemType Directory 162 | dotnet publish -c Release -r win-x64 -f net472 --no-self-contained --output $(Resolve-Path .\jellyfin\jellyfin\jellyfin-windows-tray) 163 | 164 | - name: "Build installer" 165 | working-directory: ./jellyfin-server-windows 166 | run: | 167 | $env:InstallLocation = $(Resolve-Path .\jellyfin\jellyfin) 168 | makensis /Dx64 /DUXPATH=$(Resolve-Path .\jellyfin-ux) $(Join-Path -Path $(Resolve-Path .\nsis) -ChildPath jellyfin.nsi) 169 | 170 | - name: "Rename installer" 171 | working-directory: ./jellyfin-server-windows/nsis 172 | run: | 173 | Rename-Item -Path .\jellyfin_*_windows-x64.exe -NewName ("jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe") 174 | 175 | - name: "Store artifacts for next stage" 176 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 177 | with: 178 | name: windows-installer-artifacts 179 | retention-days: 1 180 | if-no-files-found: error 181 | path: ./jellyfin-server-windows/nsis/jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe 182 | 183 | WindowsInstallerUpload: 184 | needs: 185 | - Windows 186 | - WindowsInstaller 187 | runs-on: ubuntu-24.04 188 | strategy: 189 | fail-fast: false 190 | matrix: 191 | arch: 192 | - amd64 193 | continue-on-error: false # true in prod, false for testing 194 | steps: 195 | - name: "Set version from previous job" 196 | id: version 197 | run: |- 198 | echo "JELLYFIN_VERSION=${{ needs.Windows.outputs.JELLYFIN_VERSION }}" >> $GITHUB_ENV 199 | echo "JELLYFIN_RELEASE_TYPE=${{ needs.Windows.outputs.JELLYFIN_RELEASE_TYPE }}" >> $GITHUB_ENV 200 | 201 | - name: "Fetch artifact from previous stage" 202 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 203 | with: 204 | name: windows-installer-artifacts 205 | 206 | - name: "Upload artifacts to repository server" 207 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 208 | with: 209 | host: "${{ secrets.REPO_HOST }}" 210 | username: "${{ secrets.REPO_USER }}" 211 | key: "${{ secrets.REPO_KEY }}" 212 | source: "*.exe" 213 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 214 | 215 | - name: "Move artifacts into repository" 216 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 217 | with: 218 | host: "${{ secrets.REPO_HOST }}" 219 | username: "${{ secrets.REPO_USER }}" 220 | key: "${{ secrets.REPO_KEY }}" 221 | debug: false 222 | script: | 223 | set -e 224 | export BASEDIR="/srv/repository/main/server/windows" 225 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}/*.exe 226 | -------------------------------------------------------------------------------- /.github/workflows/preview-build.yaml: -------------------------------------------------------------------------------- 1 | name: "Preview Build" 2 | 3 | on: 4 | workflow_dispatch: 5 | # Manual trigger (from bot) 6 | inputs: 7 | version: 8 | required: true 9 | type: string 10 | description: 'The server and web stable release tag (i.e. "vX.Y.Z")' 11 | preview_id: 12 | required: true 13 | type: string 14 | description: 'The numerical preview ID (e.g. 1 for `rc1`)' 15 | 16 | env: 17 | SDK_VERSION: "9.0.x" 18 | 19 | permissions: 20 | contents: read 21 | 22 | jobs: 23 | Docker: 24 | runs-on: ubuntu-24.04 25 | continue-on-error: false # true in prod, false for testing 26 | steps: 27 | - name: "Set dated version for unstable builds" 28 | id: version 29 | run: |- 30 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 31 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 32 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 33 | 34 | - name: "Install dependencies" 35 | run: |- 36 | sudo apt-get update 37 | sudo apt-get install --yes python3-git python3-yaml 38 | 39 | - name: "Checkout repository" 40 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 41 | 42 | - name: "Prepare repository" 43 | run: |- 44 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 45 | 46 | - name: "Run builder for Docker containers" 47 | env: 48 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 49 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 50 | GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }} 51 | GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} 52 | run: |- 53 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} docker ${{ env.DEBUG_FLAG }} 54 | 55 | Debian: 56 | runs-on: ubuntu-24.04 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | release: 61 | - bullseye 62 | - bookworm 63 | - trixie 64 | arch: 65 | - amd64 66 | - arm64 67 | continue-on-error: false # true in prod, false for testing 68 | steps: 69 | - name: "Set dated version for unstable builds" 70 | id: version 71 | run: |- 72 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 73 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 74 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 75 | 76 | - name: "Install dependencies" 77 | run: |- 78 | sudo apt-get update 79 | sudo apt-get install --yes python3-git python3-yaml debsigs devscripts 80 | 81 | - name: "Checkout repository" 82 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 83 | 84 | - name: "Prepare repository" 85 | run: |- 86 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 87 | 88 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 89 | run: |- 90 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} debian ${{ matrix.arch }} ${{ matrix.release }} ${{ env.DEBUG_FLAG }} 91 | sudo chown --recursive $USER out/debian 92 | 93 | - name: "Import repository signing GPG key" 94 | run: | 95 | echo -n "${{ secrets.DEBIAN_SIGNING_KEY }}" | base64 --decode | gpg --batch --yes --import 96 | 97 | - name: "Sign Debian package and source files" 98 | run: | 99 | for file in out/debian/*.deb; do 100 | debsigs --sign=origin --default-key=${{ secrets.DEBIAN_SIGNING_KEY_ID }} ${file} 101 | done 102 | debsign -k ${{ secrets.DEBIAN_SIGNING_KEY_ID }} out/debian/*.changes 103 | 104 | - name: "Remove repository signing GPG key" 105 | run: | 106 | gpg --batch --yes --delete-secret-keys ${{ secrets.DEBIAN_SIGNING_KEY_ID }} 107 | 108 | - name: "Upload artifacts to repository server" 109 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 110 | with: 111 | host: "${{ secrets.REPO_HOST }}" 112 | username: "${{ secrets.REPO_USER }}" 113 | key: "${{ secrets.REPO_KEY }}" 114 | source: "out/debian/*" 115 | strip_components: 2 116 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}" 117 | 118 | - name: "Move artifacts into repository" 119 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 120 | with: 121 | host: "${{ secrets.REPO_HOST }}" 122 | username: "${{ secrets.REPO_USER }}" 123 | key: "${{ secrets.REPO_KEY }}" 124 | debug: false 125 | script: | 126 | export BASEDIR="/srv/repository/main/server/debian" 127 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 128 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 129 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 130 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 131 | 132 | Ubuntu: 133 | runs-on: ubuntu-24.04 134 | strategy: 135 | fail-fast: false 136 | matrix: 137 | release: 138 | - focal 139 | - jammy 140 | - noble 141 | arch: 142 | - amd64 143 | - arm64 144 | continue-on-error: false # true in prod, false for testing 145 | steps: 146 | - name: "Set dated version for unstable builds" 147 | id: version 148 | run: |- 149 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 150 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 151 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 152 | 153 | - name: "Install dependencies" 154 | run: |- 155 | sudo apt-get update 156 | sudo apt-get install --yes python3-git python3-yaml debsigs devscripts 157 | 158 | - name: "Checkout repository" 159 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 160 | 161 | - name: "Prepare repository" 162 | run: |- 163 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 164 | 165 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 166 | run: |- 167 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} ubuntu ${{ matrix.arch }} ${{ matrix.release }} ${{ env.DEBUG_FLAG }} 168 | sudo chown --recursive $USER out/ubuntu 169 | 170 | - name: "Import repository signing GPG key" 171 | run: | 172 | echo -n "${{ secrets.DEBIAN_SIGNING_KEY }}" | base64 --decode | gpg --batch --yes --import 173 | 174 | - name: "Sign Ubuntu package and source files" 175 | run: | 176 | for file in out/ubuntu/*.deb; do 177 | debsigs --sign=origin --default-key=${{ secrets.DEBIAN_SIGNING_KEY_ID }} ${file} 178 | done 179 | debsign -k ${{ secrets.DEBIAN_SIGNING_KEY_ID }} out/ubuntu/*.changes 180 | 181 | - name: "Remove repository signing GPG key" 182 | run: | 183 | gpg --batch --yes --delete-secret-keys ${{ secrets.DEBIAN_SIGNING_KEY_ID }} 184 | 185 | - name: "Upload artifacts to repository server" 186 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 187 | with: 188 | host: "${{ secrets.REPO_HOST }}" 189 | username: "${{ secrets.REPO_USER }}" 190 | key: "${{ secrets.REPO_KEY }}" 191 | source: "out/ubuntu/*" 192 | strip_components: 2 193 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}" 194 | 195 | - name: "Move artifacts into repository" 196 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 197 | with: 198 | host: "${{ secrets.REPO_HOST }}" 199 | username: "${{ secrets.REPO_USER }}" 200 | key: "${{ secrets.REPO_KEY }}" 201 | debug: false 202 | script: | 203 | export BASEDIR="/srv/repository/main/server/ubuntu" 204 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 205 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 206 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 207 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 208 | 209 | Linux: 210 | runs-on: ubuntu-24.04 211 | strategy: 212 | fail-fast: false 213 | matrix: 214 | arch: 215 | - amd64 216 | - amd64-musl 217 | - arm64 218 | - arm64-musl 219 | continue-on-error: false # true in prod, false for testing 220 | steps: 221 | - name: "Set dated version for unstable builds" 222 | id: version 223 | run: |- 224 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 225 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 226 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 227 | 228 | - name: "Install dependencies" 229 | run: |- 230 | sudo apt-get update 231 | sudo apt-get install --yes python3-git python3-yaml 232 | 233 | - name: "Checkout repository" 234 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 235 | 236 | - name: "Prepare repository" 237 | run: |- 238 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 239 | 240 | - name: "Run builder for ${{ matrix.arch }}" 241 | run: |- 242 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} linux ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 243 | 244 | - name: "Upload artifacts to repository server" 245 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 246 | with: 247 | host: "${{ secrets.REPO_HOST }}" 248 | username: "${{ secrets.REPO_USER }}" 249 | key: "${{ secrets.REPO_KEY }}" 250 | source: "out/linux/*" 251 | strip_components: 2 252 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/linux/${{ matrix.arch }}" 253 | 254 | - name: "Move artifacts into repository" 255 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 256 | with: 257 | host: "${{ secrets.REPO_HOST }}" 258 | username: "${{ secrets.REPO_USER }}" 259 | key: "${{ secrets.REPO_KEY }}" 260 | debug: false 261 | script: | 262 | set -e 263 | export BASEDIR="/srv/repository/main/server/linux" 264 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 265 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/linux/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 266 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 267 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 268 | 269 | Windows: 270 | runs-on: ubuntu-24.04 271 | strategy: 272 | fail-fast: false 273 | matrix: 274 | arch: 275 | - amd64 276 | - arm64 277 | outputs: 278 | JELLYFIN_VERSION: ${{ steps.version.outputs.JELLYFIN_VERSION }} 279 | JELLYFIN_RELEASE_TYPE: ${{ steps.version.outputs.JELLYFIN_RELEASE_TYPE }} 280 | continue-on-error: false # true in prod, false for testing 281 | steps: 282 | - name: "Set dated version for unstable builds" 283 | id: version 284 | run: |- 285 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 286 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_OUTPUT 287 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 288 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_OUTPUT 289 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 290 | echo "DEBUG_FLAG=--debug" >>$GITHUB_OUTPUT 291 | 292 | - name: "Install dependencies" 293 | run: |- 294 | sudo apt-get update 295 | sudo apt-get install --yes python3-git python3-yaml 296 | 297 | - name: "Checkout repository" 298 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 299 | 300 | - name: "Prepare repository" 301 | run: |- 302 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 303 | 304 | - name: "Run builder for ${{ matrix.arch }}" 305 | run: |- 306 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} windows ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 307 | 308 | - name: "Upload artifacts to repository server" 309 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 310 | with: 311 | host: "${{ secrets.REPO_HOST }}" 312 | username: "${{ secrets.REPO_USER }}" 313 | key: "${{ secrets.REPO_KEY }}" 314 | source: "out/windows/*" 315 | strip_components: 2 316 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 317 | 318 | - name: "Move artifacts into repository" 319 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 320 | with: 321 | host: "${{ secrets.REPO_HOST }}" 322 | username: "${{ secrets.REPO_USER }}" 323 | key: "${{ secrets.REPO_KEY }}" 324 | debug: false 325 | script: | 326 | set -e 327 | export BASEDIR="/srv/repository/main/server/windows" 328 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 329 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 330 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 331 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 332 | 333 | - name: "Store artifacts for next stage" 334 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 335 | with: 336 | name: windows-artifacts 337 | retention-days: 1 338 | if-no-files-found: ignore # Ignore for arm64 build, as no file will be found 339 | path: out/windows/jellyfin_*-amd64.zip 340 | 341 | WindowsInstaller: 342 | needs: 343 | - Windows 344 | runs-on: windows-latest 345 | strategy: 346 | fail-fast: false 347 | matrix: 348 | arch: 349 | - amd64 350 | continue-on-error: false # true in prod, false for testing 351 | env: 352 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 353 | steps: 354 | - name: "Set dated version for unstable builds" 355 | id: version 356 | shell: pwsh 357 | run: | 358 | $version = "${{ needs.Windows.outputs.JELLYFIN_VERSION }}" 359 | $cleanVersion = $version.Substring(1) 360 | echo "JELLYFIN_VERSION=$cleanVersion" | Out-File -FilePath $env:GITHUB_ENV -Append 361 | 362 | - name: "Checkout repository" 363 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 364 | 365 | - name: "Install dependencies" 366 | run: |- 367 | python -m pip install --upgrade gitpython 368 | 369 | - name: "Install NSIS" 370 | uses: negrutiu/nsis-install@f3339c88dba6fd08910d5275a943f8f746d94876 # v2 371 | with: 372 | distro: official 373 | 374 | - name: "Prepare repository" 375 | run: |- 376 | python ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 377 | 378 | - name: "Fetch artifacts from previous stage" 379 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 380 | with: 381 | name: windows-artifacts 382 | path: ./jellyfin-server-windows 383 | 384 | - name: "Clone UX repository" 385 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 386 | with: 387 | repository: jellyfin/jellyfin-ux 388 | path: .\jellyfin-server-windows\jellyfin-ux 389 | 390 | - name: "Extract Jellyfin server archive" 391 | working-directory: ./jellyfin-server-windows 392 | run: | 393 | New-Item -ItemType Directory -Path jellyfin 394 | Expand-Archive -Path 'jellyfin_*-amd64.zip' -DestinationPath jellyfin -Force 395 | Copy-Item ".\Support Files\LICENSE" -Destination $(Resolve-Path .\jellyfin\jellyfin) 396 | 397 | - name: "Add NSSM" 398 | working-directory: ./jellyfin-server-windows 399 | run: | 400 | Invoke-WebRequest 'https://repo.jellyfin.org/files/other/nssm.zip' -OutFile 'nssm.zip' 401 | Expand-Archive 'nssm.zip' 402 | Copy-Item ".\nssm\nssm.exe" -Destination $(Resolve-Path .\jellyfin\jellyfin) 403 | 404 | - name: "Publish tray" 405 | working-directory: ./jellyfin-server-windows 406 | run: | 407 | New-Item -Path .\jellyfin\jellyfin\jellyfin-windows-tray -ItemType Directory 408 | dotnet publish -c Release -r win-x64 -f net472 --no-self-contained --output $(Resolve-Path .\jellyfin\jellyfin\jellyfin-windows-tray) 409 | 410 | - name: "Build installer" 411 | working-directory: ./jellyfin-server-windows 412 | run: | 413 | $env:InstallLocation = $(Resolve-Path .\jellyfin\jellyfin) 414 | makensis /Dx64 /DUXPATH=$(Resolve-Path .\jellyfin-ux) $(Join-Path -Path $(Resolve-Path .\nsis) -ChildPath jellyfin.nsi) 415 | 416 | - name: "Rename installer" 417 | working-directory: ./jellyfin-server-windows/nsis 418 | run: | 419 | Rename-Item -Path .\jellyfin_*_windows-x64.exe -NewName ("jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe") 420 | 421 | - name: "Store artifacts for next stage" 422 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 423 | with: 424 | name: windows-installer-artifacts 425 | retention-days: 1 426 | if-no-files-found: error 427 | path: ./jellyfin-server-windows/nsis/jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe 428 | 429 | WindowsInstallerUpload: 430 | needs: 431 | - Windows 432 | - WindowsInstaller 433 | runs-on: ubuntu-24.04 434 | strategy: 435 | fail-fast: false 436 | matrix: 437 | arch: 438 | - amd64 439 | continue-on-error: false # true in prod, false for testing 440 | steps: 441 | - name: "Set version from previous job" 442 | id: version 443 | run: |- 444 | echo "JELLYFIN_VERSION=${{ needs.Windows.outputs.JELLYFIN_VERSION }}" >> $GITHUB_ENV 445 | echo "JELLYFIN_RELEASE_TYPE=${{ needs.Windows.outputs.JELLYFIN_RELEASE_TYPE }}" >> $GITHUB_ENV 446 | 447 | - name: "Fetch artifact from previous stage" 448 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 449 | with: 450 | name: windows-installer-artifacts 451 | 452 | - name: "Upload artifacts to repository server" 453 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 454 | with: 455 | host: "${{ secrets.REPO_HOST }}" 456 | username: "${{ secrets.REPO_USER }}" 457 | key: "${{ secrets.REPO_KEY }}" 458 | source: "*.exe" 459 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 460 | 461 | - name: "Move artifacts into repository" 462 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 463 | with: 464 | host: "${{ secrets.REPO_HOST }}" 465 | username: "${{ secrets.REPO_USER }}" 466 | key: "${{ secrets.REPO_KEY }}" 467 | debug: false 468 | script: | 469 | set -e 470 | export BASEDIR="/srv/repository/main/server/windows" 471 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}/*.exe 472 | 473 | MacOS: 474 | runs-on: ubuntu-24.04 475 | strategy: 476 | fail-fast: false 477 | matrix: 478 | arch: 479 | - amd64 480 | - arm64 481 | continue-on-error: false # true in prod, false for testing 482 | steps: 483 | - name: "Set dated version for unstable builds" 484 | id: version 485 | run: |- 486 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 487 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 488 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 489 | 490 | - name: "Install dependencies" 491 | run: |- 492 | sudo apt-get update 493 | sudo apt-get install --yes python3-git python3-yaml 494 | 495 | - name: "Checkout repository" 496 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 497 | 498 | - name: "Prepare repository" 499 | run: |- 500 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 501 | 502 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 503 | run: |- 504 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} macos ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 505 | 506 | - name: "Upload artifacts to repository server" 507 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 508 | with: 509 | host: "${{ secrets.REPO_HOST }}" 510 | username: "${{ secrets.REPO_USER }}" 511 | key: "${{ secrets.REPO_KEY }}" 512 | source: "out/macos/*" 513 | strip_components: 2 514 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/macos/${{ matrix.arch }}" 515 | 516 | - name: "Move artifacts into repository" 517 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 518 | with: 519 | host: "${{ secrets.REPO_HOST }}" 520 | username: "${{ secrets.REPO_USER }}" 521 | key: "${{ secrets.REPO_KEY }}" 522 | debug: false 523 | script: | 524 | set -e 525 | export BASEDIR="/srv/repository/main/server/macos" 526 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 527 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/macos/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 528 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 529 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 530 | 531 | Portable: 532 | runs-on: ubuntu-24.04 533 | continue-on-error: false # true in prod, false for testing 534 | steps: 535 | - name: "Set dated version for unstable builds" 536 | id: version 537 | run: |- 538 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 539 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 540 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 541 | 542 | - name: "Install dependencies" 543 | run: |- 544 | sudo apt-get update 545 | sudo apt-get install --yes python3-git python3-yaml 546 | 547 | - name: "Checkout repository" 548 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 549 | 550 | - name: "Prepare repository" 551 | run: |- 552 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 553 | 554 | - name: "Run builder for Portable" 555 | run: |- 556 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} portable ${{ env.DEBUG_FLAG }} 557 | 558 | - name: "Upload artifacts to repository server" 559 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 560 | with: 561 | host: "${{ secrets.REPO_HOST }}" 562 | username: "${{ secrets.REPO_USER }}" 563 | key: "${{ secrets.REPO_KEY }}" 564 | source: "out/portable/*" 565 | strip_components: 2 566 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/portable" 567 | 568 | - name: "Move artifacts into repository" 569 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 570 | with: 571 | host: "${{ secrets.REPO_HOST }}" 572 | username: "${{ secrets.REPO_USER }}" 573 | key: "${{ secrets.REPO_KEY }}" 574 | debug: false 575 | script: | 576 | set -e 577 | export BASEDIR="/srv/repository/main/server/portable" 578 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/any || exit 1 579 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/any/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/portable/${{ matrix.release }}/* || exit 1 580 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 581 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 582 | 583 | Nuget: 584 | runs-on: ubuntu-24.04 585 | steps: 586 | - name: "Set dated version for unstable builds" 587 | id: version 588 | run: |- 589 | echo "JELLYFIN_VERSION=${{ inputs.version }}-rc${{ inputs.preview_id }}" >> $GITHUB_ENV 590 | echo "JELLYFIN_RELEASE_TYPE=preview" >> $GITHUB_ENV 591 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 592 | 593 | - name: "Install dependencies" 594 | run: |- 595 | sudo apt-get update 596 | sudo apt-get install --yes python3-git python3-yaml 597 | 598 | - name: "Setup .NET" 599 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 600 | with: 601 | dotnet-version: ${{ env.SDK_VERSION }} 602 | 603 | - name: "Checkout repository" 604 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 605 | 606 | - name: "Prepare repository" 607 | run: |- 608 | ./checkout.py ${{ inputs.version }}-rc${{ inputs.preview_id }} 609 | 610 | - name: "Run builder for Nuget" 611 | env: 612 | NUGET_STABLE_KEY: ${{ secrets.NUGET_STABLE_KEY }} 613 | NUGET_UNSTABLE_KEY: ${{ secrets.NUGET_UNSTABLE_KEY }} 614 | run: |- 615 | ./build.py ${{ env.JELLYFIN_VERSION }} nuget 616 | -------------------------------------------------------------------------------- /.github/workflows/release-build.yaml: -------------------------------------------------------------------------------- 1 | name: "Release Build" 2 | 3 | on: 4 | workflow_dispatch: 5 | # Manual trigger (from bot) 6 | inputs: 7 | version: 8 | required: true 9 | type: string 10 | description: 'The server and web stable release tag (i.e. "vX.Y.Z")' 11 | 12 | env: 13 | SDK_VERSION: "9.0.x" 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | Docker: 20 | runs-on: ubuntu-24.04 21 | continue-on-error: false # true in prod, false for testing 22 | steps: 23 | - name: "Set dated version for unstable builds" 24 | id: version 25 | run: |- 26 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 27 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 28 | 29 | - name: "Install dependencies" 30 | run: |- 31 | sudo apt-get update 32 | sudo apt-get install --yes python3-git python3-yaml 33 | 34 | - name: "Checkout repository" 35 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 36 | 37 | - name: "Prepare repository" 38 | run: |- 39 | ./checkout.py ${{ inputs.version || 'master' }} 40 | 41 | - name: "Run builder for Docker containers" 42 | env: 43 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 44 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 45 | GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }} 46 | GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} 47 | run: |- 48 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} docker ${{ env.DEBUG_FLAG }} 49 | 50 | Debian: 51 | runs-on: ubuntu-24.04 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | release: 56 | - bullseye 57 | - bookworm 58 | - trixie 59 | arch: 60 | - amd64 61 | - arm64 62 | continue-on-error: false # true in prod, false for testing 63 | steps: 64 | - name: "Set dated version for unstable builds" 65 | id: version 66 | run: |- 67 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 68 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 69 | 70 | - name: "Install dependencies" 71 | run: |- 72 | sudo apt-get update 73 | sudo apt-get install --yes python3-git python3-yaml debsigs devscripts 74 | 75 | - name: "Checkout repository" 76 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 77 | 78 | - name: "Prepare repository" 79 | run: |- 80 | ./checkout.py ${{ inputs.version || 'master' }} 81 | 82 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 83 | run: |- 84 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} debian ${{ matrix.arch }} ${{ matrix.release }} ${{ env.DEBUG_FLAG }} 85 | sudo chown --recursive $USER out/debian 86 | 87 | - name: "Import repository signing GPG key" 88 | run: | 89 | echo -n "${{ secrets.DEBIAN_SIGNING_KEY }}" | base64 --decode | gpg --batch --yes --import 90 | 91 | - name: "Sign Debian package and source files" 92 | run: | 93 | for file in out/debian/*.deb; do 94 | debsigs --sign=origin --default-key=${{ secrets.DEBIAN_SIGNING_KEY_ID }} ${file} 95 | done 96 | debsign -k ${{ secrets.DEBIAN_SIGNING_KEY_ID }} out/debian/*.changes 97 | 98 | - name: "Remove repository signing GPG key" 99 | run: | 100 | gpg --batch --yes --delete-secret-keys ${{ secrets.DEBIAN_SIGNING_KEY_ID }} 101 | 102 | - name: "Upload artifacts to repository server" 103 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 104 | with: 105 | host: "${{ secrets.REPO_HOST }}" 106 | username: "${{ secrets.REPO_USER }}" 107 | key: "${{ secrets.REPO_KEY }}" 108 | source: "out/debian/*" 109 | strip_components: 2 110 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}" 111 | 112 | - name: "Import artifacts into reprepro" 113 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 114 | with: 115 | host: "${{ secrets.REPO_HOST }}" 116 | username: "${{ secrets.REPO_USER }}" 117 | key: "${{ secrets.REPO_KEY }}" 118 | debug: false 119 | script: | 120 | set -o xtrace 121 | COMPONENT="main" 122 | # Only include the architecture-dependent deb here, as the others are done only for amd64 123 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/*_${{ matrix.arch }}.deb || exit 1 124 | if [[ ${{ matrix.arch }} == "amd64" ]]; then 125 | # Only include the architecture-independent packages for amd64; the other architectures are the same and conflict 126 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/*_all.deb || exit 1 127 | # Only include the source DSC for amd64; the other architectures are the same and conflict 128 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} includedsc ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/*.dsc || exit 1 129 | fi 130 | 131 | # Only keep the latest point release for a given stable version; i.e. 10.10.14 obsoletes 10.10.13 and removes it, while leaving 10.9.10 132 | NEW_VERSION=${{ matrix.release }} 133 | NEW_MAJOR_VERSION="${NEW_VERSION#v}" 134 | NEW_MAJOR_VERSION="${NEW_MAJOR_VERSION%.*}" 135 | NEW_POINT_VERSION="${NEW_VERSION##*.}" 136 | if [[ ${NEW_POINT_VERSION} -gt 0 ]]; then 137 | LAST_POINT_VERSION=$(( NEW_POINT_VERSION - 1 )) 138 | LAST_VERSION="${NEW_MAJOR_VERSION}.${LAST_POINT_VERSION}" 139 | for PACKAGE in jellyfin jellyfin-server jellyfin-web; do 140 | for SCRAP_VERSION in $( sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} list ${{ matrix.release }} | grep "${PACKAGE} " | grep "${LAST_VERSION}" | sort | uniq ); do 141 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} remove ${{ matrix.release }} ${PACKAGE}=${SCRAP_VERSION} 142 | done 143 | done 144 | fi 145 | 146 | - name: "Move artifacts into repository" 147 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 148 | with: 149 | host: "${{ secrets.REPO_HOST }}" 150 | username: "${{ secrets.REPO_USER }}" 151 | key: "${{ secrets.REPO_KEY }}" 152 | debug: false 153 | script: | 154 | export BASEDIR="/srv/repository/main/server/debian" 155 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 156 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 157 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 158 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 159 | sudo rm ${BASEDIR}/latest || true 160 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest || exit 1 161 | 162 | Ubuntu: 163 | runs-on: ubuntu-24.04 164 | strategy: 165 | fail-fast: false 166 | matrix: 167 | release: 168 | - focal 169 | - jammy 170 | - noble 171 | arch: 172 | - amd64 173 | - arm64 174 | continue-on-error: false # true in prod, false for testing 175 | steps: 176 | - name: "Set dated version for unstable builds" 177 | id: version 178 | run: |- 179 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 180 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 181 | 182 | - name: "Install dependencies" 183 | run: |- 184 | sudo apt-get update 185 | sudo apt-get install --yes python3-git python3-yaml debsigs devscripts 186 | 187 | - name: "Checkout repository" 188 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 189 | 190 | - name: "Prepare repository" 191 | run: |- 192 | ./checkout.py ${{ inputs.version || 'master' }} 193 | 194 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 195 | run: |- 196 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} ubuntu ${{ matrix.arch }} ${{ matrix.release }} ${{ env.DEBUG_FLAG }} 197 | sudo chown --recursive $USER out/ubuntu 198 | 199 | - name: "Import repository signing GPG key" 200 | run: | 201 | echo -n "${{ secrets.DEBIAN_SIGNING_KEY }}" | base64 --decode | gpg --batch --yes --import 202 | 203 | - name: "Sign Ubuntu package and source files" 204 | run: | 205 | for file in out/ubuntu/*.deb; do 206 | debsigs --sign=origin --default-key=${{ secrets.DEBIAN_SIGNING_KEY_ID }} ${file} 207 | done 208 | debsign -k ${{ secrets.DEBIAN_SIGNING_KEY_ID }} out/ubuntu/*.changes 209 | 210 | - name: "Remove repository signing GPG key" 211 | run: | 212 | gpg --batch --yes --delete-secret-keys ${{ secrets.DEBIAN_SIGNING_KEY_ID }} 213 | 214 | - name: "Upload artifacts to repository server" 215 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 216 | with: 217 | host: "${{ secrets.REPO_HOST }}" 218 | username: "${{ secrets.REPO_USER }}" 219 | key: "${{ secrets.REPO_KEY }}" 220 | source: "out/ubuntu/*" 221 | strip_components: 2 222 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}" 223 | 224 | - name: "Import artifacts into reprepro" 225 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 226 | with: 227 | host: "${{ secrets.REPO_HOST }}" 228 | username: "${{ secrets.REPO_USER }}" 229 | key: "${{ secrets.REPO_KEY }}" 230 | debug: false 231 | script: | 232 | set -o xtrace 233 | COMPONENT="main" 234 | # Only include the architecture-dependent deb here, as the others are done only for amd64 235 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/*_${{ matrix.arch }}.deb || exit 1 236 | if [[ ${{ matrix.arch }} == "amd64" ]]; then 237 | # Only include the architecture-independent packages for amd64; the other architectures are the same and conflict 238 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/*_all.deb || exit 1 239 | # Only include the source DSC for amd64; the other architectures are the same and conflict 240 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} includedsc ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/*.dsc || exit 1 241 | fi 242 | 243 | # Only keep the latest point release for a given stable version; i.e. 10.10.14 obsoletes 10.10.13 and removes it, while leaving 10.9.10 244 | NEW_VERSION=${{ matrix.release }} 245 | NEW_MAJOR_VERSION="${NEW_VERSION#v}" 246 | NEW_MAJOR_VERSION="${NEW_MAJOR_VERSION%.*}" 247 | NEW_POINT_VERSION="${NEW_VERSION##*.}" 248 | if [[ ${NEW_POINT_VERSION} -gt 0 ]]; then 249 | LAST_POINT_VERSION=$(( NEW_POINT_VERSION - 1 )) 250 | LAST_VERSION="${NEW_MAJOR_VERSION}.${LAST_POINT_VERSION}" 251 | for PACKAGE in jellyfin jellyfin-server jellyfin-web; do 252 | for SCRAP_VERSION in $( sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} list ${{ matrix.release }} | grep "${PACKAGE} " | grep "${LAST_VERSION}" | sort | uniq ); do 253 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} remove ${{ matrix.release }} ${PACKAGE}=${SCRAP_VERSION} 254 | done 255 | done 256 | fi 257 | 258 | - name: "Move artifacts into repository" 259 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 260 | with: 261 | host: "${{ secrets.REPO_HOST }}" 262 | username: "${{ secrets.REPO_USER }}" 263 | key: "${{ secrets.REPO_KEY }}" 264 | debug: false 265 | script: | 266 | export BASEDIR="/srv/repository/main/server/ubuntu" 267 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 268 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 269 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 270 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 271 | sudo rm ${BASEDIR}/latest || true 272 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest || exit 1 273 | 274 | Linux: 275 | runs-on: ubuntu-24.04 276 | strategy: 277 | fail-fast: false 278 | matrix: 279 | arch: 280 | - amd64 281 | - amd64-musl 282 | - arm64 283 | - arm64-musl 284 | continue-on-error: false # true in prod, false for testing 285 | steps: 286 | - name: "Set dated version for unstable builds" 287 | id: version 288 | run: |- 289 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 290 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 291 | 292 | - name: "Install dependencies" 293 | run: |- 294 | sudo apt-get update 295 | sudo apt-get install --yes python3-git python3-yaml 296 | 297 | - name: "Checkout repository" 298 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 299 | 300 | - name: "Prepare repository" 301 | run: |- 302 | ./checkout.py ${{ inputs.version || 'master' }} 303 | 304 | - name: "Run builder for ${{ matrix.arch }}" 305 | run: |- 306 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} linux ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 307 | 308 | - name: "Upload artifacts to repository server" 309 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 310 | with: 311 | host: "${{ secrets.REPO_HOST }}" 312 | username: "${{ secrets.REPO_USER }}" 313 | key: "${{ secrets.REPO_KEY }}" 314 | source: "out/linux/*" 315 | strip_components: 2 316 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/linux/${{ matrix.arch }}" 317 | 318 | - name: "Move artifacts into repository" 319 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 320 | with: 321 | host: "${{ secrets.REPO_HOST }}" 322 | username: "${{ secrets.REPO_USER }}" 323 | key: "${{ secrets.REPO_KEY }}" 324 | debug: false 325 | script: | 326 | set -e 327 | export BASEDIR="/srv/repository/main/server/linux" 328 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 329 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/linux/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 330 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 331 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 332 | sudo rm ${BASEDIR}/latest || true 333 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest || exit 1 334 | 335 | Windows: 336 | runs-on: ubuntu-24.04 337 | strategy: 338 | fail-fast: false 339 | matrix: 340 | arch: 341 | - amd64 342 | - arm64 343 | outputs: 344 | JELLYFIN_VERSION: ${{ steps.version.outputs.JELLYFIN_VERSION }} 345 | JELLYFIN_RELEASE_TYPE: ${{ steps.version.outputs.JELLYFIN_RELEASE_TYPE }} 346 | continue-on-error: false # true in prod, false for testing 347 | steps: 348 | - name: "Set dated version for unstable builds" 349 | id: version 350 | run: |- 351 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 352 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_OUTPUT 353 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 354 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_OUTPUT 355 | 356 | - name: "Install dependencies" 357 | run: |- 358 | sudo apt-get update 359 | sudo apt-get install --yes python3-git python3-yaml 360 | 361 | - name: "Checkout repository" 362 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 363 | 364 | - name: "Prepare repository" 365 | run: |- 366 | ./checkout.py ${{ inputs.version || 'master' }} 367 | 368 | - name: "Run builder for ${{ matrix.arch }}" 369 | run: |- 370 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} windows ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 371 | 372 | - name: "Upload artifacts to repository server" 373 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 374 | with: 375 | host: "${{ secrets.REPO_HOST }}" 376 | username: "${{ secrets.REPO_USER }}" 377 | key: "${{ secrets.REPO_KEY }}" 378 | source: "out/windows/*" 379 | strip_components: 2 380 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 381 | 382 | - name: "Move artifacts into repository" 383 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 384 | with: 385 | host: "${{ secrets.REPO_HOST }}" 386 | username: "${{ secrets.REPO_USER }}" 387 | key: "${{ secrets.REPO_KEY }}" 388 | debug: false 389 | script: | 390 | set -e 391 | export BASEDIR="/srv/repository/main/server/windows" 392 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 393 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 394 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 395 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 396 | sudo rm ${BASEDIR}/latest || true 397 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest || exit 1 398 | 399 | - name: "Store artifacts for next stage" 400 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 401 | with: 402 | name: windows-artifacts 403 | retention-days: 1 404 | if-no-files-found: ignore # Ignore for arm64 build, as no file will be found 405 | path: out/windows/jellyfin_*-amd64.zip 406 | 407 | WindowsInstaller: 408 | needs: 409 | - Windows 410 | runs-on: windows-latest 411 | strategy: 412 | fail-fast: false 413 | matrix: 414 | arch: 415 | - amd64 416 | continue-on-error: false # true in prod, false for testing 417 | env: 418 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 419 | steps: 420 | - name: "Set dated version for unstable builds" 421 | id: version 422 | shell: pwsh 423 | run: | 424 | $version = "${{ needs.Windows.outputs.JELLYFIN_VERSION }}" 425 | $cleanVersion = $version.Substring(1) 426 | echo "JELLYFIN_VERSION=$cleanVersion" | Out-File -FilePath $env:GITHUB_ENV -Append 427 | 428 | - name: "Checkout repository" 429 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 430 | 431 | - name: "Install dependencies" 432 | run: |- 433 | python -m pip install --upgrade gitpython 434 | 435 | - name: "Install NSIS" 436 | uses: negrutiu/nsis-install@f3339c88dba6fd08910d5275a943f8f746d94876 # v2 437 | with: 438 | distro: official 439 | 440 | - name: "Prepare repository" 441 | run: |- 442 | python checkout.py ${{ inputs.version || 'master' }} 443 | 444 | - name: "Fetch artifacts from previous stage" 445 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 446 | with: 447 | name: windows-artifacts 448 | path: ./jellyfin-server-windows 449 | 450 | - name: "Clone UX repository" 451 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 452 | with: 453 | repository: jellyfin/jellyfin-ux 454 | path: .\jellyfin-server-windows\jellyfin-ux 455 | 456 | - name: "Extract Jellyfin server archive" 457 | working-directory: ./jellyfin-server-windows 458 | run: | 459 | New-Item -ItemType Directory -Path jellyfin 460 | Expand-Archive -Path 'jellyfin_*-amd64.zip' -DestinationPath jellyfin -Force 461 | Copy-Item ".\Support Files\LICENSE" -Destination $(Resolve-Path .\jellyfin\jellyfin) 462 | 463 | - name: "Add NSSM" 464 | working-directory: ./jellyfin-server-windows 465 | run: | 466 | Invoke-WebRequest 'https://repo.jellyfin.org/files/other/nssm.zip' -OutFile 'nssm.zip' 467 | Expand-Archive 'nssm.zip' 468 | Copy-Item ".\nssm\nssm.exe" -Destination $(Resolve-Path .\jellyfin\jellyfin) 469 | 470 | - name: "Publish tray" 471 | working-directory: ./jellyfin-server-windows 472 | run: | 473 | New-Item -Path .\jellyfin\jellyfin\jellyfin-windows-tray -ItemType Directory 474 | dotnet publish -c Release -r win-x64 -f net472 --no-self-contained --output $(Resolve-Path .\jellyfin\jellyfin\jellyfin-windows-tray) 475 | 476 | - name: "Build installer" 477 | working-directory: ./jellyfin-server-windows 478 | run: | 479 | $env:InstallLocation = $(Resolve-Path .\jellyfin\jellyfin) 480 | makensis /Dx64 /DUXPATH=$(Resolve-Path .\jellyfin-ux) $(Join-Path -Path $(Resolve-Path .\nsis) -ChildPath jellyfin.nsi) 481 | 482 | - name: "Rename installer" 483 | working-directory: ./jellyfin-server-windows/nsis 484 | run: | 485 | Rename-Item -Path .\jellyfin_*_windows-x64.exe -NewName ("jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe") 486 | 487 | - name: "Store artifacts for next stage" 488 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 489 | with: 490 | name: windows-installer-artifacts 491 | retention-days: 1 492 | if-no-files-found: error 493 | path: ./jellyfin-server-windows/nsis/jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe 494 | 495 | WindowsInstallerUpload: 496 | needs: 497 | - Windows 498 | - WindowsInstaller 499 | runs-on: ubuntu-24.04 500 | strategy: 501 | fail-fast: false 502 | matrix: 503 | arch: 504 | - amd64 505 | continue-on-error: false # true in prod, false for testing 506 | steps: 507 | - name: "Set version from previous job" 508 | id: version 509 | run: |- 510 | echo "JELLYFIN_VERSION=${{ needs.Windows.outputs.JELLYFIN_VERSION }}" >> $GITHUB_ENV 511 | echo "JELLYFIN_RELEASE_TYPE=${{ needs.Windows.outputs.JELLYFIN_RELEASE_TYPE }}" >> $GITHUB_ENV 512 | 513 | - name: "Fetch artifact from previous stage" 514 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 515 | with: 516 | name: windows-installer-artifacts 517 | 518 | - name: "Upload artifacts to repository server" 519 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 520 | with: 521 | host: "${{ secrets.REPO_HOST }}" 522 | username: "${{ secrets.REPO_USER }}" 523 | key: "${{ secrets.REPO_KEY }}" 524 | source: "*.exe" 525 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 526 | 527 | - name: "Move artifacts into repository" 528 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 529 | with: 530 | host: "${{ secrets.REPO_HOST }}" 531 | username: "${{ secrets.REPO_USER }}" 532 | key: "${{ secrets.REPO_KEY }}" 533 | debug: false 534 | script: | 535 | set -e 536 | export BASEDIR="/srv/repository/main/server/windows" 537 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}/*.exe 538 | 539 | MacOS: 540 | runs-on: ubuntu-24.04 541 | strategy: 542 | fail-fast: false 543 | matrix: 544 | arch: 545 | - amd64 546 | - arm64 547 | continue-on-error: false # true in prod, false for testing 548 | steps: 549 | - name: "Set dated version for unstable builds" 550 | id: version 551 | run: |- 552 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 553 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 554 | 555 | - name: "Install dependencies" 556 | run: |- 557 | sudo apt-get update 558 | sudo apt-get install --yes python3-git python3-yaml 559 | 560 | - name: "Checkout repository" 561 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 562 | 563 | - name: "Prepare repository" 564 | run: |- 565 | ./checkout.py ${{ inputs.version || 'master' }} 566 | 567 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 568 | run: |- 569 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} macos ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 570 | 571 | - name: "Upload artifacts to repository server" 572 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 573 | with: 574 | host: "${{ secrets.REPO_HOST }}" 575 | username: "${{ secrets.REPO_USER }}" 576 | key: "${{ secrets.REPO_KEY }}" 577 | source: "out/macos/*" 578 | strip_components: 2 579 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/macos/${{ matrix.arch }}" 580 | 581 | - name: "Move artifacts into repository" 582 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 583 | with: 584 | host: "${{ secrets.REPO_HOST }}" 585 | username: "${{ secrets.REPO_USER }}" 586 | key: "${{ secrets.REPO_KEY }}" 587 | debug: false 588 | script: | 589 | set -e 590 | export BASEDIR="/srv/repository/main/server/macos" 591 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 592 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/macos/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 593 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 594 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 595 | sudo rm ${BASEDIR}/latest || true 596 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest || exit 1 597 | 598 | Portable: 599 | runs-on: ubuntu-24.04 600 | continue-on-error: false # true in prod, false for testing 601 | steps: 602 | - name: "Set dated version for unstable builds" 603 | id: version 604 | run: |- 605 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 606 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 607 | 608 | - name: "Install dependencies" 609 | run: |- 610 | sudo apt-get update 611 | sudo apt-get install --yes python3-git python3-yaml 612 | 613 | - name: "Checkout repository" 614 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 615 | 616 | - name: "Prepare repository" 617 | run: |- 618 | ./checkout.py ${{ inputs.version || 'master' }} 619 | 620 | - name: "Run builder for Portable" 621 | run: |- 622 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} portable ${{ env.DEBUG_FLAG }} 623 | 624 | - name: "Upload artifacts to repository server" 625 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 626 | with: 627 | host: "${{ secrets.REPO_HOST }}" 628 | username: "${{ secrets.REPO_USER }}" 629 | key: "${{ secrets.REPO_KEY }}" 630 | source: "out/portable/*" 631 | strip_components: 2 632 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/portable" 633 | 634 | - name: "Move artifacts into repository" 635 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 636 | with: 637 | host: "${{ secrets.REPO_HOST }}" 638 | username: "${{ secrets.REPO_USER }}" 639 | key: "${{ secrets.REPO_KEY }}" 640 | debug: false 641 | script: | 642 | set -e 643 | export BASEDIR="/srv/repository/main/server/portable" 644 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/any || exit 1 645 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/any/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/portable/${{ matrix.release }}/* || exit 1 646 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 647 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 648 | sudo rm ${BASEDIR}/latest || true 649 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest || exit 1 650 | 651 | Nuget: 652 | runs-on: ubuntu-24.04 653 | steps: 654 | - name: "Set dated version for unstable builds" 655 | id: version 656 | run: |- 657 | echo "JELLYFIN_VERSION=${{ inputs.version }}" >> $GITHUB_ENV 658 | echo "JELLYFIN_RELEASE_TYPE=stable" >> $GITHUB_ENV 659 | 660 | - name: "Install dependencies" 661 | run: |- 662 | sudo apt-get update 663 | sudo apt-get install --yes python3-git python3-yaml 664 | 665 | - name: "Setup .NET" 666 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 667 | with: 668 | dotnet-version: ${{ env.SDK_VERSION }} 669 | 670 | - name: "Checkout repository" 671 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 672 | 673 | - name: "Prepare repository" 674 | run: |- 675 | ./checkout.py ${{ inputs.version || 'master' }} 676 | 677 | - name: "Run builder for Nuget" 678 | env: 679 | NUGET_STABLE_KEY: ${{ secrets.NUGET_STABLE_KEY }} 680 | NUGET_UNSTABLE_KEY: ${{ secrets.NUGET_UNSTABLE_KEY }} 681 | run: |- 682 | ./build.py ${{ env.JELLYFIN_VERSION }} nuget 683 | -------------------------------------------------------------------------------- /.github/workflows/unstable-build.yaml: -------------------------------------------------------------------------------- 1 | name: "Unstable Build" 2 | 3 | on: 4 | schedule: 5 | # Weekly unstable trigger on Monday at 05:00 GMT 6 | - cron: '0 5 * * 1' 7 | workflow_dispatch: 8 | # Manual trigger 9 | 10 | env: 11 | SDK_VERSION: "9.0.x" 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | Docker: 18 | runs-on: ubuntu-24.04 19 | continue-on-error: false # true in prod, false for testing 20 | steps: 21 | - name: "Set dated version for unstable builds" 22 | id: version 23 | run: |- 24 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 25 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 26 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 27 | 28 | - name: "Install dependencies" 29 | run: |- 30 | sudo apt-get update 31 | sudo apt-get install --yes python3-git python3-yaml 32 | 33 | - name: "Checkout repository" 34 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 35 | 36 | - name: "Prepare repository" 37 | run: |- 38 | ./checkout.py master 39 | 40 | - name: "Run builder for Docker containers" 41 | env: 42 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 43 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 44 | GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }} 45 | GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} 46 | run: |- 47 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} docker ${{ env.DEBUG_FLAG }} 48 | 49 | Debian: 50 | runs-on: ubuntu-24.04 51 | strategy: 52 | fail-fast: false 53 | matrix: 54 | release: 55 | - bullseye 56 | - bookworm 57 | - trixie 58 | arch: 59 | - amd64 60 | - arm64 61 | continue-on-error: false # true in prod, false for testing 62 | steps: 63 | - name: "Set dated version for unstable builds" 64 | id: version 65 | run: |- 66 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 67 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 68 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 69 | 70 | - name: "Install dependencies" 71 | run: |- 72 | sudo apt-get update 73 | sudo apt-get install --yes python3-git python3-yaml debsigs devscripts 74 | 75 | - name: "Checkout repository" 76 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 77 | 78 | - name: "Prepare repository" 79 | run: |- 80 | ./checkout.py master 81 | 82 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 83 | run: |- 84 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} debian ${{ matrix.arch }} ${{ matrix.release }} ${{ env.DEBUG_FLAG }} 85 | sudo chown --recursive $USER out/debian 86 | 87 | - name: "Import repository signing GPG key" 88 | run: | 89 | echo -n "${{ secrets.DEBIAN_SIGNING_KEY }}" | base64 --decode | gpg --batch --yes --import 90 | 91 | - name: "Sign Debian package and source files" 92 | run: | 93 | for file in out/debian/*.deb; do 94 | debsigs --sign=origin --default-key=${{ secrets.DEBIAN_SIGNING_KEY_ID }} ${file} 95 | done 96 | debsign -k ${{ secrets.DEBIAN_SIGNING_KEY_ID }} out/debian/*.changes 97 | 98 | - name: "Remove repository signing GPG key" 99 | run: | 100 | gpg --batch --yes --delete-secret-keys ${{ secrets.DEBIAN_SIGNING_KEY_ID }} 101 | 102 | - name: "Upload artifacts to repository server" 103 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 104 | with: 105 | host: "${{ secrets.REPO_HOST }}" 106 | username: "${{ secrets.REPO_USER }}" 107 | key: "${{ secrets.REPO_KEY }}" 108 | source: "out/debian/*" 109 | strip_components: 2 110 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}" 111 | 112 | - name: "Import artifacts into reprepro" 113 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 114 | with: 115 | host: "${{ secrets.REPO_HOST }}" 116 | username: "${{ secrets.REPO_USER }}" 117 | key: "${{ secrets.REPO_KEY }}" 118 | debug: false 119 | script: | 120 | set -o xtrace 121 | COMPONENT="unstable" 122 | # Only include the architecture-dependent deb here, as the others are done only for amd64 123 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/*_${{ matrix.arch }}.deb || exit 1 124 | if [[ ${{ matrix.arch }} == "amd64" ]]; then 125 | # Only include the architecture-independent packages for amd64; the other architectures are the same and conflict 126 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/*_all.deb || exit 1 127 | # Only include the source DSC for amd64; the other architectures are the same and conflict 128 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} includedsc ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/*.dsc || exit 1 129 | fi 130 | 131 | # Only keep ${NKEEP} (3) unstables; we have to do this manually since reprepro doesn't have granularity for this at the component level 132 | NKEEP=3 133 | # Get whatever the oldest version is; since these are dated, a simple numerical sort works 134 | LIST="$( sudo reprepro -b /srv/debian -C unstable list ${{ matrix.release }} | awk '{ print $NF }' | sort -rn | uniq )" 135 | COUNT=$( wc -l <<<"${LIST}" ) 136 | if [[ ${COUNT} -gt ${NKEEP} ]]; then 137 | # Get anything that isn't in the latest ${NKEEP} entries 138 | OBSOLETE=( $( sed "1,${NKEEP}d" <<<"${LIST}" ) ) 139 | for VERSION in ${OBSOLETE[@]}; do 140 | for PACKAGE in jellyfin jellyfin-server jellyfin-web; do 141 | for SCRAP_VERSION in $( sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} list ${{ matrix.release }} | grep "${PACKAGE} " | grep "${VERSION}" | sort | uniq ); do 142 | sudo reprepro --waitforlock 30 --basedir /srv/debian --component ${COMPONENT} remove ${{ matrix.release }} ${PACKAGE}=${SCRAP_VERSION} 143 | done 144 | done 145 | done 146 | fi 147 | 148 | - name: "Move artifacts into repository" 149 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 150 | with: 151 | host: "${{ secrets.REPO_HOST }}" 152 | username: "${{ secrets.REPO_USER }}" 153 | key: "${{ secrets.REPO_KEY }}" 154 | debug: false 155 | script: | 156 | export BASEDIR="/srv/repository/main/server/debian" 157 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 158 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/debian/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 159 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 160 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 161 | 162 | Ubuntu: 163 | runs-on: ubuntu-24.04 164 | strategy: 165 | fail-fast: false 166 | matrix: 167 | release: 168 | - focal 169 | - jammy 170 | - noble 171 | arch: 172 | - amd64 173 | - arm64 174 | continue-on-error: false # true in prod, false for testing 175 | steps: 176 | - name: "Set dated version for unstable builds" 177 | id: version 178 | run: |- 179 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 180 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 181 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 182 | 183 | - name: "Install dependencies" 184 | run: |- 185 | sudo apt-get update 186 | sudo apt-get install --yes python3-git python3-yaml debsigs devscripts 187 | 188 | - name: "Checkout repository" 189 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 190 | 191 | - name: "Prepare repository" 192 | run: |- 193 | ./checkout.py master 194 | 195 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 196 | run: |- 197 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} ubuntu ${{ matrix.arch }} ${{ matrix.release }} ${{ env.DEBUG_FLAG }} 198 | sudo chown --recursive $USER out/ubuntu 199 | 200 | - name: "Import repository signing GPG key" 201 | run: | 202 | echo -n "${{ secrets.DEBIAN_SIGNING_KEY }}" | base64 --decode | gpg --batch --yes --import 203 | 204 | - name: "Sign Ubuntu package and source files" 205 | run: | 206 | for file in out/ubuntu/*.deb; do 207 | debsigs --sign=origin --default-key=${{ secrets.DEBIAN_SIGNING_KEY_ID }} ${file} 208 | done 209 | debsign -k ${{ secrets.DEBIAN_SIGNING_KEY_ID }} out/ubuntu/*.changes 210 | 211 | - name: "Remove repository signing GPG key" 212 | run: | 213 | gpg --batch --yes --delete-secret-keys ${{ secrets.DEBIAN_SIGNING_KEY_ID }} 214 | 215 | - name: "Upload artifacts to repository server" 216 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 217 | with: 218 | host: "${{ secrets.REPO_HOST }}" 219 | username: "${{ secrets.REPO_USER }}" 220 | key: "${{ secrets.REPO_KEY }}" 221 | source: "out/ubuntu/*" 222 | strip_components: 2 223 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}" 224 | 225 | - name: "Import artifacts into reprepro" 226 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 227 | with: 228 | host: "${{ secrets.REPO_HOST }}" 229 | username: "${{ secrets.REPO_USER }}" 230 | key: "${{ secrets.REPO_KEY }}" 231 | debug: false 232 | script: | 233 | set -o xtrace 234 | COMPONENT="unstable" 235 | # Only include the architecture-dependent deb here, as the others are done only for amd64 236 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/*_${{ matrix.arch }}.deb || exit 1 237 | if [[ ${{ matrix.arch }} == "amd64" ]]; then 238 | # Only include the architecture-independent packages for amd64; the other architectures are the same and conflict 239 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} includedeb ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/*_all.deb || exit 1 240 | # Only include the source DSC for amd64; the other architectures are the same and conflict 241 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} includedsc ${{ matrix.release }} /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/*.dsc || exit 1 242 | fi 243 | 244 | # Only keep ${NKEEP} (4) unstables; we have to do this manually since reprepro doesn't have granularity for this at the component level 245 | NKEEP=3 246 | # Get whatever the oldest version is; since these are dated, a simple numerical sort works 247 | LIST="$( sudo reprepro -b /srv/ubuntu -C unstable list ${{ matrix.release }} | awk '{ print $NF }' | sort -rn | uniq )" 248 | COUNT=$( wc -l <<<"${LIST}" ) 249 | if [[ ${COUNT} -gt ${NKEEP} ]]; then 250 | # Get anything that isn't in the latest ${NKEEP} entries 251 | OBSOLETE=( $( sed "1,${NKEEP}d" <<<"${LIST}" ) ) 252 | for VERSION in ${OBSOLETE[@]}; do 253 | for PACKAGE in jellyfin jellyfin-server jellyfin-web; do 254 | for SCRAP_VERSION in $( sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} list ${{ matrix.release }} | grep "${PACKAGE} " | grep "${VERSION}" | sort | uniq ); do 255 | sudo reprepro --waitforlock 30 --basedir /srv/ubuntu --component ${COMPONENT} remove ${{ matrix.release }} ${PACKAGE}=${SCRAP_VERSION} 256 | done 257 | done 258 | done 259 | fi 260 | 261 | - name: "Move artifacts into repository" 262 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 263 | with: 264 | host: "${{ secrets.REPO_HOST }}" 265 | username: "${{ secrets.REPO_USER }}" 266 | key: "${{ secrets.REPO_KEY }}" 267 | debug: false 268 | script: | 269 | export BASEDIR="/srv/repository/main/server/ubuntu" 270 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 271 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/ubuntu/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 272 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 273 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 274 | if [[ ${{ env.JELLYFIN_RELEASE_TYPE }} == "stable" ]]; then 275 | sudo rm ${BASEDIR}/latest || true 276 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest || exit 1 277 | fi 278 | 279 | Linux: 280 | runs-on: ubuntu-24.04 281 | strategy: 282 | fail-fast: false 283 | matrix: 284 | arch: 285 | - amd64 286 | - amd64-musl 287 | - arm64 288 | - arm64-musl 289 | continue-on-error: false # true in prod, false for testing 290 | steps: 291 | - name: "Set dated version for unstable builds" 292 | id: version 293 | run: |- 294 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 295 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 296 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 297 | 298 | - name: "Install dependencies" 299 | run: |- 300 | sudo apt-get update 301 | sudo apt-get install --yes python3-git python3-yaml 302 | 303 | - name: "Checkout repository" 304 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 305 | 306 | - name: "Prepare repository" 307 | run: |- 308 | ./checkout.py ${{ inputs.version || 'master' }} 309 | 310 | - name: "Run builder for ${{ matrix.arch }}" 311 | run: |- 312 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} linux ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 313 | 314 | - name: "Upload artifacts to repository server" 315 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 316 | with: 317 | host: "${{ secrets.REPO_HOST }}" 318 | username: "${{ secrets.REPO_USER }}" 319 | key: "${{ secrets.REPO_KEY }}" 320 | source: "out/linux/*" 321 | strip_components: 2 322 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/linux/${{ matrix.arch }}" 323 | 324 | - name: "Move artifacts into repository" 325 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 326 | with: 327 | host: "${{ secrets.REPO_HOST }}" 328 | username: "${{ secrets.REPO_USER }}" 329 | key: "${{ secrets.REPO_KEY }}" 330 | debug: false 331 | script: | 332 | set -e 333 | export BASEDIR="/srv/repository/main/server/linux" 334 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 335 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/linux/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 336 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 337 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 338 | 339 | Windows: 340 | runs-on: ubuntu-24.04 341 | strategy: 342 | fail-fast: false 343 | matrix: 344 | arch: 345 | - amd64 346 | - arm64 347 | outputs: 348 | JELLYFIN_VERSION: ${{ steps.version.outputs.JELLYFIN_VERSION }} 349 | JELLYFIN_RELEASE_TYPE: ${{ steps.version.outputs.JELLYFIN_RELEASE_TYPE }} 350 | continue-on-error: false # true in prod, false for testing 351 | steps: 352 | - name: "Set dated version for unstable builds" 353 | id: version 354 | run: |- 355 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 356 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_OUTPUT 357 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 358 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_OUTPUT 359 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 360 | 361 | - name: "Install dependencies" 362 | run: |- 363 | sudo apt-get update 364 | sudo apt-get install --yes python3-git python3-yaml 365 | 366 | - name: "Checkout repository" 367 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 368 | 369 | - name: "Prepare repository" 370 | run: |- 371 | ./checkout.py ${{ inputs.version || 'master' }} 372 | 373 | - name: "Run builder for ${{ matrix.arch }}" 374 | run: |- 375 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} windows ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 376 | 377 | - name: "Upload artifacts to repository server" 378 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 379 | with: 380 | host: "${{ secrets.REPO_HOST }}" 381 | username: "${{ secrets.REPO_USER }}" 382 | key: "${{ secrets.REPO_KEY }}" 383 | source: "out/windows/*" 384 | strip_components: 2 385 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 386 | 387 | - name: "Move artifacts into repository" 388 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 389 | with: 390 | host: "${{ secrets.REPO_HOST }}" 391 | username: "${{ secrets.REPO_USER }}" 392 | key: "${{ secrets.REPO_KEY }}" 393 | debug: false 394 | script: | 395 | set -e 396 | export BASEDIR="/srv/repository/main/server/windows" 397 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 398 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 399 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 400 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 401 | 402 | - name: "Store artifacts for next stage" 403 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 404 | with: 405 | name: windows-artifacts 406 | retention-days: 1 407 | if-no-files-found: ignore # Ignore for arm64 build, as no file will be found 408 | path: out/windows/jellyfin_*-amd64.zip 409 | 410 | WindowsInstaller: 411 | needs: 412 | - Windows 413 | runs-on: windows-latest 414 | strategy: 415 | fail-fast: false 416 | matrix: 417 | arch: 418 | - amd64 419 | continue-on-error: false # true in prod, false for testing 420 | env: 421 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 422 | steps: 423 | - name: "Set dated version for unstable builds" 424 | id: version 425 | shell: pwsh 426 | run: | 427 | $version = "${{ needs.Windows.outputs.JELLYFIN_VERSION }}" 428 | $cleanVersion = $version 429 | echo "JELLYFIN_VERSION=$cleanVersion" | Out-File -FilePath $env:GITHUB_ENV -Append 430 | 431 | - name: "Checkout repository" 432 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 433 | 434 | - name: "Install dependencies" 435 | run: |- 436 | python -m pip install --upgrade gitpython 437 | 438 | - name: "Install NSIS" 439 | uses: negrutiu/nsis-install@f3339c88dba6fd08910d5275a943f8f746d94876 # v2 440 | with: 441 | distro: official 442 | 443 | - name: "Prepare repository" 444 | run: |- 445 | python checkout.py master 446 | 447 | - name: "Fetch artifacts from previous stage" 448 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 449 | with: 450 | name: windows-artifacts 451 | path: ./jellyfin-server-windows 452 | 453 | - name: "Clone UX repository" 454 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 455 | with: 456 | repository: jellyfin/jellyfin-ux 457 | path: .\jellyfin-server-windows\jellyfin-ux 458 | 459 | - name: "Extract Jellyfin server archive" 460 | working-directory: ./jellyfin-server-windows 461 | run: | 462 | New-Item -ItemType Directory -Path jellyfin 463 | Expand-Archive -Path 'jellyfin_*-amd64.zip' -DestinationPath jellyfin -Force 464 | Copy-Item ".\Support Files\LICENSE" -Destination $(Resolve-Path .\jellyfin\jellyfin) 465 | 466 | - name: "Add NSSM" 467 | working-directory: ./jellyfin-server-windows 468 | run: | 469 | Invoke-WebRequest 'https://repo.jellyfin.org/files/other/nssm.zip' -OutFile 'nssm.zip' 470 | Expand-Archive 'nssm.zip' 471 | Copy-Item ".\nssm\nssm.exe" -Destination $(Resolve-Path .\jellyfin\jellyfin) 472 | 473 | - name: "Publish tray" 474 | working-directory: ./jellyfin-server-windows 475 | run: | 476 | New-Item -Path .\jellyfin\jellyfin\jellyfin-windows-tray -ItemType Directory 477 | dotnet publish -c Release -r win-x64 -f net472 --no-self-contained --output $(Resolve-Path .\jellyfin\jellyfin\jellyfin-windows-tray) 478 | 479 | - name: "Build installer" 480 | working-directory: ./jellyfin-server-windows 481 | run: | 482 | $env:InstallLocation = $(Resolve-Path .\jellyfin\jellyfin) 483 | makensis /Dx64 /DUXPATH=$(Resolve-Path .\jellyfin-ux) $(Join-Path -Path $(Resolve-Path .\nsis) -ChildPath jellyfin.nsi) 484 | 485 | - name: "Rename installer" 486 | working-directory: ./jellyfin-server-windows/nsis 487 | run: | 488 | Rename-Item -Path .\jellyfin_*_windows-x64.exe -NewName ("jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe") 489 | 490 | - name: "Store artifacts for next stage" 491 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 492 | with: 493 | name: windows-installer-artifacts 494 | retention-days: 1 495 | if-no-files-found: error 496 | path: ./jellyfin-server-windows/nsis/jellyfin_${{ env.JELLYFIN_VERSION }}_windows-x64.exe 497 | 498 | WindowsInstallerUpload: 499 | needs: 500 | - Windows 501 | - WindowsInstaller 502 | runs-on: ubuntu-24.04 503 | strategy: 504 | fail-fast: false 505 | matrix: 506 | arch: 507 | - amd64 508 | continue-on-error: false # true in prod, false for testing 509 | steps: 510 | - name: "Set version from previous job" 511 | id: version 512 | run: |- 513 | echo "JELLYFIN_VERSION=${{ needs.Windows.outputs.JELLYFIN_VERSION }}" >> $GITHUB_ENV 514 | echo "JELLYFIN_RELEASE_TYPE=${{ needs.Windows.outputs.JELLYFIN_RELEASE_TYPE }}" >> $GITHUB_ENV 515 | 516 | - name: "Fetch artifact from previous stage" 517 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 518 | with: 519 | name: windows-installer-artifacts 520 | 521 | - name: "Upload artifacts to repository server" 522 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 523 | with: 524 | host: "${{ secrets.REPO_HOST }}" 525 | username: "${{ secrets.REPO_USER }}" 526 | key: "${{ secrets.REPO_KEY }}" 527 | source: "*.exe" 528 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}" 529 | 530 | - name: "Move artifacts into repository" 531 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 532 | with: 533 | host: "${{ secrets.REPO_HOST }}" 534 | username: "${{ secrets.REPO_USER }}" 535 | key: "${{ secrets.REPO_KEY }}" 536 | debug: false 537 | script: | 538 | set -e 539 | export BASEDIR="/srv/repository/main/server/windows" 540 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/windows/${{ matrix.arch }}/*.exe 541 | 542 | MacOS: 543 | runs-on: ubuntu-24.04 544 | strategy: 545 | fail-fast: false 546 | matrix: 547 | arch: 548 | - amd64 549 | - arm64 550 | continue-on-error: false # true in prod, false for testing 551 | steps: 552 | - name: "Set dated version for unstable builds" 553 | id: version 554 | run: |- 555 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 556 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 557 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 558 | 559 | - name: "Install dependencies" 560 | run: |- 561 | sudo apt-get update 562 | sudo apt-get install --yes python3-git python3-yaml 563 | 564 | - name: "Checkout repository" 565 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 566 | 567 | - name: "Prepare repository" 568 | run: |- 569 | ./checkout.py master 570 | 571 | - name: "Run builder for ${{ matrix.version }} ${{ matrix.arch }}" 572 | run: |- 573 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} macos ${{ matrix.arch }} ${{ env.DEBUG_FLAG }} 574 | 575 | - name: "Upload artifacts to repository server" 576 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 577 | with: 578 | host: "${{ secrets.REPO_HOST }}" 579 | username: "${{ secrets.REPO_USER }}" 580 | key: "${{ secrets.REPO_KEY }}" 581 | source: "out/macos/*" 582 | strip_components: 2 583 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/macos/${{ matrix.arch }}" 584 | 585 | - name: "Move artifacts into repository" 586 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 587 | with: 588 | host: "${{ secrets.REPO_HOST }}" 589 | username: "${{ secrets.REPO_USER }}" 590 | key: "${{ secrets.REPO_KEY }}" 591 | debug: false 592 | script: | 593 | set -e 594 | export BASEDIR="/srv/repository/main/server/macos" 595 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }} || exit 1 596 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/${{ matrix.arch }}/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/macos/${{ matrix.release }}/${{ matrix.arch }}/* || exit 1 597 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 598 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 599 | 600 | Portable: 601 | runs-on: ubuntu-24.04 602 | continue-on-error: false # true in prod, false for testing 603 | steps: 604 | - name: "Set dated version for unstable builds" 605 | id: version 606 | run: |- 607 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 608 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 609 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 610 | 611 | - name: "Install dependencies" 612 | run: |- 613 | sudo apt-get update 614 | sudo apt-get install --yes python3-git python3-yaml 615 | 616 | - name: "Checkout repository" 617 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 618 | 619 | - name: "Prepare repository" 620 | run: |- 621 | ./checkout.py ${{ inputs.version || 'master' }} 622 | 623 | - name: "Run builder for Portable" 624 | run: |- 625 | sudo --preserve-env ./build.py ${{ env.JELLYFIN_VERSION }} portable ${{ env.DEBUG_FLAG }} 626 | 627 | - name: "Upload artifacts to repository server" 628 | uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0 629 | with: 630 | host: "${{ secrets.REPO_HOST }}" 631 | username: "${{ secrets.REPO_USER }}" 632 | key: "${{ secrets.REPO_KEY }}" 633 | source: "out/portable/*" 634 | strip_components: 2 635 | target: "/srv/incoming/server/${{ env.JELLYFIN_VERSION }}/portable" 636 | 637 | - name: "Move artifacts into repository" 638 | uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2 639 | with: 640 | host: "${{ secrets.REPO_HOST }}" 641 | username: "${{ secrets.REPO_USER }}" 642 | key: "${{ secrets.REPO_KEY }}" 643 | debug: false 644 | script: | 645 | set -e 646 | export BASEDIR="/srv/repository/main/server/portable" 647 | sudo mkdir -p ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/any || exit 1 648 | sudo mv -t ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }}/any/ /srv/incoming/server/${{ env.JELLYFIN_VERSION }}/portable/${{ matrix.release }}/* || exit 1 649 | sudo rm ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || true 650 | sudo ln -sf ${BASEDIR}/${{ env.JELLYFIN_RELEASE_TYPE }}/${{ env.JELLYFIN_VERSION }} ${BASEDIR}/latest-${{ env.JELLYFIN_RELEASE_TYPE }} || exit 1 651 | 652 | Nuget: 653 | runs-on: ubuntu-24.04 654 | steps: 655 | - name: "Set dated version for unstable builds" 656 | id: version 657 | run: |- 658 | echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H')" >> $GITHUB_ENV 659 | echo "JELLYFIN_RELEASE_TYPE=unstable" >> $GITHUB_ENV 660 | echo "DEBUG_FLAG=--debug" >>$GITHUB_ENV 661 | 662 | - name: "Install dependencies" 663 | run: |- 664 | sudo apt-get update 665 | sudo apt-get install --yes python3-git python3-yaml 666 | 667 | - name: "Setup .NET" 668 | uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 669 | with: 670 | dotnet-version: ${{ env.SDK_VERSION }} 671 | 672 | - name: "Checkout repository" 673 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 674 | 675 | - name: "Prepare repository" 676 | run: |- 677 | ./checkout.py master 678 | 679 | - name: "Run builder for Nuget" 680 | env: 681 | NUGET_STABLE_KEY: ${{ secrets.NUGET_STABLE_KEY }} 682 | NUGET_UNSTABLE_KEY: ${{ secrets.NUGET_UNSTABLE_KEY }} 683 | run: |- 684 | ./build.py ${{ env.JELLYFIN_VERSION }} nuget 685 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore temporary build files 2 | build/ 3 | # Ignore the output artifact directory 4 | out/ 5 | # Ignore the generated Debian changelog 6 | debian/changelog 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jellyfin-server"] 2 | path = jellyfin-server 3 | url = https://github.com/jellyfin/jellyfin 4 | [submodule "jellyfin-web"] 5 | path = jellyfin-web 6 | url = https://github.com/jellyfin/jellyfin-web 7 | [submodule "jellyfin-server-windows"] 8 | path = jellyfin-server-windows 9 | url = https://github.com/jellyfin/jellyfin-server-windows.git 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Jellyfin Packaging

2 |

Part of Jellyfin: The Free Software Media System

3 | 4 | --- 5 | 6 |

7 | Logo Banner 8 |
9 |
10 | 11 | GPL 3.0 License 12 | 13 | 14 | Current Release 15 | 16 | 17 | Translation Status 18 | 19 | 20 | Docker Pull Count 21 | 22 |
23 | 24 | Donate 25 | 26 | 27 | Submit Feature Requests 28 | 29 | 30 | Chat on Matrix 31 | 32 | 33 | Join our Forum 34 | 35 | 36 | Release RSS Feed 37 | 38 | 39 | Master Commits RSS Feed 40 | 41 |

42 | 43 | --- 44 | 45 | Jellyfin is the Free Software Media System that puts you in control of managing and streaming your media. It is an alternative to the proprietary Emby and Plex, to provide media from a dedicated server to end-user devices via multiple apps. 46 | 47 | This repository contains operating system and Docker packaging for Jellyfin, for use by manual builders and our release CI system with GitHub workflows. All packaging has henceforth been removed from the main code repositories for the [Jellyfin Server](https://github.com/jellyfin/jellyfin) and [Primary WebUI](https://github.com/jellyfin/jellyfin-web) and moved here. 48 | 49 | ## Quickstart 50 | 51 | To build Jellyfin packages for yourself, follow this quickstart guide. You will need to be running on an amd64 Linux system, preferably Debian- or Ubuntu-based, with Docker, Python3 and the Python packages `PyYAML` and `git` (`python3-yaml` and `python3-git` in Debian). Other systems including WSL are untested. 52 | 53 | 1. Install Docker on your system. The build scripts leverage Docker containers to perform clean builds and avoid contaminating the host system with dependencies. 54 | 55 | 1. Clone this repository somewhere on your system and enter it. 56 | 57 | 1. Run `git submodule update --init` to check out the submodules (`jellyfin-server`, `jellyfin-web`). 58 | 59 | 1. Run `./checkout.py` to update the submodules to the correct `HEAD`s. This command takes one argument, the tag or branch (i.e. `master`) of the repositories to check out; if nothing is specified, `master` is assumed. For example, `./checkout.py master` checks out the current `master` branch of both `jellyfin-server` and `jellyfin-web`, `./checkout.py v10.8.13` checks out the `v10.8.13` tag of both, etc. If a tag is used and one (or more) of the repositories are missing the tag, this command will error out. 60 | 61 | ### Non-Docker Platforms 62 | 63 | If you want a non-Docker image output (`.deb`, `tar`/`zip` archive, etc.) follow this process: 64 | 65 | 1. Run `./build.py`. This command takes up to 4 arguments, depending on what you're trying to build: 66 | 67 | * The first argument is the version you want to tag your build as. For our official releases, we use either dates for unstable builds (`YYYYMMDDHH` numerical format or `auto` for autogeneration) or the tag without `v` for stable release builds (`10.8.13`, `10.9.0`, etc.), but you can use any version tag you wish here. 68 | 69 | * The second argument is the "platform" you want to build for. The available options are listed as top-level keys in the `build.yaml` configuration file or in the `-h` help output. 70 | 71 | * The third argument is, for all platforms except `portable` (DotNET portable), the architecture you want to build for. For each platform, the available architectures can be found as the keys under `archmaps` in the `build.yaml` configuration file. Cross-building of `arm64` on `amd64` is supported, but not the other way around. 72 | 73 | * The fourth argument is exclusive to `debian` and `ubuntu` `.deb` packages, and is the release codename of Debian or Ubuntu to build for. For each platform, the available releases can be found as the keys under `releases` in the `build.yaml` configuration file. 74 | 75 | **NOTE:** Your running user must have Docker privileges, or you should run `build.py` as root/with `sudo`. 76 | 77 | 1. The output binaries will be in the `out/` directory, ready for use. The exact format varies depending on the build type, and can be found, for each archive-based platform, as the values to the `archivetypes` key in the `build.yaml` configuration file. 78 | 79 | #### Examples 80 | 81 | Build `.deb` packages for Debian 13 "Trixie" `amd64`: 82 | 83 | ``` 84 | ./build.py auto debian amd64 trixie 85 | ``` 86 | 87 | Build Linux `.tar.xx` archives for `arm64-musl`: 88 | 89 | ``` 90 | ./build.py auto linux arm64-musl 91 | ``` 92 | 93 | Build Windows `.zip` for `amd64`: 94 | 95 | ``` 96 | ./build.py auto windows amd64 97 | ``` 98 | 99 | Build a .NET portable `.zip`: 100 | 101 | ``` 102 | ./build.py auto portable 103 | ``` 104 | 105 | ### Docker Image Platform 106 | 107 | If you want a Docker image output follow this process: 108 | 109 | 1. Install `docker` and `docker-buildx-plugin` for your OS. 110 | 111 | 1. Run `./build.py`. This command takes up to 4 arguments specific to Docker builds: 112 | 113 | * The first argument is the version you want to tag your build as. For our official releases, we use either dates for unstable builds (`YYYYMMDDHH` numerical format or `auto` for autogeneration) or the tag without `v` for stable release builds (`10.8.13`, `10.9.0`, etc.), but you can use any version tag you wish here. 114 | 115 | * The second argument is the "platform" you want to build for. For Docker images, this should be `docker`. 116 | 117 | * The third argument is the architecture you wish to build for. This argument is optional, and not providing it will build images for all supported architectures (sequentially). Cross-building of `arm64` on `amd64` is supported, but not the other way around. 118 | 119 | * The fourth argument is `--local`, which should be provided to prevent the script from trying to generate image manifests and push the resulting images to our repositories. 120 | 121 | 1. The output container image(s) will be present in your `docker image ls` as `jellyfin/jellyfin` with the tag(s) `-`. 122 | 123 | #### Examples 124 | 125 | Build an `amd64` Docker image: 126 | 127 | ``` 128 | ./build.py auto docker amd64 --local 129 | ``` 130 | 131 | ## Design 132 | 133 | Inside this repository are 7 major components: 134 | 135 | 1. Submodules for the `jellyfin` (as `jellyfin-server`) and `jellyfin-web` repositories. These are dynamic submodules; the `checkout.py` script will check them out to the required `HEAD` on each build, and thus their actual committed value is irrelevant. Nonetheless, they should be bumped occasionally just to avoid excessive checkout times later. 136 | 137 | 1. Debian/Ubuntu packaging configurations (under `debian`). These will build the 3 Jellyfin packages (`jellyfin` metapackage, `jellyfin-server` core server, and `jellyfin-web` web client) from a single Dockerfile and helper script (`build.sh`) under `debian/docker/`. Future packages (e.g. Vue) may be added here as well if and when they are promoted to a production build alongside the others, following one consistent versioning scheme. 138 | 139 | 1. Docker image builder (under `docker`). Like the above two as well, only building the combined Docker images with a single Dockerfile as well as preparing the various manifests needed to push these to the container repos. 140 | 141 | 1. Portable image builder (under `portable`), which covers all the "archive" builds (.NET portable, Linux, Windows, and MacOS) again from a single Dockerfile. 142 | 143 | 1. NuGet package builder, to prepare NuGet packages for consumption by plugins and 3rd party clients. 144 | 145 | 1. Script infrastructure to handle coordinating builds (`build.py`). This script takes basic arguments and, using its internal logic, fires the correct Dockerized builds for the given build type. 146 | 147 | 1. The GitHub Actions CI to build all the above for every supported version and architecture. 148 | 149 | ## Design Decisions 150 | 151 | ### General 152 | 153 | * Unified packaging: all packaging is in this repository (vs. within the `jellyfin-server` and `jellyfin-web` repositories) 154 | 155 | This helps ensure two things: 156 | 1. There is a single source of truth for packaging. Previously, there were at least 3 sources of truth, and this became very confusing to update. 157 | 2. Packaging can be run and managed independently of actual code, simplifying the release and build process. 158 | 159 | * GitHub Actions for CI: all builds use the GitHub Actions system instead of Azure DevOps 160 | 161 | This helps ensure that CI is visible in a "single pane of glass" (GitHub) and is easier to manage long-term. 162 | 163 | * Python script-based builds: Building actually happens via the `build.py` script 164 | 165 | This helps reduce the complexity of the builds by ensuring that the logic to actually generate specific builds is handled in one place in a consistent, well-known language, instead of the CI definitions. 166 | 167 | * Git Submodules to handle code (vs. cross-repo builds) 168 | 169 | This ensures that the code checked out is consistent to both repositories, and allows for the unified builds described below without extra steps to combine. 170 | 171 | * Remote manual-only triggers: CI workers are triggered by a remote bot 172 | 173 | This reduces the complexity of triggering builds; while it can be done manually in this repo, using an external bot allows for more release wrapper actions to occur before triggering builds. 174 | 175 | ### Debian/Ubuntu Packages 176 | 177 | * Unified package build: this entire repo is the "source" and the source package is named "jellyfin". 178 | 179 | This was chosen to simplify the source package system and simplify building. Now, there is only a single "jellyfin" source package rather than 2. There may be more in the future as other repos might be included (e.g. "jellyfin-ffmpeg", "jellyfin-vue", etc.) 180 | 181 | * Dockerized build (`debian/docker/`): the build is run inside a Docker container that matches the target OS release 182 | 183 | This was chosen to ensure a clean slate for every build, as well as enable release-specific builds due to the complexities of our shared dependencies (e.g. `libssl`). 184 | 185 | * Per-release/version builds: package versions contain the specific OS version (e.g. `-deb11`, `-ubu2204`) 186 | 187 | This enables support for different builds and packages for each OS release, simplifying shared dependency handling as mentioned above. 188 | 189 | * Ubuntu LTS-only support: non-LTS Ubuntu versions have been dropped 190 | 191 | This simplifies our builds as we do not need to then track many 9-month-only releases of Ubuntu, and also reduces the build burden. Users of non-LTS Ubuntu releases can use either the closest Ubuntu LTS version or use Docker containers instead. 192 | 193 | * Signing of Debian packages with `debsigs`. 194 | 195 | This was suggested in https://github.com/jellyfin/jellyfin-packaging/issues/14 and was not something we had ever done, but has become trivial with this CI. This alows for the end-user verification of the ownership and integrity of manually downloaded binary `.deb` files obtained from the repository with the `debsigs-verify` command and the policy detailed in that issue. Note that since Debian as a whole (i.e. `dpkg`, `apt`, etc.) does not enforce package signing at this time, enabling this for the *repository* is not possible; conventional repository signatures (using the same signing key) are considered sufficient. 196 | 197 | ### Docker 198 | 199 | * Single unified Docker build: the entirety of our Docker images are built as one container from one Dockerfile. 200 | 201 | This was chosen to keep our Docker builds as simple as possible without requiring 2 intervening images (as was the case with our previous CI). 202 | 203 | * Push to both DockerHub and GHCR (GitHub Packages) 204 | 205 | This ensures flexibility for container users to fetch the containers from whatever repository they choose. 206 | 207 | * Seamless rebuilds: The root images are appended with the build date to keep them unique 208 | 209 | This ensures we can trigger rebuilds of the Docker containers arbitrarily, in response to things like base OS updates or packaging changes (e.g. a new version of the Intel compute engine for instance). 210 | 211 | * Based on Debian 12 ("Bookworm"): the latest base Debian release 212 | 213 | While possibly not as "up-to-date" as Ubuntu, this release is quite current and should cover all major compatibility issues we had with the old images based on Debian 11. 214 | 215 | ### Portable Builds (Portable .NET, Linux, MacOS, Windows) 216 | 217 | * Single unified build: the entirety of the output package is built in one container from one Dockerfile 218 | 219 | This was chosen to keep the portable builds as simple as possible without requiring complex archive combining (as was the case with our previous CI). 220 | 221 | * Multiple archive type support (`.tar.gz` vs. `.zip`) 222 | 223 | The output archive type (`.tar.gz` or `.zip`) is chosen based on the build target, with Portable providing both for maximum compatibility, Windows providing `.zip`, and Linux and MacOS providing `.tar.gz`. This can be changed later, for example to add more formats (e.g. `.tar.xz`) or change what produces what, without major complications. 224 | 225 | * Full architecture support 226 | 227 | The portable builds support all major architectures now, specifically adding `arm64` Windows builds (I'm certain that _someone_ out there uses it), and making it quite trivial to add new architectures in the future if needed. 228 | -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # build.py - Build packages in a Docker wrapper 4 | # 5 | # Part of the Jellyfin CI system 6 | ############################################################################### 7 | 8 | from argparse import ArgumentParser 9 | from datetime import datetime 10 | from email.utils import format_datetime, localtime 11 | from git import Repo 12 | from os import getenv 13 | import os.path 14 | from packaging.version import Version 15 | from subprocess import run, PIPE 16 | import sys 17 | from yaml import load, SafeLoader 18 | 19 | # Determine top level directory of this repository ("jellyfin-packaging") 20 | revparse = run(["git", "rev-parse", "--show-toplevel"], stdout=PIPE) 21 | repo_root_dir = revparse.stdout.decode().strip() 22 | 23 | # Base Docker commands 24 | docker_build_cmd = "docker buildx build --progress=plain --no-cache" 25 | docker_run_cmd = "docker run --rm" 26 | 27 | 28 | def log(message): 29 | print(message, flush=True) 30 | 31 | 32 | # Configuration loader 33 | try: 34 | with open("build.yaml", encoding="utf-8") as fh: 35 | configurations = load(fh, Loader=SafeLoader) 36 | except Exception as e: 37 | log(f"Error: Failed to find 'build.yaml' configuration: {e}") 38 | exit(1) 39 | 40 | 41 | # Shared functions 42 | def _determine_arch(build_type, build_arch, build_version): 43 | PACKAGE_ARCH = ( 44 | configurations[build_type]["archmaps"][build_arch]["PACKAGE_ARCH"] 45 | if build_arch in configurations[build_type]["archmaps"].keys() 46 | else None 47 | ) 48 | if PACKAGE_ARCH is None: 49 | raise ValueError( 50 | f"{build_arch} is not a valid {build_type} {build_version} architecture in {configurations[build_type]['archmaps'].keys()}" 51 | ) 52 | else: 53 | return PACKAGE_ARCH 54 | 55 | def _determine_framework_versions(): 56 | # Prepare repo object for this repository 57 | this_repo = Repo(repo_root_dir) 58 | 59 | framework_args = dict() 60 | 61 | for submodule in this_repo.submodules: 62 | if submodule.name in configurations["frameworks"].keys(): 63 | for framework_arg in configurations["frameworks"][submodule.name].keys(): 64 | framework_args[framework_arg] = None 65 | def sort_versions(input_dict): 66 | return dict(sorted(input_dict.items(), key=lambda item: Version(str(item[1])))) 67 | for commit_hash in sort_versions(configurations["frameworks"][submodule.name][framework_arg]): 68 | try: 69 | commit = submodule.module().commit(commit_hash) 70 | if commit in submodule.module().iter_commits('HEAD'): 71 | framework_args[framework_arg] = configurations["frameworks"][submodule.name][framework_arg][commit_hash] 72 | except ValueError: 73 | continue 74 | 75 | log(f"Determined the following framework versions based on current HEAD values:") 76 | for k, v in framework_args.items(): 77 | log(f" * {k}: {v}") 78 | log("") 79 | 80 | return framework_args 81 | 82 | 83 | def build_package_deb( 84 | jellyfin_version, build_type, build_arch, build_version, local=False, debug=False 85 | ): 86 | """ 87 | Build a .deb package (Debian or Ubuntu) within a Docker container that matches the requested distribution version 88 | """ 89 | log(f"> Building an {build_arch} {build_type} .deb package...") 90 | log("") 91 | 92 | try: 93 | os_type = build_type if build_type in configurations.keys() else None 94 | if os_type is None: 95 | raise ValueError( 96 | f"{build_type} is not a valid OS type in {configurations.keys()}" 97 | ) 98 | os_version = ( 99 | configurations[build_type]["releases"][build_version] 100 | if build_version in configurations[build_type]["releases"].keys() 101 | else None 102 | ) 103 | if os_version is None: 104 | raise ValueError( 105 | f"{build_version} is not a valid {build_type} version in {configurations[build_type]['releases'].keys()}" 106 | ) 107 | PACKAGE_ARCH = _determine_arch(build_type, build_arch, build_version) 108 | GCC_ARCH = configurations[build_type]["archmaps"][build_arch]["GCC_ARCH"] 109 | except Exception as e: 110 | log(f"Invalid/unsupported arguments: {e}") 111 | exit(1) 112 | 113 | # Set the dockerfile 114 | dockerfile = configurations[build_type]["dockerfile"] 115 | 116 | # Set the cross-gcc version 117 | crossgccvers = configurations[build_type]["cross-gcc"][build_version] 118 | 119 | # Prepare the debian changelog file 120 | changelog_src = f"{repo_root_dir}/debian/changelog.in" 121 | changelog_dst = f"{repo_root_dir}/debian/changelog" 122 | 123 | with open(changelog_src) as fh: 124 | changelog = fh.read() 125 | 126 | if "v" in jellyfin_version: 127 | comment = f"Jellyfin release {jellyfin_version}, see https://github.com/jellyfin/jellyfin/releases/{jellyfin_version} for details." 128 | else: 129 | comment = f"Jellyin unstable release {jellyfin_version}." 130 | jellyfin_version = jellyfin_version.replace("v", "") 131 | 132 | changelog = changelog.format( 133 | package_version=jellyfin_version, 134 | package_build=f"{build_type[:3]}{os_version.replace('.', '')}", 135 | release_comment=comment, 136 | release_date=format_datetime(localtime()), 137 | ) 138 | 139 | with open(changelog_dst, "w") as fh: 140 | fh.write(changelog) 141 | 142 | # Use a unique docker image name for consistency 143 | imagename = f"{configurations[build_type]['imagename']}-{jellyfin_version}_{build_arch}-{build_type}-{build_version}" 144 | 145 | # Prepare the list of build-args 146 | build_args = list() 147 | build_args.append(f"--build-arg PACKAGE_TYPE={os_type}") 148 | build_args.append(f"--build-arg PACKAGE_VERSION={os_version}") 149 | build_args.append(f"--build-arg PACKAGE_ARCH={PACKAGE_ARCH}") 150 | build_args.append(f"--build-arg GCC_ARCH={GCC_ARCH}") 151 | build_args.append(f"--build-arg GCC_VERSION={crossgccvers}") 152 | 153 | # Determine framework versions 154 | framework_versions = _determine_framework_versions() 155 | for arg in framework_versions.keys(): 156 | if framework_versions[arg] is not None: 157 | build_args.append( 158 | f"--build-arg {arg}={framework_versions[arg]}" 159 | ) 160 | 161 | build_args = ' '.join(build_args) 162 | 163 | # Build the dockerfile and packages 164 | log( 165 | f">>> {docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 166 | ) 167 | os.system( 168 | f"{docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 169 | ) 170 | 171 | log( 172 | f">>> {docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 173 | ) 174 | os.system( 175 | f"{docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 176 | ) 177 | 178 | 179 | def build_linux( 180 | jellyfin_version, build_type, build_arch, _build_version, local=False, debug=False 181 | ): 182 | """ 183 | Build a portable Linux archive 184 | """ 185 | log(f"> Building a portable {build_arch} Linux archive...") 186 | log("") 187 | 188 | try: 189 | PACKAGE_ARCH = _determine_arch(build_type, build_arch, _build_version) 190 | DOTNET_ARCH = configurations[build_type]["archmaps"][build_arch]["DOTNET_ARCH"] 191 | except Exception as e: 192 | log(f"Invalid/unsupported arguments: {e}") 193 | exit(1) 194 | 195 | jellyfin_version = jellyfin_version.replace("v", "") 196 | 197 | # Set the dockerfile 198 | dockerfile = configurations[build_type]["dockerfile"] 199 | 200 | # Use a unique docker image name for consistency 201 | imagename = f"{configurations[build_type]['imagename']}-{jellyfin_version}_{build_arch}-{build_type}" 202 | 203 | # Set the archive type (tar-gz or zip) 204 | archivetypes = f"{configurations[build_type]['archivetypes']}" 205 | 206 | # Prepare the list of build-args 207 | build_args = list() 208 | 209 | # Determine framework versions 210 | framework_versions = _determine_framework_versions() 211 | for arg in framework_versions.keys(): 212 | if framework_versions[arg] is not None: 213 | build_args.append( 214 | f"--build-arg {arg}={framework_versions[arg]}" 215 | ) 216 | 217 | build_args = ' '.join(build_args) 218 | 219 | # Build the dockerfile and packages 220 | log( 221 | f">>> {docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 222 | ) 223 | os.system( 224 | f"{docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 225 | ) 226 | 227 | log( 228 | f">>> {docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env PACKAGE_ARCH={PACKAGE_ARCH} --env DOTNET_TYPE=linux --env DOTNET_ARCH={DOTNET_ARCH} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 229 | ) 230 | os.system( 231 | f"{docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env PACKAGE_ARCH={PACKAGE_ARCH} --env DOTNET_TYPE=linux --env DOTNET_ARCH={DOTNET_ARCH} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 232 | ) 233 | 234 | 235 | def build_windows( 236 | jellyfin_version, build_type, _build_arch, _build_version, local=False, debug=False 237 | ): 238 | """ 239 | Build a portable Windows archive 240 | """ 241 | log(f"> Building a portable {build_arch} Windows archive...") 242 | log("") 243 | 244 | try: 245 | PACKAGE_ARCH = _determine_arch(build_type, build_arch, _build_version) 246 | DOTNET_ARCH = configurations[build_type]["archmaps"][build_arch]["DOTNET_ARCH"] 247 | except Exception as e: 248 | log(f"Invalid/unsupported arguments: {e}") 249 | exit(1) 250 | 251 | jellyfin_version = jellyfin_version.replace("v", "") 252 | 253 | # Set the dockerfile 254 | dockerfile = configurations[build_type]["dockerfile"] 255 | 256 | # Use a unique docker image name for consistency 257 | imagename = f"{configurations[build_type]['imagename']}-{jellyfin_version}_{build_arch}-{build_type}" 258 | 259 | # Set the archive type (tar-gz or zip) 260 | archivetypes = f"{configurations[build_type]['archivetypes']}" 261 | 262 | # Prepare the list of build-args 263 | build_args = list() 264 | 265 | # Determine framework versions 266 | framework_versions = _determine_framework_versions() 267 | for arg in framework_versions.keys(): 268 | if framework_versions[arg] is not None: 269 | build_args.append( 270 | f"--build-arg {arg}={framework_versions[arg]}" 271 | ) 272 | 273 | build_args = ' '.join(build_args) 274 | 275 | # Build the dockerfile and packages 276 | log( 277 | f">>> {docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 278 | ) 279 | os.system( 280 | f"{docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 281 | ) 282 | 283 | log( 284 | f">>> {docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env PACKAGE_ARCH={PACKAGE_ARCH} --env DOTNET_TYPE=win --env DOTNET_ARCH={DOTNET_ARCH} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 285 | ) 286 | os.system( 287 | f"{docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env PACKAGE_ARCH={PACKAGE_ARCH} --env DOTNET_TYPE=win --env DOTNET_ARCH={DOTNET_ARCH} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 288 | ) 289 | 290 | 291 | def build_macos( 292 | jellyfin_version, build_type, build_arch, _build_version, local=False, debug=False 293 | ): 294 | """ 295 | Build a portable MacOS archive 296 | """ 297 | log(f"> Building a portable {build_arch} MacOS archive...") 298 | log("") 299 | 300 | try: 301 | PACKAGE_ARCH = _determine_arch(build_type, build_arch, _build_version) 302 | DOTNET_ARCH = configurations[build_type]["archmaps"][build_arch]["DOTNET_ARCH"] 303 | except Exception as e: 304 | log(f"Invalid/unsupported arguments: {e}") 305 | exit(1) 306 | 307 | jellyfin_version = jellyfin_version.replace("v", "") 308 | 309 | # Set the dockerfile 310 | dockerfile = configurations[build_type]["dockerfile"] 311 | 312 | # Use a unique docker image name for consistency 313 | imagename = f"{configurations[build_type]['imagename']}-{jellyfin_version}_{build_arch}-{build_type}" 314 | 315 | # Set the archive type (tar-gz or zip) 316 | archivetypes = f"{configurations[build_type]['archivetypes']}" 317 | 318 | # Prepare the list of build-args 319 | build_args = list() 320 | 321 | # Determine framework versions 322 | framework_versions = _determine_framework_versions() 323 | for arg in framework_versions.keys(): 324 | if framework_versions[arg] is not None: 325 | build_args.append( 326 | f"--build-arg {arg}={framework_versions[arg]}" 327 | ) 328 | 329 | build_args = ' '.join(build_args) 330 | 331 | # Build the dockerfile and packages 332 | log( 333 | f">>> {docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 334 | ) 335 | os.system( 336 | f"{docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 337 | ) 338 | 339 | log( 340 | f">>> {docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env PACKAGE_ARCH={PACKAGE_ARCH} --env DOTNET_TYPE=osx --env DOTNET_ARCH={DOTNET_ARCH} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 341 | ) 342 | os.system( 343 | f"{docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env PACKAGE_ARCH={PACKAGE_ARCH} --env DOTNET_TYPE=osx --env DOTNET_ARCH={DOTNET_ARCH} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 344 | ) 345 | 346 | 347 | def build_portable( 348 | jellyfin_version, build_type, _build_arch, _build_version, local=False, debug=False 349 | ): 350 | """ 351 | Build a portable .NET archive 352 | """ 353 | log("> Building a portable .NET archive...") 354 | log("") 355 | 356 | jellyfin_version = jellyfin_version.replace("v", "") 357 | 358 | # Set the dockerfile 359 | dockerfile = configurations[build_type]["dockerfile"] 360 | 361 | # Use a unique docker image name for consistency 362 | imagename = ( 363 | f"{configurations[build_type]['imagename']}-{jellyfin_version}_{build_type}" 364 | ) 365 | 366 | # Set the archive type (tar-gz or zip) 367 | archivetypes = f"{configurations[build_type]['archivetypes']}" 368 | 369 | # Prepare the list of build-args 370 | build_args = list() 371 | 372 | # Determine framework versions 373 | framework_versions = _determine_framework_versions() 374 | for arg in framework_versions.keys(): 375 | if framework_versions[arg] is not None: 376 | build_args.append( 377 | f"--build-arg {arg}={framework_versions[arg]}" 378 | ) 379 | 380 | build_args = ' '.join(build_args) 381 | 382 | # Build the dockerfile and packages 383 | log( 384 | f">>> {docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 385 | ) 386 | os.system( 387 | f"{docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 388 | ) 389 | 390 | log( 391 | f">>> {docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 392 | ) 393 | os.system( 394 | f"{docker_run_cmd} --volume {repo_root_dir}:/jellyfin --volume {repo_root_dir}/out/{build_type}:/dist --env JELLYFIN_VERSION={jellyfin_version} --env BUILD_TYPE={build_type} --env ARCHIVE_TYPES={archivetypes} --env CONFIG={'Debug' if debug else 'Release'} --name {imagename} {imagename}" 395 | ) 396 | 397 | 398 | def build_docker( 399 | jellyfin_version, build_type, build_arch, _build_version, local=False, debug=False 400 | ): 401 | """ 402 | Build Docker images for one or all architectures and combining manifests 403 | """ 404 | log("> Building Docker images...") 405 | 406 | if build_arch: 407 | log(f"NOTE: Building only for arch {build_arch}") 408 | 409 | # We build all architectures simultaneously to push a single tag, so no conditional checks 410 | architectures = configurations["docker"]["archmaps"].keys() 411 | 412 | if build_arch: 413 | if build_arch not in architectures: 414 | log(f"Error: Architecture {build_arch} is not valid.") 415 | exit(1) 416 | else: 417 | architectures = [build_arch] 418 | 419 | # Set the dockerfile 420 | dockerfile = configurations[build_type]["dockerfile"] 421 | 422 | # Determine if this is a "latest"-type image (v in jellyfin_version) or not 423 | if "v" in jellyfin_version and not "rc" in jellyfin_version: 424 | is_stable = True 425 | is_preview = False 426 | elif "rc" in jellyfin_version: 427 | is_stable = False 428 | is_preview = True 429 | else: 430 | is_stable = False 431 | is_preview = False 432 | 433 | jellyfin_version = jellyfin_version.replace("v", "") 434 | 435 | log(f"NOTE: Build type is {'stable' if is_stable else 'preview/unstable'} for version {jellyfin_version}") 436 | log("") 437 | 438 | # Set today's date in a convenient format for use as an image suffix 439 | date = datetime.now().strftime("%Y%m%d-%H%M%S") 440 | 441 | images_hub = list() 442 | images_ghcr = list() 443 | for _build_arch in architectures: 444 | log(f">> Building Docker image for {_build_arch}...") 445 | log("") 446 | 447 | # Get our ARCH variables from the archmaps 448 | PACKAGE_ARCH = configurations["docker"]["archmaps"][_build_arch]["PACKAGE_ARCH"] 449 | DOTNET_ARCH = configurations["docker"]["archmaps"][_build_arch]["DOTNET_ARCH"] 450 | IMAGE_ARCH = configurations["docker"]["archmaps"][_build_arch]["IMAGE_ARCH"] 451 | TARGET_ARCH = configurations["docker"]["archmaps"][_build_arch]["TARGET_ARCH"] 452 | 453 | # Use a unique docker image name for consistency 454 | if is_stable or is_preview: 455 | imagename = f"{configurations['docker']['imagename']}:{jellyfin_version}-{_build_arch}.{date}" 456 | else: 457 | imagename = f"{configurations['docker']['imagename']}:{jellyfin_version}-{_build_arch}" 458 | 459 | # Clean up any existing qemu static image 460 | log( 461 | f">>> {docker_run_cmd} --privileged linuxserver/qemu-static --reset -p yes" 462 | ) 463 | os.system( 464 | f"{docker_run_cmd} --privileged linuxserver/qemu-static --reset -p yes" 465 | ) 466 | log("") 467 | 468 | # Prepare the list of build-args 469 | build_args = list() 470 | build_args.append(f"--build-arg PACKAGE_ARCH={PACKAGE_ARCH}") 471 | build_args.append(f"--build-arg DOTNET_ARCH={DOTNET_ARCH}") 472 | build_args.append(f"--build-arg IMAGE_ARCH={IMAGE_ARCH}") 473 | build_args.append(f"--build-arg TARGET_ARCH={TARGET_ARCH}") 474 | build_args.append(f"--build-arg JELLYFIN_VERSION={jellyfin_version}") 475 | build_args.append(f"--build-arg CONFIG={'Debug' if debug else 'Release'}") 476 | 477 | # Determine framework versions 478 | framework_versions = _determine_framework_versions() 479 | for arg in framework_versions.keys(): 480 | if framework_versions[arg] is not None: 481 | build_args.append( 482 | f"--build-arg {arg}={framework_versions[arg]}" 483 | ) 484 | 485 | if local: 486 | build_args.append("--load") 487 | 488 | build_args = ' '.join(build_args) 489 | 490 | # Build the dockerfile 491 | log( 492 | f">>> {docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 493 | ) 494 | ret = os.system( 495 | f"{docker_build_cmd} {build_args} --file {repo_root_dir}/{dockerfile} --tag {imagename} {repo_root_dir}" 496 | ) 497 | if ret > 0: 498 | exit(1) 499 | images_hub.append(imagename) 500 | 501 | if not local: 502 | os.system(f"docker image tag {imagename} ghcr.io/{imagename}") 503 | images_ghcr.append(f"ghcr.io/{imagename}") 504 | 505 | log("") 506 | 507 | if local: 508 | return 509 | 510 | if not getenv('DOCKER_USERNAME') or not getenv('DOCKER_TOKEN'): 511 | log("Warning: No DOCKER_USERNAME or DOCKER_TOKEN in environment; skipping manifest build and push (DockerHub and GHCR).") 512 | return 513 | 514 | def build_manifests(server, images): 515 | # Build the manifests 516 | log(f">> Building Docker manifests for {server}...") 517 | manifests = list() 518 | 519 | if is_stable or is_preview: 520 | log(">>> Building X.Y.Z dated version manifest...") 521 | log( 522 | f">>>> docker manifest create {server}/{configurations['docker']['imagename']}:{jellyfin_version}.{date} {' '.join(images)}" 523 | ) 524 | os.system( 525 | f"docker manifest create {server}/{configurations['docker']['imagename']}:{jellyfin_version}.{date} {' '.join(images)}" 526 | ) 527 | manifests.append( 528 | f"{server}/{configurations['docker']['imagename']}:{jellyfin_version}.{date}" 529 | ) 530 | 531 | log(">>> Building X.Y.Z version manifest...") 532 | log( 533 | f">>>> docker manifest create {server}/{configurations['docker']['imagename']}:{jellyfin_version} {' '.join(images)}" 534 | ) 535 | os.system( 536 | f"docker manifest create {server}/{configurations['docker']['imagename']}:{jellyfin_version} {' '.join(images)}" 537 | ) 538 | manifests.append(f"{server}/{configurations['docker']['imagename']}:{jellyfin_version}") 539 | 540 | if is_stable: 541 | # Build major-minor point version 542 | log(">>> Building X.Y version manifest...") 543 | manifest_version_xy = '.'.join(jellyfin_version.split('.')[0:2]) 544 | log( 545 | f">>>> docker manifest create {server}/{configurations['docker']['imagename']}:{manifest_version_xy} {' '.join(images)}" 546 | ) 547 | os.system( 548 | f"docker manifest create {server}/{configurations['docker']['imagename']}:{manifest_version_xy} {' '.join(images)}" 549 | ) 550 | manifests.append(f"{server}/{configurations['docker']['imagename']}:{manifest_version_xy}") 551 | 552 | # Build major-only point version 553 | log(">>> Building X version manifest...") 554 | manifest_version_x = '.'.join(jellyfin_version.split('.')[0:1]) 555 | log( 556 | f">>>> docker manifest create {server}/{configurations['docker']['imagename']}:{manifest_version_x} {' '.join(images)}" 557 | ) 558 | os.system( 559 | f"docker manifest create {server}/{configurations['docker']['imagename']}:{manifest_version_x} {' '.join(images)}" 560 | ) 561 | manifests.append(f"{server}/{configurations['docker']['imagename']}:{manifest_version_x}") 562 | 563 | log(">>> Building latest manifest...") 564 | log( 565 | f">>>> docker manifest create {server}/{configurations['docker']['imagename']}:latest {' '.join(images)}" 566 | ) 567 | os.system( 568 | f"docker manifest create {server}/{configurations['docker']['imagename']}:latest {' '.join(images)}" 569 | ) 570 | manifests.append(f"{server}/{configurations['docker']['imagename']}:latest") 571 | elif is_preview: 572 | log(">>> Building preview manifest...") 573 | log( 574 | f">>>> docker manifest create {server}/{configurations['docker']['imagename']}:preview {' '.join(images)}" 575 | ) 576 | os.system( 577 | f"docker manifest create {server}/{configurations['docker']['imagename']}:preview {' '.join(images)}" 578 | ) 579 | manifests.append(f"{server}/{configurations['docker']['imagename']}:preview") 580 | else: 581 | log(">>> Building unstable manifest...") 582 | log( 583 | f">>>> docker manifest create {server}/{configurations['docker']['imagename']}:unstable {' '.join(images)}" 584 | ) 585 | os.system( 586 | f"docker manifest create {server}/{configurations['docker']['imagename']}:unstable {' '.join(images)}" 587 | ) 588 | manifests.append(f"{server}/{configurations['docker']['imagename']}:unstable") 589 | 590 | return manifests 591 | 592 | # Log in to DockerHub 593 | os.system( 594 | f"docker login -u {getenv('DOCKER_USERNAME')} -p {getenv('DOCKER_TOKEN')} 2>&1" 595 | ) 596 | 597 | # Push the images to DockerHub 598 | for image in images_hub: 599 | log(f">>> Pushing image {image} to DockerHub") 600 | log(f">>>> docker push {image} 2>&1") 601 | os.system(f"docker push {image} 2>&1") 602 | 603 | manifests_hub = build_manifests("docker.io", images_hub) 604 | 605 | # Push the images and manifests to DockerHub 606 | for manifest in manifests_hub: 607 | log(f">>> Pushing manifest {manifest} to DockerHub") 608 | log(f">>>> docker manifest push --purge {manifest} 2>&1") 609 | os.system(f"docker manifest push --purge {manifest} 2>&1") 610 | 611 | # Log out of DockerHub 612 | os.system("docker logout") 613 | 614 | # Log in to GHCR 615 | os.system( 616 | f"docker login -u {getenv('GHCR_USERNAME')} -p {getenv('GHCR_TOKEN')} ghcr.io 2>&1" 617 | ) 618 | 619 | # Push the images to GHCR 620 | for image in images_ghcr: 621 | log(f">>> Pushing image {image} to GHCR") 622 | log(f">>>> docker push {image} 2>&1") 623 | os.system(f"docker push {image} 2>&1") 624 | 625 | manifests_ghcr = build_manifests("ghcr.io", images_ghcr) 626 | 627 | # Push the images and manifests to GHCR 628 | for manifest in manifests_ghcr: 629 | log(f">>> Pushing manifest {manifest} to GHCR") 630 | log(f">>>> docker manifest push --purge {manifest} 2>&1") 631 | os.system(f"docker manifest push --purge {manifest} 2>&1") 632 | 633 | # Log out of GHCR 634 | os.system("docker logout") 635 | 636 | 637 | def build_nuget( 638 | jellyfin_version, build_type, _build_arch, _build_version, local=False, debug=False 639 | ): 640 | """ 641 | Pack and upload nuget packages 642 | """ 643 | log("> Building Nuget packages...") 644 | log("") 645 | 646 | project_files = configurations["nuget"]["projects"] 647 | log(project_files) 648 | 649 | # Determine if this is a "latest"-type image (v in jellyfin_version) or not 650 | if "v" in jellyfin_version and not "rc" in jellyfin_version: 651 | is_stable = True 652 | is_preview = False 653 | elif "rc" in jellyfin_version: 654 | is_stable = False 655 | is_preview = True 656 | else: 657 | is_stable = False 658 | is_preview = False 659 | 660 | jellyfin_version = jellyfin_version.replace("v", "") 661 | 662 | # Set today's date in a convenient format for use as an image suffix 663 | date = datetime.now().strftime("%Y%m%d%H%M%S") 664 | 665 | pack_command_base = "dotnet pack -o out/nuget/" 666 | if is_stable or is_preview: 667 | pack_command = f"{pack_command_base} -p:Version={jellyfin_version}" 668 | else: 669 | pack_command = ( 670 | f"{pack_command_base} --version-suffix {date} -p:Stability=Unstable" 671 | ) 672 | 673 | for project in project_files: 674 | log(f">> Packing {project}...") 675 | log("") 676 | 677 | project_pack_command = f"{pack_command} jellyfin-server/{project}" 678 | log(f">>>> {project_pack_command}") 679 | os.system(project_pack_command) 680 | 681 | if local: 682 | return 683 | 684 | if is_stable or is_preview: 685 | nuget_repo = configurations["nuget"]["feed_urls"]["stable"] 686 | nuget_key = getenv("NUGET_STABLE_KEY") 687 | else: 688 | nuget_repo = configurations["nuget"]["feed_urls"]["unstable"] 689 | nuget_key = getenv("NUGET_UNSTABLE_KEY") 690 | 691 | if nuget_key is None: 692 | log(f"Error: Failed to get NUGET_*_KEY environment variable") 693 | exit(1) 694 | 695 | push_command = f"dotnet nuget push out/nuget/*.nupkg -s {nuget_repo} -k {nuget_key}" 696 | log(f">>>> {push_command}") 697 | os.system(push_command) 698 | 699 | 700 | def usage(): 701 | """ 702 | Print usage information on error 703 | """ 704 | log(f"{sys.argv[0]} JELLYFIN_VERSION BUILD_TYPE [BUILD_ARCH] [BUILD_VERSION]") 705 | log(" JELLYFIN_VERSION: The Jellyfin version being built") 706 | log(" * Stable releases should be tag names with a 'v' e.g. v10.9.0") 707 | log( 708 | " * Unstable releases should be 'master' or a date-to-the-hour version e.g. 2024021600" 709 | ) 710 | log(" BUILD_TYPE: The type of build to execute") 711 | log(f" * Valid options are: {', '.join(configurations.keys())}") 712 | log(" BUILD_ARCH: The CPU architecture of the build") 713 | log(" * Valid options are: [portable/docker only], amd64, arm64") 714 | log(" BUILD_VERSION: A valid OS distribution version (.deb build types only)") 715 | 716 | 717 | # Define a map of possible build functions from the YAML configuration 718 | function_definitions = { 719 | "build_package_deb": build_package_deb, 720 | "build_portable": build_portable, 721 | "build_linux": build_linux, 722 | "build_windows": build_windows, 723 | "build_macos": build_macos, 724 | "build_portable": build_portable, 725 | "build_docker": build_docker, 726 | "build_nuget": build_nuget, 727 | } 728 | 729 | 730 | parser = ArgumentParser( 731 | prog='build.py', 732 | description='Jellyfin build automator', 733 | epilog='See "README.md" and "build.yaml" for the full details of what this tool can build at this time.', 734 | ) 735 | 736 | parser.add_argument('jellyfin_version', help='The output version') 737 | parser.add_argument('build_type', choices=configurations.keys(), help='The build platform') 738 | parser.add_argument('build_arch', default=None, nargs='?', help='The build architecture') 739 | parser.add_argument('build_version', default=None, nargs='?', help='The build release version [debian/ubuntu only]') 740 | parser.add_argument('--local', action='store_true', help='Local build, do not generate manifests or push them [docker only]') 741 | parser.add_argument('--debug', action='store_true', help='Debug build, set .NET to use Debug instead of Release') 742 | 743 | args = parser.parse_args() 744 | 745 | jellyfin_version = args.jellyfin_version 746 | build_type = args.build_type 747 | build_arch = args.build_arch 748 | build_version = args.build_version 749 | 750 | if build_type not in ["portable", "docker", "nuget"] and not build_arch: 751 | log(f"Error: You must specify an architecture for build platform {build_type}") 752 | exit(1) 753 | 754 | # Autocorrect "master" to a dated version string 755 | if jellyfin_version in ["auto", "master"]: 756 | jellyfin_version = datetime.now().strftime("%Y%m%d%H") 757 | log(f"NOTE: Autocorrecting 'master' version to {jellyfin_version}") 758 | 759 | # Launch the builder function 760 | function_definitions[configurations[build_type]["build_function"]]( 761 | jellyfin_version, build_type, build_arch, build_version, local=args.local, debug=args.debug 762 | ) 763 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Build definitions for `build.py` 3 | 4 | # Upstream Framework versions 5 | # This section defines target commits after which a particular framework is required. 6 | # This is used by build.py to automatically determine which framework version to use 7 | # within the build containers based on whatever HEAD the project is at (from checkout.py). 8 | # Target commits should be a merge commit! 9 | # Target commits should be in order from oldest to newest! 10 | # HOW THIS WORKS: 11 | # For each submodule, and then for each upstream framework version ARG to Docker... 12 | # Provide a commit hash as a key, and a version as a value... 13 | # If the given commit is in the commit tree of the current HEAD of the repo... 14 | # Use the given version. Otherwise use the default. 15 | frameworks: 16 | jellyfin-web: 17 | NODEJS_VERSION: 18 | 6c0a64ef12b9eb40a7c4ee4b9d43d0a5f32f2287: 20 # Default 19 | jellyfin-server: 20 | DOTNET_VERSION: 21 | 6d1abf67c36379f0b061095619147a3691841e21: 8.0 # Default 22 | ceb850c77052c465af8422dcf152f1d1d1530457: 9.0 23 | 24 | # DEB packages 25 | debian: 26 | releases: 27 | bullseye: '11' 28 | bookworm: '12' 29 | trixie: '13' 30 | archmaps: 31 | amd64: 32 | PACKAGE_ARCH: amd64 33 | GCC_ARCH: x86-64 34 | arm64: 35 | PACKAGE_ARCH: arm64 36 | GCC_ARCH: aarch64 37 | cross-gcc: 38 | bullseye: '10' 39 | bookworm: '12' 40 | trixie: '14' 41 | build_function: build_package_deb 42 | dockerfile: debian/docker/Dockerfile 43 | imagename: jellyfin-builder-debian 44 | ubuntu: 45 | releases: 46 | focal: '20.04' 47 | jammy: '22.04' 48 | noble: '24.04' 49 | archmaps: 50 | amd64: 51 | PACKAGE_ARCH: amd64 52 | GCC_ARCH: x86-64 53 | arm64: 54 | PACKAGE_ARCH: arm64 55 | GCC_ARCH: aarch64 56 | cross-gcc: 57 | focal: '10' 58 | jammy: '12' 59 | noble: '13' 60 | build_function: build_package_deb 61 | dockerfile: debian/docker/Dockerfile 62 | imagename: jellyfin-builder-ubuntu 63 | 64 | # Portable archives 65 | linux: 66 | build_function: build_linux 67 | archivetypes: targz,tarxz 68 | archmaps: 69 | amd64: 70 | DOTNET_ARCH: x64 71 | PACKAGE_ARCH: amd64 72 | amd64-musl: 73 | DOTNET_ARCH: musl-x64 74 | PACKAGE_ARCH: amd64-musl 75 | arm64: 76 | DOTNET_ARCH: arm64 77 | PACKAGE_ARCH: arm64 78 | arm64-musl: 79 | DOTNET_ARCH: musl-arm64 80 | PACKAGE_ARCH: arm64-musl 81 | dockerfile: portable/Dockerfile 82 | imagename: jellyfin-builder-linux 83 | windows: 84 | build_function: build_windows 85 | archivetypes: zip 86 | archmaps: 87 | amd64: 88 | DOTNET_ARCH: x64 89 | PACKAGE_ARCH: amd64 90 | arm64: 91 | DOTNET_ARCH: arm64 92 | PACKAGE_ARCH: arm64 93 | dockerfile: portable/Dockerfile 94 | imagename: jellyfin-builder-windows 95 | macos: 96 | build_function: build_macos 97 | archivetypes: tarxz 98 | archmaps: 99 | amd64: 100 | DOTNET_ARCH: x64 101 | PACKAGE_ARCH: amd64 102 | arm64: 103 | DOTNET_ARCH: arm64 104 | PACKAGE_ARCH: arm64 105 | dockerfile: portable/Dockerfile 106 | imagename: jellyfin-builder-macos 107 | portable: 108 | build_function: build_portable 109 | archivetypes: targz,tarxz,zip 110 | dockerfile: portable/Dockerfile 111 | imagename: jellyfin-builder-portable 112 | 113 | # Docker images 114 | docker: 115 | build_function: build_docker 116 | archmaps: 117 | amd64: 118 | DOTNET_ARCH: x64 119 | IMAGE_ARCH: amd64 120 | PACKAGE_ARCH: amd64 121 | TARGET_ARCH: amd64 122 | arm64: 123 | DOTNET_ARCH: arm64 124 | IMAGE_ARCH: arm64v8 125 | PACKAGE_ARCH: arm64 126 | TARGET_ARCH: arm64/v8 127 | dockerfile: docker/Dockerfile 128 | imagename: jellyfin/jellyfin 129 | 130 | # Nuget packages 131 | nuget: 132 | build_function: build_nuget 133 | projects: 134 | - Jellyfin.Data/Jellyfin.Data.csproj 135 | - MediaBrowser.Common/MediaBrowser.Common.csproj 136 | - MediaBrowser.Controller/MediaBrowser.Controller.csproj 137 | - MediaBrowser.Model/MediaBrowser.Model.csproj 138 | - Emby.Naming/Emby.Naming.csproj 139 | - src/Jellyfin.Extensions/Jellyfin.Extensions.csproj 140 | - src/Jellyfin.Database/Jellyfin.Database.Implementations/Jellyfin.Database.Implementations.csproj 141 | - src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj 142 | feed_urls: 143 | stable: https://api.nuget.org/v3/index.json 144 | unstable: https://nuget.pkg.github.com/jellyfin/index.json 145 | -------------------------------------------------------------------------------- /checkout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # checkout.py - Checkout submodules for a build (checkout a given tag or head) 4 | # 5 | # Part of the Jellyfin CI system 6 | ############################################################################### 7 | 8 | from datetime import datetime 9 | from subprocess import run, PIPE 10 | import sys 11 | 12 | from git import Repo 13 | 14 | try: 15 | target_release = sys.argv[1] 16 | except IndexError: 17 | target_release = "master" 18 | 19 | print(f"Preparing targets for {target_release}") 20 | 21 | # Determine top level directory of this repository ("jellyfin-packaging") 22 | revparse = run(["git", "rev-parse", "--show-toplevel"], stdout=PIPE) 23 | revparse_dir = revparse.stdout.decode().strip() 24 | 25 | # Prepare repo object for this repository 26 | this_repo = Repo(revparse_dir) 27 | 28 | # Update all the submodules 29 | while True: 30 | try: 31 | this_repo.submodule_update(init=True, recursive=True) 32 | break 33 | except Exception as e: 34 | print(e) 35 | pass 36 | 37 | # Prepare a dictionary form of the submodules so we can reference them by name 38 | submodules = dict() 39 | for submodule in this_repo.submodules: 40 | submodules[submodule.name] = submodule.module() 41 | 42 | # Validate that the provided tag is valid; if not, fall back to "master" 43 | if target_release != "master": 44 | if ( 45 | target_release not in submodules["jellyfin-server"].tags 46 | or target_release not in submodules["jellyfin-web"].tags 47 | ): 48 | print( 49 | f"WARNING: Provided tag {target_release} is not a valid tag for both jellyfin-server and jellyfin-web; using master instead" 50 | ) 51 | target_release = "master" 52 | 53 | for submodule in submodules.keys(): 54 | if target_release == "master" or submodule == 'jellyfin-server-windows': 55 | target_head = "origin/master" 56 | else: 57 | target_head = f"refs/tags/{target_release}" 58 | # Checkout the given head and reset the working tree 59 | submodules[submodule].head.reference = target_head 60 | submodules[submodule].head.reset(index=True, working_tree=True) 61 | sha = submodules[submodule].head.object.hexsha 62 | author = submodules[submodule].head.object.author.name 63 | summary = submodules[submodule].head.object.summary 64 | date = datetime.fromtimestamp(submodules[submodule].head.object.committed_date) 65 | print(f"Submodule {submodule} now at {target_head} (\"{summary}\" commit {sha} by {author} @ {date})") 66 | 67 | print(f"Successfully checked out submodules to ref {target_release}") 68 | -------------------------------------------------------------------------------- /debian/changelog.in: -------------------------------------------------------------------------------- 1 | jellyfin ({package_version}+{package_build}) unstable; urgency=medium 2 | 3 | * {release_comment} 4 | 5 | -- Jellyfin Packaging Team {release_date} 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/conf/jellyfin: -------------------------------------------------------------------------------- 1 | # Jellyfin default configuration options 2 | # This is a POSIX shell fragment 3 | 4 | # Use this file to override the default configurations; add additional 5 | # options with JELLYFIN_ADD_OPTS. 6 | 7 | # Under systemd, use 8 | # /etc/systemd/system/jellyfin.service.d/jellyfin.service.conf 9 | # to override the user or this config file's location. 10 | 11 | # 12 | # General options 13 | # 14 | 15 | # Program directories 16 | JELLYFIN_DATA_DIR="/var/lib/jellyfin" 17 | JELLYFIN_CONFIG_DIR="/etc/jellyfin" 18 | JELLYFIN_LOG_DIR="/var/log/jellyfin" 19 | JELLYFIN_CACHE_DIR="/var/cache/jellyfin" 20 | 21 | # temporary directory path; used to construct paths for short-lived temporary files 22 | # the old default was $JELLYFIN_CACHE_DIR/temp; use that to restore previous behaviour, 23 | # though it will have an additional jellyfin/ subdir appended 24 | # otherwise, leave this commented 25 | #TMPDIR="/tmp" 26 | 27 | # web client path, installed by the jellyfin-web package 28 | JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin/web" 29 | 30 | # ffmpeg binary paths, overriding the system values 31 | JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg" 32 | 33 | # Use jemalloc2 for improved RAM usage (#11588) 34 | LD_PRELOAD=/usr/lib/jellyfin/libjemalloc.so 35 | 36 | # Disable glibc dynamic heap adjustment 37 | MALLOC_TRIM_THRESHOLD_=131072 38 | 39 | # [OPTIONAL] run Jellyfin as a headless service 40 | #JELLYFIN_SERVICE_OPT="--service" 41 | 42 | # [OPTIONAL] run Jellyfin without the web app 43 | #JELLYFIN_NOWEBAPP_OPT="--nowebclient" 44 | 45 | # Space to add additional command line options to jellyfin (for help see ~$ jellyfin --help) 46 | JELLYFIN_ADDITIONAL_OPTS="" 47 | 48 | # [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC) 49 | # 0 = Workstation 50 | # 1 = Server 51 | #COMPlus_gcServer=1 52 | 53 | # Uncomment this option to stop Jellyfin from auto-starting during package installation; for systemd, `mask` is preferred. 54 | #DISABLE_JELLYFIN_AUTOSTART="yes" 55 | 56 | # 57 | # SysV init/Upstart options 58 | # 59 | # Note: These options are ignored by systemd; use /etc/systemd/system/jellyfin.d overrides instead. 60 | # 61 | 62 | # Application username 63 | JELLYFIN_USER="jellyfin" 64 | # Full application command 65 | JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLFIN_ADDITIONAL_OPTS --datadir $JELLYFIN_DATA_DIR --configdir $JELLYFIN_CONFIG_DIR --logdir $JELLYFIN_LOG_DIR --cachedir $JELLYFIN_CACHE_DIR" 66 | -------------------------------------------------------------------------------- /debian/conf/jellyfin.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: Jellyfin Media Server 4 | # Required-Start: $local_fs $network 5 | # Required-Stop: $local_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Jellyfin Media Server 9 | # Description: Runs Jellyfin Server 10 | ### END INIT INFO 11 | 12 | set -e 13 | 14 | # Carry out specific functions when asked to by the system 15 | 16 | if test -f /etc/default/jellyfin; then 17 | . /etc/default/jellyfin 18 | fi 19 | 20 | . /lib/lsb/init-functions 21 | 22 | PIDFILE="/run/jellyfin.pid" 23 | 24 | case "$1" in 25 | start) 26 | log_daemon_msg "Starting Jellyfin Media Server" "jellyfin" || true 27 | 28 | if start-stop-daemon --start --quiet --oknodo --background --chdir $JELLYFIN_DATA_DIR --pidfile $PIDFILE --make-pidfile --user $JELLYFIN_USER --chuid $JELLYFIN_USER --exec /usr/bin/jellyfin -- $JELLYFIN_ARGS; then 29 | log_end_msg 0 || true 30 | else 31 | log_end_msg 1 || true 32 | fi 33 | ;; 34 | 35 | stop) 36 | log_daemon_msg "Stopping Jellyfin Media Server" "jellyfin" || true 37 | if start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --remove-pidfile; then 38 | log_end_msg 0 || true 39 | else 40 | log_end_msg 1 || true 41 | fi 42 | ;; 43 | 44 | restart) 45 | log_daemon_msg "Restarting Jellyfin Media Server" "jellyfin" || true 46 | start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile $PIDFILE --remove-pidfile 47 | if start-stop-daemon --start --quiet --oknodo --background --chdir $JELLYFIN_DATA_DIR --pidfile $PIDFILE --make-pidfile --user $JELLYFIN_USER --chuid $JELLYFIN_USER --exec /usr/bin/jellyfin -- $JELLYFIN_ARGS; then 48 | log_end_msg 0 || true 49 | else 50 | log_end_msg 1 || true 51 | fi 52 | ;; 53 | 54 | status) 55 | status_of_proc -p $PIDFILE /usr/bin/jellyfin jellyfin && exit 0 || exit $? 56 | ;; 57 | 58 | *) 59 | echo "Usage: $0 {start|stop|restart|status}" 60 | exit 1 61 | ;; 62 | esac 63 | -------------------------------------------------------------------------------- /debian/conf/jellyfin.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description = Jellyfin Media Server 3 | After = network-online.target 4 | 5 | [Service] 6 | Type = simple 7 | EnvironmentFile = /etc/default/jellyfin 8 | User = jellyfin 9 | Group = jellyfin 10 | WorkingDirectory = /var/lib/jellyfin 11 | ExecStart = /usr/bin/jellyfin $JELLYFIN_WEB_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLYFIN_ADDITIONAL_OPTS 12 | Restart = on-failure 13 | TimeoutSec = 15 14 | SuccessExitStatus=0 143 15 | 16 | [Install] 17 | WantedBy = multi-user.target 18 | -------------------------------------------------------------------------------- /debian/conf/jellyfin.service.conf: -------------------------------------------------------------------------------- 1 | # Jellyfin systemd configuration options 2 | 3 | # Use this file to override default systemd unit values 4 | 5 | [Service] 6 | # Alter the user/group that Jellyfin runs as 7 | #User = jellyfin 8 | #Group = jellyfin 9 | 10 | # Alter where environment variables are sourced from 11 | #EnvironmentFile = /etc/default/jellyfin 12 | 13 | # Alter the working directory (useful if changing the data path) 14 | #WorkingDirectory = /var/lib/jellyfin 15 | 16 | # Service hardening options 17 | # These optional options provide additional service hardening for Jellyfin 18 | # These are an ADVANCED FEATURE - if you enable these and encounter issues, 19 | # please disable them first and triage which if any are causing the trouble 20 | # before reporting any issues. 21 | #NoNewPrivileges=true 22 | #SystemCallArchitectures=native 23 | #RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK 24 | #RestrictNamespaces=false 25 | #RestrictRealtime=true 26 | #RestrictSUIDSGID=true 27 | #ProtectControlGroups=false 28 | #ProtectHostname=true 29 | #ProtectKernelLogs=false 30 | #ProtectKernelModules=false 31 | #ProtectKernelTunables=false 32 | #LockPersonality=true 33 | #PrivateTmp=false 34 | #PrivateDevices=false 35 | #PrivateUsers=true 36 | #RemoveIPC=true 37 | #SystemCallFilter=~@clock 38 | #SystemCallFilter=~@aio 39 | #SystemCallFilter=~@chown 40 | #SystemCallFilter=~@cpu-emulation 41 | #SystemCallFilter=~@debug 42 | #SystemCallFilter=~@keyring 43 | #SystemCallFilter=~@memlock 44 | #SystemCallFilter=~@module 45 | #SystemCallFilter=~@mount 46 | #SystemCallFilter=~@obsolete 47 | #SystemCallFilter=~@privileged 48 | #SystemCallFilter=~@raw-io 49 | #SystemCallFilter=~@reboot 50 | #SystemCallFilter=~@setuid 51 | #SystemCallFilter=~@swap 52 | #SystemCallErrorNumber=EPERM 53 | -------------------------------------------------------------------------------- /debian/conf/jellyfin.upstart: -------------------------------------------------------------------------------- 1 | description "jellyfin daemon" 2 | 3 | start on (local-filesystems and net-device-up IFACE!=lo) 4 | stop on runlevel [!2345] 5 | 6 | console log 7 | respawn 8 | respawn limit 10 5 9 | 10 | kill timeout 20 11 | 12 | script 13 | set -x 14 | echo "Starting $UPSTART_JOB" 15 | 16 | # Log file 17 | logger -t "$0" "DEBUG: `set`" 18 | . /etc/default/jellyfin 19 | exec su -u $JELLYFIN_USER -c /usr/bin/jellyfin $JELLYFIN_ARGS 20 | end script 21 | -------------------------------------------------------------------------------- /debian/conf/logging.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": "Information", 4 | "WriteTo": [ 5 | { 6 | "Name": "Console", 7 | "Args": { 8 | "outputTemplate": "[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}" 9 | } 10 | }, 11 | { 12 | "Name": "Async", 13 | "Args": { 14 | "configure": [ 15 | { 16 | "Name": "File", 17 | "Args": { 18 | "path": "%JELLYFIN_LOG_DIR%//jellyfin.log", 19 | "fileSizeLimitBytes": 10485700, 20 | "rollOnFileSizeLimit": true, 21 | "retainedFileCountLimit": 10, 22 | "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}" 23 | } 24 | } 25 | ] 26 | } 27 | } 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: jellyfin 2 | Section: misc 3 | Priority: optional 4 | Maintainer: Jellyfin Packaging Team 5 | Build-Depends: debhelper (>= 10), 6 | npm | nodejs, 7 | Standards-Version: 3.9.4 8 | Homepage: https://jellyfin.org 9 | Vcs-Browser: https://github.com/jellyfin 10 | 11 | Package: jellyfin 12 | Architecture: all 13 | Depends: jellyfin-server, jellyfin-web, jellyfin-ffmpeg7 14 | Description: Jellyfin is the Free Software Media System. 15 | This metapackage provides the core Jellyfin components from one name. 16 | 17 | Package: jellyfin-server 18 | Architecture: any 19 | Depends: libfontconfig1, libjemalloc2, adduser, ${icu:Depends} 20 | Recommends: jellyfin-web, sudo, jellyfin-ffmpeg7 | ffmpeg 21 | Description: Jellyfin is the Free Software Media System. 22 | This package provides the Jellyfin server backend and API. 23 | 24 | Package: jellyfin-web 25 | Architecture: all 26 | Recommends: jellyfin-server 27 | Description: Jellyfin is the Free Software Media System. 28 | This package provides the Jellyfin web client. 29 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: jellyfin 3 | Source: https://github.com/jellyfin 4 | 5 | Files: * 6 | Copyright: 2018-2024 Jellyfin Contributors 7 | License: GPL-U 8 | 9 | Files: debian/* 10 | Copyright: 2020-2024 Joshua M. Boniface 11 | License: GPL-3.0 12 | 13 | License: GPL-U 14 | NOTICE: The licensing situation of Jellyfin is complex. This code 15 | is considered "GPL unversioned" and resulting binaries are treated 16 | as GPL version 3 only for license compatibility reasons. 17 | . 18 | This package is free software; you can redistribute it and/or modify 19 | it under the terms of the GNU General Public License as published by 20 | the Free Software Foundation; version 3 of the License. 21 | . 22 | This package is distributed in the hope that it will be useful, 23 | but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 | GNU General Public License for more details. 26 | . 27 | You should have received a copy of the GNU General Public License 28 | along with this program. If not, see 29 | . 30 | On Debian systems, the complete text of the GNU General 31 | Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". 32 | 33 | License: GPL-3.0 34 | 35 | This package is free software; you can redistribute it and/or modify 36 | it under the terms of the GNU General Public License as published by 37 | the Free Software Foundation; either version 3 of the License, or 38 | (at your option) any later version. 39 | . 40 | This package is distributed in the hope that it will be useful, 41 | but WITHOUT ANY WARRANTY; without even the implied warranty of 42 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 43 | GNU General Public License for more details. 44 | . 45 | You should have received a copy of the GNU General Public License 46 | along with this program. If not, see 47 | . 48 | On Debian systems, the complete text of the GNU General 49 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 50 | -------------------------------------------------------------------------------- /debian/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker build arguments 2 | ARG DOTNET_VERSION=8.0 3 | ARG NODEJS_VERSION=20 4 | # Default to 12, but set externally by the `build.py` script 5 | ARG GCC_VERSION=12 6 | 7 | ARG PACKAGE_TYPE 8 | ARG PACKAGE_VERSION 9 | ARG PACKAGE_ARCH 10 | ARG GCC_ARCH 11 | 12 | FROM ${PACKAGE_TYPE}:${PACKAGE_VERSION} 13 | 14 | ARG SOURCE_DIR=/jellyfin 15 | ARG ARTIFACT_DIR=/dist 16 | ARG DOTNET_VERSION 17 | ARG NODEJS_VERSION 18 | ARG PACKAGE_TYPE 19 | ARG PACKAGE_VERSION 20 | ARG PACKAGE_ARCH 21 | ARG GCC_ARCH 22 | ARG GCC_VERSION 23 | 24 | # Docker run environment 25 | ENV SOURCE_DIR=/jellyfin 26 | ENV ARTIFACT_DIR=/dist 27 | ENV DEB_BUILD_OPTIONS=noddebs 28 | ENV TYPE=${PACKAGE_TYPE} 29 | ENV VERSION=${PACKAGE_VERSION} 30 | ENV ARCH=${PACKAGE_ARCH} 31 | ENV GCC_ARCH=${GCC_ARCH} 32 | 33 | # Prepare Debian build environment 34 | RUN apt-get update -y \ 35 | && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC \ 36 | apt-get install --no-install-recommends -y \ 37 | wget \ 38 | debhelper \ 39 | gnupg \ 40 | devscripts \ 41 | build-essential \ 42 | git \ 43 | mmv \ 44 | lsb-release \ 45 | libssl*.* \ 46 | liblttng-ust* \ 47 | libfontconfig*-dev \ 48 | libcurl*openssl-dev \ 49 | libfreetype*-dev \ 50 | libssl-dev \ 51 | libicu-dev \ 52 | icu-devtools \ 53 | && apt-get clean autoclean -y \ 54 | && apt-get autoremove -y \ 55 | && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* \ 56 | && git config --global --add safe.directory /jellyfin/jellyfin-web 57 | 58 | # Prepare the cross-toolchain 59 | RUN if test "${PACKAGE_ARCH}" != "$( dpkg --print-architecture )"; then \ 60 | if grep -q -i ubuntu /etc/os-release; then \ 61 | rm /etc/apt/sources.list /etc/apt/sources.list.d/* || true \ 62 | && export CODENAME="$( lsb_release -c -s )" \ 63 | && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ 64 | && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ 65 | && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ 66 | && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \ 67 | && echo "deb [arch=${PACKAGE_ARCH}] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/${PACKAGE_ARCH}.list \ 68 | && echo "deb [arch=${PACKAGE_ARCH}] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/${PACKAGE_ARCH}.list \ 69 | && echo "deb [arch=${PACKAGE_ARCH}] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/${PACKAGE_ARCH}.list \ 70 | && echo "deb [arch=${PACKAGE_ARCH}] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/${PACKAGE_ARCH}.list \ 71 | ; fi \ 72 | && set -o xtrace \ 73 | && dpkg --add-architecture ${PACKAGE_ARCH} \ 74 | && apt-get update -y \ 75 | && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC \ 76 | apt-get -f install --no-install-recommends -o Dpkg::Options::="--force-overwrite" -y \ 77 | bison \ 78 | flex \ 79 | libtool \ 80 | gdb \ 81 | sharutils \ 82 | netbase \ 83 | libmpc-dev \ 84 | libmpfr-dev \ 85 | libgmp-dev \ 86 | systemtap-sdt-dev \ 87 | autogen \ 88 | expect \ 89 | chrpath \ 90 | zlib1g-dev \ 91 | zip \ 92 | binutils-aarch64-linux-gnu \ 93 | binutils-arm-linux-gnueabihf \ 94 | gcc-${GCC_VERSION}-source \ 95 | gcc-${GCC_ARCH}-linux-gnu \ 96 | binutils-${GCC_ARCH}-linux-gnu \ 97 | libstdc++-${GCC_VERSION}-dev-${PACKAGE_ARCH}-cross \ 98 | libc6-dev:${PACKAGE_ARCH} \ 99 | linux-libc-dev:${PACKAGE_ARCH} \ 100 | libgcc1:${PACKAGE_ARCH} \ 101 | libstdc++-${GCC_VERSION}-dev:${PACKAGE_ARCH} \ 102 | libfontconfig*-dev:${PACKAGE_ARCH} \ 103 | libcurl*openssl-dev:${PACKAGE_ARCH} \ 104 | libfreetype*-dev:${PACKAGE_ARCH} \ 105 | libssl-dev:${PACKAGE_ARCH} \ 106 | libssl[13].*:${PACKAGE_ARCH} \ 107 | liblttng-ust*:${PACKAGE_ARCH} \ 108 | && apt-get clean autoclean -y \ 109 | && apt-get autoremove -y \ 110 | && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* \ 111 | ; fi 112 | 113 | # Prepare dotnet SDK 114 | RUN wget -O- https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel ${DOTNET_VERSION} --install-dir /usr/local/bin 115 | 116 | # Prepare nodejs 117 | RUN wget https://deb.nodesource.com/setup_${NODEJS_VERSION}.x -O nodejs-install.sh \ 118 | && chmod +x ./nodejs-install.sh \ 119 | && ./nodejs-install.sh \ 120 | && apt-get install -y \ 121 | nodejs 122 | 123 | # Clean up dependencies 124 | RUN apt-get clean autoclean -y \ 125 | && apt-get autoremove -y \ 126 | && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* 127 | 128 | # Link to build script 129 | RUN ln -sf ${SOURCE_DIR}/debian/docker/build.sh /build.sh 130 | 131 | VOLUME ${SOURCE_DIR}/ 132 | 133 | VOLUME ${ARTIFACT_DIR}/ 134 | 135 | ENTRYPOINT ["/build.sh"] 136 | -------------------------------------------------------------------------------- /debian/docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #= Debian .deb builder 4 | 5 | set -o errexit 6 | set -o xtrace 7 | 8 | # Move to source directory 9 | pushd "${SOURCE_DIR}" 10 | 11 | # Build DEB 12 | if [[ ${ARCH} != $( dpkg --print-architecture ) ]]; then 13 | export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} 14 | export CONFIG_CROSS="-a ${ARCH}" 15 | fi 16 | dpkg-buildpackage -us -uc ${CONFIG_CROSS} --pre-clean --post-clean 17 | 18 | mkdir -p "${ARTIFACT_DIR}/" 19 | mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/" 20 | 21 | popd 22 | -------------------------------------------------------------------------------- /debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | pristine-tar = False 3 | cleaner = fakeroot debian/rules clean 4 | 5 | [import-orig] 6 | filter = [ ".git*", ".hg*", ".vs*", ".vscode*" ] 7 | -------------------------------------------------------------------------------- /debian/jellyfin-server.conffiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellyfin/jellyfin-packaging/0878e2583bc2893542c150feb9dc3adb9c1d774f/debian/jellyfin-server.conffiles -------------------------------------------------------------------------------- /debian/jellyfin-server.install: -------------------------------------------------------------------------------- 1 | build/usr/lib/jellyfin usr/lib/ 2 | debian/conf/jellyfin etc/default/ 3 | debian/conf/logging.json etc/jellyfin/ 4 | debian/conf/jellyfin.service lib/systemd/system/ 5 | debian/conf/jellyfin.service.conf etc/systemd/system/jellyfin.service.d/ 6 | -------------------------------------------------------------------------------- /debian/jellyfin-server.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | NAME=jellyfin 5 | DEFAULT_FILE=/etc/default/${NAME} 6 | 7 | # Source Jellyfin default configuration 8 | if [[ -f $DEFAULT_FILE ]]; then 9 | . $DEFAULT_FILE 10 | fi 11 | 12 | JELLYFIN_USER=${JELLYFIN_USER:-jellyfin} 13 | RENDER_GROUP=${RENDER_GROUP:-render} 14 | VIDEO_GROUP=${VIDEO_GROUP:-video} 15 | 16 | # Data directories for program data (cache, db), configs, and logs 17 | PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} 18 | CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} 19 | LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} 20 | CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} 21 | 22 | case "$1" in 23 | configure) 24 | # create jellyfin group if it does not exist 25 | if [[ -z "$(getent group ${JELLYFIN_USER})" ]]; then 26 | addgroup --quiet --system ${JELLYFIN_USER} > /dev/null 2>&1 27 | fi 28 | # create jellyfin user if it does not exist 29 | if [[ -z "$(getent passwd ${JELLYFIN_USER})" ]]; then 30 | adduser --system --ingroup ${JELLYFIN_USER} --shell /bin/false ${JELLYFIN_USER} --no-create-home --home ${PROGRAMDATA} \ 31 | --gecos "Jellyfin default user" > /dev/null 2>&1 32 | fi 33 | # add jellyfin to the render group for hwa 34 | if [[ ! -z "$(getent group ${RENDER_GROUP})" ]]; then 35 | usermod -aG ${RENDER_GROUP} ${JELLYFIN_USER} > /dev/null 2>&1 36 | fi 37 | # add jellyfin to the video group for hwa 38 | if [[ ! -z "$(getent group ${VIDEO_GROUP})" ]]; then 39 | usermod -aG ${VIDEO_GROUP} ${JELLYFIN_USER} > /dev/null 2>&1 40 | fi 41 | 42 | # Ensure directories exist and, on initial creation, are chowned to the correct user(s) 43 | for DIRECTORY in $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA; do 44 | if [[ ! -d $DIRECTORY ]]; then 45 | mkdir -p $DIRECTORY 46 | fi 47 | if [[ $(stat -c '%u' $DIRECTORY) -eq 0 ]]; then 48 | chown -R ${JELLYFIN_USER}:adm $DIRECTORY 49 | chmod 0750 $DIRECTORY 50 | fi 51 | done 52 | 53 | # Refresh ldconfig cache and get libjemalloc.so path 54 | ldconfig &>/dev/null 55 | LIBJEMALLOC_PATH="$( ldconfig -p | grep libjemalloc | awk '{print $NF}' | head -n1 )" 56 | if [[ -z ${LIBJEMALLOC_PATH} ]]; then 57 | # Try to find the path with `find` instead of `ldconfig`; hackier, but a good fallback 58 | LIBJEMALLOC_PATH="$( find /usr/lib -type f -name "libjemalloc.so*" | head -n1 )" 59 | fi 60 | # Install libjemalloc.so symlink into /usr/lib/jellyfin 61 | if [[ -n ${LIBJEMALLOC_PATH} && -f ${LIBJEMALLOC_PATH} ]]; then 62 | ln -sf ${LIBJEMALLOC_PATH} /usr/lib/jellyfin/libjemalloc.so 63 | else 64 | echo "failed to find libjemalloc on system; ensure the 'libjemalloc2' package is [being] installed" 65 | exit 1 66 | fi 67 | 68 | # Install jellyfin symlink into /usr/bin 69 | ln -sf /usr/lib/jellyfin/bin/jellyfin /usr/bin/jellyfin 70 | 71 | ;; 72 | abort-upgrade|abort-remove|abort-deconfigure) 73 | ;; 74 | *) 75 | echo "postinst called with unknown argument \`$1'" >&2 76 | exit 1 77 | ;; 78 | esac 79 | 80 | #DEBHELPER 81 | 82 | if [[ -x "/usr/bin/deb-systemd-helper" ]]; then 83 | # was-enabled defaults to true, so new installations run enable. 84 | if deb-systemd-helper --quiet was-enabled jellyfin.service; then 85 | # Enables the unit on first installation, creates new 86 | # symlinks on upgrades if the unit file has changed. 87 | deb-systemd-helper enable jellyfin.service >/dev/null || true 88 | else 89 | # Update the statefile to add new symlinks (if any), which need to be 90 | # cleaned up on purge. Also remove old symlinks. 91 | deb-systemd-helper update-state jellyfin.service >/dev/null || true 92 | fi 93 | fi 94 | 95 | if [[ "$1" == "configure" ]] || [[ "$1" == "abort-upgrade" ]]; then 96 | if [[ -d "/run/systemd/system" ]]; then 97 | systemctl --system daemon-reload >/dev/null || true 98 | if [[ -z ${DISABLE_JELLYFIN_AUTOSTART} ]]; then 99 | deb-systemd-invoke start jellyfin >/dev/null || true 100 | fi 101 | elif [[ -x "/etc/init.d/jellyfin" ]] || [[ -e "/etc/init/jellyfin.conf" ]]; then 102 | if [[ -z ${DISABLE_JELLYFIN_AUTOSTART} ]]; then 103 | update-rc.d jellyfin defaults >/dev/null 104 | invoke-rc.d jellyfin start || exit $? 105 | fi 106 | fi 107 | fi 108 | exit 0 109 | -------------------------------------------------------------------------------- /debian/jellyfin-server.postrm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | NAME=jellyfin 5 | DEFAULT_FILE=/etc/default/${NAME} 6 | 7 | # Source Jellyfin default configuration 8 | if [[ -f $DEFAULT_FILE ]]; then 9 | . $DEFAULT_FILE 10 | fi 11 | 12 | # Data directories for program data (cache, db), configs, and logs 13 | PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} 14 | CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} 15 | LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} 16 | CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} 17 | 18 | # In case this system is running systemd, we make systemd reload the unit files 19 | # to pick up changes. 20 | if [[ -d /run/systemd/system ]] ; then 21 | systemctl --system daemon-reload >/dev/null || true 22 | fi 23 | 24 | case "$1" in 25 | purge) 26 | echo PURGE | debconf-communicate $NAME > /dev/null 2>&1 || true 27 | 28 | if [[ -x "/etc/init.d/jellyfin" ]] || [[ -e "/etc/init/jellyfin.conf" ]]; then 29 | update-rc.d jellyfin remove >/dev/null 2>&1 || true 30 | fi 31 | 32 | if [[ -x "/usr/bin/deb-systemd-helper" ]]; then 33 | deb-systemd-helper purge jellyfin.service >/dev/null 34 | fi 35 | 36 | # Remove user and group 37 | userdel jellyfin > /dev/null 2>&1 || true 38 | delgroup --quiet jellyfin > /dev/null 2>&1 || true 39 | # Remove config dir 40 | if [[ -d $CONFIGDATA ]]; then 41 | rm -rf $CONFIGDATA || true 42 | fi 43 | # Remove log dir 44 | if [[ -d $LOGDATA ]]; then 45 | rm -rf $LOGDATA || true 46 | fi 47 | # Remove cache dir 48 | if [[ -d $CACHEDATA ]]; then 49 | rm -rf $CACHEDATA || true 50 | fi 51 | # Remove program data dir 52 | if [[ -d $PROGRAMDATA ]]; then 53 | rm -rf $PROGRAMDATA || true 54 | fi 55 | # Remove binary symlink 56 | rm -f /usr/bin/jellyfin 57 | # Remove sudoers config 58 | [[ -f /etc/sudoers.d/jellyfin-sudoers ]] && rm /etc/sudoers.d/jellyfin-sudoers 59 | # Remove anything at the default locations; catches situations where the user moved the defaults 60 | [[ -e /etc/jellyfin ]] && rm -rf /etc/jellyfin || true 61 | [[ -e /var/log/jellyfin ]] && rm -rf /var/log/jellyfin || true 62 | [[ -e /var/cache/jellyfin ]] && rm -rf /var/cache/jellyfin || true 63 | [[ -e /var/lib/jellyfin ]] && rm -rf /var/lib/jellyfin || true 64 | ;; 65 | remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 66 | ;; 67 | *) 68 | echo "postrm called with unknown argument \`$1'" >&2 69 | exit 1 70 | ;; 71 | esac 72 | 73 | #DEBHELPER# 74 | 75 | exit 0 76 | -------------------------------------------------------------------------------- /debian/jellyfin-server.preinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | NAME=jellyfin 5 | DEFAULT_FILE=/etc/default/${NAME} 6 | 7 | # Source Jellyfin default configuration 8 | if [[ -f $DEFAULT_FILE ]]; then 9 | . $DEFAULT_FILE 10 | fi 11 | 12 | # Data directories for program data (cache, db), configs, and logs 13 | PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} 14 | CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} 15 | LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} 16 | CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} 17 | 18 | # In case this system is running systemd, we make systemd reload the unit files 19 | # to pick up changes. 20 | if [[ -d /run/systemd/system ]] ; then 21 | systemctl --system daemon-reload >/dev/null || true 22 | fi 23 | 24 | case "$1" in 25 | install|upgrade) 26 | # try graceful termination; 27 | if [[ -d /run/systemd/system ]]; then 28 | deb-systemd-invoke stop ${NAME}.service > /dev/null 2>&1 || true 29 | elif [ -x "/etc/init.d/${NAME}" ] || [ -e "/etc/init/${NAME}.conf" ]; then 30 | invoke-rc.d ${NAME} stop > /dev/null 2>&1 || true 31 | fi 32 | # try and figure out if jellyfin is running 33 | PIDFILE=$(find /var/run/ -maxdepth 1 -mindepth 1 -name "jellyfin*.pid" -print -quit) 34 | [[ -n "$PIDFILE" ]] && [[ -s "$PIDFILE" ]] && JELLYFIN_PID=$(cat ${PIDFILE}) 35 | # if its running, let's stop it 36 | if [[ -n "$JELLYFIN_PID" ]]; then 37 | echo "Stopping Jellyfin!" 38 | # if jellyfin is still running, kill it 39 | if [[ -n "$(ps -p $JELLYFIN_PID -o pid=)" ]]; then 40 | CPIDS=$(pgrep -P $JELLYFIN_PID) 41 | sleep 2 && kill -KILL $CPIDS 42 | kill -TERM $CPIDS > /dev/null 2>&1 43 | fi 44 | sleep 1 45 | # if it's still running, show error 46 | if [[ -n "$(ps -p $JELLYFIN_PID -o pid=)" ]]; then 47 | echo "Could not successfully stop Jellyfin, please do so before installing." 48 | exit 1 49 | else 50 | [[ -f $PIDFILE ]] && rm $PIDFILE 51 | fi 52 | fi 53 | ;; 54 | abort-upgrade) 55 | ;; 56 | *) 57 | echo "preinst called with unknown argument \`$1'" >&2 58 | exit 1 59 | ;; 60 | esac 61 | #DEBHELPER# 62 | 63 | exit 0 64 | -------------------------------------------------------------------------------- /debian/jellyfin-server.prerm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | NAME=jellyfin 5 | DEFAULT_FILE=/etc/default/${NAME} 6 | 7 | # Source Jellyfin default configuration 8 | if [[ -f $DEFAULT_FILE ]]; then 9 | . $DEFAULT_FILE 10 | fi 11 | 12 | # Data directories for program data (cache, db), configs, and logs 13 | PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} 14 | CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} 15 | LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} 16 | CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} 17 | 18 | case "$1" in 19 | remove|upgrade|deconfigure) 20 | echo "Stopping Jellyfin!" 21 | # try graceful termination; 22 | if [[ -d /run/systemd/system ]]; then 23 | deb-systemd-invoke stop ${NAME}.service > /dev/null 2>&1 || true 24 | elif [ -x "/etc/init.d/${NAME}" ] || [ -e "/etc/init/${NAME}.conf" ]; then 25 | invoke-rc.d ${NAME} stop > /dev/null 2>&1 || true 26 | fi 27 | # Ensure that it is shutdown 28 | PIDFILE=$(find /var/run/ -maxdepth 1 -mindepth 1 -name "jellyfin*.pid" -print -quit) 29 | [[ -n "$PIDFILE" ]] && [[ -s "$PIDFILE" ]] && JELLYFIN_PID=$(cat ${PIDFILE}) 30 | # if its running, let's stop it 31 | if [[ -n "$JELLYFIN_PID" ]]; then 32 | # if jellyfin is still running, kill it 33 | if [[ -n "$(ps -p $JELLYFIN_PID -o pid=)" ]]; then 34 | CPIDS=$(pgrep -P $JELLYFIN_PID) 35 | sleep 2 && kill -KILL $CPIDS 36 | kill -TERM $CPIDS > /dev/null 2>&1 37 | fi 38 | sleep 1 39 | # if it's still running, show error 40 | if [[ -n "$(ps -p $JELLYFIN_PID -o pid=)" ]]; then 41 | echo "Could not successfully stop Jellyfin, please do so before uninstalling." 42 | exit 1 43 | else 44 | [[ -f $PIDFILE ]] && rm $PIDFILE 45 | fi 46 | fi 47 | if [[ -f /usr/lib/jellyfin/bin/MediaBrowser.Server.Mono.exe.so ]]; then 48 | rm /usr/lib/jellyfin/bin/MediaBrowser.Server.Mono.exe.so 49 | fi 50 | ;; 51 | failed-upgrade) 52 | ;; 53 | *) 54 | echo "prerm called with unknown argument \`$1'" >&2 55 | exit 1 56 | ;; 57 | esac 58 | 59 | #DEBHELPER# 60 | 61 | exit 0 62 | -------------------------------------------------------------------------------- /debian/jellyfin-web.conffiles: -------------------------------------------------------------------------------- 1 | /usr/share/jellyfin/web/config.json 2 | -------------------------------------------------------------------------------- /debian/jellyfin-web.install: -------------------------------------------------------------------------------- 1 | build/web usr/share/jellyfin/ 2 | -------------------------------------------------------------------------------- /debian/po/POTFILES.in: -------------------------------------------------------------------------------- 1 | [type: gettext/rfc822deb] templates 2 | -------------------------------------------------------------------------------- /debian/po/templates.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: jellyfin-server\n" 10 | "Report-Msgid-Bugs-To: jellyfin-server@packages.debian.org\n" 11 | "POT-Creation-Date: 2015-06-12 20:51-0600\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #. Type: note 21 | #. Description 22 | #: ../templates:1001 23 | msgid "Jellyfin permission info:" 24 | msgstr "" 25 | 26 | #. Type: note 27 | #. Description 28 | #: ../templates:1001 29 | msgid "" 30 | "Jellyfin by default runs under a user named \"jellyfin\". Please ensure that the " 31 | "user jellyfin has read and write access to any folders you wish to add to your " 32 | "library. Otherwise please run jellyfin under a different user." 33 | msgstr "" 34 | 35 | #. Type: string 36 | #. Description 37 | #: ../templates:2001 38 | msgid "Username to run Jellyfin as:" 39 | msgstr "" 40 | 41 | #. Type: string 42 | #. Description 43 | #: ../templates:2001 44 | msgid "The user that jellyfin will run as." 45 | msgstr "" 46 | 47 | #. Type: note 48 | #. Description 49 | #: ../templates:3001 50 | msgid "Jellyfin still running" 51 | msgstr "" 52 | 53 | #. Type: note 54 | #. Description 55 | #: ../templates:3001 56 | msgid "Jellyfin is currently running. Please close it and try again." 57 | msgstr "" 58 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #! /usr/bin/make -f 2 | CONFIG ?= Release 3 | TERM := xterm 4 | SHELL := /bin/bash 5 | 6 | HOST_ARCH := $(shell arch) 7 | BUILD_ARCH := ${DEB_HOST_MULTIARCH} 8 | ifeq ($(HOST_ARCH),x86_64) 9 | # Building AMD64 10 | DOTNETRUNTIME := linux-x64 11 | ifeq ($(BUILD_ARCH),arm-linux-gnueabihf) 12 | # Cross-building ARM on AMD64 13 | DOTNETRUNTIME := linux-arm 14 | endif 15 | ifeq ($(BUILD_ARCH),aarch64-linux-gnu) 16 | # Cross-building ARM on AMD64 17 | DOTNETRUNTIME := linux-arm64 18 | endif 19 | endif 20 | ifeq ($(HOST_ARCH),armv7l) 21 | # Building ARM 22 | DOTNETRUNTIME := linux-arm 23 | endif 24 | ifeq ($(HOST_ARCH),arm64) 25 | # Building ARM 26 | DOTNETRUNTIME := linux-arm64 27 | endif 28 | 29 | export DH_VERBOSE=1 30 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 31 | export COMPlus_LTTng=0 32 | export COMPlus_EnableDiagnostics=0 33 | 34 | %: 35 | dh $@ 36 | 37 | override_dh_gencontrol: 38 | ICU_MAJOR="$$(apt-cache policy libicu-dev 2>/dev/null \ 39 | | sed -n 's/ Candidate: \([0-9]\+\)\..*/\1/p' | head -n1)" && \ 40 | dh_gencontrol -- -Vicu:Depends="libicu$$ICU_MAJOR" 41 | 42 | override_dh_auto_test: 43 | 44 | override_dh_clistrip: 45 | 46 | override_dh_auto_build: 47 | cd jellyfin-server && dotnet publish -maxcpucount:1 --configuration $(CONFIG) \ 48 | --output='$(CURDIR)/build/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \ 49 | -p:DebugSymbols=false -p:DebugType=none Jellyfin.Server 50 | rm -f $(CURDIR)/build/usr/lib/jellyfin/bin/libcoreclrtraceptprovider.so 51 | cd jellyfin-web && npm ci --no-audit --unsafe-perm && \ 52 | npm run build:production && mv $(CURDIR)/jellyfin-web/dist $(CURDIR)/build/web 53 | 54 | override_dh_auto_clean: 55 | cd jellyfin-server && dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true 56 | cd jellyfin-server && find . -type d \( -name bin -o -name obj \) -exec rm -r {} \; || true 57 | test -d $(CURDIR)/jellyfin-web/dist && \ 58 | rm -rf '$(CURDIR)/jellyfin-web/dist' || true 59 | test -d $(CURDIR)/jellyfin-web/web && \ 60 | rm -rf '$(CURDIR)/jellyfin-web/web' || true 61 | test -d $(CURDIR)/jellyfin-web/node_modules && \ 62 | rm -rf '$(CURDIR)/jellyfin-web/node_modules' || true 63 | rm -rf '$(CURDIR)/build' 64 | 65 | override_dh_installinit: 66 | dh_installinit --name=jellyfin 67 | 68 | override_dh_shdeplibs: 69 | dh_shlibdeps -- --ignore-missing-info 70 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 1.0 2 | -------------------------------------------------------------------------------- /debian/source/options: -------------------------------------------------------------------------------- 1 | tar-ignore='.git*' 2 | tar-ignore='**/.git' 3 | tar-ignore='**/.hg' 4 | tar-ignore='**/.vs' 5 | tar-ignore='**/.vscode' 6 | tar-ignore='**/obj' 7 | tar-ignore='**/bin' 8 | tar-ignore = "out/" 9 | tar-ignore = "fedora/" 10 | tar-ignore = "docker/" 11 | tar-ignore = "*.py" 12 | tar-ignore = "*.md" 13 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker build arguments 2 | ARG DOTNET_VERSION=8.0 3 | ARG NODEJS_VERSION=20 4 | 5 | # Combined image version (Debian) 6 | ARG OS_VERSION=trixie 7 | 8 | # Jellyfin FFMPEG package 9 | ARG FFMPEG_PACKAGE=jellyfin-ffmpeg7 10 | 11 | # https://github.com/intel/compute-runtime/releases 12 | ARG GMMLIB_VER=22.8.1 13 | # >= Gen12 graphics (current) 14 | ARG IGC2_VER=2.18.5 15 | ARG IGC2_BUILD=19820 16 | ARG NEO_VER=25.35.35096.9 17 | # <= Gen11 graphics (legacy) 18 | ARG IGC1_LEGACY_VER=1.0.17537.24 19 | ARG NEO_LEGACY_VER=24.35.30872.36 20 | 21 | # https://github.com/tsukumijima/libmali-rockchip 22 | ARG MALI_PKG_VER=1.9-1_arm64 23 | ARG MALI_PKG_TAG=v1.9-1-2131373 24 | ARG MALI_PKG_CFG=valhall-g610-g24p0-gbm 25 | 26 | # Debian architecture (amd64, arm64, armhf), set by build script 27 | ARG PACKAGE_ARCH 28 | # Dotnet architeture (x64, arm64, arm), set by build script 29 | ARG DOTNET_ARCH 30 | # Base Image architecture (amd64, arm64v8, arm32v7), set by build script 31 | ARG IMAGE_ARCH 32 | # Target platform architecture (amd64, arm64/v8, arm/v7), set by build script 33 | ARG TARGET_ARCH 34 | 35 | # Jellyfin version 36 | ARG JELLYFIN_VERSION 37 | 38 | # 39 | # Build the web artifacts 40 | # 41 | FROM node:${NODEJS_VERSION}-alpine AS web 42 | 43 | ARG SOURCE_DIR=/src 44 | ARG ARTIFACT_DIR=/web 45 | 46 | ARG JELLYFIN_VERSION 47 | ENV JELLYFIN_VERSION=${JELLYFIN_VERSION} 48 | 49 | RUN apk add \ 50 | autoconf \ 51 | g++ \ 52 | make \ 53 | libpng-dev \ 54 | gifsicle \ 55 | alpine-sdk \ 56 | automake \ 57 | libtool \ 58 | gcc \ 59 | musl-dev \ 60 | nasm \ 61 | python3 \ 62 | git \ 63 | && git config --global --add safe.directory /jellyfin/jellyfin-web 64 | 65 | WORKDIR ${SOURCE_DIR} 66 | COPY jellyfin-web . 67 | 68 | RUN npm ci --no-audit --unsafe-perm \ 69 | && npm run build:production \ 70 | && mv dist ${ARTIFACT_DIR} 71 | 72 | # 73 | # Build the server artifacts 74 | # 75 | FROM debian:${OS_VERSION}-slim AS server 76 | 77 | ARG DOTNET_ARCH 78 | ARG DOTNET_VERSION 79 | 80 | ARG SOURCE_DIR=/src 81 | ARG ARTIFACT_DIR=/server 82 | 83 | ARG CONFIG=Release 84 | ENV CONFIG=${CONFIG} 85 | 86 | WORKDIR ${SOURCE_DIR} 87 | COPY jellyfin-server . 88 | ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 89 | 90 | RUN apt-get update \ 91 | && apt-get install --no-install-recommends --no-install-suggests --yes \ 92 | curl \ 93 | ca-certificates \ 94 | libicu76 \ 95 | && curl -fsSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel ${DOTNET_VERSION} --install-dir /usr/local/bin 96 | 97 | RUN dotnet publish Jellyfin.Server --arch ${DOTNET_ARCH} --configuration ${CONFIG} \ 98 | --output="${ARTIFACT_DIR}" --self-contained \ 99 | -p:DebugSymbols=false -p:DebugType=none 100 | 101 | # 102 | # Build the final combined image 103 | # 104 | FROM --platform=linux/${TARGET_ARCH} ${IMAGE_ARCH}/debian:${OS_VERSION}-slim AS combined 105 | 106 | ARG OS_VERSION 107 | ARG FFMPEG_PACKAGE 108 | 109 | ARG GMMLIB_VER 110 | ARG IGC2_VER 111 | ARG IGC2_BUILD 112 | ARG NEO_VER 113 | ARG IGC1_LEGACY_VER 114 | ARG NEO_LEGACY_VER 115 | 116 | ARG MALI_PKG_VER 117 | ARG MALI_PKG_TAG 118 | ARG MALI_PKG_CFG 119 | 120 | ARG PACKAGE_ARCH 121 | 122 | # Set the health URL 123 | ENV HEALTHCHECK_URL=http://localhost:8096/health 124 | 125 | # Default environment variables for the Jellyfin invocation 126 | ENV DEBIAN_FRONTEND="noninteractive" \ 127 | LC_ALL="en_US.UTF-8" \ 128 | LANG="en_US.UTF-8" \ 129 | LANGUAGE="en_US:en" \ 130 | JELLYFIN_DATA_DIR="/config" \ 131 | JELLYFIN_CACHE_DIR="/cache" \ 132 | JELLYFIN_CONFIG_DIR="/config/config" \ 133 | JELLYFIN_LOG_DIR="/config/log" \ 134 | JELLYFIN_WEB_DIR="/jellyfin/jellyfin-web" \ 135 | JELLYFIN_FFMPEG="/usr/lib/jellyfin-ffmpeg/ffmpeg" 136 | 137 | # required for fontconfig cache 138 | ENV XDG_CACHE_HOME=${JELLYFIN_CACHE_DIR} 139 | 140 | # https://github.com/dlemstra/Magick.NET/issues/707#issuecomment-785351620 141 | ENV MALLOC_TRIM_THRESHOLD_=131072 142 | 143 | # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) 144 | ENV NVIDIA_VISIBLE_DEVICES="all" 145 | ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" 146 | 147 | # Install dependencies: 148 | RUN apt-get update \ 149 | && apt-get install --no-install-recommends --no-install-suggests --yes \ 150 | ca-certificates \ 151 | gnupg \ 152 | curl \ 153 | && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key \ 154 | | gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg \ 155 | && cat < /etc/apt/sources.list.d/jellyfin.sources 156 | Types: deb 157 | URIs: https://repo.jellyfin.org/debian 158 | Suites: ${OS_VERSION} 159 | Components: main 160 | Architectures: ${PACKAGE_ARCH} 161 | Signed-By: /etc/apt/keyrings/jellyfin.gpg 162 | EOF 163 | 164 | RUN apt-get update \ 165 | && apt-get install --no-install-recommends --no-install-suggests --yes \ 166 | ${FFMPEG_PACKAGE} \ 167 | openssl \ 168 | locales \ 169 | libicu76 \ 170 | libfontconfig1 \ 171 | libfreetype6 \ 172 | libjemalloc2 \ 173 | && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen \ 174 | && apt-get remove gnupg apt-transport-https --yes \ 175 | && apt-get clean autoclean --yes \ 176 | && apt-get autoremove --yes \ 177 | && rm -rf /var/cache/apt/archives* /var/lib/apt/lists/* 178 | 179 | # Intel OpenCL Tone mapping dependencies: 180 | RUN if test "${PACKAGE_ARCH}" = "amd64"; then \ 181 | mkdir intel-compute-runtime \ 182 | && cd intel-compute-runtime \ 183 | && curl -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VER}/libigdgmm12_${GMMLIB_VER}_amd64.deb \ 184 | -LO https://github.com/intel/intel-graphics-compiler/releases/download/v${IGC2_VER}/intel-igc-core-2_${IGC2_VER}+${IGC2_BUILD}_amd64.deb \ 185 | -LO https://github.com/intel/intel-graphics-compiler/releases/download/v${IGC2_VER}/intel-igc-opencl-2_${IGC2_VER}+${IGC2_BUILD}_amd64.deb \ 186 | -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VER}/intel-opencl-icd_${NEO_VER}-0_amd64.deb \ 187 | -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC1_LEGACY_VER}/intel-igc-core_${IGC1_LEGACY_VER}_amd64.deb \ 188 | -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC1_LEGACY_VER}/intel-igc-opencl_${IGC1_LEGACY_VER}_amd64.deb \ 189 | -LO https://github.com/intel/compute-runtime/releases/download/${NEO_LEGACY_VER}/intel-opencl-icd-legacy1_${NEO_LEGACY_VER}_amd64.deb \ 190 | && apt-get install --no-install-recommends --no-install-suggests -f -y ./*.deb \ 191 | && cd .. \ 192 | && rm -rf intel-compute-runtime \ 193 | ; fi \ 194 | && apt-get clean autoclean --yes \ 195 | && apt-get autoremove --yes \ 196 | && rm -rf /var/cache/apt/archives* /var/lib/apt/lists/* 197 | 198 | # Rockchip RK3588 libmali OpenCL dependencies: 199 | RUN if test "${PACKAGE_ARCH}" = "arm64"; then \ 200 | mkdir libmali-rockchip \ 201 | && cd libmali-rockchip \ 202 | && curl -LO https://github.com/tsukumijima/libmali-rockchip/releases/download/${MALI_PKG_TAG}/libmali-${MALI_PKG_CFG}_${MALI_PKG_VER}.deb \ 203 | && apt-get install --no-install-recommends --no-install-suggests -f -y ./*.deb \ 204 | && cd .. \ 205 | && rm -rf libmali-rockchip \ 206 | ; fi \ 207 | && apt-get clean autoclean --yes \ 208 | && apt-get autoremove --yes \ 209 | && rm -rf /var/cache/apt/archives* /var/lib/apt/lists/* 210 | 211 | # Add fonts for east asian languages rendering 212 | RUN apt update --yes \ 213 | && apt install --no-install-recommends --no-install-suggests --yes \ 214 | fonts-wqy-zenhei \ 215 | fonts-wqy-microhei \ 216 | fonts-arphic-ukai \ 217 | fonts-arphic-uming \ 218 | fonts-noto-cjk \ 219 | fonts-ipafont-mincho \ 220 | fonts-ipafont-gothic \ 221 | fonts-unfonts-core \ 222 | && apt clean autoclean --yes \ 223 | && apt autoremove --yes \ 224 | && rm -rf /var/cache/apt/archives* /var/lib/apt/lists/* 225 | 226 | # Setup jemalloc: link the library to a path owned by us to handle arch specific library paths 227 | RUN mkdir -p /usr/lib/jellyfin \ 228 | && JEMALLOC_LINKED=0 \ 229 | && if [ "${PACKAGE_ARCH}" = "amd64" ]; then \ 230 | if [ -f "/usr/lib/x86_64-linux-gnu/libjemalloc.so.2" ]; then \ 231 | ln -s /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/jellyfin/libjemalloc.so.2 && JEMALLOC_LINKED=1; \ 232 | fi; \ 233 | elif [ "${PACKAGE_ARCH}" = "arm64" ]; then \ 234 | if [ -f "/usr/lib/aarch64-linux-gnu/libjemalloc.so.2" ]; then \ 235 | ln -s /usr/lib/aarch64-linux-gnu/libjemalloc.so.2 /usr/lib/jellyfin/libjemalloc.so.2 && JEMALLOC_LINKED=1; \ 236 | fi; \ 237 | fi \ 238 | && if [ "$JEMALLOC_LINKED" -eq 1 ]; then \ 239 | echo "jemalloc library linked successfully for ${PACKAGE_ARCH}." ; \ 240 | else \ 241 | echo "WARNING: jemalloc library .so file not found for PACKAGE_ARCH ${PACKAGE_ARCH}. Either the arch is not supported or copy failed. LD_PRELOAD might not work as expected." >&2; \ 242 | fi 243 | 244 | # Set LD_PRELOAD to use the linked jemalloc library 245 | ENV LD_PRELOAD=/usr/lib/jellyfin/libjemalloc.so.2 246 | 247 | RUN mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \ 248 | && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} 249 | 250 | COPY --from=server /server /jellyfin 251 | COPY --from=web /web /jellyfin/jellyfin-web 252 | ARG JELLYFIN_VERSION 253 | LABEL "org.opencontainers.image.source"="https://github.com/jellyfin/jellyfin-packaging" 254 | LABEL "org.opencontainers.image.title"="Jellyfin" 255 | LABEL "org.opencontainers.image.description"="The Free Software Media System" 256 | LABEL "org.opencontainers.image.documentation"="https://jellyfin.org/docs/" 257 | LABEL "org.opencontainers.image.version"="${JELLYFIN_VERSION}" 258 | LABEL "org.opencontainers.image.url"="https://jellyfin.org" 259 | 260 | EXPOSE 8096 261 | VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} 262 | ENTRYPOINT ["/jellyfin/jellyfin"] 263 | 264 | HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ 265 | CMD curl --noproxy 'localhost' -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 266 | -------------------------------------------------------------------------------- /portable/Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker build arguments 2 | ARG DOTNET_VERSION=8.0 3 | ARG NODEJS_VERSION=20 4 | 5 | ARG PACKAGE_TYPE=debian 6 | ARG PACKAGE_VERSION=12 7 | 8 | FROM ${PACKAGE_TYPE}:${PACKAGE_VERSION} 9 | 10 | ARG SOURCE_DIR=/jellyfin 11 | ARG ARTIFACT_DIR=/dist 12 | ARG DOTNET_VERSION 13 | ARG NODEJS_VERSION 14 | ARG PACKAGE_TYPE 15 | ARG PACKAGE_VERSION 16 | 17 | # Docker run environment 18 | ENV SOURCE_DIR=/jellyfin 19 | ENV ARTIFACT_DIR=/dist 20 | ENV TYPE=${PACKAGE_TYPE} 21 | ENV VERSION=${PACKAGE_VERSION} 22 | ENV ARCHIVE_TYPES=targz 23 | 24 | # Prepare Debian build environment 25 | RUN apt-get update -y \ 26 | && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC \ 27 | apt-get install --no-install-recommends -y \ 28 | wget \ 29 | unzip \ 30 | git \ 31 | debhelper \ 32 | gnupg \ 33 | devscripts \ 34 | build-essential \ 35 | git \ 36 | mmv \ 37 | lsb-release\ 38 | zip \ 39 | libssl*.* \ 40 | liblttng-ust*\ 41 | libssl-dev \ 42 | libicu-dev \ 43 | icu-devtools \ 44 | libfontconfig*-dev \ 45 | libcurl*openssl-dev \ 46 | libfreetype*-dev \ 47 | && apt-get clean autoclean -y \ 48 | && apt-get autoremove -y \ 49 | && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* \ 50 | && git config --global --add safe.directory /jellyfin/jellyfin-web 51 | 52 | # Prepare dotnet SDK 53 | RUN wget https://packages.microsoft.com/config/${PACKAGE_TYPE}/${PACKAGE_VERSION}/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \ 54 | && dpkg -i packages-microsoft-prod.deb \ 55 | && apt-get -f install \ 56 | && apt-get update \ 57 | && apt-get install -y dotnet-sdk-${DOTNET_VERSION} 58 | 59 | # Prepare nodejs 60 | RUN wget https://deb.nodesource.com/setup_${NODEJS_VERSION}.x -O nodejs-install.sh \ 61 | && chmod +x ./nodejs-install.sh \ 62 | && ./nodejs-install.sh \ 63 | && apt-get install -y \ 64 | nodejs 65 | 66 | # Clean up dependencies 67 | RUN apt-get clean autoclean -y \ 68 | && apt-get autoremove -y \ 69 | && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* 70 | 71 | # Link to build script 72 | RUN ln -sf ${SOURCE_DIR}/portable/build.sh /build.sh 73 | 74 | VOLUME ${SOURCE_DIR}/ 75 | 76 | VOLUME ${ARTIFACT_DIR}/ 77 | 78 | ENTRYPOINT ["/build.sh"] 79 | -------------------------------------------------------------------------------- /portable/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #= Generic portable builder (portable, linux, macos, windows) 4 | 5 | set -o errexit 6 | set -o xtrace 7 | 8 | # Set global variables 9 | REPOSITORY_URI="https://repo.jellyfin.org" 10 | FFMPEG_VERSION="7.x" 11 | 12 | # Create the intermediate build dir 13 | BUILD_DIR="/build" 14 | mkdir -p ${BUILD_DIR}/jellyfin 15 | 16 | # Move to source directory 17 | pushd "${SOURCE_DIR}" 18 | 19 | # Build server 20 | pushd jellyfin-server 21 | case ${BUILD_TYPE} in 22 | portable) 23 | RUNTIME="" 24 | APPHOST="-p:UseAppHost=false" 25 | ;; 26 | *) 27 | RUNTIME="--self-contained --runtime ${DOTNET_TYPE}-${DOTNET_ARCH}" 28 | APPHOST="-p:UseAppHost=true" 29 | ;; 30 | esac 31 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 32 | if [[ -z ${CONFIG} ]]; then 33 | CONFIG="Release" 34 | fi 35 | dotnet publish Jellyfin.Server --configuration ${CONFIG} ${RUNTIME} --output ${BUILD_DIR}/jellyfin/ -p:DebugSymbols=false -p:DebugType=none ${APPHOST} 36 | popd 37 | 38 | # Build web 39 | pushd jellyfin-web 40 | npm ci --no-audit --unsafe-perm 41 | npm run build:production 42 | mv dist ${BUILD_DIR}/jellyfin/jellyfin-web 43 | popd 44 | 45 | mkdir -p "${ARTIFACT_DIR}/" 46 | 47 | if [[ -n ${PACKAGE_ARCH} ]]; then 48 | VERSION_SUFFIX="${JELLYFIN_VERSION}-${PACKAGE_ARCH}" 49 | else 50 | VERSION_SUFFIX="${JELLYFIN_VERSION}" 51 | fi 52 | 53 | pushd ${BUILD_DIR} 54 | 55 | pushd jellyfin 56 | # Fetch any additional package(s) needed here (i.e. FFmpeg) 57 | # This is a hack because the ffmpeg naming is very inconsistent and we need to find the right URL(s) from the repo browser 58 | case ${BUILD_TYPE}-${PACKAGE_ARCH} in 59 | # linux-amd64*) 60 | # FFMPEG_PATH=$( curl ${REPOSITORY_URI}/?path=/ffmpeg/linux/latest-${FFMPEG_VERSION}/amd64 | grep -o "/files/.*_portable_linux64-gpl.tar.xz'" | sed "s/'$//" ) 61 | # curl --location --output ffmpeg.tar.xz ${REPOSITORY_URI}${FFMPEG_PATH} 62 | # tar -xvJf ffmpeg.tar.xz 63 | # rm ffmpeg.tar.xz 64 | # ;; 65 | # linux-arm64*) 66 | # FFMPEG_PATH=$( curl ${REPOSITORY_URI}/?path=/ffmpeg/linux/latest-${FFMPEG_VERSION}/arm64 | grep -o "/files/.*_portable_linuxarm64-gpl.tar.xz'" | sed "s/'$//" ) 67 | # curl --location --output ffmpeg.tar.xz ${REPOSITORY_URI}${FFMPEG_PATH} 68 | # tar -xvJf ffmpeg.tar.xz 69 | # rm ffmpeg.tar.xz 70 | # ;; 71 | # macos-amd64) 72 | # FFMPEG_PATH=$( curl ${REPOSITORY_URI}/?path=/ffmpeg/macos/latest-${FFMPEG_VERSION}/x86_64 | grep -o "/files/.*_portable_mac64-gpl.tar.xz'" | sed "s/'$//" ) 73 | # curl --location --output ffmpeg.tar.xz ${REPOSITORY_URI}${FFMPEG_PATH} 74 | # tar -xvJf ffmpeg.tar.xz 75 | # rm ffmpeg.tar.xz 76 | # ;; 77 | # macos-arm64) 78 | # FFMPEG_PATH=$( curl ${REPOSITORY_URI}/?path=/ffmpeg/macos/latest-${FFMPEG_VERSION}/arm64 | grep -o "/files/.*_portable_macarm64-gpl.tar.xz'" | sed "s/'$//" ) 79 | # curl --location --output ffmpeg.tar.xz ${REPOSITORY_URI}${FFMPEG_PATH} 80 | # tar -xvJf ffmpeg.tar.xz 81 | # rm ffmpeg.tar.xz 82 | # ;; 83 | windows-amd64) 84 | FFMPEG_PATH=$( curl ${REPOSITORY_URI}/?path=/ffmpeg/windows/latest-${FFMPEG_VERSION}/win64 | grep -o "/files/.*_portable_win64-clang-gpl.zip'" | sed "s/'$//" ) 85 | curl --location --output ffmpeg.zip ${REPOSITORY_URI}${FFMPEG_PATH} 86 | unzip ffmpeg.zip 87 | rm ffmpeg.zip 88 | ;; 89 | *) 90 | true 91 | ;; 92 | esac 93 | popd 94 | 95 | for ARCHIVE_TYPE in $( tr ',' '\n' <<<"${ARCHIVE_TYPES}" ); do 96 | case ${ARCHIVE_TYPE} in 97 | targz) 98 | tar -czf "${ARTIFACT_DIR}"/jellyfin_${VERSION_SUFFIX}.tar.gz jellyfin/ 99 | ;; 100 | tarxz) 101 | tar -cJf "${ARTIFACT_DIR}"/jellyfin_${VERSION_SUFFIX}.tar.xz jellyfin/ 102 | ;; 103 | zip) 104 | zip -qr "${ARTIFACT_DIR}"/jellyfin_${VERSION_SUFFIX}.zip jellyfin/ 105 | ;; 106 | esac 107 | done 108 | popd 109 | 110 | # Clean up any lingering artifacts 111 | make -f debian/rules clean 112 | rm -rf ${BUILD_DIR} 113 | 114 | popd 115 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | GitPython 2 | pyyaml 3 | packaging 4 | --------------------------------------------------------------------------------