diff --git a/packages/ns-api/files/ns.backup b/packages/ns-api/files/ns.backup index 65f8f8fb7..b8bd4a9fc 100755 --- a/packages/ns-api/files/ns.backup +++ b/packages/ns-api/files/ns.backup @@ -170,8 +170,14 @@ elif cmd == 'call': print(json.dumps(utils.generic_error(f'remote list failed'))) elif action == 'registered-backup': - if os.path.exists(PASSPHRASE_PATH): - print(utils.validation_error('passphrase', 'missing')) + if not os.path.exists(PASSPHRASE_PATH): + # Refuse the call before running sysupgrade/uploading, and emit + # valid JSON so the HTTP API wraps it as a 422 ValidationError + # the UI can render (the previous form printed a Python dict + # repr, which was silently dropped upstream and caused the run + # modal to stay open after a successful upload). + print(json.dumps(utils.validation_error('passphrase', 'missing'))) + sys.exit(0) try: # create backup file_name = create_backup() @@ -225,10 +231,12 @@ elif cmd == 'call': elif action == 'registered-delete-backup': try: data = json.load(sys.stdin) - p = subprocess.run(['/usr/sbin/remote-backup', 'delete', data['id']], + subprocess.run(['/usr/sbin/remote-backup', 'delete', data['id']], check=True, capture_output=True, text=True) - # return content - print(p.stdout) + # The remote side returns a structured JSON response; the UI + # only needs a success flag, matching the pattern of the + # other registered-* handlers (backup, restore). + print(json.dumps({'message': 'success'})) except subprocess.CalledProcessError as error: print(json.dumps(utils.generic_error('remote backup delete failed'))) except KeyError as error: diff --git a/packages/ns-phonehome/files/phonehome b/packages/ns-phonehome/files/phonehome index 08f64b71a..95e92e1e2 100755 --- a/packages/ns-phonehome/files/phonehome +++ b/packages/ns-phonehome/files/phonehome @@ -36,6 +36,15 @@ for func in dir(inventory): if func.startswith("info_"): info[func.removeprefix('info_')] = method(EUci()) +# Migration fingerprint. Populated only on enterprise units that went +# through migrate-to-my or the native my register — my uses this to +# track which units have already rotated off the translation proxy +# and decide when the proxy can be decommissioned. +migration = { + "from_legacy_system_id": u.get('ns-plug', 'config', 'legacy_system_id', default='') or None, + "migrated_at": u.get('ns-plug', 'config', 'migrated_at', default='') or None, +} + data = { "$schema": "https://schema.nethserver.org/facts/2022-12.json", "uuid": sid, @@ -61,7 +70,8 @@ data = { }, "pci": list(pci.values()), "mountpoints": mount_points, - "features": features + "features": features, + "migration": migration } } diff --git a/packages/ns-plug/Makefile b/packages/ns-plug/Makefile index b1715109b..ccfe69917 100644 --- a/packages/ns-plug/Makefile +++ b/packages/ns-plug/Makefile @@ -21,7 +21,7 @@ define Package/ns-plug CATEGORY:=NethSecurity TITLE:=NethSecurity controller client URL:=https://github.com/NethServer/nethsecurity-controller/ - DEPENDS:=+openvpn +lscpu +python3-nethsec +python3-yaml + DEPENDS:=+openvpn +lscpu +python3-nethsec +python3-yaml +jq PKGARCH:=all endef @@ -75,6 +75,7 @@ define Package/ns-plug/install $(INSTALL_BIN) ./files/ns-plug.init $(1)/etc/init.d/ns-plug $(INSTALL_BIN) ./files/ns-plug $(1)/usr/sbin/ns-plug $(INSTALL_BIN) ./files/distfeed-setup $(1)/usr/sbin/distfeed-setup + $(INSTALL_BIN) ./files/migrate-to-my $(1)/usr/sbin $(INSTALL_BIN) ./files/remote-backup $(1)/usr/sbin $(INSTALL_BIN) ./files/send-backup $(1)/usr/sbin $(INSTALL_BIN) ./files/send-heartbeat $(1)/usr/sbin diff --git a/packages/ns-plug/files/config b/packages/ns-plug/files/config index 9f32f0262..fd208ddf7 100644 --- a/packages/ns-plug/files/config +++ b/packages/ns-plug/files/config @@ -5,6 +5,7 @@ config main 'config' option unit_name '' option tls_verify '1' option backup_url 'https://backupd.nethesis.it' + option collect_url 'https://my.nethesis.it/collect/api/systems' option repository_url 'https://updates.nethsecurity.nethserver.org' option channel '' option tun_mtu '' diff --git a/packages/ns-plug/files/migrate-to-my b/packages/ns-plug/files/migrate-to-my new file mode 100644 index 000000000..c8f96d02d --- /dev/null +++ b/packages/ns-plug/files/migrate-to-my @@ -0,0 +1,91 @@ +#!/bin/sh + +# +# Copyright (C) 2026 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +# +# Idempotent one-shot migration from legacy my.nethesis.it / backupd +# credentials to the my collect native credentials, so the unit can +# authenticate directly against the my collect endpoints. +# +# Only enterprise units are migrated. Community (my.nethserver.com) +# has its own infrastructure and keeps using the legacy send-* +# endpoints — no credential rotation is applicable there. +# +# ns-plug.config.migrated='1' is the persistent marker. It is written +# by this script after a successful rotation, and also by +# /usr/sbin/register when it registers a fresh unit directly against +# the my collect endpoint (so a brand new install never triggers the +# rotation path and never hits /proxy/credentials with unmapped +# credentials). +# +# On the first successful invocation the script: +# 1. Calls the my translation proxy's /proxy/credentials endpoint +# with the legacy Basic-Auth pair and retrieves the mapped my +# system key / secret. +# 2. Writes the new credentials to ns-plug.config.system_id / secret +# and preserves the legacy pair under legacy_system_id / +# legacy_secret (for audit and manual rollback). +# 3. Re-asserts ns-plug.config.collect_url, because /etc/config/ +# ns-plug is a conffile: on registered units opkg keeps the +# user-modified copy across upgrades, so a new default alone +# would not reach them. +# 4. Sets the migrated='1' marker. +# +# The uci commit is atomic — a partial write cannot leave the unit in +# an inconsistent half-migrated state. +# + +# Marker: set only after a successful rotation or a native my register. +[ "$(uci -q get ns-plug.config.migrated)" = "1" ] && exit 0 + +# Community units stay on the legacy my.nethserver.com infrastructure. +TYPE=$(uci -q get ns-plug.config.type) +if [ "$TYPE" != "enterprise" ]; then + exit 0 +fi + +SYSTEM_ID=$(uci -q get ns-plug.config.system_id) +SYSTEM_SECRET=$(uci -q get ns-plug.config.secret) +if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ]; then + # Unregistered unit — nothing to migrate yet. + exit 0 +fi + +# Fetch the mapped my credentials via the translation proxy. +resp=$(/usr/bin/curl --silent --location-trusted --fail-with-body \ + --max-time 30 --retry 2 \ + --user "$SYSTEM_ID:$SYSTEM_SECRET" \ + https://my.nethesis.it/proxy/credentials 2>/dev/null) || { + logger -t migrate-to-my "credential fetch failed; will retry on next run" + exit 0 +} + +new_key=$(echo "$resp" | jq -r '.data.system_key // empty' 2>/dev/null) +new_secret=$(echo "$resp" | jq -r '.data.system_secret // empty' 2>/dev/null) +if [ -z "$new_key" ] || [ -z "$new_secret" ]; then + logger -t migrate-to-my "credentials missing in response" + exit 0 +fi + +# Timestamp the rotation so phonehome can publish the event and my +# can plot the fleet migration curve / decide when the translation +# proxy can be decommissioned. +migrated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ) + +# Rotate atomically; legacy pair preserved for audit / rollback. +uci -q batch </dev/null) ;; enterprise) - url="https://my.nethesis.it/api/" + url="https://my.nethesis.it/backend/api/" - system_id=$(curl -s -m $timeout --retry 3 -L \ + register_resp=$(curl -s -m $timeout --retry 3 -L \ -H "Content-Type: application/json" -H "Accept: application/json" \ - -d '{"secret": "'$secret'"}' "${url}systems/info" | jq -r ".uuid" 2>/dev/null) + -d '{"system_secret": "'$secret'"}' "${url}systems/register") + + system_id=$(echo "$register_resp" | jq -r '.data.system_key // empty' 2>/dev/null) ;; *) exit_error "Invalid type '$type'" @@ -68,9 +75,12 @@ case "$type" in ;; enterprise) uci set ns-plug.config.type="enterprise" - uci set ns-plug.config.alerts_url="https://my.nethesis.it/isa/" uci set ns-plug.config.api_url="$url" - uci set ns-plug.config.inventory_url="https://my.nethesis.it/isa/inventory/store/" + uci set ns-plug.config.collect_url="https://my.nethesis.it/collect/api/systems" + # Native my register: no legacy credentials to rotate, so + # mark the unit as already migrated. migrate-to-my will be a + # no-op on every subsequent run. + uci set ns-plug.config.migrated="1" ;; esac diff --git a/packages/ns-plug/files/remote-backup b/packages/ns-plug/files/remote-backup index d3e283e0d..829ee40b3 100755 --- a/packages/ns-plug/files/remote-backup +++ b/packages/ns-plug/files/remote-backup @@ -6,8 +6,20 @@ # # -# Manage remote backup +# Manage configuration backups. # +# Enterprise units (type=enterprise) talk to my collect after the +# migrate-to-my credential rotation. Community units (type=community) +# keep using the legacy backupd.nethesis.it endpoint with the same +# URL layout they have always used — backupd still accepts both +# tenants behind the $TYPE/api/v2/backup/ path. +# +# Pipefail so the curl exit status survives the jq stage in `list`; +# without it a HTTP error on the server would be masked by a successful +# jq parse and ns.backup would report success to the UI. +# + +set -o pipefail function exit_error { >&2 echo "[ERROR] $@" @@ -15,55 +27,105 @@ function exit_error { } function help { - >&2 echo "Usage: $0 " + >&2 echo "Usage: $0 " >&2 echo "Commands:" - >&2 echo " - list: retrieve the list of available backups from remote server" - >&2 echo " - download [output]: download the given backup, if 'output' is empty downloaded file will be named as as 'file'" - >&2 echo " - upload : upload the given backup" + >&2 echo " - list: fetch the list of backups stored for this system" + >&2 echo " - download [output]: download the backup ; defaults to writing to a file named " + >&2 echo " - upload : upload a backup file" + >&2 echo " - delete : remove the backup " } SYSTEM_ID=$(uci -q get ns-plug.config.system_id) SYSTEM_SECRET=$(uci -q get ns-plug.config.secret) TYPE=$(uci -q get ns-plug.config.type) -URL=$(uci -q get ns-plug.config.backup_url) -if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ] || [ -z "$URL" ]; then - exit_error "System ID, system secret or backup url not found. Please configure ns-plug." +if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ]; then + exit_error "System ID or system secret not found. Please configure ns-plug." +fi + +cmd=${1:-list} + +if [ "$TYPE" = "enterprise" ]; then + /usr/sbin/migrate-to-my + + COLLECT_URL=$(uci -q get ns-plug.config.collect_url) + if [ -z "$COLLECT_URL" ]; then + exit_error "Collect URL not set. Pre-migration unit — retry later." + fi + + BASE="$COLLECT_URL/backups" + # --fail-with-body: exit 22 on 4xx/5xx while still writing the body + # so the caller can inspect the error payload. + curl_args="--silent --location-trusted --fail-with-body --user $SYSTEM_ID:$SYSTEM_SECRET" + + case "$cmd" in + list) + # my returns {code, message, data: {backups: [...]}} on + # success. Unwrap `data` so ns.backup can pass it through as + # {values: } without double-nesting. Fall back to an + # empty list on failure. + response=$(curl $curl_args "$BASE") + echo "$response" | jq 'if .data and (.data.backups // empty) then .data else {backups: []} end' + ;; + download) + file=$2 + [ -z "$file" ] && exit_error "No file specified" + output=${3-$file} + curl $curl_args -o "$output" "$BASE/$file" + ;; + upload) + file=$2 + [ -z "$file" ] && exit_error "No file specified" + curl $curl_args -X POST \ + -H "Content-Type: application/octet-stream" \ + -H "X-Filename: $(basename "$file")" \ + --data-binary "@$file" \ + "$BASE" + ;; + delete) + file=$2 + [ -z "$file" ] && exit_error "No file specified" + curl $curl_args -X DELETE "$BASE/$file" + ;; + *) + help + ;; + esac + + exit $? +fi + +# Community (legacy): backupd.nethesis.it with the /$TYPE/api/v2/backup/ +# URL layout. Unchanged from the pre-migration behaviour. +URL=$(uci -q get ns-plug.config.backup_url) +if [ -z "$URL" ]; then + exit_error "Backup URL not set. Please configure ns-plug." fi curl_args="--silent --location-trusted --user $SYSTEM_ID:$SYSTEM_SECRET" base_url="$URL/$TYPE/api/v2/backup/" -cmd=${1:-list} - case "$cmd" in list) curl $curl_args $base_url ;; download) file=$2 - if [ -z "$file" ]; then - exit_error "No file specified" - fi + [ -z "$file" ] && exit_error "No file specified" output=${3-$file} curl $curl_args $base_url$file -J -o "$output" - ;; - upload) - file=$2 - if [ -z "$file" ]; then - exit_error "No file specified" - fi - curl $curl_args $base_url --upload-file $file - ;; - delete) - file=$2 - if [ -z "$file" ]; then - exit_error "No file specified" - fi - curl $curl_args -X DELETE $base_url$file - ;; - - *) - help - ;; + ;; + upload) + file=$2 + [ -z "$file" ] && exit_error "No file specified" + curl $curl_args $base_url --upload-file $file + ;; + delete) + file=$2 + [ -z "$file" ] && exit_error "No file specified" + curl $curl_args -X DELETE $base_url$file + ;; + *) + help + ;; esac diff --git a/packages/ns-plug/files/send-backup b/packages/ns-plug/files/send-backup index fe92dabd0..4ba8eaf5d 100755 --- a/packages/ns-plug/files/send-backup +++ b/packages/ns-plug/files/send-backup @@ -38,6 +38,12 @@ if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ]; then exit 0 fi +# remote-backup handles the enterprise/community branching and calls +# migrate-to-my when needed; this script just prepares the payload and +# delegates the upload. An enterprise unit still waiting on the +# migration surfaces its error through remote-backup, which is caught +# by set -e above. + # hack: avoid to backup non-config file if [ -f /etc/acme/http.header ]; then mv /etc/acme/http.header /tmp diff --git a/packages/ns-plug/files/send-heartbeat b/packages/ns-plug/files/send-heartbeat index 36b328349..8f4d02c0e 100755 --- a/packages/ns-plug/files/send-heartbeat +++ b/packages/ns-plug/files/send-heartbeat @@ -5,11 +5,19 @@ # SPDX-License-Identifier: GPL-2.0-only # -# Send the heartbeat +# Send the heartbeat. +# +# Enterprise units (type=enterprise) post to the my collect endpoint +# with the rotated my credentials; migrate-to-my runs up front so a +# unit upgraded from the legacy my.nethesis.it path transparently +# flips over on the first successful rotation. +# +# Community units (type=community) are left on the legacy +# my.nethserver.com heartbeat path — that infrastructure has no +# counterpart on the new my and is out of scope for this migration. SYSTEM_ID=$(uci -q get ns-plug.config.system_id) SYSTEM_SECRET=$(uci -q get ns-plug.config.secret) -URL=$(uci -q get ns-plug.config.alerts_url)"heartbeats/store" TYPE=$(uci -q get ns-plug.config.type) if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ]; then @@ -17,14 +25,26 @@ if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ]; then exit 0 fi -/usr/bin/curl -m 180 --retry 3 -L -s \ - --header "Authorization: token $SYSTEM_SECRET" --header "Content-Type: application/json" --header "Accept: application/json" \ - --data-raw '{"lk": "'$SYSTEM_ID'"}' "$URL" >/dev/null +case "$TYPE" in + enterprise) + /usr/sbin/migrate-to-my -# Temporary send data to new endpoint -# To be removed when the migration to new my.nethesis.it will be completed -if [ "$TYPE" = "enterprise" ]; then - /usr/bin/curl -m 180 --retry 3 -L -s -X POST \ - --user "$SYSTEM_ID:$SYSTEM_SECRET" https://my.nethesis.it/proxy/heartbeat >/dev/null - exit 0 -fi + COLLECT_URL=$(uci -q get ns-plug.config.collect_url) + if [ -z "$COLLECT_URL" ]; then + # Pre-migration — retry on next tick. + exit 0 + fi + + /usr/bin/curl -m 30 --retry 3 -L -s -X POST \ + --user "$SYSTEM_ID:$SYSTEM_SECRET" \ + "$COLLECT_URL/heartbeat" >/dev/null + ;; + community) + URL=$(uci -q get ns-plug.config.alerts_url)"heartbeats/store" + /usr/bin/curl -m 180 --retry 3 -L -s \ + --header "Authorization: token $SYSTEM_SECRET" \ + --header "Content-Type: application/json" \ + --header "Accept: application/json" \ + --data-raw '{"lk": "'$SYSTEM_ID'"}' "$URL" >/dev/null + ;; +esac diff --git a/packages/ns-plug/files/send-inventory b/packages/ns-plug/files/send-inventory index a670e9925..b6658f672 100755 --- a/packages/ns-plug/files/send-inventory +++ b/packages/ns-plug/files/send-inventory @@ -5,11 +5,16 @@ # SPDX-License-Identifier: GPL-2.0-only # -# Send the inventory +# Send the inventory. +# +# Enterprise units post to my collect with the rotated my credentials; +# community units keep using the legacy my.nethserver.com inventory +# path. See send-heartbeat for the rationale — the two flows are kept +# parallel so the community infrastructure is never touched by the my +# migration. SYSTEM_ID=$(uci -q get ns-plug.config.system_id) SYSTEM_SECRET=$(uci -q get ns-plug.config.secret) -URL=$(uci -q get ns-plug.config.inventory_url) TYPE=$(uci -q get ns-plug.config.type) if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ]; then @@ -17,28 +22,40 @@ if [ -z "$SYSTEM_ID" ] || [ -z "$SYSTEM_SECRET" ]; then exit 0 fi -echo "{\"data\": {\"lk\": \"$SYSTEM_ID\", \"data\": $(/usr/sbin/inventory) }}" | \ -/usr/bin/curl -m 180 --retry 5 -L -s \ - --header "Authorization: token $SYSTEM_SECRET" --header "Content-Type: application/json" --header "Accept: application/json" \ - --data-binary @- "$URL" > /dev/null - -if [ $? -ne 0 ]; then - status="error" -else - status="success" -fi +status="error" + +case "$TYPE" in + enterprise) + /usr/sbin/migrate-to-my + + COLLECT_URL=$(uci -q get ns-plug.config.collect_url) + if [ -z "$COLLECT_URL" ]; then + # Pre-migration — retry on next tick. + exit 0 + fi + + /usr/sbin/phonehome | /usr/bin/curl -m 180 --retry 3 -L -s -X POST \ + --user "$SYSTEM_ID:$SYSTEM_SECRET" \ + -H "Content-Type: application/json" \ + --data-binary @- "$COLLECT_URL/inventory" >/dev/null + + if [ $? -eq 0 ]; then + status="success" + fi + ;; + community) + URL=$(uci -q get ns-plug.config.inventory_url) + echo "{\"data\": {\"lk\": \"$SYSTEM_ID\", \"data\": $(/usr/sbin/inventory) }}" | \ + /usr/bin/curl -m 180 --retry 5 -L -s \ + --header "Authorization: token $SYSTEM_SECRET" \ + --header "Content-Type: application/json" \ + --header "Accept: application/json" \ + --data-binary @- "$URL" > /dev/null + + if [ $? -eq 0 ]; then + status="success" + fi + ;; +esac echo '{"status": "'$status'", "last_attempt": "'$(date -Iseconds)'"}' > /tmp/inventory-sent.json - -if [ "$TYPE" = "enterprise" ]; then - # Update registration date - /usr/bin/curl -m 180 --retry 5 -L -s \ - --header "Content-Type: application/json" --header "Accept: application/json" \ - -d '{"secret":"'$SYSTEM_SECRET'"}' https://my.nethesis.it/api/systems/info >/dev/null - - # Temporary send data to new endpoint - # To be removed when the migration to new my.nethesis.it will be completed - /usr/sbin/phonehome | /usr/bin/curl -m 180 --retry 3 -L -s --user "$SYSTEM_ID:$SYSTEM_SECRET" \ - -H "Content-Type: application/json" \ - --data-binary @- https://my.nethesis.it/proxy/inventory >/dev/null || : -fi diff --git a/packages/ns-plug/files/subscription-info b/packages/ns-plug/files/subscription-info index d78d3cbdd..e0d60ecad 100755 --- a/packages/ns-plug/files/subscription-info +++ b/packages/ns-plug/files/subscription-info @@ -6,8 +6,16 @@ # # -# Retrieve subscription information -# The script takes an optional timeout parameter +# Retrieve subscription information. +# +# Enterprise units query the my collect /info endpoint with their +# native credentials and synthesise a payload shaped like the legacy +# my-old /api/systems/info response the UI expects. The subscription +# plan / expiration fields are left empty — the new my data model +# no longer tracks them at the system level; the ns.subscription +# info handler falls back to "-" / 0 / "active" in those cases. +# +# Community units keep hitting my.nethserver.com as before. # timeout=${1:-20} @@ -15,20 +23,49 @@ timeout=${1:-20} system_id=$(uci -q get ns-plug.config.system_id) if [ -z "$system_id" ]; then - # no subscription echo '{"uuid": ""}' exit 0 fi type=$(uci -q get ns-plug.config.type) secret=$(uci -q get ns-plug.config.secret) -url=$(uci -q get ns-plug.config.api_url | sed 's/\/$//') if [ "$type" = "enterprise" ]; then - curl -f -s -m $timeout --retry-delay 1 --retry 2 -L \ - -H "Content-Type: application/json" -H "Accept: application/json" \ - -d '{"secret": "'$secret'"}' "$url/systems/info" + /usr/sbin/migrate-to-my + + collect_url=$(uci -q get ns-plug.config.collect_url) + if [ -z "$collect_url" ]; then + # Pre-migration unit — nothing to report yet; caller falls + # back to a default payload built from ns-plug.config. + exit 1 + fi + + # /info returns {code, message, data: {system_id, registered, + # registered_at, suspended, organization: {name, ...}, ...}}. + # Translate to the legacy shape the UI/info handler parses. + resp=$(/usr/bin/curl -f -s -m $timeout --retry-delay 1 --retry 2 -L \ + -H "Accept: application/json" \ + --user "$system_id:$secret" \ + "$collect_url/info") || exit $? + + jq -c ' + .data as $s | + { + uuid: ($s.system_id // ""), + id: ($s.system_key // ""), + subscription: { + status: (if $s.registered and (($s.suspended // false) | not) then "valid" else "invalid" end), + valid_until: null, + subscription_plan: { name: ($s.organization.name // "-") } + } + } + ' </dev/null +# Release the legacy slot on my-old for migrated enterprise units — +# the /api/Utils/freekey PHP endpoint still exists on my-ent and lets +# the old dashboard record the unit as gone. Native enterprise units +# have no legacy slot to release (registered directly on my collect), +# and community has no freekey equivalent on dartagnan. +if [ "$TYPE" = "enterprise" ]; then + LEGACY_ID=$(uci -q get ns-plug.config.legacy_system_id) + LEGACY_SECRET=$(uci -q get ns-plug.config.legacy_secret) + if [ -n "$LEGACY_ID" ] && [ -n "$LEGACY_SECRET" ]; then + curl -s -m 180 --retry 3 -L \ + -H "Content-type: application/json" -H "Accept: application/json" \ + -d "{\"lk\":\"$LEGACY_ID\",\"secret\":\"$LEGACY_SECRET\"}" \ + https://my.nethesis.it/api/Utils/freekey >/dev/null + fi +fi # Reset ns-plug configuration uci set ns-plug.config.type="" @@ -28,6 +42,14 @@ uci set ns-plug.config.inventory_url="" uci set ns-plug.config.system_id="" uci set ns-plug.config.secret="" uci set ns-plug.config.repository_url="https://updates.nethsecurity.nethserver.org/$(cat /etc/repo-channel)" +# Drop the enterprise migration fingerprint so a subsequent register +# starts from a clean slate and migrate-to-my can run again if the +# unit re-registers with legacy credentials. +uci -q delete ns-plug.config.collect_url +uci -q delete ns-plug.config.migrated +uci -q delete ns-plug.config.migrated_at +uci -q delete ns-plug.config.legacy_system_id +uci -q delete ns-plug.config.legacy_secret # Save config uci commit ns-plug