mirror of
https://github.com/TheBinaryNinja/tvapp2.git
synced 2026-06-04 05:55:40 -04:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
321a0e8540 | ||
|
|
cb8f769e34 | ||
|
|
38ff77a04e | ||
|
|
292cd8dd94 | ||
|
|
3878059314 | ||
|
|
7a7e50c7ba | ||
|
|
564dd536fc | ||
|
|
0e29805351 | ||
| dc76267da3 | |||
| 147b11b22d | |||
|
|
b992e4ff01 | ||
|
|
b46a922464 | ||
| d4abc705a0 | |||
| 454d13c608 | |||
|
|
6086dbbad2 | ||
|
|
b9607dddce | ||
|
|
1a7aeb4450 | ||
|
|
d973af6a8d | ||
|
2dae279f93
|
|||
|
09d17717ab
|
|||
|
bf4454f635
|
|||
|
9e531d823f
|
|||
| d17aa23e98 | |||
| 63f7c1d665 | |||
|
c5c2f741f0
|
|||
|
ec24c51eea
|
|||
|
fa2c4073e3
|
|||
|
255d093269
|
|||
|
73a264b1c2
|
|||
|
|
c112230e05 | ||
|
|
02dd911e93 | ||
|
9c3ee3d146
|
|||
|
4c8d5d03d9
|
|||
|
c729594864
|
|||
|
713626810b
|
|||
|
6e5c261065
|
|||
|
2f1027e068
|
|||
|
739f547731
|
|||
|
3f7ecdb84e
|
|||
|
e037764c3f
|
|||
|
c8aa866dfd
|
|||
|
84b1199878
|
|||
|
04150d5320
|
|||
|
11ccf2909f
|
|||
|
631942ca75
|
|||
|
4ee603d7a2
|
|||
|
7cfe22b72e
|
|||
|
e6701cda95
|
|||
|
865a2fd645
|
|||
|
05f362153f
|
|||
|
997eb72378
|
|||
|
69805151c8
|
|||
|
47ec5267ec
|
|||
|
3a87b51f41
|
|||
|
ffc8cfe68e
|
|||
|
7f5fffa5e6
|
|||
|
b16f4a9fb3
|
|||
|
ebf0b84a05
|
|||
|
b724930c6a
|
|||
|
603e444d35
|
|||
|
f274b807f2
|
|||
|
d0c8920b98
|
|||
|
4c0d49508f
|
|||
|
2a09bc1ea3
|
|||
|
259d27a2ce
|
|||
|
|
8aefbb39e0 | ||
|
|
e417b9f5d8 | ||
|
|
9458587d59 | ||
|
|
468c8c10fc | ||
|
|
6d90a88b60 | ||
|
|
7231199f9e | ||
|
|
41c0c9f685 | ||
|
|
79c5c648c9 | ||
|
|
0ba2e23171 | ||
|
|
b0f3869621 | ||
|
|
b709d53e40 | ||
|
|
b198168d75 | ||
|
bd41ab603d
|
|||
|
0059431fbb
|
|||
|
863addce39
|
|||
|
07b7272eb1
|
|||
|
c59de1fcf9
|
|||
|
2d24d8e379
|
|||
|
60fd32e4d5
|
|||
|
f32504e76b
|
|||
|
8eed126fa4
|
|||
| 9242cbccc4 | |||
|
|
a7d209b370 | ||
|
29c1b6286f
|
|||
|
|
1ae4ab46d4 | ||
| cd33470b12 | |||
| 083feeef90 | |||
| 42f6267539 | |||
|
|
5f669092c2 | ||
| 9a36aad9cb | |||
| 898983f724 | |||
|
c35a726e93
|
|||
|
9cda4061d5
|
1
.github/ISSUE_TEMPLATE/roadmap.yml
vendored
1
.github/ISSUE_TEMPLATE/roadmap.yml
vendored
@@ -41,6 +41,7 @@ body:
|
||||
- Distribution
|
||||
- Documentation
|
||||
- M3U / EPG Functionality
|
||||
- Refactor (Code)
|
||||
- Repository
|
||||
- S6-Overlay
|
||||
default: 0
|
||||
|
||||
4
.github/workflows/deploy-clean.yml
vendored
4
.github/workflows/deploy-clean.yml
vendored
@@ -152,8 +152,8 @@ jobs:
|
||||
cleanup:
|
||||
name: >-
|
||||
🧹 Deployments › Clean
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
permissions: write-all
|
||||
|
||||
|
||||
94
.github/workflows/deploy-docker-dockerhub.yml
vendored
94
.github/workflows/deploy-docker-dockerhub.yml
vendored
@@ -62,18 +62,6 @@ on:
|
||||
default: 'tvapp2'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Image Version
|
||||
#
|
||||
# used to create new release tag, and add version to docker image name
|
||||
# #
|
||||
|
||||
IMAGE_VERSION:
|
||||
description: '🏷️ Image Version'
|
||||
required: true
|
||||
default: '1.0.0'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Registry Name
|
||||
#
|
||||
@@ -163,8 +151,7 @@ on:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.event.inputs.IMAGE_NAME || 'tvapp2' }}
|
||||
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
|
||||
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_VERSION || 'dockerhub' }}
|
||||
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_REGISTRY || 'dockerhub' }}
|
||||
IMAGE_DOCKERHUB_AUTHOR: ${{ github.event.inputs.IMAGE_DOCKERHUB_AUTHOR || 'thebinaryninja' }}
|
||||
IMAGE_DOCKERHUB_USERNAME: ${{ github.event.inputs.IMAGE_DOCKERHUB_USERNAME || 'thebinaryninja' }}
|
||||
IMAGE_ALPINE_VERSION: ${{ github.event.inputs.IMAGE_ALPINE_VERSION || '3.22' }}
|
||||
@@ -195,9 +182,11 @@ jobs:
|
||||
job-docker-release-tags-create:
|
||||
name: >-
|
||||
📦 Release › Create Tag
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 4
|
||||
outputs:
|
||||
package_version: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
@@ -223,6 +212,26 @@ jobs:
|
||||
uses: qoomon/actions--context@v4
|
||||
id: 'context'
|
||||
|
||||
# #
|
||||
# Release › Tags › Set › Package.json › Version
|
||||
# #
|
||||
|
||||
- name: '👁️🗨️ Package Version › Set'
|
||||
id: task_initialize_package_getversion
|
||||
working-directory: ./tvapp2
|
||||
run: |
|
||||
VER=$(cat package.json | jq -r '.version')
|
||||
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_OUTPUT
|
||||
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_ENV
|
||||
|
||||
# #
|
||||
# Initialize › Get › Package.json › Version
|
||||
# #
|
||||
|
||||
- name: '👁️🗨️ Package Version › Get'
|
||||
run: |
|
||||
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
|
||||
|
||||
# #
|
||||
# Release › Tags › Start
|
||||
# #
|
||||
@@ -259,10 +268,10 @@ jobs:
|
||||
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
|
||||
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
|
||||
@@ -352,9 +361,9 @@ jobs:
|
||||
id: task_release_tags_create
|
||||
if: ( github.event_name != 'workflow_dispatch' && inputs.DRY_RUN == false )
|
||||
with:
|
||||
tag: "${{ env.IMAGE_VERSION }}"
|
||||
tag: "${{ env.PACKAGE_VERSION }}"
|
||||
tag_exists_error: false
|
||||
message: '${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}'
|
||||
message: '${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}'
|
||||
gpg_private_key: ${{ secrets.ADMINSERV_GPG_KEY_ASC }}
|
||||
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
|
||||
|
||||
@@ -365,15 +374,17 @@ jobs:
|
||||
job-docker-release-dockerhub:
|
||||
name: >-
|
||||
📦 Release › Dockerhub
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 10
|
||||
needs: [ job-docker-release-tags-create ]
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
needs: [ job-docker-release-tags-create ]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
|
||||
steps:
|
||||
|
||||
# #
|
||||
@@ -430,10 +441,10 @@ jobs:
|
||||
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
|
||||
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
|
||||
@@ -616,7 +627,7 @@ jobs:
|
||||
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
|
||||
|
||||
# tag add 1.0.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
|
||||
|
||||
# tag add 1.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
|
||||
@@ -634,28 +645,28 @@ jobs:
|
||||
labels: |
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
@@ -684,14 +695,14 @@ jobs:
|
||||
build-args: |-
|
||||
ARCH=amd64
|
||||
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
|
||||
VERSION=${{ env.IMAGE_VERSION }}
|
||||
VERSION=${{ env.PACKAGE_VERSION }}
|
||||
BUILDDATE=${{ env.NOW_DOCKER }}
|
||||
GIT_SHA1=${{ env.SHA1 }}
|
||||
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.architecture=amd64
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
@@ -699,7 +710,7 @@ jobs:
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-architecture=amd64
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
@@ -757,14 +768,14 @@ jobs:
|
||||
build-args: |-
|
||||
ARCH=arm64
|
||||
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
|
||||
VERSION=${{ env.IMAGE_VERSION }}
|
||||
VERSION=${{ env.PACKAGE_VERSION }}
|
||||
BUILDDATE=${{ env.NOW_DOCKER }}
|
||||
GIT_SHA1=${{ env.SHA1 }}
|
||||
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.architecture=arm64
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
@@ -772,7 +783,7 @@ jobs:
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-architecture=arm64
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
@@ -824,7 +835,6 @@ jobs:
|
||||
echo ""
|
||||
echo "---- [ INPUTS ] ----------------------------------------------------------------------------------------"
|
||||
echo "inputs.IMAGE_NAME ........................ ${{ inputs.IMAGE_NAME }}"
|
||||
echo "inputs.IMAGE_VERSION ..................... ${{ inputs.IMAGE_VERSION }}"
|
||||
echo "inputs.IMAGE_DOCKERHUB_AUTHOR ............ ${{ inputs.IMAGE_DOCKERHUB_AUTHOR }}"
|
||||
echo "inputs.IMAGE_DOCKERHUB_USERNAME .......... ${{ inputs.IMAGE_DOCKERHUB_USERNAME }}"
|
||||
echo "inputs.DEV_RELEASE ....................... ${{ inputs.DEV_RELEASE }}"
|
||||
@@ -832,7 +842,7 @@ jobs:
|
||||
echo ""
|
||||
echo "---- [ ENV ] -------------------------------------------------------------------------------------------"
|
||||
echo "env.IMAGE_NAME ........................... ${{ env.IMAGE_NAME }}"
|
||||
echo "env.IMAGE_VERSION ........................ ${{ env.IMAGE_VERSION }}"
|
||||
echo "env.PACKAGE_VERSION ...................... ${{ env.PACKAGE_VERSION }}"
|
||||
echo "env.PKG_VER_1DIGIT ....................... ${{ env.PKG_VER_1DIGIT }}"
|
||||
echo "env.PKG_VER_2DIGIT ....................... ${{ env.PKG_VER_2DIGIT }}"
|
||||
echo "env.IMAGE_DOCKERHUB_AUTHOR ............... ${{ env.IMAGE_DOCKERHUB_AUTHOR }}"
|
||||
@@ -906,20 +916,20 @@ jobs:
|
||||
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
|
||||
embed-description: |
|
||||
### 📦 Deploy (Dockerhub) ${{ job.status == 'success' && '✅' || '❌' }} › `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
### 📦 Deploy (Dockerhub) ${{ job.status == 'success' && '✅' || '❌' }} › `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
${{ inputs.DEV_RELEASE == true && '### ⚠️⚠️ Development / Pre-release ⚠️⚠️' || '' }}
|
||||
|
||||
A new version of the docker container `${{ env.IMAGE_NAME }}` has been released from Github to Dockerhub. The image is available at:
|
||||
- https://hub.docker.com/r/${{ env.IMAGE_DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- Version: `${{ env.IMAGE_VERSION }}`
|
||||
- Version: `${{ env.PACKAGE_VERSION }}`
|
||||
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
|
||||
- Pull: `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}`
|
||||
- Pull: `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.PACKAGE_VERSION }}`
|
||||
- Pull (amd64): `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_dh_push_amd64.outputs.digest }}`
|
||||
- Pull (arm64): `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_dh_push_arm64.outputs.digest }}`
|
||||
- Dry Run: `${{ inputs.DRY_RUN }}`
|
||||
- Source: `Dockerhub` https://hub.docker.com/r/${{ env.IMAGE_DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}
|
||||
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
- Branch: `${{ github.ref_name }}`
|
||||
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
|
||||
- Runner: `${{ runner.name }}`
|
||||
|
||||
146
.github/workflows/deploy-docker-gitea.yml
vendored
146
.github/workflows/deploy-docker-gitea.yml
vendored
@@ -62,18 +62,6 @@ on:
|
||||
default: 'tvapp2'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Image Version
|
||||
#
|
||||
# used to create new release tag, and add version to docker image name
|
||||
# #
|
||||
|
||||
IMAGE_VERSION:
|
||||
description: '🏷️ Image Version'
|
||||
required: true
|
||||
default: '1.0.0'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Registry Name
|
||||
#
|
||||
@@ -174,8 +162,7 @@ on:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.event.inputs.IMAGE_NAME || 'tvapp2' }}
|
||||
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
|
||||
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_VERSION || 'gitea' }}
|
||||
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_REGISTRY || 'gitea' }}
|
||||
IMAGE_GITEA_AUTHOR: ${{ github.event.inputs.IMAGE_GITEA_AUTHOR || 'BinaryNinja' }}
|
||||
IMAGE_GITEA_USERNAME: ${{ github.event.inputs.IMAGE_GITEA_USERNAME || 'BinaryNinja' }}
|
||||
IMAGE_GITEA_WEBSITE: ${{ github.event.inputs.IMAGE_GITEA_WEBSITE || 'git.binaryninja.net' }}
|
||||
@@ -207,9 +194,11 @@ jobs:
|
||||
job-docker-release-tags-create:
|
||||
name: >-
|
||||
📦 Release › Create Tag
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 4
|
||||
outputs:
|
||||
package_version: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
@@ -235,6 +224,26 @@ jobs:
|
||||
uses: qoomon/actions--context@v4
|
||||
id: 'context'
|
||||
|
||||
# #
|
||||
# Release › Tags › Set › Package.json › Version
|
||||
# #
|
||||
|
||||
- name: '👁️🗨️ Package Version › Set'
|
||||
id: task_initialize_package_getversion
|
||||
working-directory: ./tvapp2
|
||||
run: |
|
||||
VER=$(cat package.json | jq -r '.version')
|
||||
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_OUTPUT
|
||||
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_ENV
|
||||
|
||||
# #
|
||||
# Initialize › Get › Package.json › Version
|
||||
# #
|
||||
|
||||
- name: '👁️🗨️ Package Version › Get'
|
||||
run: |
|
||||
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
|
||||
|
||||
# #
|
||||
# Release › Tags › Start
|
||||
# #
|
||||
@@ -271,10 +280,10 @@ jobs:
|
||||
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
|
||||
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
|
||||
@@ -364,9 +373,9 @@ jobs:
|
||||
id: task_release_tags_create
|
||||
if: ( github.event_name != 'workflow_dispatch' && inputs.DRY_RUN == false )
|
||||
with:
|
||||
tag: "${{ env.IMAGE_VERSION }}"
|
||||
tag: "${{ env.PACKAGE_VERSION }}"
|
||||
tag_exists_error: false
|
||||
message: '${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}'
|
||||
message: '${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}'
|
||||
gpg_private_key: ${{ secrets.ADMINSERV_GPG_KEY_ASC }}
|
||||
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
|
||||
|
||||
@@ -377,15 +386,17 @@ jobs:
|
||||
job-docker-release-gitea:
|
||||
name: >-
|
||||
📦 Release › Gitea
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 10
|
||||
needs: [ job-docker-release-tags-create ]
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
needs: [ job-docker-release-tags-create ]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
|
||||
steps:
|
||||
|
||||
# #
|
||||
@@ -442,10 +453,10 @@ jobs:
|
||||
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
|
||||
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
|
||||
@@ -560,6 +571,32 @@ jobs:
|
||||
id: task_release_gi_qemu
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
# #
|
||||
# Required to fix insecure SSL error with docker buildx
|
||||
# #
|
||||
|
||||
- name: '⚙️ Configure Docker daemon to allow insecure registry'
|
||||
run: |
|
||||
echo "Configuring daemon to treat ${REGISTRY_HOST} as insecure"
|
||||
sudo mkdir -p /etc/docker
|
||||
sudo tee /etc/docker/daemon.json > /dev/null <<'JSON'
|
||||
{
|
||||
"insecure-registries": ["git.binaryninja.net:443"]
|
||||
}
|
||||
JSON
|
||||
|
||||
# Restart Docker
|
||||
sudo service docker restart
|
||||
env:
|
||||
REGISTRY_HOST: git.binaryninja.net
|
||||
|
||||
# #
|
||||
# Make sure change in docker daemon config successful
|
||||
# #
|
||||
|
||||
- name: '⚙️ Check Docker Daemon Configuration'
|
||||
run: cat /etc/docker/daemon.json
|
||||
|
||||
# #
|
||||
# Release › Gitea › Setup BuildX › Amd64
|
||||
# #
|
||||
@@ -570,6 +607,10 @@ jobs:
|
||||
with:
|
||||
version: latest
|
||||
driver-opts: 'image=moby/buildkit:latest'
|
||||
driver: docker
|
||||
buildkitd-flags: --allow-insecure-entitlement
|
||||
install: true
|
||||
use: true
|
||||
|
||||
# #
|
||||
# Release › Gitea › Registry Login › Amd64
|
||||
@@ -629,10 +670,10 @@ jobs:
|
||||
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-amd64,event=tag
|
||||
|
||||
# tag add 1.0.0-amd64 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-amd64,value=${{ env.IMAGE_VERSION }}
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-amd64,value=${{ env.PACKAGE_VERSION }}
|
||||
|
||||
# tag add 1.0.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
|
||||
|
||||
# tag add 1.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
|
||||
@@ -650,28 +691,28 @@ jobs:
|
||||
labels: |
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
@@ -697,7 +738,7 @@ jobs:
|
||||
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-arm64,event=tag
|
||||
|
||||
# tag add 1.0.0-arm64 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-arm64,value=${{ env.IMAGE_VERSION }}
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-arm64,value=${{ env.PACKAGE_VERSION }}
|
||||
|
||||
# dispatch add development-arm64 ( dispatch only + only dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-arm64,value=development
|
||||
@@ -706,28 +747,28 @@ jobs:
|
||||
labels: |
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
@@ -756,7 +797,7 @@ jobs:
|
||||
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
|
||||
|
||||
# tag add 1.0.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
|
||||
|
||||
# tag add 1.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
|
||||
@@ -774,28 +815,28 @@ jobs:
|
||||
labels: |
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
@@ -824,14 +865,14 @@ jobs:
|
||||
build-args: |-
|
||||
ARCH=amd64
|
||||
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
|
||||
VERSION=${{ env.IMAGE_VERSION }}
|
||||
VERSION=${{ env.PACKAGE_VERSION }}
|
||||
BUILDDATE=${{ env.NOW_DOCKER }}
|
||||
GIT_SHA1=${{ env.SHA1 }}
|
||||
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.architecture=amd64
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
@@ -839,7 +880,7 @@ jobs:
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-architecture=amd64
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
@@ -897,14 +938,14 @@ jobs:
|
||||
build-args: |-
|
||||
ARCH=arm64
|
||||
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
|
||||
VERSION=${{ env.IMAGE_VERSION }}
|
||||
VERSION=${{ env.PACKAGE_VERSION }}
|
||||
BUILDDATE=${{ env.NOW_DOCKER }}
|
||||
GIT_SHA1=${{ env.SHA1 }}
|
||||
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.architecture=arm64
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
@@ -912,7 +953,7 @@ jobs:
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-architecture=arm64
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
@@ -964,7 +1005,6 @@ jobs:
|
||||
echo ""
|
||||
echo "---- [ INPUTS ] ----------------------------------------------------------------------------------------"
|
||||
echo "inputs.IMAGE_NAME ........................ ${{ inputs.IMAGE_NAME }}"
|
||||
echo "inputs.IMAGE_VERSION ..................... ${{ inputs.IMAGE_VERSION }}"
|
||||
echo "inputs.IMAGE_GITEA_USERNAME .............. ${{ inputs.IMAGE_GITEA_AUTHOR }}"
|
||||
echo "inputs.IMAGE_GITEA_USERNAME .............. ${{ inputs.IMAGE_GITEA_USERNAME }}"
|
||||
echo "inputs.IMAGE_GITEA_WEBSITE ............... ${{ inputs.IMAGE_GITEA_WEBSITE }}"
|
||||
@@ -973,7 +1013,7 @@ jobs:
|
||||
echo ""
|
||||
echo "---- [ ENV ] -------------------------------------------------------------------------------------------"
|
||||
echo "env.IMAGE_NAME ........................... ${{ env.IMAGE_NAME }}"
|
||||
echo "env.IMAGE_VERSION ........................ ${{ env.IMAGE_VERSION }}"
|
||||
echo "env.PACKAGE_VERSION ...................... ${{ env.PACKAGE_VERSION }}"
|
||||
echo "env.PKG_VER_1DIGIT ....................... ${{ env.PKG_VER_1DIGIT }}"
|
||||
echo "env.PKG_VER_2DIGIT ....................... ${{ env.PKG_VER_2DIGIT }}"
|
||||
echo "env.IMAGE_GITEA_AUTHOR ................... ${{ env.IMAGE_GITEA_AUTHOR }}"
|
||||
@@ -1052,20 +1092,20 @@ jobs:
|
||||
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
|
||||
embed-description: |
|
||||
### 📦 Deploy (Gitea) ${{ job.status == 'success' && '✅' || '❌' }} › `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
### 📦 Deploy (Gitea) ${{ job.status == 'success' && '✅' || '❌' }} › `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
${{ inputs.DEV_RELEASE == true && '### ⚠️⚠️ Development / Pre-release ⚠️⚠️' || '' }}
|
||||
|
||||
A new version of the docker container `${{ env.IMAGE_NAME }}` has been released from Github to Gitea. The image is available at:
|
||||
- https://${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
|
||||
|
||||
- Version: `${{ env.IMAGE_VERSION }}`
|
||||
- Version: `${{ env.PACKAGE_VERSION }}`
|
||||
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
|
||||
- Pull: `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}`
|
||||
- Pull: `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.PACKAGE_VERSION }}`
|
||||
- Pull (amd64): `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push_amd64.outputs.digest }}`
|
||||
- Pull (arm64): `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push_arm64.outputs.digest }}`
|
||||
- Dry Run: `${{ inputs.DRY_RUN }}`
|
||||
- Source: `Gitea` https://${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
|
||||
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
- Branch: `${{ github.ref_name }}`
|
||||
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
|
||||
- Runner: `${{ runner.name }}`
|
||||
|
||||
112
.github/workflows/deploy-docker-github.yml
vendored
112
.github/workflows/deploy-docker-github.yml
vendored
@@ -62,18 +62,6 @@ on:
|
||||
default: 'tvapp2'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Image Version
|
||||
#
|
||||
# used to create new release tag, and add version to docker image name
|
||||
# #
|
||||
|
||||
IMAGE_VERSION:
|
||||
description: '🏷️ Image Version'
|
||||
required: true
|
||||
default: '1.0.0'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Registry Name
|
||||
#
|
||||
@@ -162,8 +150,7 @@ on:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.event.inputs.IMAGE_NAME || 'tvapp2' }}
|
||||
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
|
||||
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_VERSION || 'github' }}
|
||||
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_REGISTRY || 'github' }}
|
||||
IMAGE_GHCR_AUTHOR: ${{ github.event.inputs.IMAGE_GHCR_AUTHOR || 'BinaryNinja' }}
|
||||
IMAGE_GHCR_USERNAME: ${{ github.event.inputs.IMAGE_GHCR_USERNAME || 'BinaryNinja' }}
|
||||
IMAGE_ALPINE_VERSION: ${{ github.event.inputs.IMAGE_ALPINE_VERSION || '3.22' }}
|
||||
@@ -194,9 +181,11 @@ jobs:
|
||||
job-docker-release-tags-create:
|
||||
name: >-
|
||||
📦 Release › Create Tag
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 4
|
||||
outputs:
|
||||
package_version: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
@@ -222,6 +211,26 @@ jobs:
|
||||
uses: qoomon/actions--context@v4
|
||||
id: 'context'
|
||||
|
||||
# #
|
||||
# Release › Tags › Set › Package.json › Version
|
||||
# #
|
||||
|
||||
- name: '👁️🗨️ Package Version › Set'
|
||||
id: task_initialize_package_getversion
|
||||
working-directory: ./tvapp2
|
||||
run: |
|
||||
VER=$(cat package.json | jq -r '.version')
|
||||
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_OUTPUT
|
||||
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_ENV
|
||||
|
||||
# #
|
||||
# Initialize › Get › Package.json › Version
|
||||
# #
|
||||
|
||||
- name: '👁️🗨️ Package Version › Get'
|
||||
run: |
|
||||
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
|
||||
|
||||
# #
|
||||
# Release › Tags › Start
|
||||
# #
|
||||
@@ -258,10 +267,10 @@ jobs:
|
||||
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
|
||||
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
|
||||
@@ -323,7 +332,7 @@ jobs:
|
||||
echo ""
|
||||
|
||||
# #
|
||||
# Tags › Tags › Fix Permissions
|
||||
# Release › Tags › Fix Permissions
|
||||
# #
|
||||
|
||||
- name: '#️⃣ Manage Permissions'
|
||||
@@ -349,9 +358,9 @@ jobs:
|
||||
- uses: rickstaa/action-create-tag@v1
|
||||
if: ( github.event_name != 'workflow_dispatch' && inputs.DRY_RUN == false )
|
||||
with:
|
||||
tag: "${{ env.IMAGE_VERSION }}"
|
||||
tag: "${{ env.PACKAGE_VERSION }}"
|
||||
tag_exists_error: false
|
||||
message: '${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}'
|
||||
message: '${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}'
|
||||
gpg_private_key: ${{ secrets.ADMINSERV_GPG_KEY_ASC }}
|
||||
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
|
||||
|
||||
@@ -362,15 +371,17 @@ jobs:
|
||||
job-docker-release-github:
|
||||
name: >-
|
||||
📦 Release › Github
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 10
|
||||
needs: [ job-docker-release-tags-create ]
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
needs: [ job-docker-release-tags-create ]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
|
||||
steps:
|
||||
|
||||
# #
|
||||
@@ -427,10 +438,10 @@ jobs:
|
||||
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
|
||||
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
|
||||
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
|
||||
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
|
||||
|
||||
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
|
||||
@@ -611,7 +622,7 @@ jobs:
|
||||
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
|
||||
|
||||
# tag add 1.0.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
|
||||
|
||||
# tag add 1.0 ( dispatch only + no dev )
|
||||
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
|
||||
@@ -629,28 +640,28 @@ jobs:
|
||||
labels: |
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
|
||||
@@ -679,14 +690,14 @@ jobs:
|
||||
build-args: |-
|
||||
ARCH=amd64
|
||||
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
|
||||
VERSION=${{ env.IMAGE_VERSION }}
|
||||
VERSION=${{ env.PACKAGE_VERSION }}
|
||||
BUILDDATE=${{ env.NOW_DOCKER }}
|
||||
GIT_SHA1=${{ env.SHA1 }}
|
||||
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.architecture=amd64
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
@@ -694,7 +705,7 @@ jobs:
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-architecture=amd64
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
@@ -750,14 +761,14 @@ jobs:
|
||||
build-args: |-
|
||||
ARCH=arm64
|
||||
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
|
||||
VERSION=${{ env.IMAGE_VERSION }}
|
||||
VERSION=${{ env.PACKAGE_VERSION }}
|
||||
BUILDDATE=${{ env.NOW_DOCKER }}
|
||||
GIT_SHA1=${{ env.SHA1 }}
|
||||
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
annotations: |-
|
||||
org.opencontainers.image.description=TVApp2
|
||||
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
|
||||
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
|
||||
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.architecture=arm64
|
||||
org.opencontainers.image.revision=${{ env.SHA1 }}
|
||||
@@ -765,7 +776,7 @@ jobs:
|
||||
org.opencontainers.image.ref.name=${{ github.ref_name }}
|
||||
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
|
||||
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
|
||||
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
|
||||
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
|
||||
org.tvapp2.image.build-architecture=arm64
|
||||
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
|
||||
@@ -815,7 +826,6 @@ jobs:
|
||||
echo ""
|
||||
echo "---- [ INPUTS ] ----------------------------------------------------------------------------------------"
|
||||
echo "inputs.IMAGE_NAME ........................ ${{ inputs.IMAGE_NAME }}"
|
||||
echo "inputs.IMAGE_VERSION ..................... ${{ inputs.IMAGE_VERSION }}"
|
||||
echo "inputs.IMAGE_GHCR_AUTHOR ................. ${{ inputs.IMAGE_GHCR_AUTHOR }}"
|
||||
echo "inputs.IMAGE_GHCR_USERNAME ............... ${{ inputs.IMAGE_GHCR_USERNAME }}"
|
||||
echo "inputs.DEV_RELEASE ....................... ${{ inputs.DEV_RELEASE }}"
|
||||
@@ -823,7 +833,7 @@ jobs:
|
||||
echo ""
|
||||
echo "---- [ ENV ] -------------------------------------------------------------------------------------------"
|
||||
echo "env.IMAGE_NAME ........................... ${{ env.IMAGE_NAME }}"
|
||||
echo "env.IMAGE_VERSION ........................ ${{ env.IMAGE_VERSION }}"
|
||||
echo "env.PACKAGE_VERSION ...................... ${{ env.PACKAGE_VERSION }}"
|
||||
echo "env.PKG_VER_1DIGIT ....................... ${{ env.PKG_VER_1DIGIT }}"
|
||||
echo "env.PKG_VER_2DIGIT ....................... ${{ env.PKG_VER_2DIGIT }}"
|
||||
echo "env.IMAGE_GHCR_AUTHOR .................... ${{ env.IMAGE_GHCR_AUTHOR }}"
|
||||
@@ -897,20 +907,20 @@ jobs:
|
||||
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
|
||||
embed-description: |
|
||||
### 📦 Deploy (Github) ${{ job.status == 'success' && '✅' || '❌' }} › `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
### 📦 Deploy (Github) ${{ job.status == 'success' && '✅' || '❌' }} › `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
${{ inputs.DEV_RELEASE == true && '### ⚠️⚠️ Development / Pre-release ⚠️⚠️' || '' }}
|
||||
|
||||
A new version of the docker container `${{ env.IMAGE_NAME }}` has been released from Github to Github GHCR. The image is available at:
|
||||
- https://github.com/${{ github.repository }}/pkgs/container/${{ env.IMAGE_NAME }}
|
||||
|
||||
- Version: `${{ env.IMAGE_VERSION }}`
|
||||
- Version: `${{ env.PACKAGE_VERSION }}`
|
||||
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
|
||||
- Pull: `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}`
|
||||
- Pull: `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.PACKAGE_VERSION }}`
|
||||
- Pull (amd64): `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gh_push_amd64.outputs.digest }}`
|
||||
- Pull (arm64): `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gh_push_arm64.outputs.digest }}`
|
||||
- Dry Run: `${{ inputs.DRY_RUN }}`
|
||||
- Source: `Github` https://github.com/${{ github.repository }}
|
||||
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
|
||||
- Branch: `${{ github.ref_name }}`
|
||||
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
|
||||
- Runner: `${{ runner.name }}`
|
||||
@@ -944,15 +954,17 @@ jobs:
|
||||
job-docker-release-cleanup:
|
||||
name: >-
|
||||
🧹 Release › Cleanup
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
needs: [ job-docker-release-tags-create, job-docker-release-github ]
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
needs: [ job-docker-release-tags-create, job-docker-release-github ]
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
|
||||
steps:
|
||||
|
||||
# #
|
||||
@@ -964,6 +976,14 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# #
|
||||
# Release › Cleanup › Print Version Debug
|
||||
# #
|
||||
|
||||
- name: '🪪 Get Package Version'
|
||||
run: |
|
||||
echo "VERSION: ${{ env.PACKAGE_VERSION }}"
|
||||
|
||||
# #
|
||||
# Release › Cleanup › Clean Untagged Images
|
||||
# #
|
||||
|
||||
4
.github/workflows/documentation.yml
vendored
4
.github/workflows/documentation.yml
vendored
@@ -170,8 +170,8 @@ env:
|
||||
|
||||
jobs:
|
||||
build-docs:
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
45
.github/workflows/history-clean.yml
vendored
45
.github/workflows/history-clean.yml
vendored
@@ -67,37 +67,25 @@ on:
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Main Branch
|
||||
# Branch
|
||||
#
|
||||
# main branch re-recreate
|
||||
# select branch to clean
|
||||
# you must also run the workflow from that branch
|
||||
# #
|
||||
|
||||
BRANCH_MAIN:
|
||||
description: '🌳 Main Branch'
|
||||
BRANCH:
|
||||
description: '🌳 Branch'
|
||||
required: true
|
||||
default: 'main'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# Deployment Environment Name
|
||||
#
|
||||
# this is the name of the deployment item
|
||||
# #
|
||||
|
||||
DEPLOYMENT_ENV:
|
||||
description: '📦 Deployment Environment'
|
||||
required: true
|
||||
default: 'orion'
|
||||
type: string
|
||||
|
||||
# #
|
||||
# environment variables
|
||||
# #
|
||||
|
||||
env:
|
||||
COMMIT_LABEL: ${{ github.event.inputs.COMMIT_LABEL || 'cleanup' }}
|
||||
BRANCH_MAIN: ${{ github.event.inputs.BRANCH_MAIN || 'main' }}
|
||||
DEPLOYMENT_ENV: ${{ github.event.inputs.DEPLOYMENT_ENV || 'orion' }}
|
||||
BRANCH: ${{ github.event.inputs.BRANCH || 'main' }}
|
||||
BOT_NAME_1: EuropaServ
|
||||
BOT_NAME_2: BinaryServ
|
||||
BOT_NAME_DEPENDABOT: dependabot[bot]
|
||||
@@ -111,9 +99,11 @@ jobs:
|
||||
history-clean:
|
||||
name: >-
|
||||
🧹 History › Clean
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
|
||||
# #
|
||||
@@ -254,7 +244,7 @@ jobs:
|
||||
now=$(date -u '+%m/%d/%Y %H:%M')
|
||||
commit_label="${{ env.COMMIT_LABEL }}" >> $GITHUB_ENV
|
||||
echo -e "$commit_label"
|
||||
commit_message="\\\`️️🧹 $commit_label 🧹\\\` \\\`$now UTC\\\`" >> $GITHUB_ENV
|
||||
commit_message="chore(maint): \\\`️️🧹 $commit_label 🧹\\\` \\\`$now UTC\\\`" >> $GITHUB_ENV
|
||||
echo -e "$commit_message"
|
||||
echo "COMMIT_MESSAGE=$(echo $commit_message)" >> $GITHUB_ENV
|
||||
echo "NOW=$(echo $now)" >> $GITHUB_ENV
|
||||
@@ -290,24 +280,25 @@ jobs:
|
||||
git commit -m "${{ env.COMMIT_MESSAGE }}"
|
||||
|
||||
# Delete the old main branch
|
||||
git branch -D ${{ env.BRANCH_MAIN }}
|
||||
git branch -D ${{ env.BRANCH }}
|
||||
|
||||
# Rename the new orphan branch to main
|
||||
git branch -m ${{ env.BRANCH_MAIN }}
|
||||
git branch -m ${{ env.BRANCH }}
|
||||
|
||||
# Force push the new main branch to the remote repository
|
||||
git push -f origin ${{ env.BRANCH_MAIN }}
|
||||
git push -f origin ${{ env.BRANCH }}
|
||||
|
||||
# #
|
||||
# History › Clean › References
|
||||
# #
|
||||
|
||||
- name: >-
|
||||
🗑️ Clean References
|
||||
🗑️ Garbage Collection (Aggressive)
|
||||
run: |
|
||||
# Remove remote-tracking references to deleted branches (optional)
|
||||
git fetch origin --prune
|
||||
|
||||
git repack
|
||||
git prune-packed
|
||||
git reflog expire --expire=now --all
|
||||
git gc --prune=now --aggressive
|
||||
|
||||
|
||||
16
.github/workflows/issues-new.yml
vendored
16
.github/workflows/issues-new.yml
vendored
@@ -165,8 +165,8 @@ jobs:
|
||||
job-labels-create:
|
||||
name: >-
|
||||
🎫 Labels › Verify Existing
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
|
||||
@@ -335,8 +335,8 @@ jobs:
|
||||
🏷️ Labels › Assign
|
||||
needs:
|
||||
- job-labels-create
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: 'read'
|
||||
@@ -1191,8 +1191,8 @@ jobs:
|
||||
🏷️ Labels › Phrase Search
|
||||
needs:
|
||||
- job-labels-create
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: 'read'
|
||||
@@ -1280,8 +1280,8 @@ jobs:
|
||||
job-assign-assignees:
|
||||
name: >-
|
||||
✍️ Issue › Assignees
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
needs: [ job-assign-labels ]
|
||||
# disable
|
||||
|
||||
4
.github/workflows/issues-scan.yml
vendored
4
.github/workflows/issues-scan.yml
vendored
@@ -160,8 +160,8 @@ jobs:
|
||||
job-pr-scan:
|
||||
name: >-
|
||||
🎫 Issues › Autoscan
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
16
.github/workflows/issues-stale.yml
vendored
16
.github/workflows/issues-stale.yml
vendored
@@ -181,8 +181,8 @@ jobs:
|
||||
job-labels-create:
|
||||
name: >-
|
||||
🎫 Labels › Verify Existing
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
|
||||
@@ -360,8 +360,8 @@ jobs:
|
||||
job-issues-nolabel:
|
||||
name: >-
|
||||
🎫 Labels › Assign Missing
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 4
|
||||
needs: job-labels-create
|
||||
steps:
|
||||
@@ -961,8 +961,8 @@ jobs:
|
||||
job-issues-stale:
|
||||
name: >-
|
||||
💤 Scan › Check Stale
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
needs:
|
||||
- job-labels-create
|
||||
@@ -1005,8 +1005,8 @@ jobs:
|
||||
job-issues-lock:
|
||||
name: >-
|
||||
🔒 Scan › Lock Inactive
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 5
|
||||
needs:
|
||||
- job-labels-create
|
||||
|
||||
4
.github/workflows/labels-clean.yml
vendored
4
.github/workflows/labels-clean.yml
vendored
@@ -146,8 +146,8 @@ jobs:
|
||||
issues-labels-clean:
|
||||
name: >-
|
||||
🧹 Labels › Clean
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 3
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
||||
4
.github/workflows/labels-create.yml
vendored
4
.github/workflows/labels-create.yml
vendored
@@ -193,8 +193,8 @@ jobs:
|
||||
issues-labels-create:
|
||||
name: >-
|
||||
🎫 Labels › Create
|
||||
# runs-on: ubuntu-latest
|
||||
runs-on: apollo-x64
|
||||
runs-on: ubuntu-latest
|
||||
# runs-on: apollo-x64
|
||||
timeout-minutes: 3
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -53,8 +53,8 @@ on:
|
||||
type: string
|
||||
|
||||
# #
|
||||
# ENABLE: the changelog generated in releases tab will only display single commits.
|
||||
# DISABLE: the changelog shows pull requests completed based on their labels
|
||||
# true the changelog generated in releases tab will only display single commits.
|
||||
# false the changelog shows pull requests completed based on their labels
|
||||
# #
|
||||
|
||||
CHANGELOG_MODE_COMMIT:
|
||||
@@ -64,8 +64,8 @@ on:
|
||||
type: boolean
|
||||
|
||||
# #
|
||||
# ENABLE: Will show all types of commits, including uncategorized
|
||||
# DISABLE: WIll only show actions that have been categorized using the format
|
||||
# true Will show all types of commits, including uncategorized
|
||||
# false WIll only show actions that have been categorized using the format
|
||||
# type(scope): description
|
||||
# type: description
|
||||
# #
|
||||
@@ -256,7 +256,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🪪 Test Next Job Version'
|
||||
id: task_release_debug_print_ver
|
||||
run: |
|
||||
echo "VERSION: ${{ env.PACKAGE_VERSION }}"
|
||||
|
||||
@@ -265,7 +264,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🪪 NPM › Install & Lint'
|
||||
id: task_release_npm_install
|
||||
working-directory: ./tvapp2
|
||||
run: |
|
||||
npm ci
|
||||
@@ -278,7 +276,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🪪 Generate IDs'
|
||||
id: task_release_npm_env_generate
|
||||
working-directory: ./tvapp2
|
||||
run: |
|
||||
npm run root:generate
|
||||
@@ -300,7 +297,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🪪 .ENV › Read'
|
||||
id: task_dotenv_debug_print
|
||||
run: |
|
||||
echo "GUID: ${{ steps.task_release_dotenv_get.outputs.GUID }}"
|
||||
echo "UUID: ${{ steps.task_release_dotenv_get.outputs.UUID }}"
|
||||
@@ -310,7 +306,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🔨 Build › Stable › ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip'
|
||||
id: task_release_build_st
|
||||
if: |
|
||||
startsWith( inputs.RC_RELEASE, false ) ||
|
||||
startsWith( env.RC_RELEASE, false )
|
||||
@@ -320,6 +315,7 @@ jobs:
|
||||
|
||||
echo Building STABLE Package ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-docker-compose.zip
|
||||
zip -r ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-docker-compose.zip docker-compose.yml README.md LICENSE
|
||||
ls
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.ADMINSERV_TOKEN_CL }}
|
||||
|
||||
@@ -366,7 +362,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🔖 Tag › Confirm ${{ env.PACKAGE_VERSION }}'
|
||||
id: task_release_tag_get
|
||||
run: |
|
||||
echo "Tag already present: ${{ env.TAG_EXISTS }}"
|
||||
echo "Tag already present: ${{ steps.task_release_tag_create.outputs.tag_exists }}"
|
||||
@@ -378,7 +373,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🪪 GPG › Import Signing Key › W/o Passphrase'
|
||||
id: task_release_gpg_import_nopass
|
||||
if: env.GPG_KEY_BASE64 != '' && env.GPG_KEY_PASSPHRASE == ''
|
||||
run: |
|
||||
echo $GPG_KEY_BASE64 | base64 -di | gpg --import
|
||||
@@ -390,7 +384,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🪪 GPG › Import Signing Key › w/ Passphrase'
|
||||
id: task_release_gpg_import_withpass
|
||||
if: env.GPG_KEY_BASE64 != '' && env.GPG_KEY_PASSPHRASE != ''
|
||||
run: |
|
||||
echo "$GPG_KEY_BASE64" | base64 -di > /tmp/signing-key.gpg
|
||||
@@ -472,16 +465,15 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '🆔 Checksum › Print'
|
||||
id: task_release_checksum_st_get
|
||||
run: |
|
||||
echo "${{ env.SHA256SUM }}"
|
||||
echo SHA1SUM ............... ${{ env.SHA1SUM }}
|
||||
echo SHA256SUM ............. ${{ env.SHA256SUM }}
|
||||
|
||||
# #
|
||||
# Release › Contributor Images
|
||||
# #
|
||||
|
||||
- name: '🥸 Contributors › Generate'
|
||||
id: task_release_contribs_generate
|
||||
uses: jaywcjlove/github-action-contributors@main
|
||||
with:
|
||||
filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\])
|
||||
@@ -491,7 +483,7 @@ jobs:
|
||||
# #
|
||||
# Release › Changelog › Generate Tags
|
||||
#
|
||||
# generates a changelog from the github api. requires a PREVIOUS_TAG in order to figure
|
||||
# generates a changelog from the github api. requires a TAG_LAST in order to figure
|
||||
# out the changes made between the two versions.
|
||||
#
|
||||
# outputs:
|
||||
@@ -499,7 +491,6 @@ jobs:
|
||||
# #
|
||||
|
||||
- name: '📝 Changelog › Pre Setup (Categorized Commits)'
|
||||
id: task_release_changelog_categorized_sha_set
|
||||
run: |
|
||||
echo "TAG_LAST=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
echo "COMMIT_LAST=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
||||
@@ -674,7 +665,7 @@ jobs:
|
||||
if: |
|
||||
startsWith( inputs.RC_RELEASE, false ) ||
|
||||
startsWith( env.RC_RELEASE, false )
|
||||
uses: softprops/action-gh-release@v2.2.2
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
CHANGELOG_CATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
|
||||
CHANGELOG_UNCATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
|
||||
@@ -712,7 +703,7 @@ jobs:
|
||||
if: |
|
||||
startsWith( inputs.RC_RELEASE, true ) ||
|
||||
startsWith( env.RC_RELEASE, true )
|
||||
uses: softprops/action-gh-release@v2.2.2
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
CHANGELOG_CATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
|
||||
CHANGELOG_UNCATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
|
||||
|
||||
@@ -99,6 +99,7 @@ ENV DIR_RUN=/usr/bin/app
|
||||
ENV URL_REPO="https://git.binaryninja.net/binaryninja/"
|
||||
ENV WEB_IP="0.0.0.0"
|
||||
ENV WEB_PORT=4124
|
||||
ENV HDHR_PORT=6077
|
||||
ENV WEB_ENCODING="deflate, br"
|
||||
ENV WEB_PROXY_HEADER="x-forwarded-for"
|
||||
ENV STREAM_QUALITY="hd"
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 BinaryNinja
|
||||
Copyright (c) 2025-2026 BinaryNinja
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
93
README.md
93
README.md
@@ -86,6 +86,7 @@
|
||||
- [Labels](#labels-1)
|
||||
- [Dynamic.yml](#dynamicyml-1)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Run Error: `Warning: Step size 60 higher than possible maximum of 59`](#run-error-warning-step-size-60-higher-than-possible-maximum-of-59)
|
||||
- [Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`](#run-error-error-serving-playlist-enoent-no-such-file-or-directory-open-usrsrcappxmltv1xml)
|
||||
- [Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`](#build-error-s6-rc-compile-fatal-invalid-etcs6-overlays6-rcdcertsynctype-must-be-oneshot-longrun-or-bundle)
|
||||
- [Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`](#build-error-unable-to-exec-etcs6-overlays6-rcdinit-envfilerun-permission-denied)
|
||||
@@ -183,6 +184,7 @@ The following is a list of environment variables you can declare within your `do
|
||||
| `WEB_FOLDER` | `www` | Internal container folder to keep TVApp2 web files in. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
|
||||
| `WEB_ENCODING` | `deflate, br` | Defines the HTTP `Accept-Encoding` request and response header. This value specifies what content encoding the sender can understand<br /><br />Gzip compression can be enabled by specifying `'gzip, deflate, br'`, however, [it may break Jellyfin users](#build-error-err-27-jellyfinlivetvguideguidemanager-error-getting-programs-for-channel-xxxxxxxxxxxxxxx-source-2-systemxmlxmlexception--hexadecimal-value-0x1f-is-an-invalid-character-line-1-position-1). |
|
||||
| `WEB_PROXY_HEADER` | `x-forwarded-for` | Defines the header to look for when finding a client's IP address. Used to get a client's IP when behind a reverse proxy or Cloudflare |
|
||||
| `HDHR_PORT` | `6077` | HDHomeRun server default listening port |
|
||||
| `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. |
|
||||
| `FILE_URL` | `urls.txt` | Filename for `urls.txt` cache file |
|
||||
| `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file |
|
||||
@@ -411,13 +413,14 @@ If the listed tasks above are not performed, your docker container will throw th
|
||||
- `/etc/s6-overlay/s6-rc.d/init-adduser/run: line 34: aetherxown: command not found`
|
||||
- `/etc/s6-overlay/s6-rc.d/init-adduser/run: /usr/bin/aetherxown: cannot execute: required file not found`
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
#### LF over CRLF
|
||||
|
||||
You cannot utilize Windows' `Carriage Return Line Feed`. All files must be converted to Unix' `Line Feed`. This can be done with **[Visual Studio Code](https://code.visualstudio.com/)**. OR; you can run the Linux terminal command `🗔 dos2unix` to convert these files.
|
||||
|
||||
If you cloned the files from the official repository [🔆 gitea:binaryninja/tvapp2](https://git.binaryninja.net/binaryninja/tvapp2) and have not edited them, then you should not need to do this step.
|
||||
For the branches **[docker/alpine-base](https://github.com/Aetherinox/docker-base-alpine/tree/docker/alpine-base)** and your main app image, you can use the following recursive commands:
|
||||
|
||||
<br />
|
||||
|
||||
@@ -437,14 +440,57 @@ If you cloned the files from the official repository [🔆 gitea:binaryninja/tva
|
||||
find ./ -type f | grep -Ev 'docs|node_modules|.git|*.jpg|*.jpeg|*.png' | xargs dos2unix --
|
||||
|
||||
# Change run / binaries
|
||||
find ./ -type f -name 'run' | xargs dos2unix --
|
||||
find ./ -type f -name 'run' -print | xargs dos2unix --
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
For the branch **[docker/core](https://github.com/Aetherinox/docker-base-alpine/tree/docker/core)**, you can use the following commands:
|
||||
|
||||
```shell
|
||||
dos2unix docker-images.v3
|
||||
dos2unix aetherxown.v1
|
||||
dos2unix package-install.v1
|
||||
dos2unix with-contenv.v1
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
If you do not have dos2unix; you may use `sed:
|
||||
|
||||
```shell
|
||||
sed -i 's/\r$//' /etc/s6-overlay/s6-rc.d/ci-service-check/file
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
You may pre-check if a file is using Windows CRLF or Linux LF by running the command `file <filename>` on the file:
|
||||
|
||||
```shell
|
||||
$ file ./root//etc/s6-overlay/s6-rc.d/ci-service-check/type
|
||||
./root//etc/s6-overlay/s6-rc.d/ci-service-check/type: ASCII text
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
You will get one of three messages listed below:
|
||||
|
||||
1. ASCII text, with CRLF, LF line terminators
|
||||
2. ASCII text, with CRLF line terminators
|
||||
3. ASCII text
|
||||
|
||||
<br />
|
||||
|
||||
If you get messages `1` or `2`, then you need to run `dos2unix` on the file; otherwise when you bring the container up, you will get errors.
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
#### Set `+x / 0755` Permissions
|
||||
|
||||
The files contained within this repo **MUST** have `chmod 755` / `+x` executable permissions.
|
||||
The files contained within this repo **MUST** have `chmod 755` / `+x` executable permissions. If you are using our Github workflow sample **[deploy-docker-github.yml](https://github.com/Aetherinox/docker-base-alpine/blob/workflows/samples/deploy-docker-github.yml)**, this is done automatically. If you are building the images manually; you need to do this. Ensure those files have the correct permissions prior to building the Alpine base docker image.
|
||||
|
||||
If you are building the **[docker/alpine-base](https://github.com/Aetherinox/docker-base-alpine/tree/docker/alpine-base)** or your main application images, you must ensure the files in those branches have the proper permissions. All of the executable files are named `run`:
|
||||
|
||||
```shell
|
||||
find ./ -name 'run' -print -exec sudo chmod +x {} \;
|
||||
@@ -472,6 +518,17 @@ sudo chmod +x ./root/etc/s6-overlay/s6-rc.d/init-adduser/run \
|
||||
./root/etc/s6-overlay/s6-rc.d/init-nginx/run
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
For the branch **[docker/core](https://github.com/Aetherinox/docker-base-alpine/tree/docker/core)**, there are a few files to change. The ending version number may change, but the commands to change the permissions are as follows:
|
||||
|
||||
```shell
|
||||
sudo chmod +x docker-images.v3 \
|
||||
chmod +x aetherxown.v1 \
|
||||
chmod +x package-install.v1 \
|
||||
chmod +x with-contenv.v1
|
||||
```
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
@@ -1262,6 +1319,7 @@ This docker container contains the following env variables:
|
||||
| `WEB_FOLDER` | `www` | Internal container folder to keep TVApp2 web files in. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
|
||||
| `WEB_ENCODING` | `deflate, br` | Defines the HTTP `Accept-Encoding` request and response header. This value specifies what content encoding the sender can understand<br /><br />Gzip compression can be enabled by specifying `'gzip, deflate, br'`, however, [it may break Jellyfin users](#build-error-err-27-jellyfinlivetvguideguidemanager-error-getting-programs-for-channel-xxxxxxxxxxxxxxx-source-2-systemxmlxmlexception--hexadecimal-value-0x1f-is-an-invalid-character-line-1-position-1). |
|
||||
| `WEB_PROXY_HEADER` | `x-forwarded-for` | Defines the header to look for when finding a client's IP address. Used to get a client's IP when behind a reverse proxy or Cloudflare |
|
||||
| `HDHR_PORT` | `6077` | HDHomeRun server default listening port |
|
||||
| `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. |
|
||||
| `FILE_URL` | `urls.txt` | Filename for `urls.txt` cache file |
|
||||
| `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file |
|
||||
@@ -1978,7 +2036,28 @@ If you have issues building your TVApp2 docker image, please refer to the follow
|
||||
<br />
|
||||
<br />
|
||||
|
||||
#### Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`
|
||||
### Run Error: `Warning: Step size 60 higher than possible maximum of 59`
|
||||
|
||||
This error means that you have placed an incorrect value for a cron job. This error can show if you've set:
|
||||
|
||||
```shell
|
||||
environment:
|
||||
TASK_CRON_SYNC: "*/60 * * * *"
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
To correctly set the value, change your cron to:
|
||||
|
||||
```shell
|
||||
environment:
|
||||
TASK_CRON_SYNC: "0 */1 * * *"
|
||||
```
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
### Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`
|
||||
|
||||
This error occurs at run-time when attempting to spin up your TVApp2 docker container. If you receive this error, restart your TVApp2 docker container. Ensure that your docker container also has access to your docker network so that it can connect to our repository and fetch the data files it needs to generate your playlist.
|
||||
|
||||
@@ -1989,7 +2068,7 @@ If the error continues after doing the above; delete the existing image, and re-
|
||||
<br />
|
||||
<br />
|
||||
|
||||
#### Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`
|
||||
### Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`
|
||||
|
||||
This error means that you are attempting to combine files which are utilizing CRLF over LF; which is **CR** = Carriage Return and **LF** = Line Feed
|
||||
|
||||
@@ -2029,7 +2108,7 @@ find ./ -type f | grep -Ev '.git|*.jpg|*.jpeg|*.png' | sudo xargs dos2unix --
|
||||
<br />
|
||||
<br />
|
||||
|
||||
#### Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`
|
||||
### Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`
|
||||
|
||||
There are multiple errors you can receive when attempting to run your TVApp2 docker image. You may receive any of the following errors:
|
||||
|
||||
@@ -2054,7 +2133,7 @@ After you have set these permissions, re-build your docker image using `docker b
|
||||
<br />
|
||||
<br />
|
||||
|
||||
#### Build Error: `[ERR] [27] Jellyfin.LiveTv.Guide.GuideManager: Error getting programs for channel XXXXXXXXXXXXXXX (Source 2) System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character. Line 1, position 1.`
|
||||
### Build Error: `[ERR] [27] Jellyfin.LiveTv.Guide.GuideManager: Error getting programs for channel XXXXXXXXXXXXXXX (Source 2) System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character. Line 1, position 1.`
|
||||
|
||||
This error may be seen if you are attempting to import our EPG guide data directly into Jellyfin. The cause of this is due to you having **GZIP Compression** enabled in your header request and response. See the example below; which is in your TVApp2 `📄 docker-compose.yml` file:
|
||||
|
||||
|
||||
@@ -45,6 +45,6 @@ services:
|
||||
soft: -1
|
||||
hard: -1
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "--fail", "http://127.0.0.1:4124" ]
|
||||
test: [ "CMD", "curl", "--fail", "http://127.0.0.1:4124/api/health?silent=true" ]
|
||||
interval: 30s
|
||||
retries: 5
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
services:
|
||||
|
||||
# #
|
||||
# Service › TVApp2 › Traefik Labels
|
||||
# Service › TVApp2
|
||||
# #
|
||||
|
||||
tvapp2:
|
||||
@@ -40,7 +40,6 @@ services:
|
||||
volumes:
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./config:/config
|
||||
- ./app:/usr/bin/app
|
||||
ulimits:
|
||||
@@ -56,7 +55,7 @@ services:
|
||||
- traefik.enable=true
|
||||
|
||||
# #
|
||||
# Scope > http
|
||||
# Routers › Web Interface › http
|
||||
# #
|
||||
|
||||
- traefik.http.routers.tvapp2-http.rule=Host(`tvapp2.localhost`) || Host(`tvapp2.domain.lan`) || Host(`www.tvapp2.domain.lan`) || Host(`${SERVICE_IP}`)
|
||||
@@ -66,7 +65,7 @@ services:
|
||||
- traefik.http.routers.tvapp2-http.middlewares=https-redirect@file
|
||||
|
||||
# #
|
||||
# Scope > https
|
||||
# Routers › Web Interface › https
|
||||
#
|
||||
# remove the authentik@file line if you do not wish to use Authentik or middleware
|
||||
# - traefik.http.routers.tvapp2-https.middlewares=authentik@file
|
||||
@@ -83,8 +82,26 @@ services:
|
||||
- traefik.http.routers.tvapp2-https.middlewares=authentik@file
|
||||
|
||||
# #
|
||||
# Load Balancer
|
||||
# Routers › HDHomeRun
|
||||
# #
|
||||
|
||||
- traefik.http.services.tvapp2.loadbalancer.server.port=http
|
||||
- traefik.http.services.tvapp2.loadbalancer.server.scheme=4124
|
||||
- traefik.http.routers.hdhr-https.rule=Host(`hdhr.domain.lan`)
|
||||
- traefik.http.routers.hdhr-https.service=hdhr
|
||||
- traefik.http.routers.hdhr-https.entrypoints=https
|
||||
- traefik.http.routers.hdhr-https.priority=1
|
||||
- traefik.http.routers.hdhr-https.tls=true
|
||||
- traefik.http.routers.hdhr-https.tls.certresolver=cloudflare
|
||||
|
||||
# #
|
||||
# Services › Main Web Interface
|
||||
# #
|
||||
|
||||
- traefik.http.services.tvapp2.loadbalancer.server.port=4124
|
||||
- traefik.http.services.tvapp2.loadbalancer.server.scheme=http
|
||||
|
||||
# #
|
||||
# Services › HDHomeRun Server (optional)
|
||||
# #
|
||||
|
||||
- traefik.http.services.hdhr.loadbalancer.server.port=6077
|
||||
- traefik.http.services.hdhr.loadbalancer.server.scheme=http
|
||||
|
||||
@@ -295,24 +295,24 @@ http:
|
||||
- "*.domain.lan"
|
||||
|
||||
# #
|
||||
# @container TVApp2
|
||||
# @desc utomatic M3U playlist and XML guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client.
|
||||
# @container TVApp2 › Main
|
||||
# @desc automatic M3U playlist and XML guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client.
|
||||
# @url https://github.com/TheBinaryNinja/tvapp2
|
||||
#
|
||||
# remove / comment out the authentik line if you do not plan to use authentik:
|
||||
# - authentik@file
|
||||
# #
|
||||
|
||||
tvapp2-http:
|
||||
service: "tvapp2"
|
||||
tvapp2-server-http:
|
||||
service: "tvapp2-server"
|
||||
rule: "Host(`tvapp2.localhost`) || Host(`tvapp2.domain.lan`)"
|
||||
entryPoints:
|
||||
- http
|
||||
middlewares:
|
||||
- https-redirect@file
|
||||
|
||||
tvapp2-https:
|
||||
service: "tvapp2"
|
||||
tvapp2-server-https:
|
||||
service: "tvapp2-server"
|
||||
rule: "Host(`tvapp2.localhost`) || Host(`tvapp2.domain.lan`)"
|
||||
entryPoints:
|
||||
- https
|
||||
@@ -325,6 +325,37 @@ http:
|
||||
- main: "domain.lan"
|
||||
sans:
|
||||
- "*.domain.lan"
|
||||
# #
|
||||
# @container TVApp2 › HDHomeRun
|
||||
# @desc automatic M3U playlist and XML guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client.
|
||||
# @url https://github.com/TheBinaryNinja/tvapp2
|
||||
#
|
||||
# remove / comment out the authentik line if you do not plan to use authentik:
|
||||
# - authentik@file
|
||||
# #
|
||||
|
||||
tvapp2-hdhr-http:
|
||||
service: "tvapp2-hdhr"
|
||||
rule: "Host(`hdhr.localhost`) || Host(`hdhr.domain.lan`)"
|
||||
entryPoints:
|
||||
- http
|
||||
middlewares:
|
||||
- https-redirect@file
|
||||
|
||||
tvapp2-hdhr-https:
|
||||
service: "tvapp2-hdhr"
|
||||
rule: "Host(`hdhr.localhost`) || Host(`hdhr.domain.lan`)"
|
||||
entryPoints:
|
||||
- https
|
||||
middlewares:
|
||||
- redirect-www@file
|
||||
- authentik@file
|
||||
tls:
|
||||
certResolver: cloudflare
|
||||
domains:
|
||||
- main: "domain.lan"
|
||||
sans:
|
||||
- "*.domain.lan"
|
||||
|
||||
# #
|
||||
# http › Services
|
||||
@@ -351,7 +382,12 @@ http:
|
||||
servers:
|
||||
- url: "http://plex:32400"
|
||||
|
||||
tvapp2:
|
||||
tvapp2-server:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://tvapp2:4124"
|
||||
|
||||
tvapp2-hdhr:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://tvapp2:4124"
|
||||
|
||||
169
tvapp2/classes/CLib.js
Normal file
169
tvapp2/classes/CLib.js
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
Compress / Uncompress String with base64
|
||||
|
||||
these functions use a unique character table. moving the letters around will cause strings to not
|
||||
be in the correct order once uncompressed.
|
||||
|
||||
@usage new CLib().compress( 'https://daddylive.mp/' )
|
||||
new CLib().uncompress( 'burS7u6FvUHhZfrhkfJoYz8CswTD=' )
|
||||
new CLib().translate( '=', plugin.defTrans, plugin.tvaTrans )
|
||||
|
||||
a custom character set can be specified with two additional parameters. however, anything prior
|
||||
that was encoded will not be decoded by the new character set.
|
||||
|
||||
const strCompress = new CLib().compress( 'test.com' );
|
||||
const strUncompress = new CLib().uncompress( strCompress );
|
||||
|
||||
new CLib().compress( 'test.com', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'rXzxP9ZdvehYlstwiTuV1c07j45Abo2Ama6k3gqpyf8n+/NMSEIUHBQRJDLFCGKO' )
|
||||
new CLib().uncompress( 'oZcUozDkAQH=', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'rXzxP9ZdvehYlstwiTuV1c07j45Abo2Ama6k3gqpyf8n+/NMSEIUHBQRJDLFCGKO' )
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import Log from './Log.js';
|
||||
|
||||
/*
|
||||
Class > CLib
|
||||
*/
|
||||
|
||||
class CLib
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.defTrans = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
this.tvaTrans = 'TVAPp29uqXiv6g5adr1j8nfwZ0bs7Ykm3xl4hczAtoey+/CDKJULSEMBQRFGIHNO';
|
||||
}
|
||||
|
||||
compress( data, defTrans, tvaTrans )
|
||||
{
|
||||
if ( typeof data === 'string' )
|
||||
data = Buffer.from( data, 'utf8' );
|
||||
|
||||
const transDef = defTrans || this.defTrans;
|
||||
const transTva = tvaTrans || this.tvaTrans;
|
||||
|
||||
try
|
||||
{
|
||||
const dataCompress = this.translate( data.toString( 'base64' ), transDef, transTva );
|
||||
|
||||
Log.ok( `clib`, chalk.yellow( `[compress]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Compress string` ),
|
||||
chalk.blueBright( `<strRaw>` ), chalk.gray( `${ data }` ),
|
||||
chalk.blueBright( `<strCompress>` ), chalk.gray( `${ dataCompress }` ) );
|
||||
|
||||
return dataCompress;
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
Log.error( `clib`, chalk.redBright( `[compress]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Could not compress string; bad string ${ data }` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ),
|
||||
chalk.redBright( `<strCompress>` ), chalk.gray( `${ data }` ) );
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
uncompress( data, defTrans, tvaTrans )
|
||||
{
|
||||
if ( Buffer.isBuffer( data ) )
|
||||
data = data.toString();
|
||||
|
||||
const transDef = defTrans || this.defTrans;
|
||||
const transTva = tvaTrans || this.tvaTrans;
|
||||
|
||||
try
|
||||
{
|
||||
const dataTranslated = this.translate( data, transTva, transDef );
|
||||
const dataUncompress = Buffer.from( dataTranslated, 'base64' ).toString( 'utf8' );
|
||||
|
||||
Log.ok( `clib`, chalk.yellow( `[decompss]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Uncompress string` ),
|
||||
chalk.blueBright( `<strCompress>` ), chalk.gray( `${ data }` ),
|
||||
chalk.blueBright( `<strRaw>` ), chalk.gray( `${ dataUncompress }` ) );
|
||||
|
||||
return dataUncompress;
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
Log.error( `clib`, chalk.redBright( `[decompss]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Could not uncompress string; bad string ${ data }` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ),
|
||||
chalk.redBright( `<strCompress>` ), chalk.gray( `${ data }` ) );
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Translate
|
||||
|
||||
compresses or decompresses encoded strings for the functions:
|
||||
- compress
|
||||
- uncompress
|
||||
*/
|
||||
|
||||
translate( str, fromChars, toChars )
|
||||
{
|
||||
let res = '';
|
||||
for ( let i = 0;i < str.length;i++ )
|
||||
{
|
||||
const char = str[i];
|
||||
const index = fromChars.indexOf( char );
|
||||
if ( index !== -1 )
|
||||
res += toChars[index];
|
||||
else
|
||||
res += char;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
Encode: String > Hex > Base64
|
||||
|
||||
encodes a human-readable string into a hex value, and then to base64
|
||||
|
||||
@usage const clib = new CLib()
|
||||
const encoded = clib.encodeToHexBase64('hello'); // Njg2NTZjNmM2Zg==
|
||||
const decoded = clib.decodeFromHexBase64(`${ encoded }`); // hello
|
||||
*/
|
||||
|
||||
encodeToHexBase64( str )
|
||||
{
|
||||
const hex = [...str].map( ( char ) =>
|
||||
{
|
||||
const code = char.charCodeAt( 0 ).toString( 16 );
|
||||
return code.padStart( 2, '0' );
|
||||
}).join( '' );
|
||||
|
||||
const base64 = btoa( hex );
|
||||
return base64;
|
||||
}
|
||||
|
||||
/*
|
||||
Decode: Base64 > Hex > String
|
||||
|
||||
decodes a base64 value to hex, and then back into a human readable string
|
||||
|
||||
@usage const clib = new CLib()
|
||||
const encoded = clib.encodeToHexBase64('hello'); // Njg2NTZjNmM2Zg==
|
||||
const decoded = clib.decodeFromHexBase64(`${ encoded }`); // hello
|
||||
*/
|
||||
|
||||
decodeFromHexBase64( base64Str )
|
||||
{
|
||||
const hex = atob( base64Str );
|
||||
const chars = hex.match( /.{1,2}/g ); // every 2 hex chars = 1 byte
|
||||
|
||||
return chars.map( ( byte ) => String.fromCharCode( parseInt( byte, 16 ) ) ).join( '' );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export class
|
||||
|
||||
@usage import CLib from './classes/CLib.js';
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export default CLib;
|
||||
121
tvapp2/classes/Log.js
Normal file
121
tvapp2/classes/Log.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
Define > Logs
|
||||
|
||||
When assigning text colors, terminals and the windows command prompt can display any color; however apps
|
||||
such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will
|
||||
not be the same as the rgb value. It's best to just stick to Chalk's default colors.
|
||||
|
||||
Various levels of logs with the following usage:
|
||||
Log.verbose(`This is verbose`)
|
||||
Log.debug(`This is debug`)
|
||||
Log.info(`This is info`)
|
||||
Log.ok(`This is ok`)
|
||||
Log.notice(`This is notice`)
|
||||
Log.warn(`This is warn`)
|
||||
Log.error(
|
||||
`Error fetching sports data with error:`,
|
||||
chalk.white(`→`),
|
||||
chalk.grey(`This is the error message`)
|
||||
);
|
||||
|
||||
Level Type
|
||||
-----------------------------------
|
||||
6 Trace
|
||||
5 Debug
|
||||
4 Info
|
||||
3 Notice
|
||||
2 Warn
|
||||
1 Error
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/*
|
||||
chalk.level
|
||||
|
||||
@ref https://npmjs.com/package/chalk
|
||||
- 0 All colors disabled
|
||||
- 1 Basic color support (16 colors)
|
||||
- 2 256 color support
|
||||
- 3 Truecolor support (16 million colors)
|
||||
|
||||
When assigning text colors, terminals and the windows command prompt can display any color; however apps
|
||||
such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will
|
||||
not be the same as the rgb value. It's best to just stick to Chalk's default colors.
|
||||
*/
|
||||
|
||||
chalk.level = 3;
|
||||
|
||||
/*
|
||||
Define
|
||||
*/
|
||||
|
||||
const LOG_LEVEL = process.env.LOG_LEVEL || 4;
|
||||
const { name } = JSON.parse( fs.readFileSync( './package.json' ) );
|
||||
|
||||
/*
|
||||
Class > Log
|
||||
*/
|
||||
|
||||
class Log
|
||||
{
|
||||
static now()
|
||||
{
|
||||
const now = new Date();
|
||||
return chalk.gray( `[${ now.toLocaleTimeString() }]` );
|
||||
}
|
||||
|
||||
static verbose( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 6 )
|
||||
console.debug( chalk.white.bgBlack.blackBright.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static debug( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 7 )
|
||||
console.trace( chalk.white.bgMagenta.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.magentaBright( msg.join( ' ' ) ) );
|
||||
else if ( LOG_LEVEL >= 5 )
|
||||
console.debug( chalk.white.bgGray.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static info( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 4 )
|
||||
console.info( chalk.white.bgBlueBright.bold( ` ${ name } ` ), chalk.white( `ℹ️` ), this.now(), chalk.blueBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static ok( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 4 )
|
||||
console.log( chalk.white.bgGreen.bold( ` ${ name } ` ), chalk.white( `✅` ), this.now(), chalk.greenBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static notice( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 3 )
|
||||
console.log( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `📌` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static warn( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 2 )
|
||||
console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `⚠️` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static error( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 1 )
|
||||
console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `❌` ), this.now(), chalk.redBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export class
|
||||
|
||||
@usage import Log from './classes/Log.js';
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export default Log;
|
||||
47
tvapp2/classes/Semaphore.js
Normal file
47
tvapp2/classes/Semaphore.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Semaphore > Declare
|
||||
|
||||
allows multiple threads to work with the same shared resources
|
||||
*/
|
||||
|
||||
class Semaphore
|
||||
{
|
||||
constructor( max )
|
||||
{
|
||||
this.max = max;
|
||||
this.queue = [];
|
||||
this.active = 0;
|
||||
}
|
||||
|
||||
async acquire()
|
||||
{
|
||||
if ( this.active < this.max )
|
||||
{
|
||||
this.active++;
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise( ( resolve ) => this.queue.push( resolve ) );
|
||||
}
|
||||
|
||||
release()
|
||||
{
|
||||
this.active--;
|
||||
if ( this.queue.length > 0 )
|
||||
{
|
||||
const resolve = this.queue.shift();
|
||||
this.active++;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export class
|
||||
|
||||
@usage import Log from './classes/Log.js';
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export default Semaphore;
|
||||
|
||||
520
tvapp2/classes/Storage.js
Normal file
520
tvapp2/classes/Storage.js
Normal file
@@ -0,0 +1,520 @@
|
||||
/*
|
||||
Class › Storage
|
||||
|
||||
The storage classes allows you to save specific settings into a json file. These settings are better off being stored in
|
||||
a local file, instead of using up the resources being saved in a database.
|
||||
|
||||
Class supports multiple storage files, but by default, it will save settings in `www/config.json`.
|
||||
|
||||
Settings include Tuner / HDHomeRun device information, etc.
|
||||
|
||||
@usage
|
||||
const storage = new Storage( envWebFolder, FILE_CFG );
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
import nconf from 'nconf';
|
||||
import fs from 'fs';
|
||||
import Log from './Log.js';
|
||||
import Utils from './Utils.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/*
|
||||
CJS › ESM
|
||||
*/
|
||||
|
||||
const __filename = fileURLToPath( import.meta.url ); // get resolved path to file
|
||||
const __dirname = path.dirname( __filename ); // get name of directory
|
||||
|
||||
/*
|
||||
Class › Storage
|
||||
|
||||
constructor ( str:folder, str:file )
|
||||
Initialize ( bool:bForceNew )
|
||||
Setup ( bool:bForceNew )
|
||||
Get ( str:key )
|
||||
Set ( str:key, any:value )
|
||||
Save ( )
|
||||
GetConfig ( )
|
||||
isJsonString ( json:str )
|
||||
isJsonEmpty ( obj:json )
|
||||
*/
|
||||
|
||||
class Storage
|
||||
{
|
||||
/*
|
||||
Constructor › Storage
|
||||
|
||||
Initializes a Storage instance for managing the config.json file.
|
||||
Determines the full path to the config file based on folder and file arguments,
|
||||
or uses the default static fileConfig if none are provided.
|
||||
|
||||
Handles Node.js packaged apps (process.pkg) by adjusting paths accordingly.
|
||||
|
||||
@args
|
||||
folder (str) Optional folder where config.json will be stored. Defaults to 'www'.
|
||||
file (str) Optional config file name. Defaults to static Storage.fileConfig.
|
||||
|
||||
@usage
|
||||
const storage = new Storage(envWebFolder, FILE_CFG);
|
||||
*/
|
||||
|
||||
static fileConfig = path.resolve( process.cwd( ), 'www', 'config.json' );
|
||||
|
||||
constructor( folder, file )
|
||||
{
|
||||
this.folderWeb = folder || 'www';
|
||||
this.fileConfig = file ? path.resolve( folder, file ) : Storage.fileConfig;
|
||||
|
||||
if ( process.pkg )
|
||||
this.fileConfig = path.join( path.dirname( process.execPath ), this.folderWeb, this.fileConfig );
|
||||
else
|
||||
this.fileConfig = path.resolve( process.cwd( ), this.folderWeb, this.fileConfig );
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize › Activate Config Setup with Logging
|
||||
|
||||
Activates the Storage.Setup( ) function while providing detailed logging.
|
||||
Ensures the user's config.json file exists, is valid, and is initialized
|
||||
with default values if missing or corrupt.
|
||||
|
||||
Steps:
|
||||
- Logs the start of initialization.
|
||||
- Calls Setup( ) with optional force flag to recreate config.
|
||||
- Catches and logs any errors during setup.
|
||||
|
||||
@args
|
||||
bForceNew (bool) Optional. If true, forces the config file to be removed
|
||||
and regenerated from defaults.
|
||||
|
||||
@returns
|
||||
(Promise) Resolves when initialization completes, or logs an error if setup fails.
|
||||
|
||||
@usage
|
||||
const storage = new Storage(envWebFolder, FILE_CFG);
|
||||
await storage.Initialize(false);
|
||||
*/
|
||||
|
||||
async Initialize( bForceNew )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
const bForce = bForceNew || false;
|
||||
|
||||
try
|
||||
{
|
||||
Log.info( `conf`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Initializing config file` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
|
||||
await new Storage( ).Setup( bForce );
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
console.log( 'Error writing Metadata.json:' + err.message );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize › Setup User Config File
|
||||
|
||||
Sets up a user's config.json file, ensuring it exists and is valid JSON.
|
||||
If the file is missing, empty, or invalid, it will be created or replaced.
|
||||
Typically, you should call this via Storage( ).Initialize( ) rather than Setup( ) directly.
|
||||
|
||||
Steps:
|
||||
- Creates parent directory if it doesn't exist.
|
||||
- Removes existing config if bForceNew is true.
|
||||
- Validates existing JSON; backs up invalid files.
|
||||
- Creates default config if missing.
|
||||
- Wires up nconf with argv, env, file, and default values.
|
||||
|
||||
@args
|
||||
bForceNew (bool) Optional flag to force recreate the config file, wiping all existing data.
|
||||
|
||||
@returns
|
||||
(Promise) Resolves true when initialization completes successfully.
|
||||
|
||||
@usage
|
||||
const storage = new Storage(envWebFolder, FILE_CFG);
|
||||
await storage.Initialize(false);
|
||||
*/
|
||||
|
||||
async Setup( bForceNew )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
return new Promise( ( resolve, reject ) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.info( `conf`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Initializing storage setup` ),
|
||||
chalk.blueBright( `<force>` ), chalk.gray( `${ bForceNew }` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
|
||||
/*
|
||||
ensure parent directory exists
|
||||
*/
|
||||
const dirPath = path.dirname( this.fileConfig );
|
||||
|
||||
if ( !fs.existsSync( dirPath ) )
|
||||
{
|
||||
fs.mkdirSync( dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
/*
|
||||
if force flag is true, remove existing config file (force)
|
||||
*/
|
||||
|
||||
if ( bForceNew === true && fs.existsSync( this.fileConfig ) )
|
||||
{
|
||||
Log.ok( `conf`, chalk.yellow( `[generate]` ), chalk.white( `✅` ),
|
||||
chalk.greenBright( `<msg>` ), chalk.gray( `Remove original config; force new` ),
|
||||
chalk.greenBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
|
||||
try
|
||||
{
|
||||
fs.unlinkSync( this.fileConfig );
|
||||
}
|
||||
catch ( e )
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Failed to unlink existing config` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ e.message }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if config exists, validate JSON; if invalid, move to backup and recreate
|
||||
*/
|
||||
|
||||
if ( fs.existsSync( this.fileConfig ) )
|
||||
{
|
||||
let raw = null;
|
||||
let parsed = null;
|
||||
|
||||
try
|
||||
{
|
||||
raw = fs.readFileSync( this.fileConfig, { encoding: 'utf8' });
|
||||
|
||||
if ( typeof raw !== 'string' || raw.trim( ).length === 0 )
|
||||
{
|
||||
throw new Error( 'Empty config file' );
|
||||
}
|
||||
|
||||
parsed = JSON.parse( raw );
|
||||
}
|
||||
catch ( e )
|
||||
{
|
||||
const backupPath = `${ this.fileConfig }.corrupt.${ Date.now( ) }`;
|
||||
|
||||
try
|
||||
{
|
||||
fs.renameSync( this.fileConfig, backupPath );
|
||||
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Config file invalid; moved to backup` ),
|
||||
chalk.redBright( `<backup>` ), chalk.gray( `${ backupPath }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
}
|
||||
catch ( renameErr )
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Unable to backup invalid config file` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ renameErr.message }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
if ( this.rejected )
|
||||
{
|
||||
reject( renameErr );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if config does not exist (or was just moved because it was corrupt), create it atomically
|
||||
*/
|
||||
|
||||
if ( !fs.existsSync( this.fileConfig ) )
|
||||
{
|
||||
const defaults =
|
||||
{
|
||||
deviceId: 'FFFFFFFF'
|
||||
};
|
||||
|
||||
const tempPath = `${ this.fileConfig }.tmp`;
|
||||
|
||||
try
|
||||
{
|
||||
fs.writeFileSync( tempPath, JSON.stringify( defaults, null, 4 ), { encoding: 'utf8' });
|
||||
fs.renameSync( tempPath, this.fileConfig );
|
||||
|
||||
Log.ok( `conf`, chalk.yellow( `[generate]` ), chalk.white( `✅` ),
|
||||
chalk.greenBright( `<msg>` ), chalk.gray( `Created new config file with defaults` ),
|
||||
chalk.greenBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
}
|
||||
catch ( writeErr )
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Failed to create config file` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ writeErr.message }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
|
||||
if ( this.rejected )
|
||||
{
|
||||
reject( writeErr );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
now that file exists and is valid JSON, wire up nconf
|
||||
*/
|
||||
|
||||
nconf.argv( ).env({ parseValues: true }).file({ file: this.fileConfig }).defaults(
|
||||
{
|
||||
deviceId: 'FFFFFFFF'
|
||||
});
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Could not generate and write to new config file` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
|
||||
|
||||
if ( this.rejected )
|
||||
{
|
||||
reject( err );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
resolve( true );
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Get › Retrieve Configuration Value
|
||||
|
||||
Fetches a stored value from the application's persistent configuration
|
||||
using the provided key via the nconf module.
|
||||
|
||||
This function is static, so it can be called without creating a Storage instance.
|
||||
|
||||
@args
|
||||
key (str) The configuration key to retrieve.
|
||||
|
||||
@returns
|
||||
(any) The value associated with the key, or undefined if the key does not exist.
|
||||
|
||||
@usage
|
||||
const deviceId = Storage.Get('deviceId');
|
||||
*/
|
||||
|
||||
static Get( key )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
return nconf.get( key );
|
||||
}
|
||||
|
||||
/*
|
||||
Set › Store Configuration Value
|
||||
|
||||
Stores a value in the application's persistent configuration using
|
||||
the provided key via the nconf module. Automatically saves the
|
||||
updated configuration to disk by calling Storage.Save( ).
|
||||
|
||||
This function is static, so it can be called without creating a Storage instance.
|
||||
|
||||
@args
|
||||
key (str) The configuration key to set.
|
||||
value (any) The value to store under the specified key.
|
||||
|
||||
@returns
|
||||
(void) No return value.
|
||||
|
||||
@usage
|
||||
Storage.Set('deviceId', '105B35EF');
|
||||
*/
|
||||
|
||||
static Set( key, value )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ),
|
||||
chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
nconf.set( key, value );
|
||||
Storage.Save( );
|
||||
}
|
||||
|
||||
/*
|
||||
Save › Persist Configuration to Disk
|
||||
|
||||
Saves the current configuration stored in nconf to disk.
|
||||
After saving, the method reads back the file to verify it is valid JSON
|
||||
and logs detailed status messages about success or errors.
|
||||
|
||||
@purpose
|
||||
- Calls nconf.save() to write the current configuration.
|
||||
- Reads back the saved file.
|
||||
- Parses the file as JSON to confirm validity.
|
||||
- Logs success or detailed error messages for failures.
|
||||
|
||||
@args
|
||||
none
|
||||
|
||||
@returns
|
||||
(void) Logs success or error; does not return a value.
|
||||
|
||||
@usage
|
||||
Storage.Save();
|
||||
*/
|
||||
|
||||
static Save( )
|
||||
{
|
||||
const filePath = this.fileConfig;
|
||||
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ),
|
||||
chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
nconf.save( ( err ) =>
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Could not save config` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ filePath }` ) );
|
||||
return;
|
||||
}
|
||||
|
||||
fs.readFile( filePath, ( err, data ) =>
|
||||
{
|
||||
if ( err )
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Unable to read config file` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ filePath }` ) );
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const parsed = JSON.parse( data.toString( ) );
|
||||
|
||||
Log.ok( `conf`, chalk.yellow( `[snapshot]` ), chalk.white( `✅` ),
|
||||
chalk.greenBright( `<msg>` ), chalk.gray( `Save to config file successful` ),
|
||||
chalk.greenBright( `<file>` ), chalk.gray( `${ filePath }` ) );
|
||||
|
||||
Log.debug( `conf`, chalk.yellow( `[snapshot]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Read values from saved config file` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ filePath }` ),
|
||||
chalk.blueBright( `<values>` ), chalk.gray( `${ JSON.stringify( parsed ) }` ) );
|
||||
}
|
||||
catch ( parseErr )
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Config file is not valid JSON` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ parseErr.message }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ filePath }` ) );
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
GetConfig › Return Full Path to Config File
|
||||
|
||||
Returns the full path to the currently used config.json file for this Storage instance.
|
||||
This is useful when you need to know the exact file location without reading its contents.
|
||||
|
||||
@args
|
||||
none
|
||||
|
||||
@returns
|
||||
(str) Absolute path to the config.json file.
|
||||
|
||||
@usage
|
||||
const storage_config = Storage.GetConfig();
|
||||
*/
|
||||
|
||||
static GetConfig( )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
return this.fileConfig;
|
||||
}
|
||||
|
||||
/*
|
||||
isJsonString › Check if Input is Valid JSON
|
||||
|
||||
Determines whether a given string is valid JSON by attempting
|
||||
to parse it. Returns true if parsing succeeds, false if it throws
|
||||
an error.
|
||||
|
||||
@args
|
||||
json (str) The string to test for valid JSON.
|
||||
|
||||
@returns
|
||||
(bool) True if input is valid JSON, false otherwise.
|
||||
|
||||
@usage
|
||||
const valid = Storage.isJsonString('{"key":"value"}'); // returns true
|
||||
*/
|
||||
|
||||
static isJsonString( json )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
try
|
||||
{
|
||||
JSON.parse( json );
|
||||
}
|
||||
catch ( e )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
helper › json object empty
|
||||
*/
|
||||
|
||||
static isJsonEmpty( json )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
if ( Object.keys( json ).length === 0 )
|
||||
return true;
|
||||
|
||||
if ( JSON.stringify( json ) === '\"{}\"' )
|
||||
return true;
|
||||
|
||||
for ( const key in json )
|
||||
{
|
||||
if ( ! Object.prototype.hasOwnProperty.call( json, key ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export class
|
||||
|
||||
@import
|
||||
import Storage from './classes/Storage.js';
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export default Storage;
|
||||
455
tvapp2/classes/Tuner.js
Normal file
455
tvapp2/classes/Tuner.js
Normal file
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
Class › Tuner
|
||||
|
||||
Handles HDHomeRun device management and deviceId lifecycle.
|
||||
|
||||
@purpose
|
||||
- Generate / format HDHomeRun device IDs.
|
||||
- Validate device IDs against HDHomeRun rules (length, hex chars, checksum).
|
||||
- Persist device IDs using Storage class.
|
||||
- Automatically generate new device ID if missing, invalid, or uninitialized (FFFFFFFF).
|
||||
- Initialize tuner instances with validated device IDs.
|
||||
|
||||
@usage
|
||||
await new Tuner( Storage.Get( 'deviceId' ) ).Initialize( );
|
||||
const tuner = new Tuner( );
|
||||
await tuner.Initialize( );
|
||||
const validId = await tuner.VerifyDeviceId( );
|
||||
|
||||
@notes
|
||||
- Device IDs are persisted via the Storage class (config.json).
|
||||
- User's device id must be valid before HDHomeRun will initialize.
|
||||
*/
|
||||
|
||||
|
||||
import chalk from 'chalk';
|
||||
import Storage from './Storage.js';
|
||||
import Utils from './Utils.js';
|
||||
import Log from './Log.js';
|
||||
|
||||
/*
|
||||
Class › Tuner
|
||||
|
||||
constructor ( str:deviceId )
|
||||
Initialize ( )
|
||||
Start ( )
|
||||
_GenerateDeviceId ( int:len )
|
||||
GenerateDeviceId ( )
|
||||
GetDeviceId ( )
|
||||
FormatDeviceId ( str:deviceid )
|
||||
IsDeviceIdValid ( )
|
||||
VerifyDeviceId ( )
|
||||
*/
|
||||
|
||||
class Tuner
|
||||
{
|
||||
constructor( deviceId )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getConstructorName( ) }` ) );
|
||||
|
||||
this.Name = `HDHomeRun`;
|
||||
this.FriendlyName = `TVApp2`;
|
||||
this.ModelNumber = `HDHR5-4US`;
|
||||
this.FirmwareName = `hdhomerun5_atsc`;
|
||||
this.FirmwareVersion = `0.9.15.00-RC04`;
|
||||
this.SlotsConnected = 0;
|
||||
this.SlotsMax = 10;
|
||||
this.DeviceId = deviceId || Storage.Get( 'deviceId' );
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize › Setup and Start Tuner
|
||||
|
||||
Initializes the tuner by calling the Start( ) method.
|
||||
Catches and logs any errors encountered during startup.
|
||||
|
||||
@args
|
||||
none
|
||||
|
||||
@returns
|
||||
(void) Logs status; does not return a value.
|
||||
|
||||
@usage
|
||||
await tuner.Initialize( );
|
||||
*/
|
||||
|
||||
async Initialize( )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
try
|
||||
{
|
||||
await this.Start( );
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
Log.error( `hdhr`, chalk.redBright( `[initiate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Failure initializing tuner` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Start › Initialize and Verify Device ID
|
||||
|
||||
Starts the tuner by verifying the current deviceId.
|
||||
If the deviceId is missing or invalid, it will be regenerated and validated.
|
||||
Logs the status of the deviceId once verification completes.
|
||||
|
||||
@args
|
||||
none
|
||||
|
||||
@returns
|
||||
(bool) true if deviceId is valid after verification, false otherwise.
|
||||
|
||||
@usage
|
||||
await tuner.Start( );
|
||||
*/
|
||||
|
||||
async Start( )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
const verifiedId = await new Tuner( ).VerifyDeviceId( this.DeviceId );
|
||||
|
||||
if ( await this.IsDeviceIdValid( verifiedId ) )
|
||||
{
|
||||
Log.ok( `conf`, chalk.yellow( `[validate]` ), chalk.white( `✅` ),
|
||||
chalk.greenBright( `<msg>` ), chalk.gray( `User has valid deviceId` ),
|
||||
chalk.greenBright( `<deviceId>` ), chalk.gray( `${ verifiedId }` ) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
_GenerateDeviceId › Generate Raw Random Hexadecimal String
|
||||
|
||||
Generates a raw random hexadecimal string using Node.js crypto module.
|
||||
This is typically used as the random portion of a deviceId.
|
||||
|
||||
@args
|
||||
len (int) Optional number of bytes to generate. Defaults to 4 bytes.
|
||||
|
||||
@returns
|
||||
(str) Uppercase hexadecimal string, length = len * 2 characters.
|
||||
|
||||
@usage
|
||||
const randomHex = Tuner._GenerateDeviceId( 4 ); // 8-character hex string
|
||||
*/
|
||||
|
||||
static _GenerateDeviceId( len )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
return crypto.randomBytes( len || 4 ).toString( 'hex' ).toUpperCase( );
|
||||
}
|
||||
|
||||
/*
|
||||
GenerateDeviceId › Generate New HDHomeRun Device ID
|
||||
|
||||
Generates a new, properly formatted HDHomeRun deviceId.
|
||||
|
||||
Steps:
|
||||
- Generates 4 random hexadecimal characters.
|
||||
- Prepends '105' and appends '0' to form base deviceId.
|
||||
- Passes baseId to Tuner.FormatDeviceId( ) to ensure correct checksum and 8-character format.
|
||||
|
||||
@args
|
||||
None
|
||||
|
||||
@returns
|
||||
(str) A valid, 8-character HDHomeRun deviceId in uppercase hexadecimal.
|
||||
|
||||
@usage
|
||||
const newDeviceId = Tuner.GenerateDeviceId( );
|
||||
*/
|
||||
|
||||
static GenerateDeviceId( )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
const chars = '0123456789ABCDEF';
|
||||
let randomHex = '';
|
||||
|
||||
// generate 4 random hexadecimal chars
|
||||
for ( let i = 0;i < 4;i++ )
|
||||
{
|
||||
randomHex += chars[Math.floor( Math.random( ) * chars.length )];
|
||||
}
|
||||
|
||||
const baseId = '105' + randomHex + '0';
|
||||
return this.FormatDeviceId( baseId );
|
||||
}
|
||||
|
||||
/*
|
||||
GetDeviceId › Retrieve Stored HDHomeRun Device ID
|
||||
|
||||
Fetches the current deviceId from persistent storage (via Storage.Get).
|
||||
|
||||
@args
|
||||
None
|
||||
|
||||
@returns
|
||||
(str) The current deviceId stored in configuration.
|
||||
|
||||
@usage
|
||||
const deviceId = await tuner.GetDeviceId( );
|
||||
*/
|
||||
|
||||
GetDeviceId( )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
return Storage.Get( 'deviceId' );
|
||||
}
|
||||
|
||||
/*
|
||||
FormatDeviceId › Validate and Format HDHomeRun Device ID
|
||||
|
||||
Fetches the provided deviceId (or instance default) and ensures it is valid
|
||||
according to HDHomeRun rules, then returns a properly formatted ID.
|
||||
|
||||
Steps:
|
||||
- Input must be exactly 8 hexadecimal characters.
|
||||
- All characters must be 0-9 or A-F/a-f.
|
||||
- Computes checksum using HDHomeRun-specific lookup table.
|
||||
- Generates a new deviceId integer with checksum applied.
|
||||
- Converts back to 8-character uppercase hexadecimal string.
|
||||
|
||||
Logs detailed errors if the input deviceId is invalid.
|
||||
|
||||
@args
|
||||
deviceid (str) Optional deviceId to format. Defaults to instance deviceId.
|
||||
|
||||
@returns
|
||||
(str|int) Formatted 8-character hex deviceId, or 0 if input invalid.
|
||||
|
||||
@usage
|
||||
const formattedId = Tuner.FormatDeviceId( someDeviceId );
|
||||
*/
|
||||
|
||||
static FormatDeviceId( deviceid )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
const deviceId = deviceid || this.DeviceId;
|
||||
|
||||
/*
|
||||
Validate input length
|
||||
*/
|
||||
|
||||
if ( !deviceId || deviceId.length !== 8 )
|
||||
{
|
||||
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must be 8 hexadecimals` ),
|
||||
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
All chars should be valid hexadecimal
|
||||
*/
|
||||
|
||||
const hexPattern = /^[0-9A-Fa-f]+$/;
|
||||
if ( !hexPattern.test( deviceId ) )
|
||||
{
|
||||
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must contain all hex (0-9, A-F, a-f)` ),
|
||||
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Hex string to integer
|
||||
*/
|
||||
|
||||
const deviceIdInt = parseInt( deviceId, 16 );
|
||||
|
||||
/*
|
||||
Checksum lookup table
|
||||
*/
|
||||
|
||||
const checksumLookup =
|
||||
[
|
||||
0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0
|
||||
];
|
||||
|
||||
/*
|
||||
Calc checksum
|
||||
*/
|
||||
|
||||
let checksum = 0;
|
||||
checksum ^= checksumLookup[( deviceIdInt >> 28 ) & 0x0F];
|
||||
checksum ^= ( deviceIdInt >> 24 ) & 0x0F;
|
||||
checksum ^= checksumLookup[( deviceIdInt >> 20 ) & 0x0F];
|
||||
checksum ^= ( deviceIdInt >> 16 ) & 0x0F;
|
||||
checksum ^= checksumLookup[( deviceIdInt >> 12 ) & 0x0F];
|
||||
checksum ^= ( deviceIdInt >> 8 ) & 0x0F;
|
||||
checksum ^= checksumLookup[( deviceIdInt >> 4 ) & 0x0F];
|
||||
|
||||
/*
|
||||
Calc new device ID
|
||||
*/
|
||||
|
||||
const newDevId = ( deviceIdInt & 0xFFFFFFF0 ) + checksum;
|
||||
|
||||
/*
|
||||
Convert back to hex string; ensure we get 8 characters with leading zeros; convert to uppercase
|
||||
*/
|
||||
|
||||
return newDevId.toString( 16 ).toUpperCase( ).padStart( 8, '0' );
|
||||
}
|
||||
|
||||
/*
|
||||
IsDeviceIdValid › Validate HDHomeRun Device ID
|
||||
|
||||
Checks if the current deviceId on this instance is valid according to HDHomeRun rules.
|
||||
|
||||
Validation steps:
|
||||
- Must be exactly 8 characters long.
|
||||
- All characters must be hexadecimal (0-9, A-F, a-f).
|
||||
- Computes checksum using HDHomeRun-specific lookup table; must equal 0.
|
||||
|
||||
Logs detailed errors if the deviceId fails any validation step.
|
||||
|
||||
@returns
|
||||
(bool) true if deviceId is valid, false otherwise.
|
||||
|
||||
@usage
|
||||
const isValid = await tuner.IsDeviceIdValid( );
|
||||
*/
|
||||
|
||||
async IsDeviceIdValid( )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
/*
|
||||
Define Hexadecimal charset (0-9, A-F, a-f)
|
||||
*/
|
||||
|
||||
const hexDigits = new Set( '0123456789ABCDEFabcdef' );
|
||||
const deviceId = this.DeviceId;
|
||||
|
||||
/*
|
||||
Check if device ID is exactly 8 characters
|
||||
*/
|
||||
|
||||
if ( !deviceId || deviceId.length !== 8 )
|
||||
{
|
||||
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must be 8 hexadecimals` ),
|
||||
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Check if all characters are hexadecimal
|
||||
*/
|
||||
|
||||
if ( !Array.from( deviceId ).every( ( c ) => hexDigits.has( c ) ) )
|
||||
{
|
||||
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must contain all hex (0-A)` ),
|
||||
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Convert hex string to integer (equivalent to int.from_bytes with big endian)
|
||||
*/
|
||||
|
||||
const deviceIdInt = parseInt( deviceId, 16 );
|
||||
|
||||
/*
|
||||
Checksum lookup table
|
||||
*/
|
||||
|
||||
const checksumLookup =
|
||||
[
|
||||
0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0
|
||||
];
|
||||
|
||||
/*
|
||||
Calc checksum
|
||||
*/
|
||||
|
||||
let checksum = 0;
|
||||
checksum ^= checksumLookup[( deviceIdInt >>> 28 ) & 0x0F];
|
||||
checksum ^= ( deviceIdInt >>> 24 ) & 0x0F;
|
||||
checksum ^= checksumLookup[( deviceIdInt >>> 20 ) & 0x0F];
|
||||
checksum ^= ( deviceIdInt >>> 16 ) & 0x0F;
|
||||
checksum ^= checksumLookup[( deviceIdInt >>> 12 ) & 0x0F];
|
||||
checksum ^= ( deviceIdInt >>> 8 ) & 0x0F;
|
||||
checksum ^= checksumLookup[( deviceIdInt >>> 4 ) & 0x0F];
|
||||
checksum ^= ( deviceIdInt >>> 0 ) & 0x0F;
|
||||
|
||||
return checksum === 0;
|
||||
}
|
||||
|
||||
/*
|
||||
VerifyDeviceId › Validate / Generate Device ID
|
||||
|
||||
Checks if the current deviceId on this instance is valid.
|
||||
|
||||
If missing, uninitialized ('FFFFFFFF'), or fails validation:
|
||||
a new deviceId is generated via the static Tuner.GenerateDeviceId( ) method.
|
||||
|
||||
New deviceId is saved to persistent storage via Storage.Set( ) and
|
||||
updated on the instance.
|
||||
|
||||
Function also recursively verifies until a valid deviceId is established.
|
||||
|
||||
@returns
|
||||
(str) A valid deviceId for this tuner instance.
|
||||
|
||||
@usage
|
||||
const validId = await tuner.VerifyDeviceId( );
|
||||
*/
|
||||
|
||||
async VerifyDeviceId( )
|
||||
{
|
||||
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
|
||||
|
||||
const deviceId = this.DeviceId;
|
||||
|
||||
if ( !deviceId || deviceId === 'FFFFFFFF' || !await this.IsDeviceIdValid( ) )
|
||||
{
|
||||
const deviceIdNew = Tuner.GenerateDeviceId( ); // static generates a properly formatted ID
|
||||
if ( deviceId === 'FFFFFFFF' )
|
||||
{
|
||||
Log.info( `conf`, chalk.yellow( `[generate]` ), chalk.white( `📣` ),
|
||||
chalk.yellow( `<msg>` ), chalk.gray( `Generating HDHomeRun deviceId for the first time` ),
|
||||
chalk.yellow( `<deviceId>` ), chalk.gray( `${ deviceIdNew }` ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Invalid deviceId; generating new` ),
|
||||
chalk.redBright( `<oldDeviceId>` ), chalk.gray( `${ deviceId }` ),
|
||||
chalk.redBright( `<deviceIdNew>` ), chalk.gray( `${ deviceIdNew }` ) );
|
||||
}
|
||||
|
||||
Storage.Set( 'deviceId', deviceIdNew ); // save to JSON via nconf
|
||||
this.DeviceId = deviceIdNew; // update the instance so validation works
|
||||
|
||||
// verify recursively until valid
|
||||
const verifiedId = await this.VerifyDeviceId( );
|
||||
return verifiedId;
|
||||
}
|
||||
|
||||
return deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
export class
|
||||
|
||||
@image
|
||||
import Tuner from './classes/Tuner.js';
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export default Tuner;
|
||||
47
tvapp2/classes/Utils.js
Normal file
47
tvapp2/classes/Utils.js
Normal file
@@ -0,0 +1,47 @@
|
||||
class Utils
|
||||
{
|
||||
/*
|
||||
Returns the name of the function that this function was called from.
|
||||
used for Log.verbose
|
||||
*/
|
||||
|
||||
static getFuncName()
|
||||
{
|
||||
return ( new Error() ).stack.match( /at (\S+)/g )[1].slice( 3 );
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the name of the constructor that this function was called from.
|
||||
used for Log.verbose
|
||||
*/
|
||||
|
||||
static getConstructorName()
|
||||
{
|
||||
return ( new Error() ).stack.match( /new\s+(\w+)/g )[0];
|
||||
}
|
||||
|
||||
/*
|
||||
helper > str2bool
|
||||
*/
|
||||
|
||||
static str2bool( str )
|
||||
{
|
||||
if ( typeof str === 'string' )
|
||||
{
|
||||
const lower = str.toLowerCase();
|
||||
if ([
|
||||
'1', 'true', 'yes', 'y', 't'
|
||||
].includes( lower ) )
|
||||
str = true;
|
||||
if ([
|
||||
'0', 'false', 'no', 'n', 'f'
|
||||
].includes( lower ) )
|
||||
str = false;
|
||||
return str;
|
||||
}
|
||||
else return Boolean( str );
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export default Utils;
|
||||
@@ -223,7 +223,7 @@ export default
|
||||
'@stylistic/no-whitespace-before-property': ['error'],
|
||||
'@stylistic/object-curly-spacing': ['error', 'always'],
|
||||
'@stylistic/quote-props': ['error', 'as-needed'],
|
||||
'@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: true }],
|
||||
'@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }],
|
||||
'@stylistic/semi': ['error', 'always'],
|
||||
'@stylistic/space-infix-ops': ['error'],
|
||||
'@stylistic/template-curly-spacing': ['error', 'always'],
|
||||
|
||||
666
tvapp2/index.js
666
tvapp2/index.js
@@ -17,8 +17,12 @@ import ejs from 'ejs';
|
||||
import moment from 'moment';
|
||||
import TimeAgo from 'javascript-time-ago';
|
||||
import en from 'javascript-time-ago/locale/en';
|
||||
import nconf from 'nconf';
|
||||
import crypto from 'node:crypto';
|
||||
import Log from './classes/Log.js';
|
||||
import Storage from './classes/Storage.js';
|
||||
import Utils from './classes/Utils.js';
|
||||
import CLib from './classes/CLib.js';
|
||||
import Semaphore from './classes/Semaphore.js';
|
||||
import Tuner from './classes/Tuner.js';
|
||||
import cron, { schedule } from 'node-cron';
|
||||
import * as child from 'child_process';
|
||||
import * as crons from 'cron';
|
||||
@@ -28,7 +32,16 @@ import * as crons from 'cron';
|
||||
*/
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/*
|
||||
Initialize classes
|
||||
*/
|
||||
|
||||
const cache = new Map();
|
||||
const clib = new CLib();
|
||||
|
||||
const encoded = clib.encodeToHexBase64( 'tvapp2' );
|
||||
const decoded = clib.decodeFromHexBase64( `${ encoded }` );
|
||||
|
||||
/*
|
||||
Import package.json values
|
||||
@@ -37,8 +50,9 @@ const cache = new Map();
|
||||
const { name, author, version, repository, discord, docs } = JSON.parse( fs.readFileSync( './package.json' ) );
|
||||
const __filename = fileURLToPath( import.meta.url ); // get resolved path to file
|
||||
const __dirname = path.dirname( __filename ); // get name of directory
|
||||
|
||||
/*
|
||||
const gitHash = child.execSync( 'git rev-parse HEAD' ).toString().trim();
|
||||
const gitHash = child.execSync( 'git rev-parse HEAD' ).toString().trim();
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -58,19 +72,20 @@ const gitHash = child.execSync( 'git rev-parse HEAD' ).toString().trim();
|
||||
chalk.level = 3;
|
||||
|
||||
/*
|
||||
|
||||
timeAgo
|
||||
*/
|
||||
|
||||
TimeAgo.addDefaultLocale( en );
|
||||
const timeAgo = new TimeAgo( );
|
||||
|
||||
/*
|
||||
Define > General
|
||||
Define › General
|
||||
|
||||
@note if you change `envWebFolder`; ensure you re-name the folder where the
|
||||
website assets are stored.
|
||||
*/
|
||||
|
||||
let FILE_CFG;
|
||||
let FILE_URL;
|
||||
let FILE_M3U;
|
||||
let FILE_XML;
|
||||
@@ -83,11 +98,12 @@ let FILE_XML_MODIFIED = 0;
|
||||
let FILE_GZP_MODIFIED = 0;
|
||||
|
||||
/*
|
||||
Define > Environment Variables || Defaults
|
||||
Define › Environment Variables || Defaults
|
||||
*/
|
||||
|
||||
const envAppRelease = process.env.RELEASE || 'stable';
|
||||
const envUrlRepo = process.env.URL_REPO || 'https://git.binaryninja.net/binaryninja';
|
||||
const envXmlEpg = process.env.URL_EPG || 'https://epg.binaryninja.net/XMLTV-EPG';
|
||||
const envStreamQuality = process.env.STREAM_QUALITY || 'hd';
|
||||
const envFileURL = process.env.FILE_URL || 'urls.txt';
|
||||
const envFileM3U = process.env.FILE_M3U || 'playlist.m3u8';
|
||||
@@ -97,6 +113,7 @@ const envApiKey = process.env.API_KEY || null;
|
||||
const envWebIP = process.env.WEB_IP || '0.0.0.0';
|
||||
const envWebPort = process.env.WEB_PORT || `4124`;
|
||||
const envWebFolder = process.env.WEB_FOLDER || 'www';
|
||||
const envHdhrPort = process.env.HDHR_PORT || `6077`;
|
||||
const envWebEncoding = process.env.WEB_ENCODING || 'deflate, br';
|
||||
const envProxyHeader = process.env.WEB_PROXY_HEADER || 'x-forwarded-for';
|
||||
const envHealthTimer = process.env.HEALTH_TIMER || 600000;
|
||||
@@ -112,15 +129,16 @@ let serverOs = 'Unknown';
|
||||
let serverStartup = 0;
|
||||
|
||||
/*
|
||||
Define > Externals
|
||||
Define › Externals
|
||||
*/
|
||||
|
||||
const extURL = `${ envUrlRepo }/tvapp2-externals/raw/branch/main/urls.txt`;
|
||||
const extXML = `${ envUrlRepo }/XMLTV-EPG/raw/branch/main/xmltv.1.xml`;
|
||||
const extM3U = `${ envUrlRepo }/tvapp2-externals/raw/branch/main/formatted.dat`;
|
||||
const extXML = `${ envXmlEpg }/xmltv_v2.0.0.xml`;
|
||||
const extM3U = `${ envXmlEpg }/formatted_v2.0.0.dat`;
|
||||
//const extM3U = `${ envUrlRepo }/tvapp2-externals/raw/branch/main/formatted.dat`;
|
||||
|
||||
/*
|
||||
Define > Defaults
|
||||
Define › Defaults
|
||||
*/
|
||||
|
||||
let urls = [];
|
||||
@@ -159,6 +177,19 @@ const fileIpContainer = '/var/run/s6/container_environment/IP_CONTAINER';
|
||||
const envIpGateway = fs.existsSync( fileIpGateway ) ? fs.readFileSync( fileIpGateway, 'utf8' ) : `0.0.0.0`;
|
||||
const envIpContainer = fs.existsSync( fileIpContainer ) ? fs.readFileSync( fileIpContainer, 'utf8' ) : `0.0.0.0`;
|
||||
|
||||
/*
|
||||
Hosts
|
||||
*/
|
||||
|
||||
const hosts =
|
||||
[
|
||||
{ name: 'TVPass.org', url: 'https://tvpass.org' },
|
||||
{ name: 'TheTVApp.to', url: 'https://thetvapp.to' },
|
||||
{ name: 'MoveOnJoy.com', url: 'http://moveonjoy.com' },
|
||||
{ name: 'Daddylive.dad', url: 'https://daddylivestream.com' },
|
||||
{ name: 'git.binaryninja.net', url: envUrlRepo }
|
||||
];
|
||||
|
||||
/*
|
||||
Get Server OS
|
||||
|
||||
@@ -188,89 +219,6 @@ getos( ( e, json ) =>
|
||||
return serverOs;
|
||||
});
|
||||
|
||||
/*
|
||||
Define > Logs
|
||||
|
||||
When assigning text colors, terminals and the windows command prompt can display any color; however apps
|
||||
such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will
|
||||
not be the same as the rgb value. It's best to just stick to Chalk's default colors.
|
||||
|
||||
Various levels of logs with the following usage:
|
||||
Log.verbose(`This is verbose`)
|
||||
Log.debug(`This is debug`)
|
||||
Log.info(`This is info`)
|
||||
Log.ok(`This is ok`)
|
||||
Log.notice(`This is notice`)
|
||||
Log.warn(`This is warn`)
|
||||
Log.error(
|
||||
`Error fetching sports data with error:`,
|
||||
chalk.white(`→`),
|
||||
chalk.grey(`This is the error message`)
|
||||
);
|
||||
|
||||
Level Type
|
||||
-----------------------------------
|
||||
6 Trace
|
||||
5 Debug
|
||||
4 Info
|
||||
3 Notice
|
||||
2 Warn
|
||||
1 Error
|
||||
*/
|
||||
|
||||
class Log
|
||||
{
|
||||
static now()
|
||||
{
|
||||
const now = new Date();
|
||||
return chalk.gray( `[${ now.toLocaleTimeString() }]` );
|
||||
}
|
||||
|
||||
static verbose( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 6 )
|
||||
console.debug( chalk.white.bgBlack.blackBright.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static debug( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 7 )
|
||||
console.trace( chalk.white.bgMagenta.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.magentaBright( msg.join( ' ' ) ) );
|
||||
else if ( LOG_LEVEL >= 5 )
|
||||
console.debug( chalk.white.bgGray.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static info( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 4 )
|
||||
console.info( chalk.white.bgBlueBright.bold( ` ${ name } ` ), chalk.white( `ℹ️` ), this.now(), chalk.blueBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static ok( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 4 )
|
||||
console.log( chalk.white.bgGreen.bold( ` ${ name } ` ), chalk.white( `✅` ), this.now(), chalk.greenBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static notice( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 3 )
|
||||
console.log( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `📌` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static warn( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 2 )
|
||||
console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `⚠️` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
|
||||
static error( ...msg )
|
||||
{
|
||||
if ( LOG_LEVEL >= 1 )
|
||||
console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `❌` ), this.now(), chalk.redBright( msg.join( ' ' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Process
|
||||
*/
|
||||
@@ -281,6 +229,7 @@ if ( process.pkg )
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Starting server utilizing process.execPath` ) );
|
||||
|
||||
const basePath = path.dirname( process.execPath );
|
||||
FILE_CFG = path.join( basePath, envWebFolder, `config.json` );
|
||||
FILE_URL = path.join( basePath, envWebFolder, `${ envFileURL }` );
|
||||
FILE_M3U = path.join( basePath, envWebFolder, `${ envFileM3U }` );
|
||||
FILE_XML = path.join( basePath, envWebFolder, `${ envFileXML }` );
|
||||
@@ -292,6 +241,7 @@ else
|
||||
Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Starting server utilizing processed locals` ) );
|
||||
|
||||
FILE_CFG = path.resolve( __dirname, envWebFolder, `config.json` );
|
||||
FILE_URL = path.resolve( __dirname, envWebFolder, `${ envFileURL }` );
|
||||
FILE_M3U = path.resolve( __dirname, envWebFolder, `${ envFileM3U }` );
|
||||
FILE_XML = path.resolve( __dirname, envWebFolder, `${ envFileXML }` );
|
||||
@@ -299,7 +249,7 @@ else
|
||||
}
|
||||
|
||||
/*
|
||||
helper > sleep
|
||||
helper › sleep
|
||||
*/
|
||||
|
||||
function sleep( ms )
|
||||
@@ -311,45 +261,7 @@ function sleep( ms )
|
||||
}
|
||||
|
||||
/*
|
||||
Semaphore > Declare
|
||||
|
||||
allows multiple threads to work with the same shared resources
|
||||
*/
|
||||
|
||||
class Semaphore
|
||||
{
|
||||
constructor( max )
|
||||
{
|
||||
this.max = max;
|
||||
this.queue = [];
|
||||
this.active = 0;
|
||||
}
|
||||
|
||||
async acquire()
|
||||
{
|
||||
if ( this.active < this.max )
|
||||
{
|
||||
this.active++;
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise( ( resolve ) => this.queue.push( resolve ) );
|
||||
}
|
||||
|
||||
release()
|
||||
{
|
||||
this.active--;
|
||||
if ( this.queue.length > 0 )
|
||||
{
|
||||
const resolve = this.queue.shift();
|
||||
this.active++;
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Semaphore > Initialize
|
||||
Semaphore › Initialize
|
||||
|
||||
@arg int threads_max
|
||||
*/
|
||||
@@ -382,90 +294,94 @@ const clientIp = ( req ) =>
|
||||
if try 2 fails with the opposite protocol; domain is considered down
|
||||
*/
|
||||
|
||||
async function serviceCheck( service, uri )
|
||||
async function hostCheck( service, uri )
|
||||
{
|
||||
/* try 1 */
|
||||
|
||||
try
|
||||
{
|
||||
const resp = await fetch( uri );
|
||||
|
||||
/* try 1 > domain down */
|
||||
/* try 1 › domain down */
|
||||
if ( resp.status !== 200 )
|
||||
{
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uri }` ) );
|
||||
return;
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Try: Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uri }` ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/* try 1 > domain up */
|
||||
Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `<msg>` ), chalk.gray( `Service Online` ), chalk.greenBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `<service>` ), chalk.gray( `${ service }` ), chalk.greenBright( `<address>` ), chalk.gray( `${ uri }` ) );
|
||||
/* try 1 › domain up */
|
||||
Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `<msg>` ), chalk.gray( `Domain Online` ), chalk.greenBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `<service>` ), chalk.gray( `${ service }` ), chalk.greenBright( `<address>` ), chalk.gray( `${ uri }` ) );
|
||||
return true;
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
/*
|
||||
try 2 > http
|
||||
try 2 › https
|
||||
*/
|
||||
|
||||
if ( /^https:\/\//i.test( uri ) )
|
||||
{
|
||||
const uriRetry = uri.replace( /^https:\/\//ig, 'http://' );
|
||||
Log.info( `ping`, chalk.yellow( `[response]` ), chalk.white( `⚠️` ), chalk.yellowBright( `<msg>` ), chalk.gray( `Service Offline; failed to communicate with service via SSL; trying http protocol` ), chalk.yellowBright( `<service>` ), chalk.gray( `${ service }` ), chalk.yellowBright( `<uriAttempt1>` ), chalk.gray( `${ uri }` ), chalk.redBright( `(failed)` ), chalk.yellowBright( `<uriAttempt2>` ), chalk.gray( `${ uriRetry }` ), chalk.blueBright( `(pending)` ) );
|
||||
Log.info( `ping`, chalk.yellow( `[response]` ), chalk.white( `⚠️` ), chalk.yellowBright( `<msg>` ), chalk.gray( `Try: Failed via HTTPS; trying HTTP protocol` ), chalk.yellowBright( `<service>` ), chalk.gray( `${ service }` ), chalk.yellowBright( `<uriAttempt1>` ), chalk.gray( `${ uri }` ), chalk.redBright( `(failed)` ), chalk.yellowBright( `<uriAttempt2>` ), chalk.gray( `${ uriRetry }` ), chalk.blueBright( `(pending)` ) );
|
||||
|
||||
try
|
||||
{
|
||||
const resp = await fetch( uriRetry );
|
||||
|
||||
/* try 2 > http > domain down */
|
||||
/* try 2 › https › domain down */
|
||||
if ( resp.status !== 200 )
|
||||
{
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
return;
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Try: Domain Offline; failed to communicate with domain, possibly down` ), chalk.redBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/* try 2 > http > domain up */
|
||||
Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `<msg>` ), chalk.gray( `Service Online` ), chalk.greenBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `<service>` ), chalk.gray( `${ service }` ), chalk.greenBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
/* try 2 › https › domain up */
|
||||
Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `<msg>` ), chalk.gray( `Domain Online` ), chalk.greenBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `<service>` ), chalk.gray( `${ service }` ), chalk.greenBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
return true;
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
/* try 2 > http > domain not exist */
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Service Offline; failed to communicate with service, address does not exist` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uri }` ), chalk.redBright( `<message>` ), chalk.gray( `${ err }` ) );
|
||||
/* try 2 › https › domain not exist */
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Try: Domain Offline; failed to communicate with domain, address does not exist` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uri }` ), chalk.redBright( `<message>` ), chalk.gray( `${ err }` ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
try 2 > https
|
||||
try 2 › http
|
||||
*/
|
||||
|
||||
else if ( /^http:\/\//i.test( uri ) )
|
||||
{
|
||||
const uriRetry = uri.replace( /^http:\/\//ig, 'https://' );
|
||||
Log.info( `ping`, chalk.yellow( `[response]` ), chalk.white( `⚠️` ), chalk.yellowBright( `<msg>` ), chalk.gray( `Service Offline; failed to communicate with service via SSL; trying https protocol` ), chalk.yellowBright( `<service>` ), chalk.gray( `${ service }` ), chalk.yellowBright( `<uriAttempt1>` ), chalk.gray( `${ uri }` ), chalk.redBright( `(failed)` ), chalk.yellowBright( `<uriAttempt2>` ), chalk.gray( `${ uriRetry }` ), chalk.blueBright( `(pending)` ) );
|
||||
Log.info( `ping`, chalk.yellow( `[response]` ), chalk.white( `⚠️` ), chalk.yellowBright( `<msg>` ), chalk.gray( `Try: Failed via HTTP; trying HTTPS protocol` ), chalk.yellowBright( `<service>` ), chalk.gray( `${ service }` ), chalk.yellowBright( `<uriAttempt1>` ), chalk.gray( `${ uri }` ), chalk.redBright( `(failed)` ), chalk.yellowBright( `<uriAttempt2>` ), chalk.gray( `${ uriRetry }` ), chalk.blueBright( `(pending)` ) );
|
||||
|
||||
try
|
||||
{
|
||||
const resp = await fetch( uriRetry );
|
||||
|
||||
/* try 2 > https > domain down */
|
||||
/* try 2 › http › domain down */
|
||||
if ( resp.status !== 200 )
|
||||
{
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
return;
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Domain Offline; failed to communicate with domain, possibly down` ), chalk.redBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/* try 2 > https > domain up */
|
||||
Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `<msg>` ), chalk.gray( `Service Online` ), chalk.greenBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `<service>` ), chalk.gray( `${ service }` ), chalk.greenBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
/* try 2 › http › domain up */
|
||||
Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `✅` ), chalk.greenBright( `<msg>` ), chalk.gray( `Domain Online` ), chalk.greenBright( `<code>` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `<service>` ), chalk.gray( `${ service }` ), chalk.greenBright( `<address>` ), chalk.gray( `${ uriRetry }` ) );
|
||||
return true;
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
/* try 2 > https > domain not exist */
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Service Offline; failed to communicate with service, address does not exist` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uri }` ), chalk.redBright( `<message>` ), chalk.gray( `${ err }` ) );
|
||||
/* try 2 › http › domain not exist */
|
||||
Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `<msg>` ), chalk.gray( `Domain Offline; failed to communicate with domain, address does not exist` ), chalk.redBright( `<service>` ), chalk.gray( `${ service }` ), chalk.redBright( `<address>` ), chalk.gray( `${ uri }` ), chalk.redBright( `<message>` ), chalk.gray( `${ err }` ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Func > Download File
|
||||
Func › Download File
|
||||
|
||||
@arg str url https://git.binaryninja.net/binaryninja/tvapp2-externals/raw/branch/main/urls.txt
|
||||
@arg str filePath H:\Repos\github\BinaryNinja\tvapp2\tvapp2\urls.txt
|
||||
@@ -608,7 +524,35 @@ async function getFile( url, filePath )
|
||||
chalk.blueBright( `<src>` ), chalk.gray( `${ url }` ),
|
||||
chalk.blueBright( `<dest>` ), chalk.gray( `${ filePath }` ) );
|
||||
|
||||
const ok = await hostCheck( 'git.binaryninja.com', `${ envUrlRepo }` );
|
||||
|
||||
if ( ok )
|
||||
{
|
||||
try
|
||||
{
|
||||
await downloadFile( url, filePath );
|
||||
return true;
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
Log.error( `file`, chalk.redBright( `[download]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Download attempt failed after service check succeeded` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ),
|
||||
chalk.redBright( `<src>` ), chalk.gray( `${ url }` ),
|
||||
chalk.redBright( `<dest>` ), chalk.gray( `${ filePath }` ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.info( `file`, chalk.yellow( `[download]` ), chalk.white( `ℹ️` ),
|
||||
chalk.yellowBright( `<msg>` ), chalk.gray( `Skipping download because service is offline; using existing local file` ),
|
||||
chalk.yellowBright( `<url>` ), chalk.gray( `${ url }` ),
|
||||
chalk.yellowBright( `<dest>` ), chalk.gray( `${ filePath }` ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch ( err )
|
||||
{
|
||||
@@ -1479,13 +1423,14 @@ async function serveHealthCheck( req, res )
|
||||
try
|
||||
{
|
||||
const paramUrl = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'api' );
|
||||
const paramQuery = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'query' );
|
||||
const paramSilent = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'silent' );
|
||||
|
||||
if ( !paramUrl )
|
||||
{
|
||||
if ( paramQuery !== 'uptime' )
|
||||
if ( Utils.str2bool( paramSilent ) !== true )
|
||||
{
|
||||
Log.debug( `/api`, chalk.yellow( `[health]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `No API key passed to health check` ) );
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `No api-key passed to health check` ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1509,7 +1454,7 @@ async function serveHealthCheck( req, res )
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
if ( paramQuery !== 'uptime' )
|
||||
if ( Utils.str2bool( paramSilent ) !== true )
|
||||
{
|
||||
Log.ok( `/api`, chalk.yellow( `[health]` ), chalk.white( `✅` ),
|
||||
chalk.greenBright( `<msg>` ), chalk.gray( `Response` ),
|
||||
@@ -1627,18 +1572,20 @@ async function serveM3U( res, req )
|
||||
chalk.blueBright( `<to>` ), chalk.gray( `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }` ) );
|
||||
|
||||
return `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }`;
|
||||
})
|
||||
.replace( /(https?:\/\/[^\s]*fl2.moveonjoy[^\s]*)/g, ( fullUrl ) =>
|
||||
});
|
||||
/*
|
||||
.replace( /(https?:\/\/fl\d+\.moveonjoy\.com[^\s]*)/g, ( fullUrl ) =>
|
||||
{
|
||||
const urlRewrite = fullUrl.replace( 'fl2.moveonjoy', 'fl6.moveonjoy' );
|
||||
const urlRewrite = fullUrl.replace( /fl\d+\.moveonjoy\.com/, 'fl25.moveonjoy.com' );
|
||||
Log.debug( `.m3u`, chalk.yellow( `[rewriter]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Rewriting url for keyword` ),
|
||||
chalk.blueBright( `<keyword>` ), chalk.gray( `*fl2.moveonjoy` ),
|
||||
chalk.blueBright( `<keyword>` ), chalk.gray( `*fl1.moveonjoy` ),
|
||||
chalk.blueBright( `<from>` ), chalk.gray( `${ fullUrl }` ),
|
||||
chalk.blueBright( `<to>` ), chalk.gray( `${ urlRewrite }` ) );
|
||||
|
||||
return `${ urlRewrite }`;
|
||||
});
|
||||
*/
|
||||
|
||||
res.writeHead( 200, {
|
||||
'Content-Type': 'application/x-mpegURL',
|
||||
@@ -2062,11 +2009,11 @@ const server = http.createServer( ( req, resp ) =>
|
||||
loadFile channel?url=https%3A%2F%2Ftvpass.org%2Fchannel%2Fabc-wabc-new-york-ny%2F
|
||||
*/
|
||||
|
||||
const paramQuery = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'query' );
|
||||
if ( paramQuery !== 'uptime' )
|
||||
const paramSilent = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'silent' );
|
||||
if ( Utils.str2bool( paramSilent ) !== true )
|
||||
{
|
||||
Log.debug( `http`, chalk.yellow( `[requests]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Request started` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `New request` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<request.url>` ), chalk.gray( `${ req.url }` ),
|
||||
chalk.blueBright( `<reqUrl>` ), chalk.gray( `${ reqUrl }` ),
|
||||
@@ -2251,11 +2198,27 @@ const server = http.createServer( ( req, resp ) =>
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Endpoint > Health Check
|
||||
|
||||
paramQuery specifies what type of query is triggered
|
||||
options:
|
||||
uptime
|
||||
healthcheck
|
||||
sync
|
||||
|
||||
paramSilent specifies if logs should be silenced. useful for docker-compose.yml healthcheck so that console
|
||||
is not spammed every 30 seconds.
|
||||
*/
|
||||
|
||||
if ( subdomainHealth.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
|
||||
{
|
||||
const paramQuery = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'query' );
|
||||
const paramSilent = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'silent' );
|
||||
|
||||
if ( paramQuery !== 'uptime' )
|
||||
// do not show log if query is `uptime`, since uptime runs every 1 second.
|
||||
// do not show logs if query has striggered `silent?=true` in url
|
||||
|
||||
if ( Utils.str2bool( paramSilent ) !== true )
|
||||
{
|
||||
Log.info( `http`, chalk.yellow( `[requests]` ), chalk.white( `ℹ️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Requesting to access health api` ),
|
||||
@@ -2384,10 +2347,353 @@ const server = http.createServer( ( req, resp ) =>
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
|
||||
/*
|
||||
Main Server › Discovery.json
|
||||
*/
|
||||
|
||||
if ( loadFile === 'discovery.json' )
|
||||
{
|
||||
Log.notice( `http`, chalk.yellowBright( `[notice]` ), chalk.white( `📌` ),
|
||||
chalk.yellowBright( `<msg>` ), chalk.gray( `If you are attempting to load TVApp2 using an HDHomeRun tuner, please switch to the` ), chalk.yellowBright( `M3U Tuner` ) );
|
||||
|
||||
const tunerInstance = new Tuner(); // <-- use a different name
|
||||
const hdHomeRun =
|
||||
{
|
||||
FriendlyName: tunerInstance.FriendlyName,
|
||||
ModelNumber: tunerInstance.ModelNumber,
|
||||
FirmwareName: tunerInstance.FirmwareName,
|
||||
FirmwareVersion: tunerInstance.FirmwareVersion,
|
||||
DeviceID: tunerInstance.GetDeviceId(),
|
||||
TunerCount: tunerInstance.SlotsMax,
|
||||
BaseURL: `${ envIpContainer }:${ envHdhrPort }`,
|
||||
LineupURL: `${ envIpContainer }:${ envHdhrPort }/lineup.json`,
|
||||
client: clientIp( req ),
|
||||
message: 'Connected to HDHomeRun server',
|
||||
status: 'healthy',
|
||||
code: 200,
|
||||
uptime: Math.round( process.uptime() ),
|
||||
uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ),
|
||||
uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
resp.writeHead( hdHomeRun.code, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
resp.end( JSON.stringify( hdHomeRun ) );
|
||||
|
||||
return; // <- Prevent further code from executing
|
||||
}
|
||||
|
||||
const statusCheck =
|
||||
{
|
||||
ip: envIpContainer,
|
||||
gateway: envIpGateway,
|
||||
client: clientIp( req ),
|
||||
message: 'Page not found',
|
||||
status: 'healthy',
|
||||
ref: req.url,
|
||||
method: method || 'GET',
|
||||
code: 404,
|
||||
uptime: Math.round( process.uptime() ),
|
||||
uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ),
|
||||
uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
resp.writeHead( statusCheck.code, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
Log.error( `http`, chalk.redBright( `[requests]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `${ statusCheck.message }` ),
|
||||
chalk.redBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.redBright( `<code>` ), chalk.gray( `${ statusCheck.code }` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err }` ),
|
||||
chalk.redBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.redBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
|
||||
resp.end( JSON.stringify( statusCheck ) );
|
||||
}
|
||||
});
|
||||
};
|
||||
handleRequest().catch( ( err ) =>
|
||||
{
|
||||
resp.writeHead( 500, {
|
||||
'Content-Type': 'text/plain'
|
||||
});
|
||||
|
||||
Log.error( `http`, chalk.redBright( `[requests]` ), chalk.white( `❌` ),
|
||||
chalk.redBright( `<msg>` ), chalk.gray( `Cannot handle request` ),
|
||||
chalk.redBright( `<code>` ), chalk.gray( `500` ),
|
||||
chalk.redBright( `<error>` ), chalk.gray( `${ err }` ) );
|
||||
|
||||
resp.end( 'Internal Server Error' );
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
Server > HDHomeRun
|
||||
|
||||
this server will serve up the HDHomeRun lineup.json for people wishing to
|
||||
see the IPTV streams using the HDHomeRun tuner.
|
||||
*/
|
||||
|
||||
const serverHdHomeRun = http.createServer( ( req, resp ) =>
|
||||
{
|
||||
const method = req.method || 'GET';
|
||||
let reqUrl = req.url;
|
||||
if ( reqUrl === '/' )
|
||||
reqUrl = 'hdhomerun.html';
|
||||
|
||||
/*
|
||||
Remove leading forward slash
|
||||
*/
|
||||
|
||||
const loadFile = reqUrl.replace( /^\/+/, '' );
|
||||
|
||||
const handleRequest = async() =>
|
||||
{
|
||||
/*
|
||||
Define the different routes.
|
||||
Place the template system last. Getting TVApp data should take priority.
|
||||
|
||||
subdomainM3U array []
|
||||
loadFile channel?url=https%3A%2F%2Ftvpass.org%2Fchannel%2Fabc-wabc-new-york-ny%2F
|
||||
*/
|
||||
|
||||
Log.debug( `hdjr`, chalk.yellow( `[requests]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Request sent to HDHomeRun` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<request.url>` ), chalk.gray( `${ req.url }` ),
|
||||
chalk.blueBright( `<reqUrl>` ), chalk.gray( `${ reqUrl }` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
|
||||
if ( subdomainHealth.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
|
||||
{
|
||||
const paramSilent = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'silent' );
|
||||
|
||||
// do not show log if query is `uptime`, since uptime runs every 1 second.
|
||||
// do not show logs if query has striggered `silent?=true` in url
|
||||
|
||||
if ( Utils.str2bool( paramSilent ) !== true )
|
||||
{
|
||||
Log.info( `http`, chalk.yellow( `[requests]` ), chalk.white( `ℹ️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Requesting to access health api` ),
|
||||
chalk.blueBright( `<type>` ), chalk.gray( `api/health` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
}
|
||||
|
||||
await serveHealthCheck( req, resp );
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
General Template & .html / .css / .js
|
||||
read the loaded asset file
|
||||
*/
|
||||
|
||||
const tunerInstance = new Tuner();
|
||||
ejs.renderFile( `./${ envWebFolder }/${ loadFile }`,
|
||||
{
|
||||
friendlyName: tunerInstance.FriendlyName,
|
||||
modelNumber: tunerInstance.ModelNumber,
|
||||
firmwareName: tunerInstance.FirmwareName,
|
||||
firmwareVersion: tunerInstance.FirmwareVersion,
|
||||
slotsConnected: tunerInstance.SlotsConnected,
|
||||
slotsMax: tunerInstance.SlotsMax,
|
||||
deviceId: tunerInstance.GetDeviceId( ),
|
||||
hdhrIp: `${ envIpContainer }`,
|
||||
hdhrPort: `${ envHdhrPort }`,
|
||||
|
||||
healthTimer: envHealthTimer,
|
||||
appRelease: envAppRelease,
|
||||
appName: name,
|
||||
appVersion: version,
|
||||
appUrlGithub: repository.url.substr( 0, repository.url.lastIndexOf( '.' ) ),
|
||||
appUrlDiscord: discord.url,
|
||||
appUrlDocs: docs.url,
|
||||
appGitHashShort: envGitSHA1.substring( 0, 9 ),
|
||||
appGitHashLong: envGitSHA1,
|
||||
appUptimeShort: timeAgo.format( Date.now() - Math.round( process.uptime() ) * 1000, 'twitter' ),
|
||||
appUptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ),
|
||||
appUptimeFull: timeAgo.format( Date.now() - process.uptime() * 1000 ),
|
||||
appStartup: Math.round( serverStartup ) / 1000,
|
||||
serverOs: serverOs
|
||||
}, ( err, data ) =>
|
||||
{
|
||||
if ( !err )
|
||||
{
|
||||
Log.debug( `http`, chalk.yellow( `[requests]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Request accepted by ejs` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
|
||||
/*
|
||||
This allows us to serve all files locally: css, js, etc.
|
||||
the file loaded is dependent on what comes to the right of the period.
|
||||
*/
|
||||
|
||||
const fileExt = loadFile.lastIndexOf( '.' );
|
||||
const fileMime = fileExt === -1
|
||||
? 'text/plain'
|
||||
: {
|
||||
'.html' : 'text/html',
|
||||
'.htm' : 'text/html',
|
||||
'.ico' : 'image/x-icon',
|
||||
'.jpg' : 'image/jpeg',
|
||||
'.png' : 'image/png',
|
||||
'.gif' : 'image/gif',
|
||||
'.css' : 'text/css',
|
||||
'.scss' : 'text/x-sass',
|
||||
'.gz' : 'application/gzip',
|
||||
'.js' : 'text/javascript',
|
||||
'.txt' : 'text/plain',
|
||||
'.xml' : 'application/xml',
|
||||
'.json' : 'application/json',
|
||||
'.m3u' : 'text/plain',
|
||||
'.m3u8' : 'text/plain'
|
||||
}[loadFile.substring( fileExt )];
|
||||
|
||||
/*
|
||||
ejs is only for templates; if we want to load an binary data (like images); we must use fs.readFile
|
||||
*/
|
||||
|
||||
if ( fileMime !== 'text/html' )
|
||||
data = fs.readFileSync( `./${ envWebFolder }/${ loadFile }` );
|
||||
|
||||
resp.setHeader( 'Content-type', fileMime );
|
||||
resp.end( data );
|
||||
|
||||
/*
|
||||
silence logs if loading css or js files; otherwise they'll spam console each time you load
|
||||
a page by the client.
|
||||
*/
|
||||
|
||||
if ( fileMime === 'text/html' || fileMime === 'application/xml' || fileMime === 'application/json' )
|
||||
{
|
||||
Log.ok( `http`, chalk.yellow( `[requests]` ), chalk.white( `✅` ),
|
||||
chalk.greenBright( `<msg>` ), chalk.gray( `Request to load file` ),
|
||||
chalk.greenBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.greenBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.greenBright( `<mime>` ), chalk.gray( `${ fileMime }` ),
|
||||
chalk.greenBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.debug( `http`, chalk.yellow( `[requests]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Request to load file` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.blueBright( `<mime>` ), chalk.gray( `${ fileMime }` ),
|
||||
chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.debug( `http`, chalk.yellow( `[requests]` ), chalk.white( `⚙️` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Request rejected by ejs` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<error>` ), chalk.gray( `${ err }` ),
|
||||
chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ),
|
||||
chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
|
||||
|
||||
/*
|
||||
HDHomeRun › Discovery.json
|
||||
*/
|
||||
|
||||
if ( loadFile === 'discovery.json' )
|
||||
{
|
||||
Log.notice( `http`, chalk.yellowBright( `[notice]` ), chalk.white( `📌` ),
|
||||
chalk.yellowBright( `<msg>` ), chalk.gray( `If you are attempting to load TVApp2 using an HDHomeRun tuner, please switch to the` ), chalk.yellowBright( `M3U Tuner` ) );
|
||||
|
||||
const tunerInstance = new Tuner();
|
||||
const hdHomeRun =
|
||||
{
|
||||
FriendlyName: tunerInstance.FriendlyName,
|
||||
ModelNumber: tunerInstance.ModelNumber,
|
||||
FirmwareName: tunerInstance.FirmwareName,
|
||||
FirmwareVersion: tunerInstance.FirmwareVersion,
|
||||
DeviceID: tunerInstance.GetDeviceId(),
|
||||
TunerCount: tunerInstance.SlotsMax,
|
||||
BaseURL: `${ envIpContainer }:${ envHdhrPort }`,
|
||||
LineupURL: `${ envIpContainer }:${ envHdhrPort }/lineup.json`,
|
||||
client: clientIp( req ),
|
||||
message: 'Connected to HDHomeRun server',
|
||||
status: 'healthy',
|
||||
code: 200,
|
||||
uptime: Math.round( process.uptime() ),
|
||||
uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ),
|
||||
uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
resp.writeHead( hdHomeRun.code, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
Log.ok( `http`, chalk.yellow( `[requests]` ), chalk.white( `✅` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Established connection to HDHomeRun` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<friendlyName>` ), chalk.gray( `${ hdHomeRun.FriendlyName }` ),
|
||||
chalk.blueBright( `<modelNumber>` ), chalk.gray( `${ hdHomeRun.ModelNumber }` ),
|
||||
chalk.blueBright( `<deviceID>` ), chalk.gray( `${ hdHomeRun.DeviceID }` ),
|
||||
chalk.blueBright( `<tunerCount>` ), chalk.gray( `${ hdHomeRun.TunerCount }` ),
|
||||
chalk.blueBright( `<urlBase>` ), chalk.whiteBright.bgBlack( ` ${ hdHomeRun.BaseURL } ` ),
|
||||
chalk.blueBright( `<urlLineup>` ), chalk.whiteBright.bgBlack( ` ${ hdHomeRun.LineupURL } ` ) );
|
||||
|
||||
resp.end( JSON.stringify( hdHomeRun ) );
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
HDHomeRun › Lineup.json
|
||||
*/
|
||||
|
||||
if ( loadFile === 'lineup.json' )
|
||||
{
|
||||
const tunerInstance = new Tuner();
|
||||
const hdHomeRun =
|
||||
{
|
||||
FriendlyName: tunerInstance.FriendlyName,
|
||||
ModelNumber: tunerInstance.ModelNumber,
|
||||
FirmwareName: tunerInstance.FirmwareName,
|
||||
FirmwareVersion: tunerInstance.FirmwareVersion,
|
||||
DeviceID: tunerInstance.GetDeviceId(),
|
||||
TunerCount: tunerInstance.SlotsMax,
|
||||
BaseURL: `${ envIpContainer }:${ envHdhrPort }`,
|
||||
LineupURL: `${ envIpContainer }:${ envHdhrPort }/lineup.json`,
|
||||
client: clientIp( req ),
|
||||
status: 'healthy',
|
||||
code: 200,
|
||||
uptime: Math.round( process.uptime() ),
|
||||
uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ),
|
||||
uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ),
|
||||
timestamp: Date.now(),
|
||||
channels: `[]`
|
||||
};
|
||||
|
||||
resp.writeHead( hdHomeRun.code, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
|
||||
Log.ok( `http`, chalk.yellow( `[requests]` ), chalk.white( `✅` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Lineup requested` ),
|
||||
chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ),
|
||||
chalk.blueBright( `<friendlyName>` ), chalk.gray( `${ hdHomeRun.FriendlyName }` ),
|
||||
chalk.blueBright( `<modelNumber>` ), chalk.gray( `${ hdHomeRun.ModelNumber }` ),
|
||||
chalk.blueBright( `<deviceID>` ), chalk.gray( `${ hdHomeRun.DeviceID }` ),
|
||||
chalk.blueBright( `<tunerCount>` ), chalk.gray( `${ hdHomeRun.TunerCount }` ),
|
||||
chalk.blueBright( `<urlBase>` ), chalk.whiteBright.bgBlack( ` ${ hdHomeRun.BaseURL } ` ),
|
||||
chalk.blueBright( `<urlLineup>` ), chalk.whiteBright.bgBlack( ` ${ hdHomeRun.LineupURL } ` ) );
|
||||
|
||||
resp.end( JSON.stringify( hdHomeRun ) );
|
||||
return;
|
||||
}
|
||||
|
||||
const statusCheck =
|
||||
@@ -2458,17 +2764,15 @@ const server = http.createServer( ( req, resp ) =>
|
||||
initialize
|
||||
*/
|
||||
|
||||
await new Storage( envWebFolder, FILE_CFG ).Initialize();
|
||||
await new Tuner( Storage.Get( 'deviceId' ) ).Initialize();
|
||||
await initialize();
|
||||
|
||||
/*
|
||||
check service status that we depend on
|
||||
*/
|
||||
|
||||
serviceCheck( 'TVPass.org', 'https://tvpass.org' );
|
||||
serviceCheck( 'TheTVApp.to', 'https://thetvapp.to' );
|
||||
serviceCheck( 'MoveOnJoy.com', 'http://moveonjoy.com' );
|
||||
serviceCheck( 'Daddylive.dad', 'https://daddylive.dad' );
|
||||
serviceCheck( 'Newkso.ru', 'https://zekonew.newkso.ru/zeko' );
|
||||
hosts.forEach( ( host ) => hostCheck( host.name, host.url ) );
|
||||
|
||||
/*
|
||||
start web server
|
||||
@@ -2486,6 +2790,14 @@ const server = http.createServer( ( req, resp ) =>
|
||||
chalk.blueBright( `<version>` ), chalk.gray( ` ${ version } ` ),
|
||||
chalk.blueBright( `<release>` ), chalk.gray( ` ${ envAppRelease } ` ) );
|
||||
});
|
||||
|
||||
serverHdHomeRun.listen( `${ envHdhrPort }`, envWebIP, () =>
|
||||
{
|
||||
Log.ok( `core`, chalk.yellow( `[initiate]` ), chalk.white( `✅` ),
|
||||
chalk.blueBright( `<msg>` ), chalk.gray( `Starting HDHomeRun server on` ),
|
||||
chalk.blueBright( `<ipPublic>` ), chalk.whiteBright.bgBlack( ` ${ envWebIP }:${ envHdhrPort } ` ),
|
||||
chalk.blueBright( `<ipDocker>` ), chalk.whiteBright.bgBlack( ` ${ envIpContainer }:${ envHdhrPort } ` ) );
|
||||
});
|
||||
})();
|
||||
|
||||
/*
|
||||
|
||||
1296
tvapp2/package-lock.json
generated
1296
tvapp2/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tvapp2",
|
||||
"version": "1.5.4",
|
||||
"version": "1.5.9",
|
||||
"description": "This package allows you to generate M3U playlists and EPG guides from various online IPTV services.",
|
||||
"author": "BinaryNinja",
|
||||
"license": "MIT",
|
||||
@@ -75,8 +75,6 @@
|
||||
"dependencies": {
|
||||
"cron": "^4.3.1",
|
||||
"node-cron": "^4.1.0",
|
||||
"playwright": "^1.52.0",
|
||||
"user-agents": "^1.1.557",
|
||||
"chalk": "^5.4.1",
|
||||
"ejs": "^3.1.10",
|
||||
"moment": "^2.30.1",
|
||||
@@ -88,16 +86,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aetherinox/noxenv": "^1.1.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/uuid": "^11.0.0",
|
||||
"all-contributors-cli": "^6.26.1",
|
||||
"uuid": "^11.1.0",
|
||||
"uuid": "^13.0.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-plugin-chai-friendly": "^1.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-n": "^17.19.0",
|
||||
"eslint-plugin-promise": "^7.2.1",
|
||||
"@stylistic/eslint-plugin": "^4.4.1"
|
||||
"@stylistic/eslint-plugin": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -113,6 +111,7 @@
|
||||
"classes/Semaphore.js",
|
||||
"classes/Storage.js",
|
||||
"classes/Tuner.js",
|
||||
"classes/Utils.js",
|
||||
"www/index.html",
|
||||
"www/hdhomerun.html",
|
||||
"www/favicon.ico",
|
||||
|
||||
1104
tvapp2/www/css/tvapp2.min.css
vendored
1104
tvapp2/www/css/tvapp2.min.css
vendored
File diff suppressed because it is too large
Load Diff
579
tvapp2/www/hdhomerun.html
Normal file
579
tvapp2/www/hdhomerun.html
Normal file
@@ -0,0 +1,579 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<title><%= appName %> | HDHomeRun Tuner | v<%= appVersion %></title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="css/tvapp2.min.css">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<script src='https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js' integrity='sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq' crossorigin='anonymous'></script>
|
||||
<script src=' https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js '></script>
|
||||
<script src='js/tvapp2.min.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<nav class="navbar sticky-top container">
|
||||
<div class="brand">
|
||||
<i data-bs-toggle="tooltip" title="v<%= appVersion %>" class="logo fa-sharp-duotone fa-regular fa-tv" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i>
|
||||
<a target="_blank" data-bs-toggle="tooltip" title="View Github Repository" class="header-name" href="<%= appUrlGithub %>">TVApp2 for Docker</a>
|
||||
</div>
|
||||
<div class="social">
|
||||
<i id="action-health" data-bs-toggle="tooltip" title="Health" class="heart logo health fa-duotone fa-solid fa-heart" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i>
|
||||
<a href="javascript:runResync();"><i id="action-resync" data-bs-toggle="tooltip" title="Resync" class="restart fa-solid fa-rotate" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
|
||||
<a target="_blank" href="<%= appUrlDocs %>"><i data-bs-toggle="tooltip" title="Documentation" class="logo fa-duotone fa-solid fa-book-open-cover" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
|
||||
<a target="_blank" href="<%= appUrlGithub %>"><i data-bs-toggle="tooltip" title="Github" class="logo fa-logos fa-github" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
|
||||
<a target="_blank" href="<%= appUrlDiscord %>"><i data-bs-toggle="tooltip" title="Discord" class="logo fa-logos fa-discord" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Header Notification: description -->
|
||||
<div class="container">
|
||||
<div class="introduction">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="introduction-body">
|
||||
<div class="desc">
|
||||
<div class="about" style="font-size: 13px;"><code>HDHomeRun</code> is a network-attached digital television tuner box, produced by the company SiliconDust USA, Inc. Self-hosted multimedia applications such as Jellyfin allow for you to add IPTV channels either using a <code>M3U8 tuner</code>, or also with the option of specifying a <code>HDHomeRun</code> tuner.</div>
|
||||
<div class="about" style="font-size: 13px;">The TVApp2 app allows you to host your own HDHomeRun tuner, and then utilize this tuner within apps like Jellyfin in order to stream IPTV using the integrated server. Your HDHomeRun tuner settings are provided below:</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header Fontawesome Icons -->
|
||||
<div class="container main-container">
|
||||
<table id="list" class="table table-dark table-striped" style="width:60%; margin: 0 auto;">
|
||||
<thead>
|
||||
<tr class="d-none d-md-table-row">
|
||||
<th class="file cell-file">
|
||||
Property
|
||||
</th>
|
||||
<th class="link cell-link">
|
||||
Value
|
||||
</th>
|
||||
<th class="desc cell-desc">
|
||||
Description
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
FriendlyName
|
||||
</td>
|
||||
<td class="link cell-link"><%= friendlyName %></td>
|
||||
<td class="desc cell-desc">Name of tuner</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
ModelNumber
|
||||
</td>
|
||||
<td class="link cell-link"><%= modelNumber %></td>
|
||||
<td class="desc cell-desc">Virtual tuner model number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
FirmwareName
|
||||
</td>
|
||||
<td class="link cell-link"><%= firmwareName %></td>
|
||||
<td class="desc cell-desc">Firmware name for tuner</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
FirmwareVersion
|
||||
</td>
|
||||
<td class="link cell-link"><%= firmwareVersion %></td>
|
||||
<td class="desc cell-desc">Firmware version running on tuner</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
DeviceID
|
||||
</td>
|
||||
<td class="link cell-link"><%= deviceId %></td>
|
||||
<td class="desc cell-desc">Tuner device id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
TunerCount
|
||||
</td>
|
||||
<td class="link cell-link"><%= slotsConnected %> / <%= slotsMax %></td>
|
||||
<td class="desc cell-desc">Number of connection slots to view IPTV</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
BaseURL
|
||||
</td>
|
||||
<td class="link cell-link"><a href="https://<%= hdhrIp %>:<%= hdhrPort %>" id="m3u-link" target="_blank"><%= hdhrIp %>:<%= hdhrPort %></a></td>
|
||||
<td class="desc cell-desc">Base URL where HDHomeRun is hosted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
LineupURL
|
||||
</td>
|
||||
<td class="link cell-link"><a href="https://<%= hdhrIp %>:<%= hdhrPort %>/lineup.json" id="m3u-link" target="_blank"><%= hdhrIp %>:<%= hdhrPort %>/lineup.json</a></td>
|
||||
<td class="desc cell-desc">URL to IPTV channel & guide lineups</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="file cell-file" style="color: #919191;font-weight: 400;">
|
||||
Uptime
|
||||
</td>
|
||||
<td class="link cell-link"><%= appUptimeFull %></td>
|
||||
<td class="desc cell-desc">Duration that tuner has been online</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container notifications" style="padding-bottom:20px;">
|
||||
<div id="ntfy-restart" class="ntfy-parent indicator-success sticky-bottom"></div>
|
||||
<div id="ntfy-firewall" class="ntfy-parent indicator-warning sticky-bottom"></div>
|
||||
<div id="ntfy-localhost" class="ntfy-parent indicator-danger sticky-bottom"></div>
|
||||
</div>
|
||||
|
||||
<div class="sub">
|
||||
<div class="container">
|
||||
<div class="col text-center text-muted text-small text-nowrap">
|
||||
<small>Developed by BinaryNinja - <a data-bs-toggle="tooltip" title="v<%= appVersion %> <%= appRelease %> (<%= appGitHashShort %>)" href="<%= appUrlGithub %>"><%= appName %> (<%= appRelease %>)</a> v<%= appVersion %> <a target="_blank" data-bs-toggle="tooltip" title="View Github commit" href="<%= appUrlGithub %>/commit/<%= appGitHashLong %>"><%= appGitHashShort %></a></small><br />
|
||||
<span class="footer-sub"><small>Uptime <a id="uptime" href="" data-bs-toggle="tooltip" title="<%= appUptimeLong %>"> <%= appUptimeShort %> </a> | Startup <a id="startup" data-bs-toggle="tooltip" title="Startup time" href=""><%= appStartup %>s</a> | OS <a id="os" href="" data-bs-toggle="tooltip" title="Server operating system" href=""><%= serverOs %></a></small></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Toast Notifications -->
|
||||
<!-- <button type="button" class="btn btn-primary" id="btnTestToasts">Show toast</button> -->
|
||||
<div style="z-index: 9999;" class="toast position-fixed bottom-0 end-0 p-8 m-3" id="tvapp2Toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="true" data-bs-delay="10000">
|
||||
<div class="toast-body">
|
||||
<div class="d-flex gap-4">
|
||||
<span><i class="fa-solid fa-circle-check fa-lg icon-success"></i></span>
|
||||
<div class="d-flex flex-column flex-grow-1 gap-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<span id="toast-title" class="fw-semibold">Toast Title</span>
|
||||
<button type="button" class="btn-close btn-close-sm ms-auto" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<span id="toast-message">Dismiss in 6 seconds</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="modalTvapp2" tabindex="-1" data-bs-backdrop="static" aria-labelledby="modalTvapp2Label" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTvapp2Label">Modal title</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
...
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="btn-secondary" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" id="btn-primary" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/*
|
||||
this is test code. enable the "Show Toast" button and then uncomment this code.
|
||||
|
||||
document.getElementById("btnTestToasts").onclick = function()
|
||||
{
|
||||
var toastElList = [].slice.call(document.querySelectorAll('.toast'))
|
||||
var toastList = toastElList.map(function(toastEl)
|
||||
{
|
||||
return new bootstrap.Toast(toastEl)
|
||||
});
|
||||
|
||||
toastList.forEach(toast => toast.show());
|
||||
console.log(toastList);
|
||||
};
|
||||
*/
|
||||
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||
})
|
||||
|
||||
const urlBase = window.location.origin;
|
||||
const urlM3U = urlBase + '/playlist';
|
||||
const urlXML = urlBase + '/epg';
|
||||
const urlGZP = urlBase + '/gzip';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
/*
|
||||
Document Ready
|
||||
*/
|
||||
|
||||
$(function(){
|
||||
$("[data-bs-toggle=tooltip]").tooltip({ placement: 'bottom'});
|
||||
});
|
||||
|
||||
/*
|
||||
Action > DOM Status
|
||||
*/
|
||||
|
||||
document.addEventListener("DOMContentReady", function() {
|
||||
$("#tvapp2Toast").toast();
|
||||
});
|
||||
|
||||
/*
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
$('#tvapp2Toast').toast("show");
|
||||
});
|
||||
*/
|
||||
|
||||
/*
|
||||
Notify > Localhost
|
||||
*/
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', function()
|
||||
{
|
||||
const host = window.location.hostname;
|
||||
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
|
||||
if (host === 'localhost' || host === '127.0.0.1')
|
||||
{
|
||||
|
||||
const msg = "<div class='ntfy-child'><span class='danger'>Danger</span> \
|
||||
<span class='msg'> \
|
||||
If accessing this page via 127.0.0.1 / localhost, proxying will not work on other devices. Load this page using \
|
||||
your computer's IP address (e.g., 192.168.x.x) and port to access the playlist from other devices on your network. \
|
||||
<br> \
|
||||
Learn how to locate your IP address on <a href='https://youtube.com/watch?v=UAhDHXN2c6E' target = '_blank' > Windows</a> \
|
||||
or <a href='https://youtube.com/watch?v=gaIYP4TZfHI' target = '_blank' > Linux</a>.\
|
||||
</span></div>";
|
||||
|
||||
document.getElementById( 'ntfy-localhost' ).innerHTML = msg;
|
||||
document.getElementById( 'ntfy-localhost' ).style.display = 'block';
|
||||
} else {
|
||||
document.getElementById( 'ntfy-localhost' ).style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
Notify > Firewall
|
||||
*/
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', function()
|
||||
{
|
||||
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
|
||||
const msg = "<div class='ntfy-child'><span class='warning'>Warning</span> \
|
||||
<span class='msg'> \
|
||||
Port <strong> " + port + " </strong> must be open and allowed through your \
|
||||
<a href='https://youtu.be/zOZWlTplrcA?si=nGXrHKU4sAQsy18e&t=18 target='_blank'>Windows</a> \
|
||||
or \
|
||||
<a href='https://youtu.be/7c_V_3nWWbA?si=Hkd_II9myn-AkNnS&t=12' target='_blank'>Linux</a> \
|
||||
OS firewall settings. This action enables devices such as Firestick or Android to connect \
|
||||
to the server and request the playlist through the proxy. \
|
||||
</span></div>";
|
||||
|
||||
document.getElementById( 'ntfy-firewall' ).innerHTML = msg;
|
||||
document.getElementById( 'ntfy-firewall' ).style.display = 'block';
|
||||
});
|
||||
|
||||
/*
|
||||
Notify > Restart / Resync
|
||||
*/
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', function()
|
||||
{
|
||||
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
|
||||
const msg = "<div class='ntfy-child'><span class='success'>Success</span> \
|
||||
<span class='msg'> \
|
||||
Your IPTV m3u channels and xml guide data has been successfully re-synced. \
|
||||
Please refresh this window to see new data \
|
||||
</span></div>";
|
||||
|
||||
document.getElementById( 'ntfy-restart' ).innerHTML = msg;
|
||||
document.getElementById( 'ntfy-restart' ).style.display = 'none';
|
||||
});
|
||||
|
||||
/*
|
||||
Set initial health check sync time
|
||||
|
||||
first health check runs after 10 seconds
|
||||
all future health checks run after <%= healthTimer %>
|
||||
*/
|
||||
|
||||
let timerDelayMS = 10000;
|
||||
let timerStartMS = Date.now(); // returns milliseconds
|
||||
const timerHealthRun = '<%= healthTimer %>'; // time in milliseconds until health check ran AFTER initial run
|
||||
const timerUptime = 1000;
|
||||
|
||||
/*
|
||||
Action > Healthcheck
|
||||
*/
|
||||
|
||||
function runHealthCheck()
|
||||
{
|
||||
const toastTypeClass = [];
|
||||
toastTypeClass[ 'DEFAULT' ] = 'text-bg-primary';
|
||||
toastTypeClass[ 'UNHEALTHY' ] = 'text-bg-warning';
|
||||
toastTypeClass[ 'HEALTHY' ] = 'text-bg-success';
|
||||
toastTypeClass[ 'ERROR' ] = 'text-bg-danger';
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
url: 'api/health',
|
||||
type: 'GET',
|
||||
data: {
|
||||
query: 'healthcheck',
|
||||
silent: false
|
||||
},
|
||||
beforeSend: function( data )
|
||||
{
|
||||
console.log( 'Sending health check ...' )
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
const status = data.message;
|
||||
const code = data.code;
|
||||
if ( status )
|
||||
{
|
||||
const toastClass = toastTypeClass[status.toUpperCase()];
|
||||
const toastElm = document.getElementById('tvapp2Toast');
|
||||
toastElm.classList.add(toastClass);
|
||||
|
||||
$('.toast #toast-title').html(`<%= appName %> is ${ status }`);
|
||||
$('.toast #toast-message').html(`Health check returned ${ status } (${ code })`);
|
||||
$('#tvapp2Toast').toast('show');
|
||||
|
||||
const elementsList = document.querySelectorAll( '#ntfy-firewall, #ntfy-localhost, #ntfy-restart' );
|
||||
const elementsArray = [...elementsList];
|
||||
|
||||
elementsArray.forEach(element =>
|
||||
{
|
||||
element.style.transition = '1s';
|
||||
element.style.opacity = '0';
|
||||
element.style.visibility = 'hidden';
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
error: function( data )
|
||||
{
|
||||
const toastClass = toastTypeClass['ERROR'];
|
||||
const toastElm = document.getElementById('tvapp2Toast');
|
||||
toastElm.classList.add(toastClass);
|
||||
|
||||
$('.toast #toast-title').html(`Could not connect to health check api`);
|
||||
$('.toast #toast-message').html(`Failed to communicate with health check api. Try restarting the docker container to restore connection.`);
|
||||
$('#tvapp2Toast').toast('show');
|
||||
|
||||
}
|
||||
}).always(function()
|
||||
{
|
||||
timerDelayMS = parseInt(timerHealthRun);
|
||||
timerStartMS = Date.now();
|
||||
|
||||
setTimeout(function()
|
||||
{
|
||||
runHealthCheck();
|
||||
}, parseInt(timerHealthRun));
|
||||
}).responseText;
|
||||
}
|
||||
|
||||
function runUptime()
|
||||
{
|
||||
const toastTypeClass = [];
|
||||
toastTypeClass[ 'DEFAULT' ] = 'text-bg-primary';
|
||||
toastTypeClass[ 'UNHEALTHY' ] = 'text-bg-warning';
|
||||
toastTypeClass[ 'HEALTHY' ] = 'text-bg-success';
|
||||
toastTypeClass[ 'ERROR' ] = 'text-bg-danger';
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
url: 'api/health',
|
||||
type: 'GET',
|
||||
data: {
|
||||
query: 'uptime',
|
||||
silent: true
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
const status = data.message;
|
||||
const code = data.code;
|
||||
const uptimeShort = data.uptimeShort;
|
||||
const uptimeLong = data.uptimeLong;
|
||||
if ( status )
|
||||
{
|
||||
$('a#uptime').text(`${ uptimeShort }`);
|
||||
|
||||
const tooltip = bootstrap.Tooltip.getInstance('#uptime') // Returns a Bootstrap tooltip instance
|
||||
tooltip.setContent( { '.tooltip-inner': `HDHomeRun server started ${ uptimeLong }` } )
|
||||
}
|
||||
},
|
||||
error: function( data )
|
||||
{
|
||||
const toastClass = toastTypeClass['ERROR'];
|
||||
const toastElm = document.getElementById('tvapp2Toast');
|
||||
toastElm.classList.add(toastClass);
|
||||
|
||||
$('.toast #toast-title').html(`Could not get uptime from api`);
|
||||
$('.toast #toast-message').html(`Failed to communicate with the api. Try restarting the docker container to restore connection.`);
|
||||
$('#tvapp2Toast').toast('show');
|
||||
}
|
||||
}).always(function()
|
||||
{
|
||||
setTimeout(function()
|
||||
{
|
||||
runUptime();
|
||||
}, parseInt(timerUptime));
|
||||
}).responseText;
|
||||
}
|
||||
|
||||
/*
|
||||
Action > Do Resync
|
||||
*/
|
||||
|
||||
function runResync()
|
||||
{
|
||||
$.ajax(
|
||||
{
|
||||
url: 'api/restart',
|
||||
type: 'GET',
|
||||
data: {
|
||||
query: 'sync',
|
||||
silent: false
|
||||
},
|
||||
beforeSend: function( data )
|
||||
{
|
||||
const dimmer = document.createElement('div');
|
||||
dimmer.setAttribute('id', 'dimmer');
|
||||
dimmer.style.visibility = 'visible';
|
||||
dimmer.classList.add('dimmer-in');
|
||||
document.getElementsByTagName('body')[0].appendChild(dimmer);
|
||||
document.getElementById('ntfy-firewall').style.display = 'none';
|
||||
document.getElementById('ntfy-localhost').style.display = 'none';
|
||||
document.getElementById('ntfy-restart').style.display = 'none';
|
||||
|
||||
const iconResync = document.getElementsByClassName('fa-rotate');
|
||||
iconResync[0].classList.remove('restart');
|
||||
iconResync[0].classList.add('spin');
|
||||
|
||||
$('.modal-content .modal-body').html('<small>The M3U and EPG data will now be re-downloaded and synced with your TVApp2 container. Afterward, this page will be refreshed automatically.</small><br /><br /><small>Please wait...</small>')
|
||||
$('.modal-content .modal-title').html('Resyncing Data')
|
||||
$('#modalTvapp2').modal('show');
|
||||
|
||||
const modalBtnPrimary = document.querySelector('#btn-primary');
|
||||
modalBtnPrimary.style.display = 'none';
|
||||
modalBtnPrimary.style.visibility= 'hidden';
|
||||
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
|
||||
/*
|
||||
On successful restart, wait 1 second, remove dimmer, reload page in 5 seconds
|
||||
*/
|
||||
|
||||
setTimeout( () =>
|
||||
{
|
||||
document.getElementById('ntfy-restart').style.display = 'block'
|
||||
const dimmer = document.getElementById('dimmer');
|
||||
dimmer.classList.remove('dimmer-in');
|
||||
dimmer.classList.add('dimmer-out');
|
||||
dimmer.remove();
|
||||
|
||||
setTimeout( function()
|
||||
{
|
||||
const iconResync = document.getElementsByClassName('fa-rotate'); // resync favicon
|
||||
iconResync[0].classList.remove('spin'); // stop spinning
|
||||
iconResync[0].classList.add('restart'); // normal spinner class
|
||||
document.location.reload() // reload page
|
||||
}, 5000 ); // how long until refresh page
|
||||
}, 1000 ); // how long until dimmer is removed / reload page activated (also on delay)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Health check > Show time remaining as tooltip
|
||||
*/
|
||||
|
||||
function runTooltipCountdown( )
|
||||
{
|
||||
let timerHours, timerMins, timerRemainsLS;
|
||||
|
||||
function twoDigits( n )
|
||||
{
|
||||
return (n <= 9 ? "0" + n : n);
|
||||
}
|
||||
|
||||
/*
|
||||
Update Tooltip Countdown
|
||||
|
||||
MS = milliseconds
|
||||
LS = long string (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time))
|
||||
*/
|
||||
|
||||
function updateTooltipCountdown()
|
||||
{
|
||||
const timerElapsedMS = Date.now() - timerStartMS; // ( 2091 )
|
||||
const timerRemainsMS = timerDelayMS - timerElapsedMS; // ( 7909 ) divide by 1000 for seconds
|
||||
|
||||
timerRemainsLS = new Date( timerRemainsMS ); // (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time))
|
||||
timerHours = timerRemainsLS.getUTCHours(); // ( 0 )
|
||||
timerMins = timerRemainsLS.getUTCMinutes(); // ( 9 )
|
||||
const timeLeft = (timerHours ? timerHours + ':' + twoDigits( timerMins ) : timerMins) + ':' + twoDigits( timerRemainsLS.getUTCSeconds() );
|
||||
|
||||
jQuery(function($)
|
||||
{
|
||||
const tooltip = bootstrap.Tooltip.getInstance('#action-health') // Returns a Bootstrap tooltip instance
|
||||
tooltip.setContent({ '.tooltip-inner': `Health check in ${ timeLeft }` })
|
||||
});
|
||||
|
||||
const Heart = document.getElementsByClassName('fa-heart');
|
||||
Heart[0].style.color = '#FFF';
|
||||
|
||||
setTimeout( function()
|
||||
{
|
||||
const Heart = document.getElementsByClassName('fa-heart');
|
||||
Heart[0].style.color = '#FFF';
|
||||
|
||||
setTimeout( function()
|
||||
{
|
||||
Heart[0].style.color = '#FF6593';
|
||||
}, timerRemainsLS.getUTCMilliseconds() + 100 );
|
||||
|
||||
}, timerRemainsLS.getUTCMilliseconds() + 500 );
|
||||
|
||||
|
||||
setTimeout( function()
|
||||
{
|
||||
updateTooltipCountdown();
|
||||
}, timerRemainsLS.getUTCMilliseconds() + 500 );
|
||||
}
|
||||
|
||||
updateTooltipCountdown();
|
||||
}
|
||||
|
||||
/*
|
||||
Action > Healthcheck > Initialize
|
||||
*/
|
||||
|
||||
setTimeout( function() { runHealthCheck(); }, timerDelayMS );
|
||||
setTimeout( function() { runUptime(); }, 1000 );
|
||||
|
||||
/*
|
||||
Action > Tooltip Resync Timers
|
||||
*/
|
||||
|
||||
runTooltipCountdown( );
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,11 +16,11 @@
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<nav class="navbar sticky-top container">
|
||||
<div class="navbar-brand">
|
||||
<div class="brand">
|
||||
<i data-bs-toggle="tooltip" title="v<%= appVersion %>" class="logo fa-sharp-duotone fa-regular fa-tv" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i>
|
||||
<a target="_blank" data-bs-toggle="tooltip" title="View Github Repository" class="header-name" href="<%= appUrlGithub %>">TVApp2 for Docker</a>
|
||||
</div>
|
||||
<div class="navbar-social">
|
||||
<div class="social">
|
||||
<i id="action-health" data-bs-toggle="tooltip" title="Health" class="heart logo health fa-duotone fa-solid fa-heart" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i>
|
||||
<a href="javascript:runResync();"><i id="action-resync" data-bs-toggle="tooltip" title="Resync" class="restart fa-solid fa-rotate" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
|
||||
<a target="_blank" href="<%= appUrlDocs %>"><i data-bs-toggle="tooltip" title="Documentation" class="logo fa-duotone fa-solid fa-book-open-cover" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
|
||||
@@ -32,10 +32,27 @@
|
||||
|
||||
<!-- Header Notification: description -->
|
||||
<div class="container">
|
||||
<div class="container header-container">
|
||||
<div class="introduction">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="about">This page displays your most recent copies of the <code><%= fileM3U %></code> playlist and <code><%= fileXML %></code> EPG guide data. Right-click each file, select <span class="text-accent">Copy Link</span> and paste the URLs within an IPTV app such as Jellyfin. The <code><%= fileXML %></code> and <code><%= fileGZP %></code> have identical guide data, however the <code><%= fileGZP %></code> is compressed and will import into your IPTV application much faster.</div>
|
||||
<div class="introduction-body">
|
||||
<div class="desc">
|
||||
This page displays your most recent copies of the <code><%= fileM3U %></code> playlist and <code><%= fileXML %></code>
|
||||
EPG guide data. Right-click each file, select <span class="accent">Copy Link</span> and paste the URLs within an IPTV
|
||||
app such as Jellyfin. The <code><%= fileXML %></code> and <code><%= fileGZP %></code> have identical guide data,
|
||||
however the <code><%= fileGZP %></code> is compressed and will import into your IPTV application much faster.
|
||||
|
||||
<br />
|
||||
|
||||
<div class="badges">
|
||||
<img src="https://img.shields.io/github/v/tag/TheBinaryNinja/tvapp2?logo=GitHub&label=Version&color=ba5225">
|
||||
<img src="https://img.shields.io/github/downloads/TheBinaryNinja/tvapp2/total?logo=github&logoColor=FFFFFF&label=Downloads&color=376892">
|
||||
<img src="https://img.shields.io/github/repo-size/TheBinaryNinja/tvapp2?logo=github&label=Size&color=59702a">
|
||||
<img src="https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Fgithub.com%2Faetherinox%2Fbackage%2Fraw%2Findex%2FTheBinaryNinja%2Ftvapp2%2Ftvapp2.xml&query=%2Fxml%2Fdownloads&label=Pulls&logo=github&color=de1f5e">
|
||||
<img src="https://img.shields.io/github/last-commit/TheBinaryNinja/tvapp2?logo=conventionalcommits&logoColor=FFFFFF&label=Last%20Commit&color=313131">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,12 +133,13 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container" style="padding-bottom:20px;">
|
||||
<div id="ntfy-restart" class="ntfy ntfy-success sticky-bottom"></div>
|
||||
<div id="ntfy-firewall" class="ntfy ntfy-warning sticky-bottom"></div>
|
||||
<div id="ntfy-localhost" class="ntfy ntfy-danger sticky-bottom"></div>
|
||||
<div class="container notifications" style="padding-bottom:20px;">
|
||||
<div id="ntfy-restart" class="ntfy-parent indicator-success sticky-bottom"></div>
|
||||
<div id="ntfy-firewall" class="ntfy-parent indicator-warning sticky-bottom"></div>
|
||||
<div id="ntfy-localhost" class="ntfy-parent indicator-danger sticky-bottom"></div>
|
||||
</div>
|
||||
<div class="footer-inner">
|
||||
|
||||
<div class="sub">
|
||||
<div class="container">
|
||||
<div class="col text-center text-muted text-small text-nowrap">
|
||||
<small>Developed by BinaryNinja - <a data-bs-toggle="tooltip" title="v<%= appVersion %> <%= appRelease %> (<%= appGitHashShort %>)" href="<%= appUrlGithub %>"><%= appName %> (<%= appRelease %>)</a> v<%= appVersion %> <a target="_blank" data-bs-toggle="tooltip" title="View Github commit" href="<%= appUrlGithub %>/commit/<%= appGitHashLong %>"><%= appGitHashShort %></a></small><br />
|
||||
@@ -218,6 +236,53 @@
|
||||
|
||||
<script>
|
||||
|
||||
/*
|
||||
this is test code. enable the "Show Toast" button and then uncomment this code.
|
||||
|
||||
document.getElementById("btnTestToasts").onclick = function()
|
||||
{
|
||||
var toastElList = [].slice.call(document.querySelectorAll('.toast'))
|
||||
var toastList = toastElList.map(function(toastEl)
|
||||
{
|
||||
return new bootstrap.Toast(toastEl)
|
||||
});
|
||||
|
||||
toastList.forEach(toast => toast.show());
|
||||
console.log(toastList);
|
||||
};
|
||||
*/
|
||||
|
||||
var tooltipList = [].slice.call( document.querySelectorAll( '[data-bs-toggle="tooltip"]' ) )
|
||||
var tooltipList = tooltipList.map( function ( el )
|
||||
{
|
||||
return new bootstrap.Tooltip(el,
|
||||
{
|
||||
placement: "bottom",
|
||||
trigger: "hover",
|
||||
html: true
|
||||
});
|
||||
})
|
||||
|
||||
/*
|
||||
Helper > Get Multiple Elements by ID
|
||||
*/
|
||||
|
||||
function getElementsById( ids )
|
||||
{
|
||||
const idList = ids.split(" ");
|
||||
let results = [], item;
|
||||
for ( let i = 0; i < idList.length; i++ )
|
||||
{
|
||||
item = document.getElementById( idList[ i ] );
|
||||
if (item)
|
||||
{
|
||||
results.push( item );
|
||||
}
|
||||
}
|
||||
|
||||
return( results );
|
||||
}
|
||||
|
||||
/*
|
||||
Document Ready
|
||||
*/
|
||||
@@ -244,21 +309,26 @@
|
||||
Notify > Localhost
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener( 'DOMContentLoaded', function()
|
||||
{
|
||||
const host = window.location.hostname;
|
||||
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
|
||||
if (host === 'localhost' || host === '127.0.0.1')
|
||||
{
|
||||
const msg = "<p><span class='warning'>Warning</span> If you are accessing this page via 127.0.0.1 or localhost, proxying will not work on other devices.Please load \
|
||||
this page using your computer's IP address (e.g., 192.168.x.x) and port in order to access the playlist from other devices on your network.</p> \
|
||||
<br> \
|
||||
<p> Learn how to locate your IP address on <a href='https://youtube.com/watch?v=UAhDHXN2c6E' target = '_blank' > Windows</a> or \
|
||||
<a href='https://youtube.com/watch?v=gaIYP4TZfHI' target = '_blank' > Linux</a>.</p>";
|
||||
|
||||
document.getElementById('ntfy-localhost').innerHTML = msg;
|
||||
document.getElementById('ntfy-localhost').style.display = 'block';
|
||||
const msg = "<div class='ntfy-child'><span class='danger'>Danger</span> \
|
||||
<span class='msg'> \
|
||||
If accessing this page via 127.0.0.1 / localhost, proxying will not work on other devices. Load this page using \
|
||||
your computer's IP address (e.g., 192.168.x.x) and port to access the playlist from other devices on your network. \
|
||||
<br> \
|
||||
Learn how to locate your IP address on <a href='https://youtube.com/watch?v=UAhDHXN2c6E' target = '_blank' > Windows</a> \
|
||||
or <a href='https://youtube.com/watch?v=gaIYP4TZfHI' target = '_blank' > Linux</a>.\
|
||||
</span></div>";
|
||||
|
||||
document.getElementById( 'ntfy-localhost' ).innerHTML = msg;
|
||||
document.getElementById( 'ntfy-localhost' ).style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('ntfy-localhost').style.display = 'none';
|
||||
document.getElementById( 'ntfy-localhost' ).style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -266,27 +336,38 @@
|
||||
Notify > Firewall
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener( 'DOMContentLoaded', function()
|
||||
{
|
||||
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
|
||||
const msg = "<p><span class='notice'>Notice</span> Port <strong> " + port + " </strong> must be open and allowed through your <a href='https://youtu.be/zOZWlTplrcA?si=nGXrHKU4sAQsy18e&t=18 target='_blank'>Windows</a> \
|
||||
or <a href='https://youtu.be/7c_V_3nWWbA?si=Hkd_II9myn-AkNnS&t=12' target='_blank'>Linux</a> OS firewall settings \
|
||||
This action enables devices such as Firestick or Android to connect to the server and request the playlist through the proxy.</p>";
|
||||
const msg = "<div class='ntfy-child'><span class='warning'>Warning</span> \
|
||||
<span class='msg'> \
|
||||
Port <strong> " + port + " </strong> must be open and allowed through your \
|
||||
<a href='https://youtu.be/zOZWlTplrcA?si=nGXrHKU4sAQsy18e&t=18 target='_blank'>Windows</a> \
|
||||
or \
|
||||
<a href='https://youtu.be/7c_V_3nWWbA?si=Hkd_II9myn-AkNnS&t=12' target='_blank'>Linux</a> \
|
||||
OS firewall settings. This action enables devices such as Firestick or Android to connect \
|
||||
to the server and request the playlist through the proxy. \
|
||||
</span></div>";
|
||||
|
||||
document.getElementById('ntfy-firewall').innerHTML = msg;
|
||||
document.getElementById('ntfy-firewall').style.display = 'block';
|
||||
document.getElementById( 'ntfy-firewall' ).innerHTML = msg;
|
||||
document.getElementById( 'ntfy-firewall' ).style.display = 'block';
|
||||
});
|
||||
|
||||
/*
|
||||
Notify > Restart / Resync
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener( 'DOMContentLoaded', function()
|
||||
{
|
||||
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
|
||||
const msg = "<p><span class='success'>Success</span> Your IPTV m3u channels and xml guide data has been successfully re-synced. \
|
||||
Please refresh this window to see new data</p>";
|
||||
const msg = "<div class='ntfy-child'><span class='success'>Success</span> \
|
||||
<span class='msg'> \
|
||||
Your IPTV m3u channels and xml guide data has been successfully re-synced. \
|
||||
Please refresh this window to see new data \
|
||||
</span></div>";
|
||||
|
||||
document.getElementById('ntfy-restart').innerHTML = msg;
|
||||
document.getElementById('ntfy-restart').style.display = 'none';
|
||||
document.getElementById( 'ntfy-restart' ).innerHTML = msg;
|
||||
document.getElementById( 'ntfy-restart' ).style.display = 'none';
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -318,11 +399,12 @@
|
||||
url: 'api/health',
|
||||
type: 'GET',
|
||||
data: {
|
||||
query: 'healthcheck'
|
||||
query: 'healthcheck',
|
||||
silent: false
|
||||
},
|
||||
beforeSend: function( data )
|
||||
{
|
||||
console.log('Sending health check ...')
|
||||
console.log( 'Sending health check ...' )
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
@@ -337,6 +419,16 @@
|
||||
$('.toast #toast-title').html(`<%= appName %> is ${ status }`);
|
||||
$('.toast #toast-message').html(`Health check returned ${ status } (${ code })`);
|
||||
$('#tvapp2Toast').toast('show');
|
||||
|
||||
const elementsList = document.querySelectorAll( '#ntfy-firewall, #ntfy-localhost, #ntfy-restart' );
|
||||
const elementsArray = [...elementsList];
|
||||
|
||||
elementsArray.forEach(element =>
|
||||
{
|
||||
element.style.transition = '1s';
|
||||
element.style.opacity = '0';
|
||||
element.style.visibility = 'hidden';
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
@@ -376,7 +468,8 @@
|
||||
url: 'api/health',
|
||||
type: 'GET',
|
||||
data: {
|
||||
query: 'uptime'
|
||||
query: 'uptime',
|
||||
silent: true
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
@@ -422,7 +515,8 @@
|
||||
url: 'api/restart',
|
||||
type: 'GET',
|
||||
data: {
|
||||
query: 'sync'
|
||||
query: 'sync',
|
||||
silent: false
|
||||
},
|
||||
beforeSend: function( data )
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user