diff --git a/.gitignore b/.gitignore index cdf2ad6..afe8938 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /data.json -/public \ No newline at end of file +/public +/config \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4c3728c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +FROM alpine as builder + +COPY . /src +RUN apk add git && \ + echo "**** adding version ****" && \ + cd /src && \ + export VERSION=$(git describe --exact-match --tags $(git log -n1 --pretty='%h') || echo "dev - $(git rev-parse --short HEAD)") && \ + printf " ./version.php + +FROM ghcr.io/linuxserver/baseimage-alpine-nginx:2021.11.04 + +ARG gitcommithash +LABEL maintainer="Dorian Zedler " + +ENV MUSL_LOCPATH="/usr/share/i18n/locales/musl" +RUN apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/v3.13/community musl-locales musl-locales-lang \ + && cd "$MUSL_LOCPATH" \ + && for i in *.UTF-8; do \ + i1=${i%%.UTF-8}; \ + i2=${i1/_/-}; \ + i3=${i/_/-}; \ + cp -a "$i" "$i1"; \ + cp -a "$i" "$i2"; \ + cp -a "$i" "$i3"; \ + done + +RUN \ + echo "**** install packages ****" && \ + apk update && \ + apk add --no-cache \ + curl \ + mysql-client \ + php7-ctype \ + php7-curl \ + php7-dom \ + php7-gd \ + php7-ldap \ + php7-mbstring \ + php7-memcached \ + php7-mysqlnd \ + php7-openssl \ + php7-pdo_mysql \ + php7-phar \ + php7-simplexml \ + php7-tokenizer \ + php7-intl \ + tar && \ + echo "**** configure php-fpm ****" && \ + sed -i 's/;clear_env = no/clear_env = no/g' /etc/php7/php-fpm.d/www.conf && \ + echo "catch_workers_output = yes" >> /etc/php7/php-fpm.d/www.conf && \ + echo "env[PATH] = /usr/local/bin:/usr/bin:/bin" >> /etc/php7/php-fpm.conf + +RUN \ + echo "**** prepare root ****" && \ + rm -rf /var/www/html && \ + echo "**** cleanup ****" && \ + rm -rf \ + /tmp/* + +COPY root/ / +COPY src/ /var/www/html +COPY --from=builder /src/version.php /var/www/html + +VOLUME /config +EXPOSE 80 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c403b6f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: "2" +services: + money-balancer: + build: + dockerfile: Dockerfile + context: . + volumes: + - ./config:/config + - ./src:/var/www/html + ports: + - 8888:80 \ No newline at end of file diff --git a/root/etc/cont-init.d/20-config b/root/etc/cont-init.d/20-config new file mode 100755 index 0000000..b112e5f --- /dev/null +++ b/root/etc/cont-init.d/20-config @@ -0,0 +1,30 @@ +#!/usr/bin/with-contenv bash + +# make our folders +mkdir -p \ + /config/{www,log/nginx,keys,log/php} \ + /run \ + /var/lib/nginx/tmp/client_body \ + /var/tmp/nginx + +#fix php-fpm log location +sed -i "s#;error_log = log/php7/error.log.*#error_log = /config/log/php/error.log#g" /etc/php7/php-fpm.conf +sed -i "s#;log_level = notice#log_level = debug#g" /etc/php7/php-fpm.conf + +#fix php-fpm user +sed -i "s#user = nobody.*#user = abc#g" /etc/php7/php-fpm.d/www.conf +sed -i "s#group = nobody.*#group = abc#g" /etc/php7/php-fpm.d/www.conf + +# backwards compatibility for alpine >=3.14 +if [ ! -e /etc/nginx/conf.d ]; then + ln -s /etc/nginx/http.d /etc/nginx/conf.d +fi + +# permissions +chown -R abc:abc \ + /config \ + /var/lib/nginx \ + /var/tmp/nginx +chmod -R g+w \ + /config/{nginx,www} +chmod -R 644 /etc/logrotate.d \ No newline at end of file diff --git a/root/etc/cont-init.d/50-config b/root/etc/cont-init.d/50-config new file mode 100644 index 0000000..d4e720d --- /dev/null +++ b/root/etc/cont-init.d/50-config @@ -0,0 +1,11 @@ +#!/usr/bin/with-contenv bash + +chown -R abc:abc /var/www + +# create directory structure +mkdir -p \ + /config/www + +# set permissions +chown -R abc:abc \ + /config \ No newline at end of file diff --git a/root/etc/nginx/http.d/synced-timer.conf b/root/etc/nginx/http.d/synced-timer.conf new file mode 100644 index 0000000..89ca13e --- /dev/null +++ b/root/etc/nginx/http.d/synced-timer.conf @@ -0,0 +1,28 @@ +server { + listen 80 default_server; + + listen 443 ssl; + + root /var/www/html; + + server_name _; + + ssl_certificate /config/keys/cert.crt; + ssl_certificate_key /config/keys/cert.key; + + client_max_body_size 0; + + location ^~ / { + index index.php; + try_files $uri $uri/ /index.php?$query_string; + location ~* "\.php$" { + # CHANGE TO YOUR NEEDS + fastcgi_pass 127.0.0.1:9000; + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + try_files $fastcgi_script_name =404; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + } +} diff --git a/root/etc/nginx/nginx.conf b/root/etc/nginx/nginx.conf new file mode 100644 index 0000000..2695287 --- /dev/null +++ b/root/etc/nginx/nginx.conf @@ -0,0 +1,76 @@ +user abc; +worker_processes 4; +pid /run/nginx.pid; +include /etc/nginx/modules/*.conf; + +events { + worker_connections 768; + # multi_accept on; +} + +http { + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + # server_tokens off; + + # server_names_hash_bucket_size 64; + # server_name_in_redirect off; + + client_max_body_size 0; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # Logging Settings + ## + + access_log /config/log/nginx/access.log; + error_log /config/log/nginx/error.log; + + ## + # Gzip Settings + ## + + gzip on; + gzip_disable "msie6"; + + # gzip_vary on; + # gzip_proxied any; + # gzip_comp_level 6; + # gzip_buffers 16 8k; + # gzip_http_version 1.1; + # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # nginx-naxsi config + ## + # Uncomment it if you installed nginx-naxsi + ## + + #include /etc/nginx/naxsi_core.rules; + + ## + # nginx-passenger config + ## + # Uncomment it if you installed nginx-passenger + ## + + #passenger_root /usr; + #passenger_ruby /usr/bin/ruby; + + ## + # Virtual Host Configs + ## + include /etc/nginx/http.d/*.conf; +} + +daemon off; diff --git a/root/etc/services.d/nginx/run b/root/etc/services.d/nginx/run new file mode 100755 index 0000000..a6af21a --- /dev/null +++ b/root/etc/services.d/nginx/run @@ -0,0 +1,15 @@ +#!/usr/bin/with-contenv bash + +if pgrep -f "[n]ginx:" > /dev/null; then + echo "Zombie nginx processes detected, sending SIGTERM" + pkill -ef [n]ginx: + sleep 1 +fi + +if pgrep -f "[n]ginx:" > /dev/null; then + echo "Zombie nginx processes still active, sending SIGKILL" + pkill -9 -ef [n]ginx: + sleep 1 +fi + +exec /usr/sbin/nginx -c /etc/nginx/nginx.conf \ No newline at end of file diff --git a/root/etc/services.d/php-fpm/run b/root/etc/services.d/php-fpm/run new file mode 100644 index 0000000..4e0812f --- /dev/null +++ b/root/etc/services.d/php-fpm/run @@ -0,0 +1,2 @@ +#!/usr/bin/with-contenv bash +exec /usr/sbin/php-fpm7 -F diff --git a/index.php b/src/index.php similarity index 94% rename from index.php rename to src/index.php index 2187f27..3112fbc 100644 --- a/index.php +++ b/src/index.php @@ -16,7 +16,7 @@ class SyncedTimer private $_basepath; private $_resourcePath; private $_path; - private $_dataFile = "./data.json"; + private $_dataFile = "/config/data/data.json"; private $_storageHelper; public function __construct($translations) @@ -28,7 +28,7 @@ class SyncedTimer $this->_calculateBasepath(); $this->_themeConfig["basePath"] = $this->_basepath; - $this->_themeConfig["mainIcon"] = $this->_resourcePath . "IconSmallSquareOutline.png"; + $this->_themeConfig["mainIcon"] = $this->_resourcePath . "static/img/IconSmallSquareOutline.png"; $this->_theme = new LandingpageTheme($this->_themeConfig, $this->_storageHelper, $this->_translations); $this->_processRequest(); @@ -121,7 +121,9 @@ class SyncedTimer "data" => array( "time" => $userdata["time"], "startedAt" => $userdata["startedAt"], - "header" => $userdata["header"] + "header" => $userdata["header"], + "soundEnabled" => $userdata["soundEnabled"], + "repeatEnabled" => $userdata["repeatEnabled"], ) ))); } else { @@ -202,10 +204,14 @@ class SyncedTimer { $time = $_POST["time"]; $header = $_POST["header"]; + $soundEnabled = $_POST["soundEnabled"] === "on"; + $repeatEnabled = $_POST["repeatEnabled"] === "on"; $startedAt = time(); $newData = array( "time" => intval($time), "header" => $header, + "soundEnabled" => $soundEnabled, + "repeatEnabled" => $repeatEnabled, "startedAt" => $startedAt ); $this->_storageHelper->writeUserdata($_SESSION["auth"]["username"], $newData); diff --git a/IconBig.png b/src/static/img/IconBig.png similarity index 100% rename from IconBig.png rename to src/static/img/IconBig.png diff --git a/IconSmallSquareOutline.png b/src/static/img/IconSmallSquareOutline.png similarity index 100% rename from IconSmallSquareOutline.png rename to src/static/img/IconSmallSquareOutline.png diff --git a/src/static/js/alpine.min.js b/src/static/js/alpine.min.js new file mode 100644 index 0000000..42ab077 --- /dev/null +++ b/src/static/js/alpine.min.js @@ -0,0 +1,5 @@ +(()=>{var We=!1,Ge=!1,j=[];function Nt(e){nn(e)}function nn(e){j.includes(e)||j.push(e),on()}function he(e){let t=j.indexOf(e);t!==-1&&j.splice(t,1)}function on(){!Ge&&!We&&(We=!0,queueMicrotask(sn))}function sn(){We=!1,Ge=!0;for(let e=0;ee.effect(t,{scheduler:r=>{Je?Nt(r):r()}}),Ye=e.raw}function Ze(e){K=e}function Dt(e){let t=()=>{};return[n=>{let i=K(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),Y(i))},i},()=>{t()}]}var $t=[],Lt=[],Ft=[];function jt(e){Ft.push(e)}function _e(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Lt.push(t))}function Kt(e){$t.push(e)}function Bt(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function Qe(e,t){!e._x_attributeCleanups||Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}var et=new MutationObserver(Xe),tt=!1;function rt(){et.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),tt=!0}function cn(){an(),et.disconnect(),tt=!1}var ee=[],nt=!1;function an(){ee=ee.concat(et.takeRecords()),ee.length&&!nt&&(nt=!0,queueMicrotask(()=>{ln(),nt=!1}))}function ln(){Xe(ee),ee.length=0}function m(e){if(!tt)return e();cn();let t=e();return rt(),t}var it=!1,ge=[];function zt(){it=!0}function Vt(){it=!1,Xe(ge),ge=[]}function Xe(e){if(it){ge=ge.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;os.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{Qe(s,o)}),n.forEach((o,s)=>{$t.forEach(a=>a(s,o))});for(let o of r)if(!t.includes(o)&&(Lt.forEach(s=>s(o)),o._x_cleanups))for(;o._x_cleanups.length;)o._x_cleanups.pop()();t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.includes(o)||!o.isConnected||(delete o._x_ignoreSelf,delete o._x_ignore,Ft.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function xe(e){return P(N(e))}function C(e,t,r){return e._x_dataStack=[t,...N(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function ot(e,t){let r=e._x_dataStack[0];Object.entries(t).forEach(([n,i])=>{r[n]=i})}function N(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?N(e.host):e.parentNode?N(e.parentNode):[]}function P(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,c=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...c,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function ye(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function be(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>un(n,i),s=>st(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function un(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function st(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),st(e[t[0]],t.slice(1),r)}}var Ht={};function x(e,t){Ht[e]=t}function te(e,t){return Object.entries(Ht).forEach(([r,n])=>{Object.defineProperty(e,`$${r}`,{get(){let[i,o]=at(t);return i={interceptor:be,...i},_e(t,o),n(t,i)},enumerable:!1})}),e}function qt(e,t,r,...n){try{return r(...n)}catch(i){J(i,e,t)}}function J(e,t,r=void 0){Object.assign(e,{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message} + +${r?'Expression: "'+r+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var ve=!0;function Ut(e){let t=ve;ve=!1,e(),ve=t}function k(e,t,r={}){let n;return _(e,t)(i=>n=i,r),n}function _(...e){return Wt(...e)}var Wt=ct;function Gt(e){Wt=e}function ct(e,t){let r={};te(r,e);let n=[r,...N(e)];if(typeof t=="function")return fn(n,t);let i=dn(n,t,e);return qt.bind(null,e,t,i)}function fn(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(P([n,...e]),i);we(r,o)}}var lt={};function pn(e,t){if(lt[e])return lt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(() => { ${e} })()`:e,o=(()=>{try{return new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`)}catch(s){return J(s,t,e),Promise.resolve()}})();return lt[e]=o,o}function dn(e,t,r){let n=pn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=P([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>J(l,r,t));n.finished?(we(i,n.result,a,s,r),n.result=void 0):c.then(l=>{we(i,l,a,s,r)}).catch(l=>J(l,r,t)).finally(()=>n.result=void 0)}}}function we(e,t,r,n,i){if(ve&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>we(e,s,r,n)).catch(s=>J(s,i,t)):e(o)}else e(t)}var ut="x-";function E(e=""){return ut+e}function Yt(e){ut=e}var Jt={};function d(e,t){Jt[e]=t}function re(e,t,r){let n={};return Array.from(t).map(Zt((o,s)=>n[o]=s)).filter(Qt).map(hn(n,r)).sort(_n).map(o=>mn(e,o))}function Xt(e){return Array.from(e).map(Zt()).filter(t=>!Qt(t))}var ft=!1,ne=new Map,er=Symbol();function tr(e){ft=!0;let t=Symbol();er=t,ne.set(t,[]);let r=()=>{for(;ne.get(t).length;)ne.get(t).shift()();ne.delete(t)},n=()=>{ft=!1,r()};e(r),n()}function at(e){let t=[],r=a=>t.push(a),[n,i]=Dt(e);return t.push(i),[{Alpine:I,effect:n,cleanup:r,evaluateLater:_.bind(_,e),evaluate:k.bind(k,e)},()=>t.forEach(a=>a())]}function mn(e,t){let r=()=>{},n=Jt[t.type]||r,[i,o]=at(e);Bt(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),ft?ne.get(er).push(n):n())};return s.runCleanups=o,s}var Ee=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Se=e=>e;function Zt(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=rr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var rr=[];function Z(e){rr.push(e)}function Qt({name:e}){return nr().test(e)}var nr=()=>new RegExp(`^${ut}([^:^.]+)\\b`);function hn(e,t){return({name:r,value:n})=>{let i=r.match(nr()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var dt="DEFAULT",Ae=["ignore","ref","data","id","bind","init","for","mask","model","modelable","transition","show","if",dt,"teleport","element"];function _n(e,t){let r=Ae.indexOf(e.type)===-1?dt:e.type,n=Ae.indexOf(t.type)===-1?dt:t.type;return Ae.indexOf(r)-Ae.indexOf(n)}function B(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}var pt=[],mt=!1;function Te(e=()=>{}){return queueMicrotask(()=>{mt||setTimeout(()=>{Oe()})}),new Promise(t=>{pt.push(()=>{e(),t()})})}function Oe(){for(mt=!1;pt.length;)pt.shift()()}function ir(){mt=!0}function R(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>R(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)R(n,t,!1),n=n.nextElementSibling}function O(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function sr(){document.body||O("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `