wgctl/modules/identity.module.sh
2026-05-20 21:49:44 +00:00

256 lines
No EOL
8.7 KiB
Bash

#!/usr/bin/env bash
# identity.module.sh — identity file management and peer-name inference
# ===========================================================================
# Path helpers
# ===========================================================================
function identity::path() {
local name="${1:-}"
echo "$(ctx::identities)/${name}.identity"
}
# ===========================================================================
# Existence checks
# ===========================================================================
function identity::exists() {
local name="${1:-}"
json::identity_exists "$(identity::path "$name")" 2>/dev/null
}
function identity::require_exists() {
local name="${1:-}"
if ! identity::exists "$name"; then
log::error "Identity '${name}' not found. Use 'wgctl identity list' to see all identities."
return 1
fi
}
function identity::require_not_exists() {
local name="${1:-}"
if identity::exists "$name"; then
log::error "Identity '${name}' already exists."
return 1
fi
}
# identity::require_exists_for_flag <identity_name>
# Used by commands to validate --identity value before proceeding.
function identity::require_exists_for_flag() {
local identity_name="${1:-}"
[[ -z "$identity_name" ]] && {
log::error "Missing value for --identity"
return 1
}
# Identity may not exist yet for add (it will be created)
# Only require existence for commands that read from it
return 0
}
# identity::require_has_peers <identity_name>
# Used by block/unblock/list to ensure identity has peers to operate on.
function identity::require_has_peers() {
local identity_name="${1:-}"
local peers
peers=$(identity::peers "$identity_name")
if [[ -z "$peers" ]]; then
log::error "Identity '${identity_name}' has no peers"
return 1
fi
}
# ===========================================================================
# Peer name inference
# ===========================================================================
# identity::infer <peer_name>
# Parses a peer name and returns "identity_name|type|index" if it matches
# the naming convention, or empty string if not.
# phone-nuno -> "nuno|phone|1"
# phone-nuno-2 -> "nuno|phone|2"
# roboclean -> "" (no type prefix)
function identity::infer() {
local peer_name="${1:-}"
json::identity_infer "$peer_name" 2>/dev/null || true
}
# identity::next_index <identity_name> <type>
# Returns the next available device index for a type within an identity.
# If identity doesn't exist yet, returns 1.
function identity::next_index() {
local identity_name="${1:-}" peer_type="${2:-}"
local id_file
id_file=$(identity::path "$identity_name")
if [[ ! -f "$id_file" ]]; then
echo 1
return 0
fi
json::identity_next_index "$id_file" "$peer_type" 2>/dev/null || echo 1
}
# identity::next_peer_name <identity_name> <type>
# Returns the full peer name for the next device of a given type
# for an identity. Creates the name with the correct index.
# e.g. identity::next_peer_name helena phone → phone-helena-2
# (if phone-helena already exists, index 1 is taken)
function identity::next_peer_name() {
local identity_name="${1:-}" peer_type="${2:-}"
[[ -z "$identity_name" || -z "$peer_type" ]] && return 1
local index
index=$(identity::next_index "$identity_name" "$peer_type")
if [[ "$index" -eq 1 ]]; then
echo "${peer_type}-${identity_name}"
else
echo "${peer_type}-${identity_name}-${index}"
fi
}
# ===========================================================================
# Auto-attach (called from wgctl add)
# ===========================================================================
# identity::auto_attach <peer_name> <peer_type>
# Infers identity from peer name and adds the peer to the identity file.
# Creates the identity file if it doesn't exist.
# Silent — no output. Logs a note on success, silently skips if no match.
function identity::auto_attach() {
local peer_name="${1:-}" peer_type="${2:-}"
local inferred
inferred=$(identity::infer "$peer_name")
[[ -z "$inferred" ]] && return 0
local identity_name type_inferred index
identity_name=$(echo "$inferred" | cut -d'|' -f1)
type_inferred=$(echo "$inferred" | cut -d'|' -f2)
index=$(echo "$inferred" | cut -d'|' -f3)
# Use the explicit type if provided, otherwise use inferred type
local final_type="${peer_type:-$type_inferred}"
local id_file
id_file=$(identity::path "$identity_name")
json::identity_add_peer "$id_file" "$identity_name" "$peer_name" "$final_type" "$index" </dev/null
log::info "Attached '${peer_name}' to identity '${identity_name}' (${final_type} #${index})"
}
# identity::auto_detach <peer_name>
# Removes a peer from its identity file when the peer is deleted.
# If the identity has no remaining peers, removes the identity file too.
function identity::auto_detach() {
local peer_name="${1:-}"
local inferred
inferred=$(identity::infer "$peer_name")
[[ -z "$inferred" ]] && return 0
local identity_name
identity_name=$(echo "$inferred" | cut -d'|' -f1)
local id_file
id_file=$(identity::path "$identity_name")
[[ ! -f "$id_file" ]] && return 0
json::identity_remove_peer "$id_file" "$peer_name" </dev/null
# Remove identity file if now empty
local remaining
remaining=$(json::identity_peers "$id_file" 2>/dev/null) || true
if [[ -z "$remaining" ]]; then
rm -f "$id_file"
log::info "Identity '${identity_name}' removed (no remaining peers)"
fi
}
# ===========================================================================
# Peer queries
# ===========================================================================
# identity::peers <identity_name> [type_filter]
# Returns peer names belonging to an identity, one per line.
# Optional type_filter limits to peers of a specific type.
function identity::peers() {
local identity_name="${1:-}" type_filter="${2:-}"
local id_file
id_file=$(identity::path "$identity_name")
json::identity_peers "$id_file" "$type_filter" 2>/dev/null || true
}
# identity::get_name <peer_name>
# Returns the identity name for a given peer (via inference).
function identity::get_name() {
local peer_name="${1:-}"
local inferred
inferred=$(identity::infer "$peer_name")
[[ -n "$inferred" ]] && echo "${inferred%%|*}"
}
# ===========================================================================
# Data for commands
# ===========================================================================
function identity::list_data() {
json::identity_list "$(ctx::identities)" 2>/dev/null || true
}
function identity::show_data() {
local name="${1:-}"
json::identity_show "$(identity::path "$name")" 2>/dev/null
}
# ===========================================================================
# Rename helper (called from rename.command.sh)
# ===========================================================================
# identity::rename_peer <old_peer_name> <new_peer_name>
# Updates identity file entry when a peer is renamed.
# Re-infers identity from old name, removes old entry, adds new entry.
function identity::rename_peer() {
local old_name="${1:-}" new_name="${2:-}"
local old_inferred
old_inferred=$(identity::infer "$old_name")
[[ -z "$old_inferred" ]] && return 0
local identity_name old_type old_index
identity_name=$(echo "$old_inferred" | cut -d'|' -f1)
old_type=$(echo "$old_inferred" | cut -d'|' -f2)
old_index=$(echo "$old_inferred" | cut -d'|' -f3)
local id_file
id_file=$(identity::path "$identity_name")
[[ ! -f "$id_file" ]] && return 0
# Infer new identity context from new name
local new_inferred new_identity new_type new_index
new_inferred=$(identity::infer "$new_name")
if [[ -n "$new_inferred" ]]; then
new_identity=$(echo "$new_inferred" | cut -d'|' -f1)
new_type=$(echo "$new_inferred" | cut -d'|' -f2)
new_index=$(echo "$new_inferred" | cut -d'|' -f3)
else
# New name doesn't match convention — detach cleanly
json::identity_remove_peer "$id_file" "$old_name" </dev/null
return 0
fi
# Remove old entry
json::identity_remove_peer "$id_file" "$old_name" </dev/null
if [[ "$new_identity" == "$identity_name" ]]; then
# Same identity — update in place
json::identity_add_peer "$id_file" "$identity_name" "$new_name" "$new_type" "$new_index" </dev/null
else
# Identity changed (e.g. phone-nuno -> phone-helena) — move to new identity file
local new_id_file
new_id_file=$(identity::path "$new_identity")
json::identity_add_peer "$new_id_file" "$new_identity" "$new_name" "$new_type" "$new_index" </dev/null
# Clean up old identity if empty
local remaining
remaining=$(json::identity_peers "$id_file" 2>/dev/null) || true
if [[ -z "$remaining" ]]; then
rm -f "$id_file"
fi
fi
}