diff --git a/.dockerignore b/.dockerignore index b02b2dd..1fc80ed 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,11 @@ GivTCP/startup.sh *.pkl .forceFullRefresh test.txt +.git +.vscode +Dockerfile +WebDashboard/graphics + +# WebDashboard is a static site - node is not required +# except as a helper to serve the site for development +WebDashboard/package* diff --git a/Dockerfile b/Dockerfile index fca5042..3757356 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,21 @@ +# givtcp-vuejs builder +FROM node:21-alpine AS givtcp_vuejs_tmp + +# set the working directory in the container +WORKDIR /app + +# Copy file dependencies in a single layer +COPY givtcp-vuejs . + +RUN npm install && \ + npm run build && \ + mv dist/index.html dist/config.html + # set base image (host OS) #FROM python:3.11-rc-alpine FROM python:alpine3.19 RUN apk add mosquitto -RUN apk add npm RUN apk add git RUN apk add tzdata RUN apk add musl @@ -12,7 +24,6 @@ RUN apk add redis RUN apk add nginx RUN mkdir -p /run/nginx -RUN npm install -g serve # set the working directory in the container WORKDIR /app @@ -21,12 +32,6 @@ WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -COPY givtcp-vuejs/package.json /app/ingress/package.json - -RUN cd /app/ingress && npm install -COPY givtcp-vuejs ./ingress -RUN cd /app/ingress && npm run build && mv dist/index.html dist/config.html && cp -a dist/. /app/ingress/ - COPY ingress.conf /etc/nginx/http.d/ COPY ingress_no_ssl.conf /app/ingress_no_ssl.conf RUN rm /etc/nginx/http.d/default.conf @@ -43,6 +48,9 @@ COPY redis.conf redis.conf COPY settings.json ./settings.json COPY ingress/ ./ingress -EXPOSE 1883 6379 8099 +# Copy static site files +COPY --from=givtcp_vuejs_tmp /app/dist /app/ingress/ + +EXPOSE 1883 3000 6379 8099 CMD ["python3", "/app/startup.py"] diff --git a/README.md b/README.md index 7364f77..e2da7e1 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ GivTCP can auto-discover devices on the network for a (near) zero config setup Typically run through the Home Assistant Addon, it is also possible to run as a standalone docker container. +Buy Me A Coffee + ## Quick Set-up ### Install GivTCP diff --git a/WebDashboard b/WebDashboard index 4dd6de5..6ec2a6f 160000 --- a/WebDashboard +++ b/WebDashboard @@ -1 +1 @@ -Subproject commit 4dd6de564b77b0e29430446387185f0b548961d9 +Subproject commit 6ec2a6ff400159565e3f7667017282a404b826f1 diff --git a/startup.py b/startup.py index d55c888..149728e 100644 --- a/startup.py +++ b/startup.py @@ -125,7 +125,7 @@ def createsettingsjson(inv): outp.write(" first_run_evc= True\n") outp.write(" self_run_timer="+str(setts["self_run_timer"])+"\n") outp.write(" self_run_timer_full="+str(setts["self_run_timer_full"])+"\n") - outp.write(" queue_retries="+str(setts["queue_retries"])+"\n") + outp.write(" queue_retries="+str(setts["queue_retries"])+"\n") outp.write(" givtcp_instance="+str(inv)+"\n") outp.write(" default_path=\""+str(PATH)+"\"\n") outp.write(" dynamic_tariff="+str(setts["dynamic_tariff"]).capitalize()+"\n") @@ -286,7 +286,7 @@ def findinv(networks): hasMQTT=False logger.info("No HA MQTT service has been found. Install and run the Mosquitto addon, or manually configure your own MQTT broker.") - #Get Timezone + #Get Timezone url="http://supervisor/info" result = requests.get(url, headers={'Content-Type':'application/json', @@ -294,8 +294,8 @@ def findinv(networks): info=result.json() SuperTimezone=info['data']['timezone'] logger.debug("Supervisor Timezone: "+str(SuperTimezone)) - - #Get addonslug/ingress url + + #Get addonslug/ingress url url="http://supervisor/addons/self/info" result = requests.get(url, headers={'Content-Type':'application/json', @@ -303,7 +303,7 @@ def findinv(networks): baseurl=result.json()['data']['ingress_url'] logger.debug("Ingress URL is: "+str(baseurl)) - #Get Host Details + #Get Host Details url="http://supervisor/network/info" result = requests.get(url, headers={'Content-Type':'application/json', @@ -352,8 +352,8 @@ def findinv(networks): logger.info("Searching for Inverters") finv=findinv(networks) i=i+1 - if i==3: - break + if i==3: + break inverterStats=finv[0] invList=finv[1] evcList=finv[2] @@ -436,7 +436,7 @@ def findinv(networks): setts["invertorIP_"+str(num)]=inverterStats[inv]['IP_Address'] break setts['Model_'+str(inv)]=inverterStats[inv]['Model'].name.capitalize() - + if len(evcList)>0: logger.debug("evcList: "+str(evcList)) @@ -540,7 +540,7 @@ def findinv(networks): os.remove(firstrun) createsettingsjson(inv) - + ###### # Always delete lockfiles and FCRunning etc... but only delete pkl if too old? for file in os.listdir(setts["cache_location"]): @@ -585,7 +585,7 @@ def findinv(networks): logger.info ("Running Invertor "+str(inv)+" ("+str(setts["serial_number_"+str(inv)])+") read loop every "+str(setts['self_run_timer'])+"/"+str(setts['self_run_timer_full'])+"s") selfRun[inv]=subprocess.Popen(["/usr/local/bin/python3",PATH+"/read.py", "start"]) - + GUPORT=6344+inv logger.debug ("Starting Gunicorn on port "+str(GUPORT)) command=shlex.split("/usr/local/bin/gunicorn -w 3 -b :"+str(GUPORT)+" REST:giv_api") @@ -623,10 +623,21 @@ def findinv(networks): outp.write(" \"solarRate\": "+str(setts['day_rate'])+",\n") outp.write(" \"exportRate\": "+str(setts['export_rate'])+"\n") outp.write("}") + WDPORT=int(setts['Web_Dash_Port']) - logger.info ("Serving Web Dashboard from port "+str(WDPORT)) - command=shlex.split("/usr/bin/node /usr/local/bin/serve -p "+ str(WDPORT)) - webDash=subprocess.Popen(command) + logger.info (f"Serving Web Dashboard from port {WDPORT}") + with open("/etc/nginx/http.d/webdashboard.conf", 'w') as wd: + wd.write("server {\n") + wd.write(f"\tlisten {WDPORT};\n") + wd.write("\tlocation / {\n") + wd.write("\t\troot /app/WebDashboard;\n") + wd.write("\t\tindex index.html;\n\n") + wd.write("\t\ttry_files $uri $uri/ =404;\n") + wd.write("\t}\n") + wd.write("}\n") + + # reload nginx to pick up the new conf + subprocess.Popen(["nginx","-s","reload", "-c", "/etc/nginx/nginx.conf"]) if setts['Smart_Target']==True: @@ -670,16 +681,6 @@ def findinv(networks): logger.info ("Starting Gunicorn on port "+str(GUPORT)) command=shlex.split("/usr/local/bin/gunicorn -w 3 -b :"+str(GUPORT)+" REST:giv_api") gunicorn[inv]=subprocess.Popen(command) - - if setts['Web_Dash'] == True: - if not webDash.poll() == None: - webDash.kill() - logger.error("Web Dashboard process died. Restarting...") - os.chdir("/app/WebDashboard") - WDPORT = int(setts['Web_Dash_Port']) - logger.info("Serving Web Dashboard from port " + str(WDPORT)) - command = shlex.split("/usr/bin/node /usr/local/bin/serve -p " + str(WDPORT)) - webDash = subprocess.Popen(command) if setts['MQTT_Address']=="127.0.0.1": if not mqttBroker.poll()==None: