Skip to content

Commit 2bd87da

Browse files
committed
fix: managed-mode install aborted with "Invalid download URL scheme: null"
bashio::config returns the literal string "null" for unset optional fields (upstream bashio's ${2:-null} rewrites an empty default to "null"), so the install script's [[ -z ... ]] check never fired and DOWNLOAD_URL stayed as "null" when cgate_download_url was not overridden. The URL-scheme validator then aborted, breaking managed-mode C-Gate installs. Same latent bug in the SHA256 path. URL and SHA256 resolution are extracted into helpers that treat both empty and "null" as unset, with regression tests via a sourceable bashio stub. Bumps add-on to 1.8.8.
1 parent 269abdc commit 2bd87da

5 files changed

Lines changed: 122 additions & 7 deletions

File tree

homeassistant-addon/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to the C-Gate Web Bridge Home Assistant add-on will be docum
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.8.8] - 2026-05-07
9+
10+
### Fixed
11+
- **Managed mode install failed with "Invalid download URL scheme: null"**: when running the add-on in managed mode without overriding `cgate_download_url`, the install script would log `Downloading C-Gate from: null` and abort. Root cause: `bashio::config 'cgate_download_url' ''` returns the literal string `"null"` for unset optional fields (upstream bashio's `${2:-null}` rewrites an empty default to `"null"`), so the script's `[[ -z … ]]` empty-check never fired and the hardcoded fallback URL was never applied. The install script now treats both empty and `"null"` as unset for `cgate_download_url` and `cgate_download_sha256`, and the URL/SHA resolution is extracted into helpers covered by unit tests.
12+
813
## [1.8.7] - 2026-05-05
914

1015
### Fixed

homeassistant-addon/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: "C-Gate Web Bridge"
2-
version: "1.8.7"
2+
version: "1.8.8"
33
slug: cgateweb
44
description: "Bridge between Clipsal C-Bus systems and MQTT/Home Assistant"
55
url: "https://github.com/dougrathbone/cgateweb"

homeassistant-addon/rootfs/etc/cont-init.d/cgate-install.sh

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,35 @@
33
# Install C-Gate if running in managed mode
44
# ==============================================================================
55

6+
CGATEWEB_DEFAULT_DOWNLOAD_URL="https://download.se.com/files?p_Doc_Ref=C-Gate_3_Linux_Package_V3.3.2"
7+
8+
# bashio::config returns the literal string "null" for unset optional fields,
9+
# even when an empty default is passed (upstream bashio's `${2:-null}` rewrites
10+
# an empty default to "null"). Treat both empty and "null" as unset.
11+
_cgateweb_resolve_download_url() {
12+
local url
13+
url=$(bashio::config 'cgate_download_url')
14+
if [[ -z "${url}" || "${url}" == "null" ]]; then
15+
url="${CGATEWEB_DEFAULT_DOWNLOAD_URL}"
16+
fi
17+
printf '%s' "${url}"
18+
}
19+
20+
_cgateweb_resolve_download_sha256() {
21+
local sha
22+
sha=$(bashio::config 'cgate_download_sha256')
23+
if [[ "${sha}" == "null" ]]; then
24+
sha=""
25+
fi
26+
printf '%s' "${sha}"
27+
}
28+
29+
# Allow tests to source this script for unit testing the helpers above without
30+
# running the install flow.
31+
if [[ "${CGATEWEB_INSTALL_SOURCE_ONLY:-0}" == "1" ]]; then
32+
return 0 2>/dev/null || exit 0
33+
fi
34+
635
CGATE_MODE=$(bashio::config 'cgate_mode' 'remote')
736

837
if [[ "${CGATE_MODE}" != "managed" ]]; then
@@ -13,7 +42,7 @@ fi
1342
CGATE_DIR="/data/cgate"
1443
CGATE_JAR="${CGATE_DIR}/cgate.jar"
1544
INSTALL_SOURCE=$(bashio::config 'cgate_install_source' 'download')
16-
DOWNLOAD_SHA256=$(bashio::config 'cgate_download_sha256' '')
45+
DOWNLOAD_SHA256=$(_cgateweb_resolve_download_sha256)
1746
WORK_DIR=$(mktemp -d /tmp/cgate-install.XXXXXX)
1847

1948
cleanup() {
@@ -32,10 +61,7 @@ bashio::log.info "C-Gate not found, installing from source: ${INSTALL_SOURCE}"
3261
mkdir -p "${CGATE_DIR}"
3362

3463
if [[ "${INSTALL_SOURCE}" == "download" ]]; then
35-
DOWNLOAD_URL=$(bashio::config 'cgate_download_url' '')
36-
if [[ -z "${DOWNLOAD_URL}" ]]; then
37-
DOWNLOAD_URL="https://download.se.com/files?p_Doc_Ref=C-Gate_3_Linux_Package_V3.3.2"
38-
fi
64+
DOWNLOAD_URL=$(_cgateweb_resolve_download_url)
3965

4066
bashio::log.info "Downloading C-Gate from: ${DOWNLOAD_URL}"
4167

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cgateweb",
3-
"version": "1.8.7",
3+
"version": "1.8.8",
44
"description": "Node.js bridge connecting Clipsal C-Bus automation systems to MQTT for Home Assistant integration",
55
"keywords": [
66
"cbus",

tests/cgateInstallScript.test.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const { execFileSync } = require('child_process');
2+
const path = require('path');
3+
4+
const SCRIPT = path.join(
5+
__dirname,
6+
'..',
7+
'homeassistant-addon',
8+
'rootfs',
9+
'etc',
10+
'cont-init.d',
11+
'cgate-install.sh'
12+
);
13+
14+
const DEFAULT_DOWNLOAD_URL = 'https://download.se.com/files?p_Doc_Ref=C-Gate_3_Linux_Package_V3.3.2';
15+
16+
// Real bashio's bashio::config returns the literal string "null" when a key
17+
// is unset, even when the caller passes an empty string as the default
18+
// (because upstream bashio uses `local default_value=${2:-null}`, which
19+
// substitutes "null" for both unset AND empty defaults).
20+
//
21+
// This stub mirrors that behavior. Test config is passed via env vars named
22+
// CGW_TEST_<key>; the stub returns the env value when set or the default
23+
// otherwise — matching real bashio's behavior including the "null" quirk.
24+
const BASHIO_STUB = `
25+
bashio::log.info() { :; }
26+
bashio::log.warning() { :; }
27+
bashio::log.error() { :; }
28+
bashio::log.trace() { :; }
29+
bashio::config() {
30+
local key="$1"
31+
local default_value="\${2:-null}"
32+
local var_name="CGW_TEST_\${key}"
33+
if declare -p "$var_name" &>/dev/null; then
34+
printf '%s' "\${!var_name}"
35+
else
36+
printf '%s' "$default_value"
37+
fi
38+
}
39+
`;
40+
41+
function callHelper(helperName, configObject) {
42+
const env = { ...process.env, CGATEWEB_INSTALL_SOURCE_ONLY: '1' };
43+
for (const [k, v] of Object.entries(configObject || {})) {
44+
env[`CGW_TEST_${k}`] = v;
45+
}
46+
const script = `
47+
set -u
48+
${BASHIO_STUB}
49+
source "${SCRIPT}"
50+
${helperName}
51+
`;
52+
return execFileSync('bash', ['-c', script], { encoding: 'utf8', env });
53+
}
54+
55+
describe('cgate-install.sh helpers', () => {
56+
describe('_cgateweb_resolve_download_url', () => {
57+
test('falls back to default URL when cgate_download_url is unset', () => {
58+
const url = callHelper('_cgateweb_resolve_download_url', {});
59+
expect(url).toBe(DEFAULT_DOWNLOAD_URL);
60+
});
61+
62+
test('uses configured URL when cgate_download_url is set', () => {
63+
const url = callHelper('_cgateweb_resolve_download_url', {
64+
cgate_download_url: 'https://example.com/cgate.zip'
65+
});
66+
expect(url).toBe('https://example.com/cgate.zip');
67+
});
68+
});
69+
70+
describe('_cgateweb_resolve_download_sha256', () => {
71+
test('returns empty string when cgate_download_sha256 is unset', () => {
72+
const sha = callHelper('_cgateweb_resolve_download_sha256', {});
73+
expect(sha).toBe('');
74+
});
75+
76+
test('returns configured checksum when cgate_download_sha256 is set', () => {
77+
const expected = 'a'.repeat(64);
78+
const sha = callHelper('_cgateweb_resolve_download_sha256', {
79+
cgate_download_sha256: expected
80+
});
81+
expect(sha).toBe(expected);
82+
});
83+
});
84+
});

0 commit comments

Comments
 (0)