feat: update hbc via hb_install.sh instead of code patching
Server now sends a bare UPD command; client runs hb_install.sh to reinstall from the package registry, then restarts. hb_install.sh also copies itself alongside hbc on client installs. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+27
-31
@@ -14,7 +14,6 @@ import signal
|
|||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from hashlib import md5
|
|
||||||
from logging.handlers import SysLogHandler
|
from logging.handlers import SysLogHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
@@ -204,48 +203,45 @@ async def handle_command(conn: AsyncConnection, msg: dict):
|
|||||||
await conn.sendto(response)
|
await conn.sendto(response)
|
||||||
|
|
||||||
|
|
||||||
async def handle_update(conn: AsyncConnection, msg: dict):
|
async def handle_update(conn: AsyncConnection, _msg: dict): # pyright: ignore[reportUnusedParameter]
|
||||||
"""Handle self-update from server."""
|
"""Handle self-update by running hb_install.sh."""
|
||||||
import codecs
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
logger = logging.getLogger("hbc.update")
|
logger = logging.getLogger("hbc.update")
|
||||||
|
|
||||||
try:
|
installer = shutil.which("hb_install.sh")
|
||||||
code = codecs.decode(msg["code"], "base64").decode()
|
if installer is None:
|
||||||
csum = msg["csum"]
|
candidate = Path(sys.argv[0]).parent / "hb_install.sh"
|
||||||
except Exception as e:
|
if candidate.exists():
|
||||||
error = f"Missing code/csum: {e}"
|
installer = str(candidate)
|
||||||
|
|
||||||
|
if installer is None:
|
||||||
|
error = "hb_install.sh not found in PATH or alongside hbc"
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
await conn.sendto({"service": "update", "msg": error})
|
await conn.sendto({"service": "update", "msg": error})
|
||||||
return
|
return
|
||||||
|
|
||||||
# Verify checksum
|
logger.info(f"Running installer: {installer}")
|
||||||
m = md5()
|
try:
|
||||||
m.update(code.encode())
|
proc = await asyncio.create_subprocess_exec(
|
||||||
if m.hexdigest() != csum:
|
installer, "client",
|
||||||
error = "Checksum mismatch"
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
out, _ = await asyncio.wait_for(proc.communicate(), timeout=120)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
error = "Installer timed out"
|
||||||
|
logger.error(error)
|
||||||
|
await conn.sendto({"service": "update", "msg": error})
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
error = f"Installer failed: {e}"
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
await conn.sendto({"service": "update", "msg": error})
|
await conn.sendto({"service": "update", "msg": error})
|
||||||
return
|
return
|
||||||
|
|
||||||
# Backup current file
|
if proc.returncode != 0:
|
||||||
fn = sys.argv[0]
|
error = f"Installer exited {proc.returncode}: {out.decode().strip()}"
|
||||||
ofn = f"{fn}.sav"
|
|
||||||
try:
|
|
||||||
shutil.copy2(fn, ofn)
|
|
||||||
except Exception as e:
|
|
||||||
error = f"Backup failed: {e}"
|
|
||||||
logger.error(error)
|
|
||||||
await conn.sendto({"service": "update", "msg": error})
|
|
||||||
return
|
|
||||||
|
|
||||||
# Write new code
|
|
||||||
try:
|
|
||||||
with open(fn, "w") as fh:
|
|
||||||
fh.write(code)
|
|
||||||
except Exception as e:
|
|
||||||
error = f"Write failed: {e}"
|
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
await conn.sendto({"service": "update", "msg": error})
|
await conn.sendto({"service": "update", "msg": error})
|
||||||
return
|
return
|
||||||
|
|||||||
+4
-9
@@ -210,15 +210,11 @@ async def start(
|
|||||||
return err
|
return err
|
||||||
qa = request.rel_url.query
|
qa = request.rel_url.query
|
||||||
uname = urllib.parse.unquote(qa.get("h", ""))
|
uname = urllib.parse.unquote(qa.get("h", ""))
|
||||||
ucode = qa.get("c")
|
if not uname:
|
||||||
if not ucode or not uname:
|
return web.Response(status=400, text="need h= argument")
|
||||||
return web.Response(status=400, text="need h= and c= arguments")
|
|
||||||
if uname != "All" and uname not in hbdclass.Host.hosts:
|
if uname != "All" and uname not in hbdclass.Host.hosts:
|
||||||
return web.Response(status=400, text=f"h={uname} not found")
|
return web.Response(status=400, text=f"h={uname} not found")
|
||||||
if uname != "All":
|
names = [uname] if uname != "All" else list(hbdclass.Host.hosts)
|
||||||
names = [uname]
|
|
||||||
else:
|
|
||||||
names = [n for n in hbdclass.Host.hosts]
|
|
||||||
out = []
|
out = []
|
||||||
for n in names:
|
for n in names:
|
||||||
host = hbdclass.Host.hosts[n]
|
host = hbdclass.Host.hosts[n]
|
||||||
@@ -227,8 +223,7 @@ async def start(
|
|||||||
continue
|
continue
|
||||||
op_err = None
|
op_err = None
|
||||||
try:
|
try:
|
||||||
r = {"csum": None, "code": ucode}
|
host.cmds.append(("UPD", {}))
|
||||||
host.cmds.append(("UPD", r))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
op_err = str(e)
|
op_err = str(e)
|
||||||
out.append(f"update started for {n}: {op_err if op_err else 'OK'}")
|
out.append(f"update started for {n}: {op_err if op_err else 'OK'}")
|
||||||
|
|||||||
+11
-2
@@ -15,8 +15,14 @@ on_ha=0
|
|||||||
[ -z "$what" ] && what="client"
|
[ -z "$what" ] && what="client"
|
||||||
|
|
||||||
if [ -d /homeassistant ]; then
|
if [ -d /homeassistant ]; then
|
||||||
echo "cannot install in HA, run \"docker exec -it homeassistant $0 $@\""
|
echo "cannot install in HA, running \"docker exec homeassistant $0 $@\""
|
||||||
exit 1
|
docker exec homeassistant $0 $@
|
||||||
|
rc=$?
|
||||||
|
if [ $rc -ne 0 ]; then
|
||||||
|
echo "Failed to install heartbeat in HA, please check the logs for more details"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
if [ -d /config ]; then
|
if [ -d /config ]; then
|
||||||
echo "Installing on HA"
|
echo "Installing on HA"
|
||||||
@@ -78,6 +84,9 @@ if [ "$what" = "server" ]; then
|
|||||||
else
|
else
|
||||||
rm -f $where/hbc
|
rm -f $where/hbc
|
||||||
ln -sf $(which hbc) $where/hbc
|
ln -sf $(which hbc) $where/hbc
|
||||||
|
rm -f $where/hb_install.sh
|
||||||
|
cp "$0" $where/hb_install.sh
|
||||||
|
chmod +x $where/hb_install.sh
|
||||||
if [ $on_ha -eq 1 ]; then
|
if [ $on_ha -eq 1 ]; then
|
||||||
echo "restarting hbc "
|
echo "restarting hbc "
|
||||||
job=$(grep run_hbc configuration.yaml | sed 's/run_hbc://')
|
job=$(grep run_hbc configuration.yaml | sed 's/run_hbc://')
|
||||||
|
|||||||
Reference in New Issue
Block a user