From ab8ca6b4cfc1b99e74954d572af4fac00cf613aa Mon Sep 17 00:00:00 2001 From: Jens Maus Date: Tue, 25 Jan 2022 14:42:02 +0100 Subject: [PATCH] HomeMatic CCU add-on retirement/migration path (#2352) * added first version of createBackup.sh for homematic addon * move bin directory to opt/hm * debug * debug * minor cleanup change. * added a patched version of cp_security.cgi to support direct WebUI based backup creation+download. * minor fix * minor fix * version bump and removal of subsystem=tty filter for device spec * added changed to CHANGELOG.md * Added retirement notification * Update README.md * Update DOCS.md * Update CHANGELOG.md * fixed shellcheck errors. * Update homematic/CHANGELOG.md * Update homematic/config.yaml Co-authored-by: Pascal Vizeli --- homematic/CHANGELOG.md | 16 + homematic/DOCS.md | 2 + homematic/README.md | 2 + homematic/config.yaml | 6 +- homematic/rootfs/opt/hm/bin/createBackup.sh | 107 ++ .../rootfs/opt/hm/www/config/cp_security.cgi | 1620 +++++++++++++++++ 6 files changed, 1750 insertions(+), 3 deletions(-) create mode 100755 homematic/rootfs/opt/hm/bin/createBackup.sh create mode 100755 homematic/rootfs/opt/hm/www/config/cp_security.cgi diff --git a/homematic/CHANGELOG.md b/homematic/CHANGELOG.md index a68d6b1..cb4145f 100644 --- a/homematic/CHANGELOG.md +++ b/homematic/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +#### :warning: This add-on is considered to be obsolete/retired in favor of the much more advanced third-party [RaspberryMatic CCU](https://github.com/jens-maus/RaspberryMatic/tree/master/home-assistant-addon) add-on for running a HomeMatic/homematicIP smart home central within HomeAssistant. If you want to migrate to the new add-on, please make sure to update to the latest version of this old "HomeMatic CCU" add-on first and then use the WebUI-based backup routines to export a `*.sbk` config backup file which you can then restore in the new "RaspberryMatic CCU" add-on afterwards (cf. [RaspberryMatic Documentation](https://github.com/jens-maus/RaspberryMatic/wiki/Installation-HomeAssistant)) :warning: + +## 99.0.0 + +- added a notification to the main add-on README that this add-on + is now considered obsolete/retired in favor of using the + "RaspberryMatic CCU" HomeAssistant add-on instead. +- implemented a config backup routine which will allow to export + the current HomeMatic configuration in a somewhat compatible + file format (.sbk) to be able to directly import it into a + real CCU or the successor "RaspberryMatic" add-on. +- modified config.yaml to allow to specify arbitrary devices and + not just tty devices. This should allow to specify the new + raw-uart device which will be required with upcoming HAos updates + supporting dualcopro mode for HmIP-RFUSB. + ## 11.3.0 - Update OCCU to 3.59.6 diff --git a/homematic/DOCS.md b/homematic/DOCS.md index 14a2b79..525c130 100644 --- a/homematic/DOCS.md +++ b/homematic/DOCS.md @@ -1,5 +1,7 @@ # Home Assistant Add-on: HomeMatic +#### :warning: This add-on is considered to be obsolete/retired in favor of the much more advanced third-party [RaspberryMatic CCU](https://github.com/jens-maus/RaspberryMatic/tree/master/home-assistant-addon) add-on for running a HomeMatic/homematicIP smart home central within HomeAssistant. If you want to migrate to the new add-on, please make sure to update to the latest version of this old "HomeMatic CCU" add-on first and then use the WebUI-based backup routines to export a `*.sbk` config backup file which you can then restore in the new "RaspberryMatic CCU" add-on afterwards (cf. [RaspberryMatic Documentation](https://github.com/jens-maus/RaspberryMatic/wiki/Installation-HomeAssistant)) :warning: + ## Installation Follow these steps to get the add-on installed on your system: diff --git a/homematic/README.md b/homematic/README.md index 7a2a491..698af82 100644 --- a/homematic/README.md +++ b/homematic/README.md @@ -1,5 +1,7 @@ # Home Assistant Add-on: HomeMatic +#### :warning: This add-on is considered to be obsolete/retired in favor of the much more advanced third-party [RaspberryMatic CCU](https://github.com/jens-maus/RaspberryMatic/tree/master/home-assistant-addon) add-on for running a HomeMatic/homematicIP smart home central within HomeAssistant. If you want to migrate to the new add-on, please make sure to update to the latest version of this old "HomeMatic CCU" add-on first and then use the WebUI-based backup routines to export a `*.sbk` config backup file which you can then restore in the new "RaspberryMatic CCU" add-on afterwards (cf. [RaspberryMatic Documentation](https://github.com/jens-maus/RaspberryMatic/wiki/Installation-HomeAssistant)) :warning: + HomeMatic central based on OCCU. ![Supports aarch64 Architecture][aarch64-shield] ![Supports amd64 Architecture][amd64-shield] ![Supports armhf Architecture][armhf-shield] ![Supports armv7 Architecture][armv7-shield] ![Supports i386 Architecture][i386-shield] diff --git a/homematic/config.yaml b/homematic/config.yaml index a6c19c4..5e7f3a3 100644 --- a/homematic/config.yaml +++ b/homematic/config.yaml @@ -1,4 +1,4 @@ -version: 11.3.0 +version: 99.0.0 slug: homematic name: HomeMatic CCU description: HomeMatic central based on OCCU @@ -33,12 +33,12 @@ ports_description: 80/tcp: ReGaHss Webinterface (Not required for Ingress) schema: hmip: - - device: device(subsystem=tty) + - device: device type: match(HMIP_CCU2) hmip_enable: bool regahss_reset: bool? rf: - - device: device(subsystem=tty) + - device: device reset: bool? type: match(CCU2) rf_enable: bool diff --git a/homematic/rootfs/opt/hm/bin/createBackup.sh b/homematic/rootfs/opt/hm/bin/createBackup.sh new file mode 100755 index 0000000..df38513 --- /dev/null +++ b/homematic/rootfs/opt/hm/bin/createBackup.sh @@ -0,0 +1,107 @@ +#!/bin/bash +# shellcheck source=/dev/null +# +# simple backup script to create a CCU compatible .sbk type +# backup out of the core-homematic HomeAssistant add-on +# +# Copyright (c) 2022 Jens Maus +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Usage: +# createBackup.sh +# + +# Stop on error +set -e + +# default backup destination directory +BACKUPDIR=/tmp + +# let caller overwrite the backup path +if [[ -n "${1}" ]]; then + BACKUPPATH="${1}" +else + echo "ERROR: no backup path specified" +fi + +# get running firmware version +. /VERSION 2>/dev/null + +# check if specified path is a directory or file +if [[ -d "${BACKUPPATH}" ]]; then + # a directory path was specified, lets construct the complete filepath + BACKUPDIR="$(realpath "${BACKUPPATH}")" + BACKUPFILE="$(hostname)-${VERSION}-$(date +%Y-%m-%d-%H%M).sbk" +else + # a file was specified with a directory path + BACKUPDIR="$(realpath "$(dirname "${BACKUPPATH}")")" + BACKUPFILE="$(basename "${BACKUPPATH}")" +fi + +# use the backupdir as base directory for creating +# a temporary directory to create the backup stuff in there. +TMPDIR=$(mktemp -d -p "${BACKUPDIR}") +if [[ -d "${TMPDIR}" ]]; then + # make sure TMPDIR is removed under all circumstances + # shellcheck disable=SC2064 + + # make sure ReGaHSS saves its current settings + echo 'load tclrega.so; rega system.Save()' | /opt/hm/bin/tclsh >/dev/null 2>&1 + + # create a CCU conform copy of /usr/local + mkdir -p "${TMPDIR}/usr/local" + mkdir -p "${TMPDIR}/usr/local/etc/config" + cp -a /opt/hm/etc/config/* "${TMPDIR}/usr/local/etc/config/" + rm -f "${TMPDIR}/usr/local/etc/config/groups.gson" "${TMPDIR}/usr/local/etc/config/userprofiles" + cp -a /data/* "${TMPDIR}/usr/local/etc/config/" + + # cleanup + rm -f "${TMPDIR}/usr/local/etc/config/options.json" + rm -f "${TMPDIR}/usr/local/etc/config/groups.json" + rm -f "${TMPDIR}/usr/local/etc/config/HMServer.conf" + rm -f "${TMPDIR}/usr/local/etc/config/hmip_user.conf" + rm -f "${TMPDIR}/usr/local/etc/config/log4j.xml" + rm -f "${TMPDIR}/usr/local/etc/config/rega.conf" + rm -f "${TMPDIR}/usr/local/etc/config/rega_user.conf" + + # create a gzipped tar of /usr/local + set +e # disable abort on error + /bin/tar -C "${TMPDIR}" --owner=root --group=root --exclude=usr/local/tmp --exclude=usr/local/lost+found --exclude="${BACKUPDIR}" --exclude-tag=.nobackup --ignore-failed-read --warning=no-file-changed -czf "${TMPDIR}/usr_local.tar.gz" usr/local 2>/dev/null + if [[ $? -eq 2 ]]; then + exit 2 + fi + set -e # re-enable abort on error + + # remove the temp usr dir again + rm -rf "${TMPDIR:?}/usr" + + # sign the configuration with the current key + /opt/hm/bin/crypttool -s -t 1 <"${TMPDIR}/usr_local.tar.gz" >"${TMPDIR}/signature" + + # store the current key index + /opt/hm/bin/crypttool -g -t 1 >"${TMPDIR}/key_index" + + # store the firmware VERSION + echo "VERSION=${VERSION}" >"${TMPDIR}/firmware_version" + + # create sha256 checksum of all files + (cd "${TMPDIR}" && /usr/bin/sha256sum ./* >signature.sha256) + + # create sbk file + /bin/tar -C "${TMPDIR}" --owner=root --group=root -cf "${BACKUPDIR}/${BACKUPFILE}" usr_local.tar.gz signature signature.sha256 key_index firmware_version 2>/dev/null + + exit 0 +else + exit 1 +fi diff --git a/homematic/rootfs/opt/hm/www/config/cp_security.cgi b/homematic/rootfs/opt/hm/www/config/cp_security.cgi new file mode 100755 index 0000000..62559f1 --- /dev/null +++ b/homematic/rootfs/opt/hm/www/config/cp_security.cgi @@ -0,0 +1,1620 @@ +#!/bin/tclsh +source once.tcl +sourceOnce common.tcl +sourceOnce session.tcl +sourceOnce file_io.tcl +load tclrpc.so +load tclrega.so + +set PFMD_URL "bin://127.0.0.1:2002" + +set portnumber 2001 +catch { source "/etc/eq3services.ports.tcl" } +if { [info exists EQ3_SERVICE_RFD_PORT] } { + set portnumber $EQ3_SERVICE_RFD_PORT +} +set RFD_URL "bin://127.0.0.1:$portnumber" + +proc array_getValue { pArray name } { + upvar $pArray arr + + set value {} + catch { set value $arr($name) } + + return $value +} + +proc get_current_key_index {} { + set KEY_FILE "/etc/config/keys" + set CURRENT_INDEX "Current user key" + set PREVIOUS_INDEX "Previous user key" + + # Schl�ssel-Index ermitteln + set fd [open "|crypttool -g" r] + set content [read $fd] + close $fd + + array set keys {} + foreach line [split $content "\n"] { + if { [regexp {([^=]*)=(.*)} $line dummy key value] } then { + set key [string trim $key] + set value [string trim $value] + set keys($key) $value + } + } + + set currentIndex 0 + if { [info exists keys($CURRENT_INDEX)] } then { + set currentIndex $keys($CURRENT_INDEX) + } + + return $currentIndex +} + +proc set_key { key } { + global RFD_URL + + xmlrpc $RFD_URL changeKey $key + + # set always new key with crypttool + set index [get_current_key_index] + incr index 1 + + set fd [open "|crypttool -S -k $key -i $index" r] + close $fd +} + +proc getSGTIN_CCU {} { + + set fp [open "/var/hmip_board_sgtin" r] + set file_data [read $fp] + close $fp + set sgtinCCU [lindex [split $file_data "\n"] 0] + + return $sgtinCCU +} + +proc getSGTIN_Backup {migrationMode} { + + switch $migrationMode { + "CCU2_CCU2" {set path_crRFD "/tmp/backup/usr/local/etc/config/crRFD/data"} + "CCU2_CCU3" {set path_crRFD "/tmp/backup/usr/local/etc/config/crRFD/data"} + "CCU3_CCU3" {set path_crRFD "/usr/local/eQ-3-Backup/restore/etc/config/crRFD/data"} + } + + cd $path_crRFD + set sgtinBackup [lindex [split [glob *.apkx] "."] 0] + + return "$sgtinBackup" +} + +proc getBackupErrorMessage {errorCode migration_mode} { + set errorCode [expr $errorCode * 1] + # ERROR CODES: + # 9 = Check the script /bin/checkUsrBackup.sh - the java call is wrong + # 10 = OK + # 11 = Backup fehlerhaft / unvollst�ndig (z.B. *.apkx Datei fehlt) + # 12 = Internet fehlt / KeyServer Timeout + # 13 = KeyServer NAK (eine oder beide sind nicht im KeyServer) + # 14 = Fehler bei Ger�tepersistenz (einlesen fehlgeschlagen oder Versionen / Typen nicht kompatibel) + # 15 = Migration Fehlgeschlagen (nicht f�r die Backup Validierungs Main) + # 16 = Adapter (Coprozessor) nicht verf�gbar (basierend auf den Konfigurationen aus dem Backup + Default aus /etc) + # 17 = Adapter konnte nicht initialisiert werden (besch�digte Application / Fehlerr�ckmeldungen) + # 18 = Adapter Version nicht unterst�tzt + # 99 = Unknown error + + if {$errorCode == 13} { + # TODO This is currently deactivated because we cannot determine for sure if a SGTIN belongs to a CCU or to a DRAP (see TWIST-1928) + # set sgtinCCU [getSGTIN_CCU] + # set sgtinBackup [getSGTIN_Backup $migration_mode] + } + + set code(9) "
\$\{backupWrongJavaCall\}" + set code(10) "
\$\{backupOK\}" + set code(11) "
\$\{backupBackupImperfectMissingFile\}" + set code(12) "
\$\{backupNoInternet_KeyserverTimeout\}" + # TODO Activate the next line and delete the line after that when we can extract the SGTIN (see TWIST-1928) + # set code(13) "
\$\{backupKeyServer_NAK\}

SGTIN CCU: $sgtinCCU
SGTIN Backup: $sgtinBackup
 " + set code(13) "
\$\{backupKeyServer_NAK\}" + set code(14) "
\$\{backupErrorDevicePersistence_TypesNotCompatibel\}" + set code(15) "
\$\{backupMigrationFailed\}" + set code(16) "
\$\{backupCoProcessor_not_availabel\}" + set code(17) "
\$\{backupCoProcessor_NotInitialized\}" + set code(18) "
\$\{backupCoProcessor_VersionNotSupported\}" + set code(99) "
\$\{backupUnknownError\}" + return $code($errorCode) +} + +proc readBackupStatus {} { + set fp [open "/tmp/backupStatus.log" r] + set data [read $fp] + close $fp + return $data +} + +proc checkUserBackupValidility {migrationMode} { + + switch $migrationMode { + "CCU2_CCU2" {set pathBackup "/tmp/backup/usr/local/"} + "CCU2_CCU3" {set pathBackup "/tmp/backup/usr/local/"} + "CCU3_CCU3" {set pathBackup "/usr/local/eQ-3-Backup/restore/"} + } + + catch {exec killall java} + set tmp [catch {exec checkUsrBackup.sh $pathBackup}] + return [readBackupStatus] +} + +proc action_change_key {} { + global env RFD_URL + + http_head + import key1 + import key2 + + if { "$key1" != "$key2" } { + #put_message "\${dialogSettingsSecurityMessageErrorSecKeyTitle}" "Die beiden eingegebenen Schlüssel stimmen nicht überein." {\${dialogBack} "showSecurityCP();"} + put_message "\${dialogSettingsSecurityMessageErrorSecKeyTitle}" "\${dialogSettingsSecurityMessageErrorSecKeyContentKeysNotIdentical}" {\${dialogBack} "showSecurityCP();"} + return + } + if { [string length "$key1"] < 5 } { + #put_message "Sicherheitsschlüssel setzen - Fehler" "Der eingegebene Schlüssel ist zu kurz. Geben Sie einen Schlüssel ein, der mindestens 5 Zeichen lang ist." {"Zurück" "showSecurityCP();"} + put_message "\${dialogSettingsSecurityMessageErrorSecKeyTitle}" \${dialogSettingsSecurityMessageErrorSecKeyContentKeyShort} {\${dialogBack} "showSecurityCP();"} + return + } + + if { 0 == [regexp {^[0-9a-zA-Z_]+$} $key1 dummy] } { + #put_message "Sicherheitsschlüssel setzen - Fehler" "Der eingegebene Schlüssel darf keine Sonderzeichen enthalten. Erlaubt sind lediglich die Buchstaben A bis Z, die Ziffern 0 bis 9 sowie der Unterstrich." {"Zurück" "showSecurityCP();"} + put_message "\${dialogSettingsSecurityMessageErrorSecKeyTitle}" \${dialogSettingsSecurityMessageErrorSecKeyContentIllegalChar} {\${dialogBack} "showSecurityCP();"} + return + } + + # check the entered key against our current system key + if { ![catch {exec crypttool -v -t 3 -k "$key1"}]} { + + # "Der eingegebene Schl�ssel entspricht dem aktuellen Schl�ssel der Zentrale. " + # "Der Schl�ssel wird nicht ge�ndert." + put_message "\${dialogSettingsSecurityMessageHintSecKeyTitle}" \${dialogSettingsSecurityMessageErrorSecKeyContentKeysIsIdentical} {\${dialogBack} "showSecurityCP();"} + return + } + + if { [catch {set_key $key1}] } { + # "Der Schl�ssel konnte nicht gesetzt werden. Das liegt vermutlich daran, da� " + # "der aktuelle Schl�;ssel noch nicht an alle Komponenten �bertragen wurde. " + # "Hinweise darauf finden Sie in den Servicemeldungen." + put_message "\${dialogSettingsSecurityMessageErrorSecKeyTitle}" \${dialogSettingsSecurityMessageErrorSecKeyContentKeyNotAllDevices} {\${dialogBack} "showSecurityCP();"} + return + } + put_message "\${dialogSettingsSecurityMessageOKSecKeyTitle}" \${dialogSettingsSecurityMessageErrorSecKeyContentSetKeySucceed} {\${dialogBack} "showSecurityCP();"} +} + +proc action_factory_reset_check {} { + global env + + http_head + division {class="popupTitle"} { + puts "\${dialogSettingsSecurityMessagePerformSystemResetTitle}" + } + division {class="CLASS20800"} { + table {class="popupTable"} {border="1"} { + table_row { + table_data { + table {class="CLASS20810"} {width="100%"} { + set system_has_user_key [catch {exec crypttool -v -t 0}] + if { $system_has_user_key } { + table_row { + table_data {colspan="3"} {align="left"} { + puts "\${dialogSetSecKeyRebootHead}" + } + } + table_row { + td {width="20"} {} + table_data {align="left"} { + puts "\${dialogSetSecKeyRebootLbl}" + } + table_data {align="right"} { + cgi_text key= {size="16"} {id="text_key"} {type="password"} + } + } + } else { + table_row { + table_data {colspan="3"} { + puts "\${dialogSettingsSecurityMessagePerformSystemResetContent}" + cgi_put "" + } + } + } + table_row { + table_data {align="right"} {class="CLASS20812"} {colspan="3"} { + division {class="popupControls CLASS20811"} { + division {class="CLASS20813"} {onClick="OnNextStep()"} { + puts "\${dialogSettingsSecurityMessagePerformBtnSystemReset}" + } + } + } + } + } + } + } + } + } + division {class="popupControls"} { + table { + table_row { + table_data {class="CLASS20803"} { + division {class="CLASS20804"} {onClick="showSecurityCP();"} { + puts "\${btnCancel}" + } + } + } + } + } + puts "" + cgi_javascript { + puts "var url = \"$env(SCRIPT_NAME)?sid=\" + SessionId;" + puts { + OnNextStep = function() { + dlgPopup.hide(); + dlgPopup.setWidth(400); + dlgPopup.LoadFromFile(url, "action=factory_reset_go&key="+document.getElementById("text_key").value); + } + } + puts "translatePage('#messagebox');" + } +} + +proc action_factory_reset_go {} { + global env + + http_head + set system_has_user_key [catch {exec crypttool -v -t 0}] + if { $system_has_user_key } { + import key + # check the entered key against our current system key + if { [catch {exec crypttool -v -t 3 -k "$key"}]} { + put_message "\${dialogSetSecKeyRebootFalseTitle}" { + ${dialogSetSecKeyRebootFalseContent} + } {"\${dialogBack}" "showSecurityCP();"} + return + } + } + catch { + exec run-parts -a stop /etc/config/rc.d + } + if { [catch { + exec crypttool -r + # exec umount /usr/local + # exec /usr/sbin/ubidetach -p /dev/mtd6 + # exec /usr/sbin/ubiformat /dev/mtd6 -y + # exec /usr/sbin/ubiattach -p /dev/mtd6 + # exec /usr/sbin/ubimkvol /dev/ubi1 -N user -m + # exec mount /usr/local + + if {[getProduct] < 3 } { + # CCU2 + exec touch /var/doFactoryReset + } else { + exec touch /usr/local/.doFactoryReset + } + exec kill -SIGQUIT 1 + }]} { + + # TWIST-22 + set comment { + division {class="popupTitle"} { + puts "Systemreset: Fehler" + } + division {class="CLASS20800"} { + table {class="popupTable CLASS20801"} {border="1"} { + table_row {class="CLASS20802"} { + table_data { + puts { + Das System konnte nicht auf Werkseinstellungen zurückgesetzt werden. Die Zentrale wird jetzt neu gestartet. + Versuchen Sie es danach bitte erneut.
+ Falls diese Meldung danach wieder erscheint, deinstallieren Sie bitte jegliche Zusatzsoftware, starten die Zentrale neu und versuchen es nocheinmal. + } + } + } + } + } + } + } + + put_message "\${dialogPerformRebootTitle}" { + ${dialogPerformRebootContent} + } {"\${btnNewLogin}" "window.location.href='/';"} + + # Nicht mehr n�tig, siehe TWIST-22 + set comment { + else { + division {class="popupTitle"} { + puts "Systemreset: Neustart des Systems" + } + division {class="CLASS20800"} { + table {class="popupTable CLASS20801"} {border="1"} { + table_row {class="CLASS20802"} { + table_data { + puts { + Das System wurde auf Werkseinstellungen zurückgesetzt. Die Zentrale wird jetzt neu gestartet. + Bitte melden Sie sich nach dem Starten der Zentrale neu an. + } + } + } + } + } + } + division {class="popupControls"} { + table { + table_row { + table_data {class="CLASS20803" align="center"} { + division {class="CLASS20804"} {onClick="window.location.href='/';"} { + puts "Neu anmelden" + } + } + } + } + } + } + puts "" + cgi_javascript { + puts "var url = \"$env(SCRIPT_NAME)?sid=\" + SessionId;" + puts { + var pb = "action=reboot"; + var opts = { + postBody: pb, + sendXML: false + }; + new Ajax.Request(url, opts); + } + } +} + +proc action_backup_restore_check {} { + global env + cd /tmp/ + + http_head + set i 0 + if { [catch { + exec tar xf new_config.tar + file delete -force /tmp/new_config.tar + + set config_version [read_version "firmware_version"] + set ccu1_backup false + if { [version_compare $config_version 2.0.0] < 0 } { + set ccu1_backup true + } + + set system_has_user_key [catch {exec crypttool -v -t 0}] + set stored_signature [exec cat signature] + set calculated_signature [exec crypttool -s -t 0