feat(ota): prod/beta channel names for ota branch, with stable/dev aliases
Report the selected channel and the build's own channel, tag each baked-in base for CI, and list ota branch in the CLI docs and portal autocomplete.
This commit is contained in:
1 parent
cf98ea5d99
commit
fc182b6f8c
5 files changed
+72
-37
No files matched your search
@@ -88,6 +88,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
- `start ota` — serves the ElegantOTA web upload page on the station IP if joined to a Wi-Fi network, otherwise raises the `MeshCore-OTA` Wi-Fi hotspot.
|
||||
- `start ota ap` — always raises the `MeshCore-OTA` Wi-Fi hotspot, even when joined to a network. Use this when the network applies client isolation and the station IP isn't reachable.
|
||||
|
||||
### Switch the OTA release channel (observer builds)
|
||||
|
||||
- `ota branch` — shows the selected channel, the channel this build was made for, and the manifest base `ota check`/`ota update` will use.
|
||||
- `ota branch prod` (alias `stable`) / `ota branch beta` (alias `dev`) — pull future `ota update`s from that channel. The selection is saved; run `ota update` to switch.
|
||||
- `ota branch default` — follow the channel this build was made for.
|
||||
|
||||
---
|
||||
|
||||
### Erase/Factory Reset
|
||||
|
||||
@@ -1132,7 +1132,7 @@ bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command,
|
||||
strcpy(reply, "ERR: online OTA not supported on this build");
|
||||
#endif
|
||||
return true;
|
||||
} else if (memcmp(command, "ota branch", 10) == 0) {
|
||||
} else if (memcmp(command, "ota branch", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
|
||||
// Switch (or report) the OTA release channel this device pulls from. The
|
||||
// selection is persisted (NodePrefs::ota_channel) and resolved to a baked-in
|
||||
// base URL by ota_resolve_base(); it changes only WHERE updates are fetched,
|
||||
@@ -1142,18 +1142,17 @@ bool CommonCLI::handleObserverCommand(uint32_t sender_timestamp, char* command,
|
||||
const char* arg = command + 10;
|
||||
while (*arg == ' ') arg++;
|
||||
if (*arg == 0) {
|
||||
snprintf(reply, 160, "channel: %s (%s), base %s",
|
||||
ota_channel_name(_prefs->ota_channel),
|
||||
_prefs->ota_channel == OTA_CH_NATIVE ? "native" : "override",
|
||||
snprintf(reply, 160, "channel: %s (build: %s), base %s",
|
||||
ota_channel_name(_prefs->ota_channel), ota_native_channel_name(),
|
||||
ota_resolve_base(_prefs->ota_channel));
|
||||
} else {
|
||||
uint8_t ch;
|
||||
if (!ota_parse_channel(arg, &ch)) {
|
||||
strcpy(reply, "ERR: usage ota branch [stable|dev|default]");
|
||||
strcpy(reply, "ERR: usage ota branch [prod|beta|default] (aliases: stable, dev)");
|
||||
} else {
|
||||
_prefs->ota_channel = ch;
|
||||
savePrefs();
|
||||
snprintf(reply, 160, "channel set to %s, base %s",
|
||||
snprintf(reply, 160, "channel set to %s, base %s; run ota update to switch",
|
||||
ota_channel_name(ch), ota_resolve_base(ch));
|
||||
}
|
||||
}
|
||||
|
||||
+42
-26
@@ -5,36 +5,44 @@
|
||||
// OTA release-channel selector, persisted in NodePrefs::ota_channel.
|
||||
enum OtaChannel : uint8_t {
|
||||
OTA_CH_NATIVE = 0, // follow the channel this build was made for
|
||||
OTA_CH_STABLE = 1,
|
||||
OTA_CH_DEV = 2,
|
||||
OTA_CH_STABLE = 1, // production
|
||||
OTA_CH_DEV = 2, // beta
|
||||
};
|
||||
|
||||
// Resolve the effective manifest base URL for a channel selector.
|
||||
// build.sh injects the three bases as compile-time macros:
|
||||
// OTA_MANIFEST_BASE = this build's native channel (defined on every OTA build)
|
||||
// OTA_MANIFEST_BASE_STABLE = stable channel
|
||||
// OTA_MANIFEST_BASE_DEV = dev channel
|
||||
// OTA_MANIFEST_BASE_STABLE = stable (production) channel
|
||||
// OTA_MANIFEST_BASE_DEV = dev (beta) channel
|
||||
// stable/dev fall back to the native base when their macro is undefined (legacy/local
|
||||
// builds that only define OTA_MANIFEST_BASE), so this never returns nullptr on an
|
||||
// OTA-capable build. On a non-OTA build it returns nullptr.
|
||||
//
|
||||
// Each base is stored behind an "ota-base-<channel>:" tag so CI can read a binary's
|
||||
// channels back with `strings` (scripts/verify_ota_channel.sh). Returning a pointer
|
||||
// into the tagged array keeps the tag referenced, so the linker cannot drop it.
|
||||
#if defined(OTA_MANIFEST_BASE)
|
||||
#define OTA_BASE_TAG_NATIVE "ota-base-native:"
|
||||
#define OTA_BASE_TAG_STABLE "ota-base-stable:"
|
||||
#define OTA_BASE_TAG_DEV "ota-base-dev:"
|
||||
#if !defined(OTA_MANIFEST_BASE_STABLE)
|
||||
#define OTA_MANIFEST_BASE_STABLE OTA_MANIFEST_BASE
|
||||
#endif
|
||||
#if !defined(OTA_MANIFEST_BASE_DEV)
|
||||
#define OTA_MANIFEST_BASE_DEV OTA_MANIFEST_BASE
|
||||
#endif
|
||||
static const char ota_tagged_native[] = OTA_BASE_TAG_NATIVE OTA_MANIFEST_BASE;
|
||||
static const char ota_tagged_stable[] = OTA_BASE_TAG_STABLE OTA_MANIFEST_BASE_STABLE;
|
||||
static const char ota_tagged_dev[] = OTA_BASE_TAG_DEV OTA_MANIFEST_BASE_DEV;
|
||||
#endif
|
||||
|
||||
static inline const char* ota_resolve_base(uint8_t channel) {
|
||||
#if defined(OTA_MANIFEST_BASE)
|
||||
switch (channel) {
|
||||
case OTA_CH_STABLE:
|
||||
#if defined(OTA_MANIFEST_BASE_STABLE)
|
||||
return OTA_MANIFEST_BASE_STABLE;
|
||||
#else
|
||||
return OTA_MANIFEST_BASE;
|
||||
#endif
|
||||
case OTA_CH_DEV:
|
||||
#if defined(OTA_MANIFEST_BASE_DEV)
|
||||
return OTA_MANIFEST_BASE_DEV;
|
||||
#else
|
||||
return OTA_MANIFEST_BASE;
|
||||
#endif
|
||||
case OTA_CH_STABLE: return ota_tagged_stable + sizeof(OTA_BASE_TAG_STABLE) - 1;
|
||||
case OTA_CH_DEV: return ota_tagged_dev + sizeof(OTA_BASE_TAG_DEV) - 1;
|
||||
case OTA_CH_NATIVE:
|
||||
default:
|
||||
return OTA_MANIFEST_BASE;
|
||||
default: return ota_tagged_native + sizeof(OTA_BASE_TAG_NATIVE) - 1;
|
||||
}
|
||||
#else
|
||||
(void)channel;
|
||||
@@ -45,18 +53,26 @@ static inline const char* ota_resolve_base(uint8_t channel) {
|
||||
// Human label for a selector (for the `ota branch` report).
|
||||
static inline const char* ota_channel_name(uint8_t channel) {
|
||||
switch (channel) {
|
||||
case OTA_CH_STABLE: return "stable";
|
||||
case OTA_CH_DEV: return "dev";
|
||||
default: return "native";
|
||||
case OTA_CH_STABLE: return "prod";
|
||||
case OTA_CH_DEV: return "beta";
|
||||
default: return "default";
|
||||
}
|
||||
}
|
||||
|
||||
// Label for the channel this build was made for, by matching its native base.
|
||||
static inline const char* ota_native_channel_name() {
|
||||
const char* native = ota_resolve_base(OTA_CH_NATIVE);
|
||||
if (native == nullptr) return "none";
|
||||
if (strcmp(native, ota_resolve_base(OTA_CH_STABLE)) == 0) return "prod";
|
||||
if (strcmp(native, ota_resolve_base(OTA_CH_DEV)) == 0) return "beta";
|
||||
return "custom";
|
||||
}
|
||||
|
||||
// Parse an `ota branch` argument. Returns true and sets *out on a known keyword
|
||||
// (stable|dev|default; "default" -> native); returns false and leaves *out untouched
|
||||
// otherwise.
|
||||
// (prod|stable, beta|dev, default); returns false and leaves *out untouched otherwise.
|
||||
static inline bool ota_parse_channel(const char* arg, uint8_t* out) {
|
||||
if (strcmp(arg, "stable") == 0) { *out = OTA_CH_STABLE; return true; }
|
||||
if (strcmp(arg, "dev") == 0) { *out = OTA_CH_DEV; return true; }
|
||||
if (strcmp(arg, "default") == 0) { *out = OTA_CH_NATIVE; return true; }
|
||||
if (strcmp(arg, "prod") == 0 || strcmp(arg, "stable") == 0) { *out = OTA_CH_STABLE; return true; }
|
||||
if (strcmp(arg, "beta") == 0 || strcmp(arg, "dev") == 0) { *out = OTA_CH_DEV; return true; }
|
||||
if (strcmp(arg, "default") == 0) { *out = OTA_CH_NATIVE; return true; }
|
||||
return false;
|
||||
}
|
||||
@@ -11,21 +11,33 @@ TEST(OtaChannel, ResolvesStable) {
|
||||
TEST(OtaChannel, ResolvesDev) {
|
||||
EXPECT_STREQ(ota_resolve_base(OTA_CH_DEV), "https://dev.example/mqtt/dev/v");
|
||||
}
|
||||
TEST(OtaChannel, BasesAreStoredBehindCiTags) {
|
||||
EXPECT_STREQ(ota_tagged_native, "ota-base-native:https://stable.example/mqtt/v");
|
||||
EXPECT_STREQ(ota_tagged_stable, "ota-base-stable:https://stable.example/mqtt/v");
|
||||
EXPECT_STREQ(ota_tagged_dev, "ota-base-dev:https://dev.example/mqtt/dev/v");
|
||||
}
|
||||
TEST(OtaChannel, ParseKnownKeywords) {
|
||||
uint8_t ch = 99;
|
||||
EXPECT_TRUE(ota_parse_channel("prod", &ch)); EXPECT_EQ(ch, OTA_CH_STABLE);
|
||||
EXPECT_TRUE(ota_parse_channel("stable", &ch)); EXPECT_EQ(ch, OTA_CH_STABLE);
|
||||
EXPECT_TRUE(ota_parse_channel("dev", &ch)); EXPECT_EQ(ch, OTA_CH_DEV);
|
||||
EXPECT_TRUE(ota_parse_channel("beta", &ch)); EXPECT_EQ(ch, OTA_CH_DEV);
|
||||
EXPECT_TRUE(ota_parse_channel("default", &ch)); EXPECT_EQ(ch, OTA_CH_NATIVE);
|
||||
EXPECT_TRUE(ota_parse_channel("dev", &ch)); EXPECT_EQ(ch, OTA_CH_DEV);
|
||||
}
|
||||
TEST(OtaChannel, ParseRejectsUnknownAndLeavesOutputUntouched) {
|
||||
uint8_t ch = 7;
|
||||
EXPECT_FALSE(ota_parse_channel("beta", &ch));
|
||||
EXPECT_FALSE(ota_parse_channel("production", &ch));
|
||||
EXPECT_FALSE(ota_parse_channel("Beta", &ch));
|
||||
EXPECT_FALSE(ota_parse_channel("", &ch));
|
||||
EXPECT_EQ(ch, 7);
|
||||
}
|
||||
TEST(OtaChannel, NameLabels) {
|
||||
EXPECT_STREQ(ota_channel_name(OTA_CH_NATIVE), "native");
|
||||
EXPECT_STREQ(ota_channel_name(OTA_CH_STABLE), "stable");
|
||||
EXPECT_STREQ(ota_channel_name(OTA_CH_DEV), "dev");
|
||||
EXPECT_STREQ(ota_channel_name(OTA_CH_NATIVE), "default");
|
||||
EXPECT_STREQ(ota_channel_name(OTA_CH_STABLE), "prod");
|
||||
EXPECT_STREQ(ota_channel_name(OTA_CH_DEV), "beta");
|
||||
}
|
||||
TEST(OtaChannel, NativeChannelNameMatchesBase) {
|
||||
EXPECT_STREQ(ota_native_channel_name(), "prod");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -1495,6 +1495,8 @@ var CLI_VERBS=[
|
||||
["alert test","Send a test alert on the configured channel"],
|
||||
["ota check","Check for a newer build (does not flash)"],
|
||||
["ota update","Download and flash the newer build, then reboot"],
|
||||
["ota branch","Show the OTA release channel"],
|
||||
["ota branch ","Set the OTA release channel {prod|beta|default}"],
|
||||
// `start ota` is absent: it binds port 80, which this portal is already using.
|
||||
["start webconfig","Start this portal on the LAN"],
|
||||
["start webconfig ap","Start this portal on its own setup AP"],
|
||||
|
||||
Reference in new issue
Block a user