376 lines
No EOL
12 KiB
Bash
376 lines
No EOL
12 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
|
|
}
|
|
|
|
# identity::rule <identity_name>
|
|
# Returns the rule assigned to an identity, or empty string if none.
|
|
function identity::rule() {
|
|
local identity_name="${1:-}"
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${identity_name}.identity")
|
|
[[ ! -f "$id_file" ]] && return 0
|
|
json::get "$id_file" "rule" 2>/dev/null || true
|
|
}
|
|
|
|
# identity::set_rule <identity_name> <rule_name>
|
|
# Sets the rule on an identity file.
|
|
function identity::set_rule() {
|
|
local identity_name="${1:-}" rule_name="${2:-}"
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${identity_name}.identity")
|
|
if [[ ! -f "$id_file" ]]; then
|
|
log::error "Identity '${identity_name}' not found"
|
|
return 1
|
|
fi
|
|
json::set "$id_file" "rule" "$rule_name"
|
|
}
|
|
|
|
# identity::clear_rule <identity_name>
|
|
# Removes the rule from an identity file.
|
|
function identity::clear_rule() {
|
|
local identity_name="${1:-}"
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${identity_name}.identity")
|
|
[[ ! -f "$id_file" ]] && return 0
|
|
json::delete "$id_file" "rule" 2>/dev/null || true
|
|
}
|
|
|
|
# identity::policy <identity_name>
|
|
# Returns the policy name assigned to an identity, or "default".
|
|
function identity::policy() {
|
|
local identity_name="${1:-}"
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${identity_name}.identity")
|
|
[[ ! -f "$id_file" ]] && echo "default" && return 0
|
|
local result
|
|
result=$(json::get "$id_file" "policy" 2>/dev/null) || true
|
|
echo "${result:-default}"
|
|
}
|
|
|
|
# identity::set_policy <identity_name> <policy_name>
|
|
# Sets the policy on an identity file.
|
|
function identity::set_policy() {
|
|
local identity_name="${1:-}" policy_name="${2:-}"
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${identity_name}.identity")
|
|
if [[ ! -f "$id_file" ]]; then
|
|
log::error "Identity '${identity_name}' not found"
|
|
return 1
|
|
fi
|
|
json::set "$id_file" "policy" "$policy_name"
|
|
}
|
|
|
|
# identity::rule_flags <identity_name> <flag>
|
|
# Returns a specific rule_flag from the identity file.
|
|
# Falls back to the policy's value if not explicitly set on the identity.
|
|
function identity::rule_flags() {
|
|
local identity_name="${1:-}" flag="${2:-}"
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${identity_name}.identity")
|
|
|
|
local result
|
|
result=$(json::get_nested "$id_file" "rule_flags" "$flag" 2>/dev/null) || true
|
|
|
|
if [[ -n "$result" ]]; then
|
|
echo "$result"
|
|
return 0
|
|
fi
|
|
|
|
# Fall back to policy value
|
|
local policy_name
|
|
policy_name=$(identity::policy "$identity_name")
|
|
policy::get "$policy_name" "$flag"
|
|
}
|
|
|
|
# identity::set_rule_flag <identity_name> <flag> <value>
|
|
# Sets a rule_flag directly on the identity file.
|
|
function identity::set_rule_flag() {
|
|
local identity_name="${1:-}" flag="${2:-}" value="${3:-}"
|
|
local id_file
|
|
id_file=$(ctx::identity::path "${identity_name}.identity")
|
|
if [[ ! -f "$id_file" ]]; then
|
|
log::error "Identity '${identity_name}' not found"
|
|
return 1
|
|
fi
|
|
json::set_nested "$id_file" "rule_flags" "$flag" "$value"
|
|
}
|
|
|
|
# identity::reapply_rules <identity_name>
|
|
# Reapply the identity rule to all peers in this identity.
|
|
# Respects auto_apply flag — if false, does nothing.
|
|
function identity::reapply_rules() {
|
|
local identity_name="${1:-}"
|
|
|
|
# Check auto_apply
|
|
local auto
|
|
auto=$(identity::rule_flags "$identity_name" "auto_apply")
|
|
[[ "$auto" == "false" ]] && return 0
|
|
|
|
local identity_rule
|
|
identity_rule=$(identity::rule "$identity_name")
|
|
[[ -z "$identity_rule" ]] && return 0
|
|
|
|
local peers
|
|
peers=$(identity::peers "$identity_name")
|
|
[[ -z "$peers" ]] && return 0
|
|
|
|
while IFS= read -r peer_name; do
|
|
[[ -z "$peer_name" ]] && continue
|
|
local client_ip
|
|
client_ip=$(peers::get_ip "$peer_name") || continue
|
|
rule::full_restore_peer "$peer_name" "$client_ip"
|
|
done <<< "$peers"
|
|
} |