diff --git a/.github/ISSUE_TEMPLATE/issue.bug.yml b/.github/ISSUE_TEMPLATE/issue.bug.yml
index f9d4281..ce3d19a 100755
--- a/.github/ISSUE_TEMPLATE/issue.bug.yml
+++ b/.github/ISSUE_TEMPLATE/issue.bug.yml
@@ -4,11 +4,73 @@ description: Create a report to help us improve
title: "[BUG]
"
labels: [Bug]
body:
- - type: markdown
+ - type: checkboxes
attributes:
+ label: Is there an existing issue for this?
+ description: Please search to see if an issue already exists for the bug you encountered.
+ options:
+ - label: I have searched the existing issues
+ required: true
+ - type: textarea
+ attributes:
+ label: Current Behavior
+ description: Tell us what happens instead of the expected behavior.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Expected Behavior
+ description: Tell us what should happen.
+ validations:
+ required: false
+ - type: textarea
+ attributes:
+ label: Steps To Reproduce
+ description: Steps to reproduce the behavior.
+ placeholder: |
+ 1. In this environment...
+ 2. With this config...
+ 3. Run '...'
+ 4. See error...
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Environment
+ description: |
+ examples:
+ - **OS**: Ubuntu 20.04
+ - **How docker service was installed**: distro's packagemanager
value: |
- # DEPRECATION NOTICE
-
- This image is deprecated. We will not offer support for this image and it will not be updated.
-
- COPS has been abandoned by its developers and no actively maintained forks exist at this time.
+ - OS:
+ - How docker service was installed:
+ render: markdown
+ validations:
+ required: false
+ - type: dropdown
+ attributes:
+ label: CPU architecture
+ options:
+ - x86-64
+ - arm64
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Docker creation
+ description: |
+ Command used to create docker container
+ Provide your docker create/run command or compose yaml snippet, or a screenshot of settings if using a gui to create the container
+ render: bash
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ description: |
+ Provide a full docker log, output of "docker logs linuxserver.io"
+ label: Container logs
+ placeholder: |
+ Output of `docker logs linuxserver.io`
+ render: bash
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/issue.feature.yml b/.github/ISSUE_TEMPLATE/issue.feature.yml
index c2f68fc..099dcdb 100755
--- a/.github/ISSUE_TEMPLATE/issue.feature.yml
+++ b/.github/ISSUE_TEMPLATE/issue.feature.yml
@@ -4,11 +4,28 @@ description: Suggest an idea for this project
title: "[FEAT] "
labels: [enhancement]
body:
- - type: markdown
+ - type: checkboxes
attributes:
- value: |
- # DEPRECATION NOTICE
-
- This image is deprecated. We will not offer support for this image and it will not be updated.
-
- COPS has been abandoned by its developers and no actively maintained forks exist at this time.
+ label: Is this a new feature request?
+ description: Please search to see if a feature request already exists.
+ options:
+ - label: I have searched the existing issues
+ required: true
+ - type: textarea
+ attributes:
+ label: Wanted change
+ description: Tell us what you want to happen.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Reason for change
+ description: Justify your request, why do you want it, what is the benefit.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: Proposed code change
+ description: Do you have a potential code change in mind?
+ validations:
+ required: false
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 6d27392..eef3a96 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,7 +1,3 @@
-# DEPRECATION NOTICE
-
-This image is deprecated. We will not offer support for this image and it will not be updated.
-COPS has been abandoned by its developers and no actively maintained forks exist at this time.
[linuxserverurl]: https://linuxserver.io
diff --git a/.github/workflows/call_issue_pr_tracker.yml b/.github/workflows/call_issue_pr_tracker.yml
new file mode 100644
index 0000000..2c30784
--- /dev/null
+++ b/.github/workflows/call_issue_pr_tracker.yml
@@ -0,0 +1,16 @@
+name: Issue & PR Tracker
+
+on:
+ issues:
+ types: [opened,reopened,labeled,unlabeled,closed]
+ pull_request_target:
+ types: [opened,reopened,review_requested,review_request_removed,labeled,unlabeled,closed]
+ pull_request_review:
+ types: [submitted,edited,dismissed]
+
+jobs:
+ manage-project:
+ permissions:
+ issues: write
+ uses: linuxserver/github-workflows/.github/workflows/issue-pr-tracker.yml@v1
+ secrets: inherit
diff --git a/.github/workflows/call_issues_cron.yml b/.github/workflows/call_issues_cron.yml
new file mode 100644
index 0000000..559a6c8
--- /dev/null
+++ b/.github/workflows/call_issues_cron.yml
@@ -0,0 +1,13 @@
+name: Mark stale issues and pull requests
+on:
+ schedule:
+ - cron: '40 3 * * *'
+ workflow_dispatch:
+
+jobs:
+ stale:
+ permissions:
+ issues: write
+ pull-requests: write
+ uses: linuxserver/github-workflows/.github/workflows/issues-cron.yml@v1
+ secrets: inherit
diff --git a/.github/workflows/external_trigger.yml b/.github/workflows/external_trigger.yml
new file mode 100644
index 0000000..ae6f06d
--- /dev/null
+++ b/.github/workflows/external_trigger.yml
@@ -0,0 +1,104 @@
+name: External Trigger Main
+
+on:
+ workflow_dispatch:
+
+jobs:
+ external-trigger-master:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3.1.0
+
+ - name: External Trigger
+ if: github.ref == 'refs/heads/master'
+ run: |
+ if [ -n "${{ secrets.PAUSE_EXTERNAL_TRIGGER_COPS_MASTER }}" ]; then
+ echo "**** Github secret PAUSE_EXTERNAL_TRIGGER_COPS_MASTER is set; skipping trigger. ****"
+ echo "Github secret \`PAUSE_EXTERNAL_TRIGGER_COPS_MASTER\` is set; skipping trigger." >> $GITHUB_STEP_SUMMARY
+ exit 0
+ fi
+ echo "**** External trigger running off of master branch. To disable this trigger, set a Github secret named \"PAUSE_EXTERNAL_TRIGGER_COPS_MASTER\". ****"
+ echo "External trigger running off of master branch. To disable this trigger, set a Github secret named \`PAUSE_EXTERNAL_TRIGGER_COPS_MASTER\`" >> $GITHUB_STEP_SUMMARY
+ echo "**** Retrieving external version ****"
+ EXT_RELEASE=$(curl -u "${{ secrets.CR_USER }}:${{ secrets.CR_PAT }}" -sX GET "https://api.github.com/repos/mikespub-org/seblucas-cops/releases/latest" | jq -r '. | .tag_name')
+ if [ -z "${EXT_RELEASE}" ] || [ "${EXT_RELEASE}" == "null" ]; then
+ echo "**** Can't retrieve external version, exiting ****"
+ FAILURE_REASON="Can't retrieve external version for cops branch master"
+ GHA_TRIGGER_URL="https://github.com/linuxserver/docker-cops/actions/runs/${{ github.run_id }}"
+ curl -X POST -H "Content-Type: application/json" --data '{"avatar_url": "https://cdn.discordapp.com/avatars/354986384542662657/df91181b3f1cf0ef1592fbe18e0962d7.png","embeds": [{"color": 16711680,
+ "description": "**Trigger Failed** \n**Reason:** '"${FAILURE_REASON}"' \n**Trigger URL:** '"${GHA_TRIGGER_URL}"' \n"}],
+ "username": "Github Actions"}' ${{ secrets.DISCORD_WEBHOOK }}
+ exit 1
+ fi
+ EXT_RELEASE=$(echo ${EXT_RELEASE} | sed 's/[~,%@+;:/]//g')
+ echo "**** External version: ${EXT_RELEASE} ****"
+ echo "External version: ${EXT_RELEASE}" >> $GITHUB_STEP_SUMMARY
+ echo "**** Retrieving last pushed version ****"
+ image="linuxserver/cops"
+ tag="latest"
+ token=$(curl -sX GET \
+ "https://ghcr.io/token?scope=repository%3Alinuxserver%2Fcops%3Apull" \
+ | jq -r '.token')
+ multidigest=$(curl -s \
+ --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
+ --header "Authorization: Bearer ${token}" \
+ "https://ghcr.io/v2/${image}/manifests/${tag}" \
+ | jq -r 'first(.manifests[].digest)')
+ digest=$(curl -s \
+ --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
+ --header "Authorization: Bearer ${token}" \
+ "https://ghcr.io/v2/${image}/manifests/${multidigest}" \
+ | jq -r '.config.digest')
+ image_info=$(curl -sL \
+ --header "Authorization: Bearer ${token}" \
+ "https://ghcr.io/v2/${image}/blobs/${digest}")
+ if [[ $(echo $image_info | jq -r '.container_config') == "null" ]]; then
+ image_info=$(echo $image_info | jq -r '.config')
+ else
+ image_info=$(echo $image_info | jq -r '.container_config')
+ fi
+ IMAGE_RELEASE=$(echo ${image_info} | jq -r '.Labels.build_version' | awk '{print $3}')
+ IMAGE_VERSION=$(echo ${IMAGE_RELEASE} | awk -F'-ls' '{print $1}')
+ if [ -z "${IMAGE_VERSION}" ]; then
+ echo "**** Can't retrieve last pushed version, exiting ****"
+ FAILURE_REASON="Can't retrieve last pushed version for cops tag latest"
+ curl -X POST -H "Content-Type: application/json" --data '{"avatar_url": "https://cdn.discordapp.com/avatars/354986384542662657/df91181b3f1cf0ef1592fbe18e0962d7.png","embeds": [{"color": 16711680,
+ "description": "**Trigger Failed** \n**Reason:** '"${FAILURE_REASON}"' \n"}],
+ "username": "Github Actions"}' ${{ secrets.DISCORD_WEBHOOK }}
+ exit 1
+ fi
+ echo "**** Last pushed version: ${IMAGE_VERSION} ****"
+ echo "Last pushed version: ${IMAGE_VERSION}" >> $GITHUB_STEP_SUMMARY
+ if [ "${EXT_RELEASE}" == "${IMAGE_VERSION}" ]; then
+ echo "**** Version ${EXT_RELEASE} already pushed, exiting ****"
+ echo "Version ${EXT_RELEASE} already pushed, exiting" >> $GITHUB_STEP_SUMMARY
+ exit 0
+ elif [ $(curl -s https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-cops/job/master/lastBuild/api/json | jq -r '.building') == "true" ]; then
+ echo "**** New version ${EXT_RELEASE} found; but there already seems to be an active build on Jenkins; exiting ****"
+ echo "New version ${EXT_RELEASE} found; but there already seems to be an active build on Jenkins; exiting" >> $GITHUB_STEP_SUMMARY
+ exit 0
+ else
+ echo "**** New version ${EXT_RELEASE} found; old version was ${IMAGE_VERSION}. Triggering new build ****"
+ echo "New version ${EXT_RELEASE} found; old version was ${IMAGE_VERSION}. Triggering new build" >> $GITHUB_STEP_SUMMARY
+ response=$(curl -iX POST \
+ https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-cops/job/master/buildWithParameters?PACKAGE_CHECK=false \
+ --user ${{ secrets.JENKINS_USER }}:${{ secrets.JENKINS_TOKEN }} | grep -i location | sed "s|^[L|l]ocation: \(.*\)|\1|")
+ echo "**** Jenkins job queue url: ${response%$'\r'} ****"
+ echo "**** Sleeping 10 seconds until job starts ****"
+ sleep 10
+ buildurl=$(curl -s "${response%$'\r'}api/json" | jq -r '.executable.url')
+ buildurl="${buildurl%$'\r'}"
+ echo "**** Jenkins job build url: ${buildurl} ****"
+ echo "Jenkins job build url: ${buildurl}" >> $GITHUB_STEP_SUMMARY
+ echo "**** Attempting to change the Jenkins job description ****"
+ curl -iX POST \
+ "${buildurl}submitDescription" \
+ --user ${{ secrets.JENKINS_USER }}:${{ secrets.JENKINS_TOKEN }} \
+ --data-urlencode "description=GHA external trigger https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
+ --data-urlencode "Submit=Submit"
+ echo "**** Notifying Discord ****"
+ TRIGGER_REASON="A version change was detected for cops tag latest. Old version:${IMAGE_VERSION} New version:${EXT_RELEASE}"
+ curl -X POST -H "Content-Type: application/json" --data '{"avatar_url": "https://cdn.discordapp.com/avatars/354986384542662657/df91181b3f1cf0ef1592fbe18e0962d7.png","embeds": [{"color": 9802903,
+ "description": "**Build Triggered** \n**Reason:** '"${TRIGGER_REASON}"' \n**Build URL:** '"${buildurl}display/redirect"' \n"}],
+ "username": "Github Actions"}' ${{ secrets.DISCORD_WEBHOOK }}
+ fi
diff --git a/.github/workflows/external_trigger_scheduler.yml b/.github/workflows/external_trigger_scheduler.yml
new file mode 100644
index 0000000..ed84bda
--- /dev/null
+++ b/.github/workflows/external_trigger_scheduler.yml
@@ -0,0 +1,45 @@
+name: External Trigger Scheduler
+
+on:
+ schedule:
+ - cron: '37 * * * *'
+ workflow_dispatch:
+
+jobs:
+ external-trigger-scheduler:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3.1.0
+ with:
+ fetch-depth: '0'
+
+ - name: External Trigger Scheduler
+ run: |
+ echo "**** Branches found: ****"
+ git for-each-ref --format='%(refname:short)' refs/remotes
+ for br in $(git for-each-ref --format='%(refname:short)' refs/remotes)
+ do
+ br=$(echo "$br" | sed 's|origin/||g')
+ echo "**** Evaluating branch ${br} ****"
+ ls_jenkins_vars=$(curl -sX GET https://raw.githubusercontent.com/linuxserver/docker-cops/${br}/jenkins-vars.yml)
+ ls_branch=$(echo "${ls_jenkins_vars}" | yq -r '.ls_branch')
+ ls_trigger=$(echo "${ls_jenkins_vars}" | yq -r '.external_type')
+ if [[ "${br}" == "${ls_branch}" ]] && [[ "${ls_trigger}" != "os" ]]; then
+ echo "**** Branch ${br} appears to be live and trigger is not os; checking workflow. ****"
+ if curl -sfX GET https://raw.githubusercontent.com/linuxserver/docker-cops/${br}/.github/workflows/external_trigger.yml > /dev/null 2>&1; then
+ echo "**** Workflow exists. Triggering external trigger workflow for branch ${br} ****."
+ echo "Triggering external trigger workflow for branch ${br}" >> $GITHUB_STEP_SUMMARY
+ curl -iX POST \
+ -H "Authorization: token ${{ secrets.CR_PAT }}" \
+ -H "Accept: application/vnd.github.v3+json" \
+ -d "{\"ref\":\"refs/heads/${br}\"}" \
+ https://api.github.com/repos/linuxserver/docker-cops/actions/workflows/external_trigger.yml/dispatches
+ else
+ echo "**** Workflow doesn't exist; skipping trigger. ****"
+ echo "Skipping branch ${br} due to no external trigger workflow present." >> $GITHUB_STEP_SUMMARY
+ fi
+ else
+ echo "**** ${br} is either a dev branch, or has no external version; skipping trigger. ****"
+ echo "Skipping branch ${br} due to being detected as dev branch or having no external version." >> $GITHUB_STEP_SUMMARY
+ fi
+ done
diff --git a/.github/workflows/package_trigger.yml b/.github/workflows/package_trigger.yml
new file mode 100644
index 0000000..511d2f1
--- /dev/null
+++ b/.github/workflows/package_trigger.yml
@@ -0,0 +1,42 @@
+name: Package Trigger Main
+
+on:
+ workflow_dispatch:
+
+jobs:
+ package-trigger-master:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3.1.0
+
+ - name: Package Trigger
+ if: github.ref == 'refs/heads/master'
+ run: |
+ if [ -n "${{ secrets.PAUSE_PACKAGE_TRIGGER_COPS_MASTER }}" ]; then
+ echo "**** Github secret PAUSE_PACKAGE_TRIGGER_COPS_MASTER is set; skipping trigger. ****"
+ echo "Github secret \`PAUSE_PACKAGE_TRIGGER_COPS_MASTER\` is set; skipping trigger." >> $GITHUB_STEP_SUMMARY
+ exit 0
+ fi
+ if [ $(curl -s https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-cops/job/master/lastBuild/api/json | jq -r '.building') == "true" ]; then
+ echo "**** There already seems to be an active build on Jenkins; skipping package trigger ****"
+ echo "There already seems to be an active build on Jenkins; skipping package trigger" >> $GITHUB_STEP_SUMMARY
+ exit 0
+ fi
+ echo "**** Package trigger running off of master branch. To disable, set a Github secret named \"PAUSE_PACKAGE_TRIGGER_COPS_MASTER\". ****"
+ echo "Package trigger running off of master branch. To disable, set a Github secret named \`PAUSE_PACKAGE_TRIGGER_COPS_MASTER\`" >> $GITHUB_STEP_SUMMARY
+ response=$(curl -iX POST \
+ https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-cops/job/master/buildWithParameters?PACKAGE_CHECK=true \
+ --user ${{ secrets.JENKINS_USER }}:${{ secrets.JENKINS_TOKEN }} | grep -i location | sed "s|^[L|l]ocation: \(.*\)|\1|")
+ echo "**** Jenkins job queue url: ${response%$'\r'} ****"
+ echo "**** Sleeping 10 seconds until job starts ****"
+ sleep 10
+ buildurl=$(curl -s "${response%$'\r'}api/json" | jq -r '.executable.url')
+ buildurl="${buildurl%$'\r'}"
+ echo "**** Jenkins job build url: ${buildurl} ****"
+ echo "Jenkins job build url: ${buildurl}" >> $GITHUB_STEP_SUMMARY
+ echo "**** Attempting to change the Jenkins job description ****"
+ curl -iX POST \
+ "${buildurl}submitDescription" \
+ --user ${{ secrets.JENKINS_USER }}:${{ secrets.JENKINS_TOKEN }} \
+ --data-urlencode "description=GHA package trigger https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
+ --data-urlencode "Submit=Submit"
diff --git a/.github/workflows/package_trigger_scheduler.yml b/.github/workflows/package_trigger_scheduler.yml
new file mode 100644
index 0000000..e7d5c2d
--- /dev/null
+++ b/.github/workflows/package_trigger_scheduler.yml
@@ -0,0 +1,50 @@
+name: Package Trigger Scheduler
+
+on:
+ schedule:
+ - cron: '49 20 * * 0'
+ workflow_dispatch:
+
+jobs:
+ package-trigger-scheduler:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3.1.0
+ with:
+ fetch-depth: '0'
+
+ - name: Package Trigger Scheduler
+ run: |
+ echo "**** Branches found: ****"
+ git for-each-ref --format='%(refname:short)' refs/remotes
+ for br in $(git for-each-ref --format='%(refname:short)' refs/remotes)
+ do
+ br=$(echo "$br" | sed 's|origin/||g')
+ echo "**** Evaluating branch ${br} ****"
+ ls_branch=$(curl -sX GET https://raw.githubusercontent.com/linuxserver/docker-cops/${br}/jenkins-vars.yml | yq -r '.ls_branch')
+ if [ "${br}" == "${ls_branch}" ]; then
+ echo "**** Branch ${br} appears to be live; checking workflow. ****"
+ if curl -sfX GET https://raw.githubusercontent.com/linuxserver/docker-cops/${br}/.github/workflows/package_trigger.yml > /dev/null 2>&1; then
+ echo "**** Workflow exists. Triggering package trigger workflow for branch ${br}. ****"
+ echo "Triggering package trigger workflow for branch ${br}" >> $GITHUB_STEP_SUMMARY
+ triggered_branches="${triggered_branches}${br} "
+ curl -iX POST \
+ -H "Authorization: token ${{ secrets.CR_PAT }}" \
+ -H "Accept: application/vnd.github.v3+json" \
+ -d "{\"ref\":\"refs/heads/${br}\"}" \
+ https://api.github.com/repos/linuxserver/docker-cops/actions/workflows/package_trigger.yml/dispatches
+ sleep 30
+ else
+ echo "**** Workflow doesn't exist; skipping trigger. ****"
+ echo "Skipping branch ${br} due to no package trigger workflow present." >> $GITHUB_STEP_SUMMARY
+ fi
+ else
+ echo "**** ${br} appears to be a dev branch; skipping trigger. ****"
+ echo "Skipping branch ${br} due to being detected as dev branch." >> $GITHUB_STEP_SUMMARY
+ fi
+ done
+ echo "**** Package check build(s) triggered for branch(es): ${triggered_branches} ****"
+ echo "**** Notifying Discord ****"
+ curl -X POST -H "Content-Type: application/json" --data '{"avatar_url": "https://cdn.discordapp.com/avatars/354986384542662657/df91181b3f1cf0ef1592fbe18e0962d7.png","embeds": [{"color": 9802903,
+ "description": "**Package Check Build(s) Triggered for cops** \n**Branch(es):** '"${triggered_branches}"' \n**Build URL:** '"https://ci.linuxserver.io/blue/organizations/jenkins/Docker-Pipeline-Builders%2Fdocker-cops/activity/"' \n"}],
+ "username": "Github Actions"}' ${{ secrets.DISCORD_WEBHOOK }}
diff --git a/Dockerfile b/Dockerfile
index 38312b7..a8fb9f7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
-FROM ghcr.io/linuxserver/baseimage-alpine-nginx:3.17
+FROM ghcr.io/linuxserver/baseimage-alpine-nginx:3.18
# set version label
ARG BUILD_DATE
@@ -12,27 +12,24 @@ LABEL maintainer="chbmb"
RUN \
echo "**** install runtime packages ****" && \
apk add --no-cache --upgrade \
- php81-ctype \
- php81-dom \
- php81-gd \
- php81-intl \
- php81-opcache \
- php81-phar \
- php81-pdo_sqlite \
- php81-zip && \
+ # libxml2 \
+ php82-dom \
+ php82-gd \
+ php82-intl \
+ php82-pdo_sqlite \
+ php82-sqlite3 && \
+ echo "**** configure php-fpm to pass env vars ****" && \
+ sed -E -i 's/^;?clear_env ?=.*$/clear_env = no/g' /etc/php82/php-fpm.d/www.conf && \
+ grep -qxF 'clear_env = no' /etc/php82/php-fpm.d/www.conf || echo 'clear_env = no' >> /etc/php82/php-fpm.d/www.conf && \
+ echo "env[PATH] = /usr/local/bin:/usr/bin:/bin" >> /etc/php82/php-fpm.conf && \
echo "**** install cops ****" && \
- curl \
- -sS https://getcomposer.org/installer \
- | php -- --install-dir=/usr/bin --filename=composer --version=1.10.26 && \
- composer \
- global require "fxp/composer-asset-plugin:~1.1" && \
if [ -z ${COPS_RELEASE+x} ]; then \
- COPS_RELEASE=$(curl -sX GET "https://api.github.com/repos/seblucas/cops/releases/latest" \
+ COPS_RELEASE=$(curl -sX GET "https://api.github.com/repos/mikespub-org/seblucas-cops/releases/latest" \
| awk '/tag_name/{print $4;exit}' FS='[""]'); \
fi && \
curl -o \
/tmp/cops.tar.gz -L \
- "https://github.com/seblucas/cops/archive/${COPS_RELEASE}.tar.gz" && \
+ "https://github.com/mikespub-org/seblucas-cops/archive/${COPS_RELEASE}.tar.gz" && \
mkdir -p \
/app/www/public && \
tar xf /tmp/cops.tar.gz -C \
@@ -40,7 +37,6 @@ RUN \
cd /app/www/public && \
composer \
install --no-dev --optimize-autoloader && \
- sed -i 's|^[[:space:]]*return[[:space:]]@create_function[[:space:]]'\(''\''\$it'\'',[[:space:]]\$func'\)';| return function \(\$it\) use \(\$func\) \{\n return eval\(\$func\);\n \};|' vendor/seblucas/dot-php/doT.php && \
echo "**** cleanup ****" && \
rm -rf \
/root/.composer \
diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64
index 9b647ff..944afb1 100644
--- a/Dockerfile.aarch64
+++ b/Dockerfile.aarch64
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
-FROM ghcr.io/linuxserver/baseimage-alpine-nginx:arm64v8-3.17
+FROM ghcr.io/linuxserver/baseimage-alpine-nginx:3.18
# set version label
ARG BUILD_DATE
@@ -12,35 +12,32 @@ LABEL maintainer="chbmb"
RUN \
echo "**** install runtime packages ****" && \
apk add --no-cache --upgrade \
- php81-ctype \
- php81-dom \
- php81-gd \
- php81-intl \
- php81-opcache \
- php81-phar \
- php81-pdo_sqlite \
- php81-zip && \
+ # libxml2 \
+ php82-dom \
+ php82-gd \
+ php82-intl \
+ php82-pdo_sqlite \
+ php82-sqlite3 && \
+ echo "**** configure php-fpm to pass env vars ****" && \
+ sed -E -i 's/^;?clear_env ?=.*$/clear_env = no/g' /etc/php82/php-fpm.d/www.conf && \
+ grep -qxF 'clear_env = no' /etc/php82/php-fpm.d/www.conf || echo 'clear_env = no' >> /etc/php82/php-fpm.d/www.conf && \
+ echo "env[PATH] = /usr/local/bin:/usr/bin:/bin" >> /etc/php82/php-fpm.conf && \
echo "**** install cops ****" && \
- curl \
- -sS https://getcomposer.org/installer \
- | php -- --install-dir=/usr/bin --filename=composer --version=1.10.26 && \
- composer \
- global require "fxp/composer-asset-plugin:~1.1" && \
if [ -z ${COPS_RELEASE+x} ]; then \
- COPS_RELEASE=$(curl -sX GET "https://api.github.com/repos/seblucas/cops/releases/latest" \
+ COPS_RELEASE=$(curl -sX GET "https://api.github.com/repos/mikespub-org/seblucas-cops/releases/latest" \
| awk '/tag_name/{print $4;exit}' FS='[""]'); \
fi && \
curl -o \
/tmp/cops.tar.gz -L \
- "https://github.com/seblucas/cops/archive/${COPS_RELEASE}.tar.gz" && \
+ "https://github.com/mikespub-org/seblucas-cops/archive/${COPS_RELEASE}.tar.gz" && \
mkdir -p \
/app/www/public && \
tar xf /tmp/cops.tar.gz -C \
/app/www/public --strip-components=1 && \
cd /app/www/public && \
+ # use standard composer 2.x now, no need to install older 1.x version
composer \
install --no-dev --optimize-autoloader && \
- sed -i 's|^[[:space:]]*return[[:space:]]@create_function[[:space:]]'\(''\''\$it'\'',[[:space:]]\$func'\)';| return function \(\$it\) use \(\$func\) \{\n return eval\(\$func\);\n \};|' vendor/seblucas/dot-php/doT.php && \
echo "**** cleanup ****" && \
rm -rf \
/root/.composer \
diff --git a/Dockerfile.armhf b/Dockerfile.armhf
deleted file mode 100644
index fe19387..0000000
--- a/Dockerfile.armhf
+++ /dev/null
@@ -1,54 +0,0 @@
-# syntax=docker/dockerfile:1
-
-FROM ghcr.io/linuxserver/baseimage-alpine-nginx:arm32v7-3.17
-
-# set version label
-ARG BUILD_DATE
-ARG VERSION
-ARG COPS_RELEASE
-LABEL build_version="Linuxserver.io version:- ${VERSION} Build-date:- ${BUILD_DATE}"
-LABEL maintainer="chbmb"
-
-RUN \
- echo "**** install runtime packages ****" && \
- apk add --no-cache --upgrade \
- php81-ctype \
- php81-dom \
- php81-gd \
- php81-intl \
- php81-opcache \
- php81-phar \
- php81-pdo_sqlite \
- php81-zip && \
- echo "**** install cops ****" && \
- curl \
- -sS https://getcomposer.org/installer \
- | php -- --install-dir=/usr/bin --filename=composer --version=1.10.26 && \
- composer \
- global require "fxp/composer-asset-plugin:~1.1" && \
- if [ -z ${COPS_RELEASE+x} ]; then \
- COPS_RELEASE=$(curl -sX GET "https://api.github.com/repos/seblucas/cops/releases/latest" \
- | awk '/tag_name/{print $4;exit}' FS='[""]'); \
- fi && \
- curl -o \
- /tmp/cops.tar.gz -L \
- "https://github.com/seblucas/cops/archive/${COPS_RELEASE}.tar.gz" && \
- mkdir -p \
- /app/www/public && \
- tar xf /tmp/cops.tar.gz -C \
- /app/www/public --strip-components=1 && \
- cd /app/www/public && \
- composer \
- install --no-dev --optimize-autoloader && \
- sed -i 's|^[[:space:]]*return[[:space:]]@create_function[[:space:]]'\(''\''\$it'\'',[[:space:]]\$func'\)';| return function \(\$it\) use \(\$func\) \{\n return eval\(\$func\);\n \};|' vendor/seblucas/dot-php/doT.php && \
- echo "**** cleanup ****" && \
- rm -rf \
- /root/.composer \
- /tmp/*
-
-# add local files
-COPY root/ /
-
-# ports and volumes
-EXPOSE 80 443
-VOLUME /config
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..95cb43e
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,1007 @@
+pipeline {
+ agent {
+ label 'X86-64-MULTI'
+ }
+ options {
+ buildDiscarder(logRotator(numToKeepStr: '10', daysToKeepStr: '60'))
+ parallelsAlwaysFailFast()
+ }
+ // Input to determine if this is a package check
+ parameters {
+ string(defaultValue: 'false', description: 'package check run', name: 'PACKAGE_CHECK')
+ }
+ // Configuration for the variables used for this specific repo
+ environment {
+ BUILDS_DISCORD=credentials('build_webhook_url')
+ GITHUB_TOKEN=credentials('498b4638-2d02-4ce5-832d-8a57d01d97ab')
+ GITLAB_TOKEN=credentials('b6f0f1dd-6952-4cf6-95d1-9c06380283f0')
+ GITLAB_NAMESPACE=credentials('gitlab-namespace-id')
+ SCARF_TOKEN=credentials('scarf_api_key')
+ EXT_GIT_BRANCH = 'main'
+ EXT_USER = 'mikespub-org'
+ EXT_REPO = 'seblucas-cops'
+ BUILD_VERSION_ARG = 'COPS_RELEASE'
+ LS_USER = 'linuxserver'
+ LS_REPO = 'docker-cops'
+ CONTAINER_NAME = 'cops'
+ DOCKERHUB_IMAGE = 'linuxserver/cops'
+ DEV_DOCKERHUB_IMAGE = 'lsiodev/cops'
+ PR_DOCKERHUB_IMAGE = 'lspipepr/cops'
+ DIST_IMAGE = 'alpine'
+ MULTIARCH='true'
+ CI='true'
+ CI_WEB='true'
+ CI_PORT='80'
+ CI_SSL='false'
+ CI_DELAY='120'
+ CI_DOCKERENV='TZ=US/Pacific'
+ CI_AUTH='user:password'
+ CI_WEBPATH=''
+ }
+ stages {
+ // Setup all the basic environment variables needed for the build
+ stage("Set ENV Variables base"){
+ steps{
+ sh '''#! /bin/bash
+ containers=$(docker ps -aq)
+ if [[ -n "${containers}" ]]; then
+ docker stop ${containers}
+ fi
+ docker system prune -af --volumes || : '''
+ script{
+ env.EXIT_STATUS = ''
+ env.LS_RELEASE = sh(
+ script: '''docker run --rm quay.io/skopeo/stable:v1 inspect docker://ghcr.io/${LS_USER}/${CONTAINER_NAME}:latest 2>/dev/null | jq -r '.Labels.build_version' | awk '{print $3}' | grep '\\-ls' || : ''',
+ returnStdout: true).trim()
+ env.LS_RELEASE_NOTES = sh(
+ script: '''cat readme-vars.yml | awk -F \\" '/date: "[0-9][0-9].[0-9][0-9].[0-9][0-9]:/ {print $4;exit;}' | sed -E ':a;N;$!ba;s/\\r{0,1}\\n/\\\\n/g' ''',
+ returnStdout: true).trim()
+ env.GITHUB_DATE = sh(
+ script: '''date '+%Y-%m-%dT%H:%M:%S%:z' ''',
+ returnStdout: true).trim()
+ env.COMMIT_SHA = sh(
+ script: '''git rev-parse HEAD''',
+ returnStdout: true).trim()
+ env.CODE_URL = 'https://github.com/' + env.LS_USER + '/' + env.LS_REPO + '/commit/' + env.GIT_COMMIT
+ env.DOCKERHUB_LINK = 'https://hub.docker.com/r/' + env.DOCKERHUB_IMAGE + '/tags/'
+ env.PULL_REQUEST = env.CHANGE_ID
+ env.TEMPLATED_FILES = 'Jenkinsfile README.md LICENSE .editorconfig ./.github/CONTRIBUTING.md ./.github/FUNDING.yml ./.github/ISSUE_TEMPLATE/config.yml ./.github/ISSUE_TEMPLATE/issue.bug.yml ./.github/ISSUE_TEMPLATE/issue.feature.yml ./.github/PULL_REQUEST_TEMPLATE.md ./.github/workflows/external_trigger_scheduler.yml ./.github/workflows/greetings.yml ./.github/workflows/package_trigger_scheduler.yml ./.github/workflows/call_issue_pr_tracker.yml ./.github/workflows/call_issues_cron.yml ./.github/workflows/permissions.yml ./.github/workflows/external_trigger.yml ./.github/workflows/package_trigger.yml'
+ }
+ script{
+ env.LS_RELEASE_NUMBER = sh(
+ script: '''echo ${LS_RELEASE} |sed 's/^.*-ls//g' ''',
+ returnStdout: true).trim()
+ }
+ script{
+ env.LS_TAG_NUMBER = sh(
+ script: '''#! /bin/bash
+ tagsha=$(git rev-list -n 1 ${LS_RELEASE} 2>/dev/null)
+ if [ "${tagsha}" == "${COMMIT_SHA}" ]; then
+ echo ${LS_RELEASE_NUMBER}
+ elif [ -z "${GIT_COMMIT}" ]; then
+ echo ${LS_RELEASE_NUMBER}
+ else
+ echo $((${LS_RELEASE_NUMBER} + 1))
+ fi''',
+ returnStdout: true).trim()
+ }
+ }
+ }
+ /* #######################
+ Package Version Tagging
+ ####################### */
+ // Grab the current package versions in Git to determine package tag
+ stage("Set Package tag"){
+ steps{
+ script{
+ env.PACKAGE_TAG = sh(
+ script: '''#!/bin/bash
+ if [ -e package_versions.txt ] ; then
+ cat package_versions.txt | md5sum | cut -c1-8
+ else
+ echo none
+ fi''',
+ returnStdout: true).trim()
+ }
+ }
+ }
+ /* ########################
+ External Release Tagging
+ ######################## */
+ // If this is a stable github release use the latest endpoint from github to determine the ext tag
+ stage("Set ENV github_stable"){
+ steps{
+ script{
+ env.EXT_RELEASE = sh(
+ script: '''curl -H "Authorization: token ${GITHUB_TOKEN}" -s https://api.github.com/repos/${EXT_USER}/${EXT_REPO}/releases/latest | jq -r '. | .tag_name' ''',
+ returnStdout: true).trim()
+ }
+ }
+ }
+ // If this is a stable or devel github release generate the link for the build message
+ stage("Set ENV github_link"){
+ steps{
+ script{
+ env.RELEASE_LINK = 'https://github.com/' + env.EXT_USER + '/' + env.EXT_REPO + '/releases/tag/' + env.EXT_RELEASE
+ }
+ }
+ }
+ // Sanitize the release tag and strip illegal docker or github characters
+ stage("Sanitize tag"){
+ steps{
+ script{
+ env.EXT_RELEASE_CLEAN = sh(
+ script: '''echo ${EXT_RELEASE} | sed 's/[~,%@+;:/]//g' ''',
+ returnStdout: true).trim()
+
+ def semver = env.EXT_RELEASE_CLEAN =~ /(\d+)\.(\d+)\.(\d+)/
+ if (semver.find()) {
+ env.SEMVER = "${semver[0][1]}.${semver[0][2]}.${semver[0][3]}"
+ } else {
+ semver = env.EXT_RELEASE_CLEAN =~ /(\d+)\.(\d+)(?:\.(\d+))?(.*)/
+ if (semver.find()) {
+ if (semver[0][3]) {
+ env.SEMVER = "${semver[0][1]}.${semver[0][2]}.${semver[0][3]}"
+ } else if (!semver[0][3] && !semver[0][4]) {
+ env.SEMVER = "${semver[0][1]}.${semver[0][2]}.${(new Date()).format('YYYYMMdd')}"
+ }
+ }
+ }
+
+ if (env.SEMVER != null) {
+ if (BRANCH_NAME != "master" && BRANCH_NAME != "main") {
+ env.SEMVER = "${env.SEMVER}-${BRANCH_NAME}"
+ }
+ println("SEMVER: ${env.SEMVER}")
+ } else {
+ println("No SEMVER detected")
+ }
+
+ }
+ }
+ }
+ // If this is a master build use live docker endpoints
+ stage("Set ENV live build"){
+ when {
+ branch "master"
+ environment name: 'CHANGE_ID', value: ''
+ }
+ steps {
+ script{
+ env.IMAGE = env.DOCKERHUB_IMAGE
+ env.GITHUBIMAGE = 'ghcr.io/' + env.LS_USER + '/' + env.CONTAINER_NAME
+ env.GITLABIMAGE = 'registry.gitlab.com/linuxserver.io/' + env.LS_REPO + '/' + env.CONTAINER_NAME
+ env.QUAYIMAGE = 'quay.io/linuxserver.io/' + env.CONTAINER_NAME
+ if (env.MULTIARCH == 'true') {
+ env.CI_TAGS = 'amd64-' + env.EXT_RELEASE_CLEAN + '-ls' + env.LS_TAG_NUMBER + '|arm64v8-' + env.EXT_RELEASE_CLEAN + '-ls' + env.LS_TAG_NUMBER
+ } else {
+ env.CI_TAGS = env.EXT_RELEASE_CLEAN + '-ls' + env.LS_TAG_NUMBER
+ }
+ env.VERSION_TAG = env.EXT_RELEASE_CLEAN + '-ls' + env.LS_TAG_NUMBER
+ env.META_TAG = env.EXT_RELEASE_CLEAN + '-ls' + env.LS_TAG_NUMBER
+ env.EXT_RELEASE_TAG = 'version-' + env.EXT_RELEASE_CLEAN
+ }
+ }
+ }
+ // If this is a dev build use dev docker endpoints
+ stage("Set ENV dev build"){
+ when {
+ not {branch "master"}
+ environment name: 'CHANGE_ID', value: ''
+ }
+ steps {
+ script{
+ env.IMAGE = env.DEV_DOCKERHUB_IMAGE
+ env.GITHUBIMAGE = 'ghcr.io/' + env.LS_USER + '/lsiodev-' + env.CONTAINER_NAME
+ env.GITLABIMAGE = 'registry.gitlab.com/linuxserver.io/' + env.LS_REPO + '/lsiodev-' + env.CONTAINER_NAME
+ env.QUAYIMAGE = 'quay.io/linuxserver.io/lsiodev-' + env.CONTAINER_NAME
+ if (env.MULTIARCH == 'true') {
+ env.CI_TAGS = 'amd64-' + env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA + '|arm64v8-' + env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
+ } else {
+ env.CI_TAGS = env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
+ }
+ env.VERSION_TAG = env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
+ env.META_TAG = env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA
+ env.EXT_RELEASE_TAG = 'version-' + env.EXT_RELEASE_CLEAN
+ env.DOCKERHUB_LINK = 'https://hub.docker.com/r/' + env.DEV_DOCKERHUB_IMAGE + '/tags/'
+ }
+ }
+ }
+ // If this is a pull request build use dev docker endpoints
+ stage("Set ENV PR build"){
+ when {
+ not {environment name: 'CHANGE_ID', value: ''}
+ }
+ steps {
+ script{
+ env.IMAGE = env.PR_DOCKERHUB_IMAGE
+ env.GITHUBIMAGE = 'ghcr.io/' + env.LS_USER + '/lspipepr-' + env.CONTAINER_NAME
+ env.GITLABIMAGE = 'registry.gitlab.com/linuxserver.io/' + env.LS_REPO + '/lspipepr-' + env.CONTAINER_NAME
+ env.QUAYIMAGE = 'quay.io/linuxserver.io/lspipepr-' + env.CONTAINER_NAME
+ if (env.MULTIARCH == 'true') {
+ env.CI_TAGS = 'amd64-' + env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA + '-pr-' + env.PULL_REQUEST + '|arm64v8-' + env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA + '-pr-' + env.PULL_REQUEST
+ } else {
+ env.CI_TAGS = env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA + '-pr-' + env.PULL_REQUEST
+ }
+ env.VERSION_TAG = env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA + '-pr-' + env.PULL_REQUEST
+ env.META_TAG = env.EXT_RELEASE_CLEAN + '-pkg-' + env.PACKAGE_TAG + '-dev-' + env.COMMIT_SHA + '-pr-' + env.PULL_REQUEST
+ env.EXT_RELEASE_TAG = 'version-' + env.EXT_RELEASE_CLEAN
+ env.CODE_URL = 'https://github.com/' + env.LS_USER + '/' + env.LS_REPO + '/pull/' + env.PULL_REQUEST
+ env.DOCKERHUB_LINK = 'https://hub.docker.com/r/' + env.PR_DOCKERHUB_IMAGE + '/tags/'
+ }
+ }
+ }
+ // Run ShellCheck
+ stage('ShellCheck') {
+ when {
+ environment name: 'CI', value: 'true'
+ }
+ steps {
+ withCredentials([
+ string(credentialsId: 'ci-tests-s3-key-id', variable: 'S3_KEY'),
+ string(credentialsId: 'ci-tests-s3-secret-access-key', variable: 'S3_SECRET')
+ ]) {
+ script{
+ env.SHELLCHECK_URL = 'https://ci-tests.linuxserver.io/' + env.IMAGE + '/' + env.META_TAG + '/shellcheck-result.xml'
+ }
+ sh '''curl -sL https://raw.githubusercontent.com/linuxserver/docker-jenkins-builder/master/checkrun.sh | /bin/bash'''
+ sh '''#! /bin/bash
+ docker run --rm \
+ -v ${WORKSPACE}:/mnt \
+ -e AWS_ACCESS_KEY_ID=\"${S3_KEY}\" \
+ -e AWS_SECRET_ACCESS_KEY=\"${S3_SECRET}\" \
+ ghcr.io/linuxserver/baseimage-alpine:3.17 s6-envdir -fn -- /var/run/s6/container_environment /bin/bash -c "\
+ apk add --no-cache py3-pip && \
+ pip install s3cmd && \
+ s3cmd put --no-preserve --acl-public -m text/xml /mnt/shellcheck-result.xml s3://ci-tests.linuxserver.io/${IMAGE}/${META_TAG}/shellcheck-result.xml" || :'''
+ }
+ }
+ }
+ // Use helper containers to render templated files
+ stage('Update-Templates') {
+ when {
+ branch "master"
+ environment name: 'CHANGE_ID', value: ''
+ expression {
+ env.CONTAINER_NAME != null
+ }
+ }
+ steps {
+ sh '''#! /bin/bash
+ set -e
+ TEMPDIR=$(mktemp -d)
+ docker pull ghcr.io/linuxserver/jenkins-builder:latest
+ docker run --rm -e CONTAINER_NAME=${CONTAINER_NAME} -e GITHUB_BRANCH=master -v ${TEMPDIR}:/ansible/jenkins ghcr.io/linuxserver/jenkins-builder:latest
+ # Stage 1 - Jenkinsfile update
+ if [[ "$(md5sum Jenkinsfile | awk '{ print $1 }')" != "$(md5sum ${TEMPDIR}/docker-${CONTAINER_NAME}/Jenkinsfile | awk '{ print $1 }')" ]]; then
+ mkdir -p ${TEMPDIR}/repo
+ git clone https://github.com/${LS_USER}/${LS_REPO}.git ${TEMPDIR}/repo/${LS_REPO}
+ cd ${TEMPDIR}/repo/${LS_REPO}
+ git checkout -f master
+ cp ${TEMPDIR}/docker-${CONTAINER_NAME}/Jenkinsfile ${TEMPDIR}/repo/${LS_REPO}/
+ git add Jenkinsfile
+ git commit -m 'Bot Updating Templated Files'
+ git push https://LinuxServer-CI:${GITHUB_TOKEN}@github.com/${LS_USER}/${LS_REPO}.git --all
+ echo "true" > /tmp/${COMMIT_SHA}-${BUILD_NUMBER}
+ echo "Updating Jenkinsfile"
+ rm -Rf ${TEMPDIR}
+ exit 0
+ else
+ echo "Jenkinsfile is up to date."
+ fi
+ # Stage 2 - Delete old templates
+ OLD_TEMPLATES=".github/ISSUE_TEMPLATE.md .github/ISSUE_TEMPLATE/issue.bug.md .github/ISSUE_TEMPLATE/issue.feature.md .github/workflows/call_invalid_helper.yml .github/workflows/stale.yml Dockerfile.armhf"
+ for i in ${OLD_TEMPLATES}; do
+ if [[ -f "${i}" ]]; then
+ TEMPLATES_TO_DELETE="${i} ${TEMPLATES_TO_DELETE}"
+ fi
+ done
+ if [[ -n "${TEMPLATES_TO_DELETE}" ]]; then
+ mkdir -p ${TEMPDIR}/repo
+ git clone https://github.com/${LS_USER}/${LS_REPO}.git ${TEMPDIR}/repo/${LS_REPO}
+ cd ${TEMPDIR}/repo/${LS_REPO}
+ git checkout -f master
+ for i in ${TEMPLATES_TO_DELETE}; do
+ git rm "${i}"
+ done
+ git commit -m 'Bot Updating Templated Files'
+ git push https://LinuxServer-CI:${GITHUB_TOKEN}@github.com/${LS_USER}/${LS_REPO}.git --all
+ echo "true" > /tmp/${COMMIT_SHA}-${BUILD_NUMBER}
+ echo "Deleting old and deprecated templates"
+ rm -Rf ${TEMPDIR}
+ exit 0
+ else
+ echo "No templates to delete"
+ fi
+ # Stage 3 - Update templates
+ CURRENTHASH=$(grep -hs ^ ${TEMPLATED_FILES} | md5sum | cut -c1-8)
+ cd ${TEMPDIR}/docker-${CONTAINER_NAME}
+ NEWHASH=$(grep -hs ^ ${TEMPLATED_FILES} | md5sum | cut -c1-8)
+ if [[ "${CURRENTHASH}" != "${NEWHASH}" ]] || ! grep -q '.jenkins-external' "${WORKSPACE}/.gitignore" 2>/dev/null; then
+ mkdir -p ${TEMPDIR}/repo
+ git clone https://github.com/${LS_USER}/${LS_REPO}.git ${TEMPDIR}/repo/${LS_REPO}
+ cd ${TEMPDIR}/repo/${LS_REPO}
+ git checkout -f master
+ cd ${TEMPDIR}/docker-${CONTAINER_NAME}
+ mkdir -p ${TEMPDIR}/repo/${LS_REPO}/.github/workflows
+ mkdir -p ${TEMPDIR}/repo/${LS_REPO}/.github/ISSUE_TEMPLATE
+ cp --parents ${TEMPLATED_FILES} ${TEMPDIR}/repo/${LS_REPO}/ || :
+ cp --parents readme-vars.yml ${TEMPDIR}/repo/${LS_REPO}/ || :
+ cd ${TEMPDIR}/repo/${LS_REPO}/
+ if ! grep -q '.jenkins-external' .gitignore 2>/dev/null; then
+ echo ".jenkins-external" >> .gitignore
+ git add .gitignore
+ fi
+ git add readme-vars.yml ${TEMPLATED_FILES}
+ git commit -m 'Bot Updating Templated Files'
+ git push https://LinuxServer-CI:${GITHUB_TOKEN}@github.com/${LS_USER}/${LS_REPO}.git --all
+ echo "true" > /tmp/${COMMIT_SHA}-${BUILD_NUMBER}
+ else
+ echo "false" > /tmp/${COMMIT_SHA}-${BUILD_NUMBER}
+ fi
+ mkdir -p ${TEMPDIR}/gitbook
+ git clone https://github.com/linuxserver/docker-documentation.git ${TEMPDIR}/gitbook/docker-documentation
+ if [[ ("${BRANCH_NAME}" == "master") || ("${BRANCH_NAME}" == "main") ]] && [[ (! -f ${TEMPDIR}/gitbook/docker-documentation/images/docker-${CONTAINER_NAME}.md) || ("$(md5sum ${TEMPDIR}/gitbook/docker-documentation/images/docker-${CONTAINER_NAME}.md | awk '{ print $1 }')" != "$(md5sum ${TEMPDIR}/docker-${CONTAINER_NAME}/.jenkins-external/docker-${CONTAINER_NAME}.md | awk '{ print $1 }')") ]]; then
+ cp ${TEMPDIR}/docker-${CONTAINER_NAME}/.jenkins-external/docker-${CONTAINER_NAME}.md ${TEMPDIR}/gitbook/docker-documentation/images/
+ cd ${TEMPDIR}/gitbook/docker-documentation/
+ git add images/docker-${CONTAINER_NAME}.md
+ git commit -m 'Bot Updating Documentation'
+ git push https://LinuxServer-CI:${GITHUB_TOKEN}@github.com/linuxserver/docker-documentation.git --all
+ fi
+ mkdir -p ${TEMPDIR}/unraid
+ git clone https://github.com/linuxserver/docker-templates.git ${TEMPDIR}/unraid/docker-templates
+ git clone https://github.com/linuxserver/templates.git ${TEMPDIR}/unraid/templates
+ if [[ -f ${TEMPDIR}/unraid/docker-templates/linuxserver.io/img/${CONTAINER_NAME}-logo.png ]]; then
+ sed -i "s|master/linuxserver.io/img/linuxserver-ls-logo.png|master/linuxserver.io/img/${CONTAINER_NAME}-logo.png|" ${TEMPDIR}/docker-${CONTAINER_NAME}/.jenkins-external/${CONTAINER_NAME}.xml
+ elif [[ -f ${TEMPDIR}/unraid/docker-templates/linuxserver.io/img/${CONTAINER_NAME}-icon.png ]]; then
+ sed -i "s|master/linuxserver.io/img/linuxserver-ls-logo.png|master/linuxserver.io/img/${CONTAINER_NAME}-icon.png|" ${TEMPDIR}/docker-${CONTAINER_NAME}/.jenkins-external/${CONTAINER_NAME}.xml
+ fi
+ if [[ ("${BRANCH_NAME}" == "master") || ("${BRANCH_NAME}" == "main") ]] && [[ (! -f ${TEMPDIR}/unraid/templates/unraid/${CONTAINER_NAME}.xml) || ("$(md5sum ${TEMPDIR}/unraid/templates/unraid/${CONTAINER_NAME}.xml | awk '{ print $1 }')" != "$(md5sum ${TEMPDIR}/docker-${CONTAINER_NAME}/.jenkins-external/${CONTAINER_NAME}.xml | awk '{ print $1 }')") ]]; then
+ cd ${TEMPDIR}/unraid/templates/
+ if grep -wq "${CONTAINER_NAME}" ${TEMPDIR}/unraid/templates/unraid/ignore.list; then
+ echo "Image is on the ignore list, marking Unraid template as deprecated"
+ cp ${TEMPDIR}/docker-${CONTAINER_NAME}/.jenkins-external/${CONTAINER_NAME}.xml ${TEMPDIR}/unraid/templates/unraid/
+ git add -u unraid/${CONTAINER_NAME}.xml
+ git mv unraid/${CONTAINER_NAME}.xml unraid/deprecated/${CONTAINER_NAME}.xml || :
+ git commit -m 'Bot Moving Deprecated Unraid Template' || :
+ else
+ cp ${TEMPDIR}/docker-${CONTAINER_NAME}/.jenkins-external/${CONTAINER_NAME}.xml ${TEMPDIR}/unraid/templates/unraid/
+ git add unraid/${CONTAINER_NAME}.xml
+ git commit -m 'Bot Updating Unraid Template'
+ fi
+ git push https://LinuxServer-CI:${GITHUB_TOKEN}@github.com/linuxserver/templates.git --all
+ fi
+ rm -Rf ${TEMPDIR}'''
+ script{
+ env.FILES_UPDATED = sh(
+ script: '''cat /tmp/${COMMIT_SHA}-${BUILD_NUMBER}''',
+ returnStdout: true).trim()
+ }
+ }
+ }
+ // Exit the build if the Templated files were just updated
+ stage('Template-exit') {
+ when {
+ branch "master"
+ environment name: 'CHANGE_ID', value: ''
+ environment name: 'FILES_UPDATED', value: 'true'
+ expression {
+ env.CONTAINER_NAME != null
+ }
+ }
+ steps {
+ script{
+ env.EXIT_STATUS = 'ABORTED'
+ }
+ }
+ }
+ // If this is a master build check the S6 service file perms
+ stage("Check S6 Service file Permissions"){
+ when {
+ branch "master"
+ environment name: 'CHANGE_ID', value: ''
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ script{
+ sh '''#! /bin/bash
+ WRONG_PERM=$(find ./ -path "./.git" -prune -o \\( -name "run" -o -name "finish" -o -name "check" \\) -not -perm -u=x,g=x,o=x -print)
+ if [[ -n "${WRONG_PERM}" ]]; then
+ echo "The following S6 service files are missing the executable bit; canceling the faulty build: ${WRONG_PERM}"
+ exit 1
+ else
+ echo "S6 service file perms look good."
+ fi '''
+ }
+ }
+ }
+ /* #######################
+ GitLab Mirroring
+ ####################### */
+ // Ping into Gitlab to mirror this repo and have a registry endpoint
+ stage("GitLab Mirror"){
+ when {
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps{
+ sh '''curl -H "Content-Type: application/json" -H "Private-Token: ${GITLAB_TOKEN}" -X POST https://gitlab.com/api/v4/projects \
+ -d '{"namespace_id":'${GITLAB_NAMESPACE}',\
+ "name":"'${LS_REPO}'",
+ "mirror":true,\
+ "import_url":"https://github.com/linuxserver/'${LS_REPO}'.git",\
+ "issues_access_level":"disabled",\
+ "merge_requests_access_level":"disabled",\
+ "repository_access_level":"enabled",\
+ "visibility":"public"}' '''
+ }
+ }
+ /* #######################
+ Scarf.sh package registry
+ ####################### */
+ // Add package to Scarf.sh and set permissions
+ stage("Scarf.sh package registry"){
+ when {
+ branch "master"
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps{
+ sh '''#! /bin/bash
+ PACKAGE_UUID=$(curl -X GET -H "Authorization: Bearer ${SCARF_TOKEN}" https://scarf.sh/api/v1/organizations/linuxserver-ci/packages | jq -r '.[] | select(.name=="linuxserver/cops") | .uuid' || :)
+ if [ -z "${PACKAGE_UUID}" ]; then
+ echo "Adding package to Scarf.sh"
+ curl -sX POST https://scarf.sh/api/v1/organizations/linuxserver-ci/packages \
+ -H "Authorization: Bearer ${SCARF_TOKEN}" \
+ -H "Content-Type: application/json" \
+ -d '{"name":"linuxserver/cops",\
+ "shortDescription":"example description",\
+ "libraryType":"docker",\
+ "website":"https://github.com/linuxserver/docker-cops",\
+ "backendUrl":"https://ghcr.io/linuxserver/cops",\
+ "publicUrl":"https://lscr.io/linuxserver/cops"}' || :
+ else
+ echo "Package already exists on Scarf.sh"
+ fi
+ '''
+ }
+ }
+ /* ###############
+ Build Container
+ ############### */
+ // Build Docker container for push to LS Repo
+ stage('Build-Single') {
+ when {
+ expression {
+ env.MULTIARCH == 'false' || params.PACKAGE_CHECK == 'true'
+ }
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ echo "Running on node: ${NODE_NAME}"
+ sh "sed -r -i 's|(^FROM .*)|\\1\\n\\nENV LSIO_FIRST_PARTY=true|g' Dockerfile"
+ sh "docker buildx build \
+ --label \"org.opencontainers.image.created=${GITHUB_DATE}\" \
+ --label \"org.opencontainers.image.authors=linuxserver.io\" \
+ --label \"org.opencontainers.image.url=https://github.com/linuxserver/docker-cops/packages\" \
+ --label \"org.opencontainers.image.documentation=https://docs.linuxserver.io/images/docker-cops\" \
+ --label \"org.opencontainers.image.source=https://github.com/linuxserver/docker-cops\" \
+ --label \"org.opencontainers.image.version=${EXT_RELEASE_CLEAN}-ls${LS_TAG_NUMBER}\" \
+ --label \"org.opencontainers.image.revision=${COMMIT_SHA}\" \
+ --label \"org.opencontainers.image.vendor=linuxserver.io\" \
+ --label \"org.opencontainers.image.licenses=GPL-3.0-only\" \
+ --label \"org.opencontainers.image.ref.name=${COMMIT_SHA}\" \
+ --label \"org.opencontainers.image.title=Cops\" \
+ --label \"org.opencontainers.image.description=[Cops](http://blog.slucas.fr/en/oss/calibre-opds-php-server) by Sébastien Lucas, now maintained by MikesPub, stands for Calibre OPDS (and HTML) Php Server. COPS links to your Calibre library database and allows downloading and emailing of books directly from a web browser and provides a OPDS feed to connect to your devices. Changes in your Calibre library are reflected immediately in your COPS pages. See : [COPS's home](http://blog.slucas.fr/en/oss/calibre-opds-php-server) for more details. Don't forget to check the [Wiki](https://github.com/seblucas/cops/wiki). ## Why? (taken from the author's site) In my opinion Calibre is a marvelous tool but is too big and has too much dependencies to be used for its content server. That's the main reason why I coded this OPDS server. I needed a simple tool to be installed on a small server (Seagate Dockstar in my case). I initially thought of Calibre2OPDS but as it generate static file no search was possible. Later I added an simple HTML catalog that should be usable on my Kobo. So COPS's main advantages are : * No need for many dependencies. * No need for a lot of CPU or RAM. * Not much code. * Search is available. * With Dropbox / owncloud it's very easy to have an up to date OPDS server. * It was fun to code. If you want to use the OPDS feed don't forget to specify feed.php at the end of your URL. \" \
+ --no-cache --pull -t ${IMAGE}:${META_TAG} --platform=linux/amd64 \
+ --build-arg ${BUILD_VERSION_ARG}=${EXT_RELEASE} --build-arg VERSION=\"${VERSION_TAG}\" --build-arg BUILD_DATE=${GITHUB_DATE} ."
+ }
+ }
+ // Build MultiArch Docker containers for push to LS Repo
+ stage('Build-Multi') {
+ when {
+ allOf {
+ environment name: 'MULTIARCH', value: 'true'
+ expression { params.PACKAGE_CHECK == 'false' }
+ }
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ parallel {
+ stage('Build X86') {
+ steps {
+ echo "Running on node: ${NODE_NAME}"
+ sh "sed -r -i 's|(^FROM .*)|\\1\\n\\nENV LSIO_FIRST_PARTY=true|g' Dockerfile"
+ sh "docker buildx build \
+ --label \"org.opencontainers.image.created=${GITHUB_DATE}\" \
+ --label \"org.opencontainers.image.authors=linuxserver.io\" \
+ --label \"org.opencontainers.image.url=https://github.com/linuxserver/docker-cops/packages\" \
+ --label \"org.opencontainers.image.documentation=https://docs.linuxserver.io/images/docker-cops\" \
+ --label \"org.opencontainers.image.source=https://github.com/linuxserver/docker-cops\" \
+ --label \"org.opencontainers.image.version=${EXT_RELEASE_CLEAN}-ls${LS_TAG_NUMBER}\" \
+ --label \"org.opencontainers.image.revision=${COMMIT_SHA}\" \
+ --label \"org.opencontainers.image.vendor=linuxserver.io\" \
+ --label \"org.opencontainers.image.licenses=GPL-3.0-only\" \
+ --label \"org.opencontainers.image.ref.name=${COMMIT_SHA}\" \
+ --label \"org.opencontainers.image.title=Cops\" \
+ --label \"org.opencontainers.image.description=[Cops](http://blog.slucas.fr/en/oss/calibre-opds-php-server) by Sébastien Lucas, now maintained by MikesPub, stands for Calibre OPDS (and HTML) Php Server. COPS links to your Calibre library database and allows downloading and emailing of books directly from a web browser and provides a OPDS feed to connect to your devices. Changes in your Calibre library are reflected immediately in your COPS pages. See : [COPS's home](http://blog.slucas.fr/en/oss/calibre-opds-php-server) for more details. Don't forget to check the [Wiki](https://github.com/seblucas/cops/wiki). ## Why? (taken from the author's site) In my opinion Calibre is a marvelous tool but is too big and has too much dependencies to be used for its content server. That's the main reason why I coded this OPDS server. I needed a simple tool to be installed on a small server (Seagate Dockstar in my case). I initially thought of Calibre2OPDS but as it generate static file no search was possible. Later I added an simple HTML catalog that should be usable on my Kobo. So COPS's main advantages are : * No need for many dependencies. * No need for a lot of CPU or RAM. * Not much code. * Search is available. * With Dropbox / owncloud it's very easy to have an up to date OPDS server. * It was fun to code. If you want to use the OPDS feed don't forget to specify feed.php at the end of your URL. \" \
+ --no-cache --pull -t ${IMAGE}:amd64-${META_TAG} --platform=linux/amd64 \
+ --build-arg ${BUILD_VERSION_ARG}=${EXT_RELEASE} --build-arg VERSION=\"${VERSION_TAG}\" --build-arg BUILD_DATE=${GITHUB_DATE} ."
+ }
+ }
+ stage('Build ARM64') {
+ agent {
+ label 'ARM64'
+ }
+ steps {
+ echo "Running on node: ${NODE_NAME}"
+ echo 'Logging into Github'
+ sh '''#! /bin/bash
+ echo $GITHUB_TOKEN | docker login ghcr.io -u LinuxServer-CI --password-stdin
+ '''
+ sh "sed -r -i 's|(^FROM .*)|\\1\\n\\nENV LSIO_FIRST_PARTY=true|g' Dockerfile.aarch64"
+ sh "docker buildx build \
+ --label \"org.opencontainers.image.created=${GITHUB_DATE}\" \
+ --label \"org.opencontainers.image.authors=linuxserver.io\" \
+ --label \"org.opencontainers.image.url=https://github.com/linuxserver/docker-cops/packages\" \
+ --label \"org.opencontainers.image.documentation=https://docs.linuxserver.io/images/docker-cops\" \
+ --label \"org.opencontainers.image.source=https://github.com/linuxserver/docker-cops\" \
+ --label \"org.opencontainers.image.version=${EXT_RELEASE_CLEAN}-ls${LS_TAG_NUMBER}\" \
+ --label \"org.opencontainers.image.revision=${COMMIT_SHA}\" \
+ --label \"org.opencontainers.image.vendor=linuxserver.io\" \
+ --label \"org.opencontainers.image.licenses=GPL-3.0-only\" \
+ --label \"org.opencontainers.image.ref.name=${COMMIT_SHA}\" \
+ --label \"org.opencontainers.image.title=Cops\" \
+ --label \"org.opencontainers.image.description=[Cops](http://blog.slucas.fr/en/oss/calibre-opds-php-server) by Sébastien Lucas, now maintained by MikesPub, stands for Calibre OPDS (and HTML) Php Server. COPS links to your Calibre library database and allows downloading and emailing of books directly from a web browser and provides a OPDS feed to connect to your devices. Changes in your Calibre library are reflected immediately in your COPS pages. See : [COPS's home](http://blog.slucas.fr/en/oss/calibre-opds-php-server) for more details. Don't forget to check the [Wiki](https://github.com/seblucas/cops/wiki). ## Why? (taken from the author's site) In my opinion Calibre is a marvelous tool but is too big and has too much dependencies to be used for its content server. That's the main reason why I coded this OPDS server. I needed a simple tool to be installed on a small server (Seagate Dockstar in my case). I initially thought of Calibre2OPDS but as it generate static file no search was possible. Later I added an simple HTML catalog that should be usable on my Kobo. So COPS's main advantages are : * No need for many dependencies. * No need for a lot of CPU or RAM. * Not much code. * Search is available. * With Dropbox / owncloud it's very easy to have an up to date OPDS server. * It was fun to code. If you want to use the OPDS feed don't forget to specify feed.php at the end of your URL. \" \
+ --no-cache --pull -f Dockerfile.aarch64 -t ${IMAGE}:arm64v8-${META_TAG} --platform=linux/arm64 \
+ --build-arg ${BUILD_VERSION_ARG}=${EXT_RELEASE} --build-arg VERSION=\"${VERSION_TAG}\" --build-arg BUILD_DATE=${GITHUB_DATE} ."
+ sh "docker tag ${IMAGE}:arm64v8-${META_TAG} ghcr.io/linuxserver/lsiodev-buildcache:arm64v8-${COMMIT_SHA}-${BUILD_NUMBER}"
+ retry(5) {
+ sh "docker push ghcr.io/linuxserver/lsiodev-buildcache:arm64v8-${COMMIT_SHA}-${BUILD_NUMBER}"
+ }
+ sh '''#! /bin/bash
+ containers=$(docker ps -aq)
+ if [[ -n "${containers}" ]]; then
+ docker stop ${containers}
+ fi
+ docker system prune -af --volumes || : '''
+ }
+ }
+ }
+ }
+ // Take the image we just built and dump package versions for comparison
+ stage('Update-packages') {
+ when {
+ branch "master"
+ environment name: 'CHANGE_ID', value: ''
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ sh '''#! /bin/bash
+ set -e
+ TEMPDIR=$(mktemp -d)
+ if [ "${MULTIARCH}" == "true" ] && [ "${PACKAGE_CHECK}" == "false" ]; then
+ LOCAL_CONTAINER=${IMAGE}:amd64-${META_TAG}
+ else
+ LOCAL_CONTAINER=${IMAGE}:${META_TAG}
+ fi
+ touch ${TEMPDIR}/package_versions.txt
+ docker run --rm \
+ -v /var/run/docker.sock:/var/run/docker.sock:ro \
+ -v ${TEMPDIR}:/tmp \
+ ghcr.io/anchore/syft:latest \
+ ${LOCAL_CONTAINER} -o table=/tmp/package_versions.txt
+ NEW_PACKAGE_TAG=$(md5sum ${TEMPDIR}/package_versions.txt | cut -c1-8 )
+ echo "Package tag sha from current packages in buit container is ${NEW_PACKAGE_TAG} comparing to old ${PACKAGE_TAG} from github"
+ if [ "${NEW_PACKAGE_TAG}" != "${PACKAGE_TAG}" ]; then
+ git clone https://github.com/${LS_USER}/${LS_REPO}.git ${TEMPDIR}/${LS_REPO}
+ git --git-dir ${TEMPDIR}/${LS_REPO}/.git checkout -f master
+ cp ${TEMPDIR}/package_versions.txt ${TEMPDIR}/${LS_REPO}/
+ cd ${TEMPDIR}/${LS_REPO}/
+ wait
+ git add package_versions.txt
+ git commit -m 'Bot Updating Package Versions'
+ git push https://LinuxServer-CI:${GITHUB_TOKEN}@github.com/${LS_USER}/${LS_REPO}.git --all
+ echo "true" > /tmp/packages-${COMMIT_SHA}-${BUILD_NUMBER}
+ echo "Package tag updated, stopping build process"
+ else
+ echo "false" > /tmp/packages-${COMMIT_SHA}-${BUILD_NUMBER}
+ echo "Package tag is same as previous continue with build process"
+ fi
+ rm -Rf ${TEMPDIR}'''
+ script{
+ env.PACKAGE_UPDATED = sh(
+ script: '''cat /tmp/packages-${COMMIT_SHA}-${BUILD_NUMBER}''',
+ returnStdout: true).trim()
+ }
+ }
+ }
+ // Exit the build if the package file was just updated
+ stage('PACKAGE-exit') {
+ when {
+ branch "master"
+ environment name: 'CHANGE_ID', value: ''
+ environment name: 'PACKAGE_UPDATED', value: 'true'
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ script{
+ env.EXIT_STATUS = 'ABORTED'
+ }
+ }
+ }
+ // Exit the build if this is just a package check and there are no changes to push
+ stage('PACKAGECHECK-exit') {
+ when {
+ branch "master"
+ environment name: 'CHANGE_ID', value: ''
+ environment name: 'PACKAGE_UPDATED', value: 'false'
+ environment name: 'EXIT_STATUS', value: ''
+ expression {
+ params.PACKAGE_CHECK == 'true'
+ }
+ }
+ steps {
+ script{
+ env.EXIT_STATUS = 'ABORTED'
+ }
+ }
+ }
+ /* #######
+ Testing
+ ####### */
+ // Run Container tests
+ stage('Test') {
+ when {
+ environment name: 'CI', value: 'true'
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ withCredentials([
+ string(credentialsId: 'ci-tests-s3-key-id', variable: 'S3_KEY'),
+ string(credentialsId: 'ci-tests-s3-secret-access-key ', variable: 'S3_SECRET')
+ ]) {
+ script{
+ env.CI_URL = 'https://ci-tests.linuxserver.io/' + env.IMAGE + '/' + env.META_TAG + '/index.html'
+ env.CI_JSON_URL = 'https://ci-tests.linuxserver.io/' + env.IMAGE + '/' + env.META_TAG + '/report.json'
+ }
+ sh '''#! /bin/bash
+ set -e
+ docker pull ghcr.io/linuxserver/ci:latest
+ if [ "${MULTIARCH}" == "true" ]; then
+ docker pull ghcr.io/linuxserver/lsiodev-buildcache:arm64v8-${COMMIT_SHA}-${BUILD_NUMBER}
+ docker tag ghcr.io/linuxserver/lsiodev-buildcache:arm64v8-${COMMIT_SHA}-${BUILD_NUMBER} ${IMAGE}:arm64v8-${META_TAG}
+ fi
+ docker run --rm \
+ --shm-size=1gb \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -e IMAGE=\"${IMAGE}\" \
+ -e DELAY_START=\"${CI_DELAY}\" \
+ -e TAGS=\"${CI_TAGS}\" \
+ -e META_TAG=\"${META_TAG}\" \
+ -e PORT=\"${CI_PORT}\" \
+ -e SSL=\"${CI_SSL}\" \
+ -e BASE=\"${DIST_IMAGE}\" \
+ -e SECRET_KEY=\"${S3_SECRET}\" \
+ -e ACCESS_KEY=\"${S3_KEY}\" \
+ -e DOCKER_ENV=\"${CI_DOCKERENV}\" \
+ -e WEB_SCREENSHOT=\"${CI_WEB}\" \
+ -e WEB_AUTH=\"${CI_AUTH}\" \
+ -e WEB_PATH=\"${CI_WEBPATH}\" \
+ -t ghcr.io/linuxserver/ci:latest \
+ python3 test_build.py'''
+ }
+ }
+ }
+ /* ##################
+ Release Logic
+ ################## */
+ // If this is an amd64 only image only push a single image
+ stage('Docker-Push-Single') {
+ when {
+ environment name: 'MULTIARCH', value: 'false'
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ withCredentials([
+ [
+ $class: 'UsernamePasswordMultiBinding',
+ credentialsId: '3f9ba4d5-100d-45b0-a3c4-633fd6061207',
+ usernameVariable: 'DOCKERUSER',
+ passwordVariable: 'DOCKERPASS'
+ ],
+ [
+ $class: 'UsernamePasswordMultiBinding',
+ credentialsId: 'Quay.io-Robot',
+ usernameVariable: 'QUAYUSER',
+ passwordVariable: 'QUAYPASS'
+ ]
+ ]) {
+ retry(5) {
+ sh '''#! /bin/bash
+ set -e
+ echo $DOCKERPASS | docker login -u $DOCKERUSER --password-stdin
+ echo $GITHUB_TOKEN | docker login ghcr.io -u LinuxServer-CI --password-stdin
+ echo $GITLAB_TOKEN | docker login registry.gitlab.com -u LinuxServer.io --password-stdin
+ echo $QUAYPASS | docker login quay.io -u $QUAYUSER --password-stdin
+ for PUSHIMAGE in "${GITHUBIMAGE}" "${GITLABIMAGE}" "${QUAYIMAGE}" "${IMAGE}"; do
+ docker tag ${IMAGE}:${META_TAG} ${PUSHIMAGE}:${META_TAG}
+ docker tag ${PUSHIMAGE}:${META_TAG} ${PUSHIMAGE}:latest
+ docker tag ${PUSHIMAGE}:${META_TAG} ${PUSHIMAGE}:${EXT_RELEASE_TAG}
+ if [ -n "${SEMVER}" ]; then
+ docker tag ${PUSHIMAGE}:${META_TAG} ${PUSHIMAGE}:${SEMVER}
+ fi
+ docker push ${PUSHIMAGE}:latest
+ docker push ${PUSHIMAGE}:${META_TAG}
+ docker push ${PUSHIMAGE}:${EXT_RELEASE_TAG}
+ if [ -n "${SEMVER}" ]; then
+ docker push ${PUSHIMAGE}:${SEMVER}
+ fi
+ done
+ '''
+ }
+ }
+ }
+ }
+ // If this is a multi arch release push all images and define the manifest
+ stage('Docker-Push-Multi') {
+ when {
+ environment name: 'MULTIARCH', value: 'true'
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ withCredentials([
+ [
+ $class: 'UsernamePasswordMultiBinding',
+ credentialsId: '3f9ba4d5-100d-45b0-a3c4-633fd6061207',
+ usernameVariable: 'DOCKERUSER',
+ passwordVariable: 'DOCKERPASS'
+ ],
+ [
+ $class: 'UsernamePasswordMultiBinding',
+ credentialsId: 'Quay.io-Robot',
+ usernameVariable: 'QUAYUSER',
+ passwordVariable: 'QUAYPASS'
+ ]
+ ]) {
+ retry(5) {
+ sh '''#! /bin/bash
+ set -e
+ echo $DOCKERPASS | docker login -u $DOCKERUSER --password-stdin
+ echo $GITHUB_TOKEN | docker login ghcr.io -u LinuxServer-CI --password-stdin
+ echo $GITLAB_TOKEN | docker login registry.gitlab.com -u LinuxServer.io --password-stdin
+ echo $QUAYPASS | docker login quay.io -u $QUAYUSER --password-stdin
+ if [ "${CI}" == "false" ]; then
+ docker pull ghcr.io/linuxserver/lsiodev-buildcache:arm64v8-${COMMIT_SHA}-${BUILD_NUMBER}
+ docker tag ghcr.io/linuxserver/lsiodev-buildcache:arm64v8-${COMMIT_SHA}-${BUILD_NUMBER} ${IMAGE}:arm64v8-${META_TAG}
+ fi
+ for MANIFESTIMAGE in "${IMAGE}" "${GITLABIMAGE}" "${GITHUBIMAGE}" "${QUAYIMAGE}"; do
+ docker tag ${IMAGE}:amd64-${META_TAG} ${MANIFESTIMAGE}:amd64-${META_TAG}
+ docker tag ${MANIFESTIMAGE}:amd64-${META_TAG} ${MANIFESTIMAGE}:amd64-latest
+ docker tag ${MANIFESTIMAGE}:amd64-${META_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG}
+ docker tag ${IMAGE}:arm64v8-${META_TAG} ${MANIFESTIMAGE}:arm64v8-${META_TAG}
+ docker tag ${MANIFESTIMAGE}:arm64v8-${META_TAG} ${MANIFESTIMAGE}:arm64v8-latest
+ docker tag ${MANIFESTIMAGE}:arm64v8-${META_TAG} ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG}
+ if [ -n "${SEMVER}" ]; then
+ docker tag ${MANIFESTIMAGE}:amd64-${META_TAG} ${MANIFESTIMAGE}:amd64-${SEMVER}
+ docker tag ${MANIFESTIMAGE}:arm64v8-${META_TAG} ${MANIFESTIMAGE}:arm64v8-${SEMVER}
+ fi
+ docker push ${MANIFESTIMAGE}:amd64-${META_TAG}
+ docker push ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG}
+ docker push ${MANIFESTIMAGE}:amd64-latest
+ docker push ${MANIFESTIMAGE}:arm64v8-${META_TAG}
+ docker push ${MANIFESTIMAGE}:arm64v8-latest
+ docker push ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG}
+ if [ -n "${SEMVER}" ]; then
+ docker push ${MANIFESTIMAGE}:amd64-${SEMVER}
+ docker push ${MANIFESTIMAGE}:arm64v8-${SEMVER}
+ fi
+ docker manifest push --purge ${MANIFESTIMAGE}:latest || :
+ docker manifest create ${MANIFESTIMAGE}:latest ${MANIFESTIMAGE}:amd64-latest ${MANIFESTIMAGE}:arm64v8-latest
+ docker manifest annotate ${MANIFESTIMAGE}:latest ${MANIFESTIMAGE}:arm64v8-latest --os linux --arch arm64 --variant v8
+ docker manifest push --purge ${MANIFESTIMAGE}:${META_TAG} || :
+ docker manifest create ${MANIFESTIMAGE}:${META_TAG} ${MANIFESTIMAGE}:amd64-${META_TAG} ${MANIFESTIMAGE}:arm64v8-${META_TAG}
+ docker manifest annotate ${MANIFESTIMAGE}:${META_TAG} ${MANIFESTIMAGE}:arm64v8-${META_TAG} --os linux --arch arm64 --variant v8
+ docker manifest push --purge ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} || :
+ docker manifest create ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:amd64-${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG}
+ docker manifest annotate ${MANIFESTIMAGE}:${EXT_RELEASE_TAG} ${MANIFESTIMAGE}:arm64v8-${EXT_RELEASE_TAG} --os linux --arch arm64 --variant v8
+ if [ -n "${SEMVER}" ]; then
+ docker manifest push --purge ${MANIFESTIMAGE}:${SEMVER} || :
+ docker manifest create ${MANIFESTIMAGE}:${SEMVER} ${MANIFESTIMAGE}:amd64-${SEMVER} ${MANIFESTIMAGE}:arm64v8-${SEMVER}
+ docker manifest annotate ${MANIFESTIMAGE}:${SEMVER} ${MANIFESTIMAGE}:arm64v8-${SEMVER} --os linux --arch arm64 --variant v8
+ fi
+ token=$(curl -sX GET "https://ghcr.io/token?scope=repository%3Alinuxserver%2F${CONTAINER_NAME}%3Apull" | jq -r '.token')
+ digest=$(curl -s \
+ --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
+ --header "Authorization: Bearer ${token}" \
+ "https://ghcr.io/v2/linuxserver/${CONTAINER_NAME}/manifests/arm32v7-latest")
+ if [[ $(echo "$digest" | jq -r '.layers') != "null" ]]; then
+ docker manifest push --purge ${MANIFESTIMAGE}:arm32v7-latest || :
+ docker manifest create ${MANIFESTIMAGE}:arm32v7-latest ${MANIFESTIMAGE}:amd64-latest
+ docker manifest push --purge ${MANIFESTIMAGE}:arm32v7-latest
+ fi
+ docker manifest push --purge ${MANIFESTIMAGE}:latest
+ docker manifest push --purge ${MANIFESTIMAGE}:${META_TAG}
+ docker manifest push --purge ${MANIFESTIMAGE}:${EXT_RELEASE_TAG}
+ if [ -n "${SEMVER}" ]; then
+ docker manifest push --purge ${MANIFESTIMAGE}:${SEMVER}
+ fi
+ done
+ '''
+ }
+ }
+ }
+ }
+ // If this is a public release tag it in the LS Github
+ stage('Github-Tag-Push-Release') {
+ when {
+ branch "master"
+ expression {
+ env.LS_RELEASE != env.EXT_RELEASE_CLEAN + '-ls' + env.LS_TAG_NUMBER
+ }
+ environment name: 'CHANGE_ID', value: ''
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ echo "Pushing New tag for current commit ${META_TAG}"
+ sh '''curl -H "Authorization: token ${GITHUB_TOKEN}" -X POST https://api.github.com/repos/${LS_USER}/${LS_REPO}/git/tags \
+ -d '{"tag":"'${META_TAG}'",\
+ "object": "'${COMMIT_SHA}'",\
+ "message": "Tagging Release '${EXT_RELEASE_CLEAN}'-ls'${LS_TAG_NUMBER}' to master",\
+ "type": "commit",\
+ "tagger": {"name": "LinuxServer Jenkins","email": "jenkins@linuxserver.io","date": "'${GITHUB_DATE}'"}}' '''
+ echo "Pushing New release for Tag"
+ sh '''#! /bin/bash
+ curl -H "Authorization: token ${GITHUB_TOKEN}" -s https://api.github.com/repos/${EXT_USER}/${EXT_REPO}/releases/latest | jq '. |.body' | sed 's:^.\\(.*\\).$:\\1:' > releasebody.json
+ echo '{"tag_name":"'${META_TAG}'",\
+ "target_commitish": "master",\
+ "name": "'${META_TAG}'",\
+ "body": "**LinuxServer Changes:**\\n\\n'${LS_RELEASE_NOTES}'\\n\\n**'${EXT_REPO}' Changes:**\\n\\n' > start
+ printf '","draft": false,"prerelease": false}' >> releasebody.json
+ paste -d'\\0' start releasebody.json > releasebody.json.done
+ curl -H "Authorization: token ${GITHUB_TOKEN}" -X POST https://api.github.com/repos/${LS_USER}/${LS_REPO}/releases -d @releasebody.json.done'''
+ }
+ }
+ // Use helper container to sync the current README on master to the dockerhub endpoint
+ stage('Sync-README') {
+ when {
+ environment name: 'CHANGE_ID', value: ''
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ withCredentials([
+ [
+ $class: 'UsernamePasswordMultiBinding',
+ credentialsId: '3f9ba4d5-100d-45b0-a3c4-633fd6061207',
+ usernameVariable: 'DOCKERUSER',
+ passwordVariable: 'DOCKERPASS'
+ ]
+ ]) {
+ sh '''#! /bin/bash
+ set -e
+ TEMPDIR=$(mktemp -d)
+ docker pull ghcr.io/linuxserver/jenkins-builder:latest
+ docker run --rm -e CONTAINER_NAME=${CONTAINER_NAME} -e GITHUB_BRANCH="${BRANCH_NAME}" -v ${TEMPDIR}:/ansible/jenkins ghcr.io/linuxserver/jenkins-builder:latest
+ docker pull ghcr.io/linuxserver/readme-sync
+ docker run --rm=true \
+ -e DOCKERHUB_USERNAME=$DOCKERUSER \
+ -e DOCKERHUB_PASSWORD=$DOCKERPASS \
+ -e GIT_REPOSITORY=${LS_USER}/${LS_REPO} \
+ -e DOCKER_REPOSITORY=${IMAGE} \
+ -e GIT_BRANCH=master \
+ -v ${TEMPDIR}/docker-${CONTAINER_NAME}:/mnt \
+ ghcr.io/linuxserver/readme-sync bash -c 'node sync'
+ rm -Rf ${TEMPDIR} '''
+ }
+ }
+ }
+ // If this is a Pull request send the CI link as a comment on it
+ stage('Pull Request Comment') {
+ when {
+ not {environment name: 'CHANGE_ID', value: ''}
+ environment name: 'EXIT_STATUS', value: ''
+ }
+ steps {
+ sh '''#! /bin/bash
+ # Function to retrieve JSON data from URL
+ get_json() {
+ local url="$1"
+ local response=$(curl -s "$url")
+ if [ $? -ne 0 ]; then
+ echo "Failed to retrieve JSON data from $url"
+ return 1
+ fi
+ local json=$(echo "$response" | jq .)
+ if [ $? -ne 0 ]; then
+ echo "Failed to parse JSON data from $url"
+ return 1
+ fi
+ echo "$json"
+ }
+
+ build_table() {
+ local data="$1"
+
+ # Get the keys in the JSON data
+ local keys=$(echo "$data" | jq -r 'to_entries | map(.key) | .[]')
+
+ # Check if keys are empty
+ if [ -z "$keys" ]; then
+ echo "JSON report data does not contain any keys or the report does not exist."
+ return 1
+ fi
+
+ # Build table header
+ local header="| Tag | Passed |\\n| --- | --- |\\n"
+
+ # Loop through the JSON data to build the table rows
+ local rows=""
+ for build in $keys; do
+ local status=$(echo "$data" | jq -r ".[\\"$build\\"].test_success")
+ if [ "$status" = "true" ]; then
+ status="✅"
+ else
+ status="❌"
+ fi
+ local row="| "$build" | "$status" |\\n"
+ rows="${rows}${row}"
+ done
+
+ local table="${header}${rows}"
+ local escaped_table=$(echo "$table" | sed 's/\"/\\\\"/g')
+ echo "$escaped_table"
+ }
+
+ if [[ "${CI}" = "true" ]]; then
+ # Retrieve JSON data from URL
+ data=$(get_json "$CI_JSON_URL")
+ # Create table from JSON data
+ table=$(build_table "$data")
+ echo -e "$table"
+
+ curl -X POST -H "Authorization: token $GITHUB_TOKEN" \
+ -H "Accept: application/vnd.github.v3+json" \
+ "https://api.github.com/repos/$LS_USER/$LS_REPO/issues/$PULL_REQUEST/comments" \
+ -d "{\\"body\\": \\"I am a bot, here are the test results for this PR: \\n${CI_URL}\\n${SHELLCHECK_URL}\\n${table}\\"}"
+ else
+ curl -X POST -H "Authorization: token $GITHUB_TOKEN" \
+ -H "Accept: application/vnd.github.v3+json" \
+ "https://api.github.com/repos/$LS_USER/$LS_REPO/issues/$PULL_REQUEST/comments" \
+ -d "{\\"body\\": \\"I am a bot, here is the pushed image/manifest for this PR: \\n\\n\\`${GITHUBIMAGE}:${META_TAG}\\`\\"}"
+ fi
+ '''
+
+ }
+ }
+ }
+ /* ######################
+ Send status to Discord
+ ###################### */
+ post {
+ always {
+ script{
+ if (env.EXIT_STATUS == "ABORTED"){
+ sh 'echo "build aborted"'
+ }
+ else if (currentBuild.currentResult == "SUCCESS"){
+ sh ''' curl -X POST -H "Content-Type: application/json" --data '{"avatar_url": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/jenkins-avatar.png","embeds": [{"color": 1681177,\
+ "description": "**Build:** '${BUILD_NUMBER}'\\n**CI Results:** '${CI_URL}'\\n**ShellCheck Results:** '${SHELLCHECK_URL}'\\n**Status:** Success\\n**Job:** '${RUN_DISPLAY_URL}'\\n**Change:** '${CODE_URL}'\\n**External Release:**: '${RELEASE_LINK}'\\n**DockerHub:** '${DOCKERHUB_LINK}'\\n"}],\
+ "username": "Jenkins"}' ${BUILDS_DISCORD} '''
+ }
+ else {
+ sh ''' curl -X POST -H "Content-Type: application/json" --data '{"avatar_url": "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/jenkins-avatar.png","embeds": [{"color": 16711680,\
+ "description": "**Build:** '${BUILD_NUMBER}'\\n**CI Results:** '${CI_URL}'\\n**ShellCheck Results:** '${SHELLCHECK_URL}'\\n**Status:** failure\\n**Job:** '${RUN_DISPLAY_URL}'\\n**Change:** '${CODE_URL}'\\n**External Release:**: '${RELEASE_LINK}'\\n**DockerHub:** '${DOCKERHUB_LINK}'\\n"}],\
+ "username": "Jenkins"}' ${BUILDS_DISCORD} '''
+ }
+ }
+ }
+ cleanup {
+ sh '''#! /bin/bash
+ echo "Performing docker system prune!!"
+ containers=$(docker ps -aq)
+ if [[ -n "${containers}" ]]; then
+ docker stop ${containers}
+ fi
+ docker system prune -af --volumes || :
+ '''
+ cleanWs()
+ }
+ }
+}
diff --git a/README.md b/README.md
index 6bc33bf..3f94778 100644
--- a/README.md
+++ b/README.md
@@ -27,10 +27,6 @@ Find us at:
* [GitHub](https://github.com/linuxserver) - view the source for all of our repositories.
* [Open Collective](https://opencollective.com/linuxserver) - please consider helping us by either donating or contributing to our budget
-# DEPRECATION NOTICE
-
-This image is deprecated. We will not offer support for this image and it will not be updated.
-COPS has been abandoned by its developers and no actively maintained forks exist at this time.
# [linuxserver/cops](https://github.com/linuxserver/docker-cops)
[](https://scarf.sh/gateway/linuxserver-ci/docker/linuxserver%2Fcops)
@@ -44,7 +40,7 @@ COPS has been abandoned by its developers and no actively maintained forks exist
[](https://ci.linuxserver.io/job/Docker-Pipeline-Builders/job/docker-cops/job/master/)
[](https://ci-tests.linuxserver.io/linuxserver/cops/latest/index.html)
-[Cops](http://blog.slucas.fr/en/oss/calibre-opds-php-server) by Sébastien Lucas, stands for Calibre OPDS (and HTML) Php Server.
+[Cops](http://blog.slucas.fr/en/oss/calibre-opds-php-server) by Sébastien Lucas, now maintained by MikesPub, stands for Calibre OPDS (and HTML) Php Server.
COPS links to your Calibre library database and allows downloading and emailing of books directly from a web browser and provides a OPDS feed to connect to your devices.
@@ -91,7 +87,7 @@ The architectures supported by this image are:
| :----: | :----: | ---- |
| x86-64 | ✅ | amd64-\ |
| arm64 | ✅ | arm64v8-\ |
-| armhf | ✅ | arm32v7-\ |
+| armhf | ❌ | |
## Application Setup
@@ -262,6 +258,7 @@ Once registered you can define the dockerfile to use with `-f Dockerfile.aarch64
## Versions
+* **11.08.23:** - Undeprecate and add new branch with mikespub fork which is actively maintained.
* **15.05.23:** - Deprecate due to upstream dev abandonment of project.
* **13.04.23:** - Move ssl.conf include to default.conf.
* **19.01.23:** - Rebase to alpine 3.17 with php8.1.
diff --git a/jenkins-vars.yml b/jenkins-vars.yml
index 4969bf3..98decab 100644
--- a/jenkins-vars.yml
+++ b/jenkins-vars.yml
@@ -7,9 +7,9 @@ release_type: stable
release_tag: latest
ls_branch: master
repo_vars:
- - EXT_GIT_BRANCH = 'master'
- - EXT_USER = 'seblucas'
- - EXT_REPO = 'cops'
+ - EXT_GIT_BRANCH = 'main'
+ - EXT_USER = 'mikespub-org'
+ - EXT_REPO = 'seblucas-cops'
- BUILD_VERSION_ARG = 'COPS_RELEASE'
- LS_USER = 'linuxserver'
- LS_REPO = 'docker-cops'
diff --git a/readme-vars.yml b/readme-vars.yml
index 45a3125..10e90b5 100644
--- a/readme-vars.yml
+++ b/readme-vars.yml
@@ -5,7 +5,7 @@ project_name: cops
project_url: "http://blog.slucas.fr/en/oss/calibre-opds-php-server"
project_logo: "https://raw.githubusercontent.com/linuxserver/docker-templates/master/linuxserver.io/img/cops-icon.png"
project_blurb: |
- [{{ project_name|capitalize }}]({{ project_url }}) by Sébastien Lucas, stands for Calibre OPDS (and HTML) Php Server.
+ [{{ project_name|capitalize }}]({{ project_url }}) by Sébastien Lucas, now maintained by MikesPub, stands for Calibre OPDS (and HTML) Php Server.
COPS links to your Calibre library database and allows downloading and emailing of books directly from a web browser and provides a OPDS feed to connect to your devices.
@@ -39,15 +39,12 @@ project_blurb: |
If you want to use the OPDS feed don't forget to specify feed.php at the end of your URL.
project_lsio_github_repo_url: "https://github.com/linuxserver/docker-{{ project_name }}"
-project_deprecation_status: true
-project_deprecation_message: "COPS has been abandoned by its developers and no actively maintained forks exist at this time."
project_blurb_optional_extras_enabled: false
# supported architectures
available_architectures:
- { arch: "{{ arch_x86_64 }}", tag: "amd64-latest"}
- { arch: "{{ arch_arm64 }}", tag: "arm64v8-latest"}
- - { arch: "{{ arch_armhf }}", tag: "arm32v7-latest"}
# development version
development_versions: false
@@ -86,6 +83,7 @@ app_setup_block: |
# changelog
changelogs:
+ - { date: "11.08.23:", desc: "Undeprecate and add new branch with mikespub fork which is actively maintained." }
- { date: "15.05.23:", desc: "Deprecate due to upstream dev abandonment of project." }
- { date: "13.04.23:", desc: "Move ssl.conf include to default.conf." }
- { date: "19.01.23:", desc: "Rebase to alpine 3.17 with php8.1." }
diff --git a/root/etc/cont-init.d/99-deprecation b/root/etc/cont-init.d/99-deprecation
deleted file mode 100755
index e3ef8ce..0000000
--- a/root/etc/cont-init.d/99-deprecation
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/with-contenv bash
-# shellcheck shell=bash
-
-echo '
-╔════════════════════════════════════════════════════╗
-╠════════════════════════════════════════════════════╣
-║ ║
-║ This image is deprecated. ║
-║ We will not offer support for this image ║
-║ and it will not be updated. ║
-║ ║
-╠════════════════════════════════════════════════════╣
-╚════════════════════════════════════════════════════╝
-
-COPS has been abandoned by its developers and no actively maintained forks exist at this time.
-
-══════════════════════════════════════════════════════'