From c01757863119f4ea047fd185af3a0dabda37e050 Mon Sep 17 00:00:00 2001 From: Aetherinox Date: Tue, 8 Apr 2025 13:12:59 -0700 Subject: [PATCH] feat: add healthcheck api endpoint --- root/etc/services.d/tvapp2/run | 10 +++-- tvapp2/index.js | 76 ++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/root/etc/services.d/tvapp2/run b/root/etc/services.d/tvapp2/run index 0a171e41..8c8c5818 100755 --- a/root/etc/services.d/tvapp2/run +++ b/root/etc/services.d/tvapp2/run @@ -4,12 +4,14 @@ # Store env variables in s6 # # -IP_GATEWAY=$(/sbin/ip route|awk '/default/ { print $3 }') -IP_CONTAINER=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') +ip_gateway=$(/sbin/ip route|awk '/default/ { print $3 }') +ip_container=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') -printf "$IP_GATEWAY" > /var/run/s6/container_environment/IP_GATEWAY -printf "$IP_CONTAINER" > /var/run/s6/container_environment/IP_CONTAINER +printf "$ip_gateway" > /var/run/s6/container_environment/IP_GATEWAY +printf "$ip_container" > /var/run/s6/container_environment/IP_CONTAINER +export IP_GATEWAY=$ip_gateway +export IP_GATEWAY=$ip_container # # # install and startup for tvapp2 diff --git a/tvapp2/index.js b/tvapp2/index.js index 7c6717ff..9242d1cf 100755 --- a/tvapp2/index.js +++ b/tvapp2/index.js @@ -110,6 +110,18 @@ const subdomainM3U = [ 'playlist', 'm3u', 'm3u8' ]; const subdomainEPG = [ 'guide', 'epg', 'xml' ]; const subdomainKey = [ 'key', 'keys' ]; const subdomainChan = [ 'channels', 'channel' ]; +const subdomainHealth = [ 'status', 'health' ]; + +/* + Container Information + + these environment variables are defined from the s6-overlay layer of the docker image +*/ + +const fileIpGateway = '/var/run/s6/container_environment/IP_GATEWAY' +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`; /* Define > Logs @@ -805,6 +817,62 @@ async function serveM3UPlaylist( req, res ) } } +async function serveHealthCheck( req, res ) +{ + await semaphore.acquire(); + try + { + const urlParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'url' ); + if ( !urlParam ) + { + Log.debug( `No parameters passed to healthcheck`, chalk.white( `→` ), chalk.grey( `URL` ) ); + } + + const healthcheck = + { + ip: envIpContainer, + gateway: envIpGateway, + uptime: process.uptime(), + message: 'Healthy', + timestamp: Date.now() + }; + + res.writeHead( 200, { + 'Content-Type': 'application/json' + }); + + res.end( JSON.stringify(healthcheck) ); + return; + } + catch ( err ) + { + Log.error( `Error getting healthcheck:`, chalk.white( `→` ), chalk.grey( `${ err.message }` ) ); + + if ( !res.headersSent ) + { + const healthcheck = + { + ip: envIpContainer, + gateway: envIpGateway, + uptime: process.uptime(), + message: 'Unhealthy', + status: 0, + timestamp: Date.now() + }; + + res.writeHead( 503, { + 'Content-Type': 'application/json' + }); + + res.end( JSON.stringify(healthcheck) ); + } + } + finally + { + semaphore.release(); + } +} + /* Rewrites the URLs */ @@ -1118,6 +1186,14 @@ const server = http.createServer( ( request, response ) => return; } + if ( subdomainHealth.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' ) + { + Log.info( `Received healthcheck`, chalk.white( `→` ), chalk.grey( `${ loadFile }` ) ); + + await serveHealthCheck( request, response ); + return; + } + /* General Template & .html / .css / .js read the loaded asset file