nuno a révisé ce gist 1 month ago. Aller à la révision
1 file changed, 305 insertions
export.command.sh(fichier créé)
| @@ -0,0 +1,305 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # commands/export.command.sh | |
| 3 | + | ||
| 4 | + | function cmd::export::on_load() { | |
| 5 | + | flag::register --peer | |
| 6 | + | flag::register --identity | |
| 7 | + | flag::register --all | |
| 8 | + | flag::register --out | |
| 9 | + | flag::register --conf-only | |
| 10 | + | flag::register --meta-only | |
| 11 | + | flag::register --no-config | |
| 12 | + | flag::register --no-peers | |
| 13 | + | flag::register --force | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | function cmd::export::help() { | |
| 17 | + | cat <<EOF | |
| 18 | + | Usage: wgctl export [options] | |
| 19 | + | ||
| 20 | + | Export wgctl data as a portable JSON bundle. | |
| 21 | + | ||
| 22 | + | Options: | |
| 23 | + | --peer <name> Export a single peer (conf, meta, groups, identity, blocks) | |
| 24 | + | --identity <name> Export an identity | |
| 25 | + | --all Full backup (all peers, rules, identities, groups, etc.) | |
| 26 | + | --out <file> Write to file instead of stdout | |
| 27 | + | --conf-only Export peer conf only (with --peer) | |
| 28 | + | --meta-only Export peer meta only (with --peer) | |
| 29 | + | --no-config Skip wgctl.json (with --all) | |
| 30 | + | --no-peers Skip peer confs (with --all) | |
| 31 | + | --force Overwrite existing output file | |
| 32 | + | ||
| 33 | + | Examples: | |
| 34 | + | wgctl export --peer phone-nuno | |
| 35 | + | wgctl export --peer phone-nuno --out phone-nuno.json | |
| 36 | + | wgctl export --identity nuno --out nuno.json | |
| 37 | + | wgctl export --all --out backup.json | |
| 38 | + | wgctl export --all --no-config --out data-only.json | |
| 39 | + | EOF | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | function cmd::export::run() { | |
| 43 | + | local peer="" identity="" all=false out="" | |
| 44 | + | local conf_only=false meta_only=false | |
| 45 | + | local no_config=false no_peers=false force=false | |
| 46 | + | ||
| 47 | + | while [[ $# -gt 0 ]]; do | |
| 48 | + | case "$1" in | |
| 49 | + | --peer) peer="$2"; shift 2 ;; | |
| 50 | + | --identity) identity="$2"; shift 2 ;; | |
| 51 | + | --all) all=true; shift ;; | |
| 52 | + | --out) out="$2"; shift 2 ;; | |
| 53 | + | --conf-only) conf_only=true; shift ;; | |
| 54 | + | --meta-only) meta_only=true; shift ;; | |
| 55 | + | --no-config) no_config=true; shift ;; | |
| 56 | + | --no-peers) no_peers=true; shift ;; | |
| 57 | + | --force) force=true; shift ;; | |
| 58 | + | --help) cmd::export::help; return ;; | |
| 59 | + | *) log::error "Unknown flag: $1"; return 1 ;; | |
| 60 | + | esac | |
| 61 | + | done | |
| 62 | + | ||
| 63 | + | # Validate | |
| 64 | + | local mode_count=0 | |
| 65 | + | [[ -n "$peer" ]] && (( mode_count++ )) || true | |
| 66 | + | [[ -n "$identity" ]] && (( mode_count++ )) || true | |
| 67 | + | $all && (( mode_count++ )) || true | |
| 68 | + | ||
| 69 | + | if [[ "$mode_count" -eq 0 ]]; then | |
| 70 | + | log::error "Specify --peer, --identity, or --all" | |
| 71 | + | cmd::export::help | |
| 72 | + | return 1 | |
| 73 | + | fi | |
| 74 | + | if [[ "$mode_count" -gt 1 ]]; then | |
| 75 | + | log::error "Only one of --peer, --identity, --all can be used at a time" | |
| 76 | + | return 1 | |
| 77 | + | fi | |
| 78 | + | ||
| 79 | + | # Check output file | |
| 80 | + | if [[ -n "$out" && -f "$out" && ! $force ]]; then | |
| 81 | + | log::error "Output file already exists: ${out} (use --force to overwrite)" | |
| 82 | + | return 1 | |
| 83 | + | fi | |
| 84 | + | ||
| 85 | + | local json="" | |
| 86 | + | if [[ -n "$peer" ]]; then | |
| 87 | + | json=$(cmd::export::_peer "$peer" "$conf_only" "$meta_only") || return 1 | |
| 88 | + | elif [[ -n "$identity" ]]; then | |
| 89 | + | json=$(cmd::export::_identity "$identity") || return 1 | |
| 90 | + | elif $all; then | |
| 91 | + | json=$(cmd::export::_full "$no_config" "$no_peers") || return 1 | |
| 92 | + | fi | |
| 93 | + | ||
| 94 | + | if [[ -n "$out" ]]; then | |
| 95 | + | echo "$json" > "$out" | |
| 96 | + | log::wg_success "Exported to ${out}" | |
| 97 | + | else | |
| 98 | + | echo "$json" | |
| 99 | + | fi | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | # ====================================================== | |
| 103 | + | # Peer export | |
| 104 | + | # ====================================================== | |
| 105 | + | ||
| 106 | + | function cmd::export::_peer() { | |
| 107 | + | local name="${1:-}" conf_only="${2:-false}" meta_only="${3:-false}" | |
| 108 | + | ||
| 109 | + | peers::require_exists "$name" || return 1 | |
| 110 | + | ||
| 111 | + | local conf_file | |
| 112 | + | conf_file="$(ctx::clients)/${name}.conf" | |
| 113 | + | [[ ! -f "$conf_file" ]] && log::error "Client conf not found: ${conf_file}" && return 1 | |
| 114 | + | ||
| 115 | + | local conf_b64 | |
| 116 | + | conf_b64=$(base64 -w 0 < "$conf_file" 2>/dev/null || base64 < "$conf_file") | |
| 117 | + | ||
| 118 | + | if $conf_only; then | |
| 119 | + | cmd::export::_envelope "peer_conf" \ | |
| 120 | + | "$(printf '{"name":"%s","conf":"%s"}' "$name" "$conf_b64")" | |
| 121 | + | return 0 | |
| 122 | + | fi | |
| 123 | + | ||
| 124 | + | # Meta | |
| 125 | + | local meta_file meta_json="{}" | |
| 126 | + | meta_file="$(ctx::meta)/${name}.meta" | |
| 127 | + | [[ -f "$meta_file" ]] && meta_json=$(cat "$meta_file") | |
| 128 | + | ||
| 129 | + | if $meta_only; then | |
| 130 | + | cmd::export::_envelope "peer_meta" \ | |
| 131 | + | "$(printf '{"name":"%s","meta":%s}' "$name" "$meta_json")" | |
| 132 | + | return 0 | |
| 133 | + | fi | |
| 134 | + | ||
| 135 | + | # Public key | |
| 136 | + | local public_key="" | |
| 137 | + | local key_file | |
| 138 | + | key_file="$(ctx::clients)/${name}_public.key" | |
| 139 | + | [[ -f "$key_file" ]] && public_key=$(cat "$key_file") | |
| 140 | + | ||
| 141 | + | # IP | |
| 142 | + | local ip | |
| 143 | + | ip=$(peers::get_ip "$name") | |
| 144 | + | ||
| 145 | + | # Type | |
| 146 | + | local peer_type | |
| 147 | + | peer_type=$(peers::get_type "$name" 2>/dev/null || echo "") | |
| 148 | + | ||
| 149 | + | # Direct rule | |
| 150 | + | local direct_rule | |
| 151 | + | direct_rule=$(peers::get_meta "$name" "rule" 2>/dev/null || echo "") | |
| 152 | + | ||
| 153 | + | # Identity | |
| 154 | + | local identity | |
| 155 | + | identity=$(peers::get_identity "$name" 2>/dev/null || echo "") | |
| 156 | + | ||
| 157 | + | # Groups | |
| 158 | + | local -a group_list=() | |
| 159 | + | while IFS= read -r g; do | |
| 160 | + | [[ -n "$g" ]] && group_list+=("\"$g\"") | |
| 161 | + | done < <(json::peer_groups "$(ctx::groups)" "$name" 2>/dev/null) | |
| 162 | + | local groups_json="[]" | |
| 163 | + | [[ ${#group_list[@]} -gt 0 ]] && \ | |
| 164 | + | groups_json="[$(printf '%s,' "${group_list[@]}" | sed 's/,$//')]" | |
| 165 | + | ||
| 166 | + | # Blocks | |
| 167 | + | local block_file is_blocked="false" block_json="null" | |
| 168 | + | block_file="$(ctx::blocks)/${name}.block" | |
| 169 | + | if [[ -f "$block_file" ]]; then | |
| 170 | + | is_blocked="true" | |
| 171 | + | block_json=$(base64 -w 0 < "$block_file" 2>/dev/null || base64 < "$block_file") | |
| 172 | + | block_json="\"${block_json}\"" | |
| 173 | + | fi | |
| 174 | + | ||
| 175 | + | local peer_data | |
| 176 | + | peer_data=$(printf \ | |
| 177 | + | '{"name":"%s","ip":"%s","type":"%s","public_key":"%s","conf":"%s","meta":%s,"identity":"%s","groups":%s,"direct_rule":"%s","blocks":{"is_blocked":%s,"block_file":%s}}' \ | |
| 178 | + | "$name" "$ip" "$peer_type" "$public_key" "$conf_b64" \ | |
| 179 | + | "$meta_json" "$identity" "$groups_json" "$direct_rule" \ | |
| 180 | + | "$is_blocked" "$block_json") | |
| 181 | + | ||
| 182 | + | cmd::export::_envelope "peer" "$peer_data" | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | # ====================================================== | |
| 186 | + | # Identity export | |
| 187 | + | # ====================================================== | |
| 188 | + | ||
| 189 | + | function cmd::export::_identity() { | |
| 190 | + | local name="${1:-}" | |
| 191 | + | identity::require_exists "$name" || return 1 | |
| 192 | + | ||
| 193 | + | local id_file | |
| 194 | + | id_file="$(ctx::identities)/${name}.identity" | |
| 195 | + | local id_json | |
| 196 | + | id_json=$(cat "$id_file") | |
| 197 | + | ||
| 198 | + | cmd::export::_envelope "identity" \ | |
| 199 | + | "$(printf '{"name":"%s","identity":%s}' "$name" "$id_json")" | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | # ====================================================== | |
| 203 | + | # Full backup | |
| 204 | + | # ====================================================== | |
| 205 | + | ||
| 206 | + | function cmd::export::_full() { | |
| 207 | + | local no_config="${1:-false}" no_peers="${2:-false}" | |
| 208 | + | local version | |
| 209 | + | version=$(wgctl::version 2>/dev/null || echo "unknown") | |
| 210 | + | ||
| 211 | + | python3 "$(ctx::json_helper)" export_full \ | |
| 212 | + | "$(ctx::clients)" \ | |
| 213 | + | "$(ctx::meta)" \ | |
| 214 | + | "$(ctx::rules)" \ | |
| 215 | + | "$(ctx::identities)" \ | |
| 216 | + | "$(ctx::groups)" \ | |
| 217 | + | "$(ctx::blocks)" \ | |
| 218 | + | "$(ctx::block_history)" \ | |
| 219 | + | "$(ctx::config_file)" \ | |
| 220 | + | "$(ctx::policies)" \ | |
| 221 | + | "$(ctx::subnets)" \ | |
| 222 | + | "$(ctx::net)" \ | |
| 223 | + | "$(ctx::hosts)" \ | |
| 224 | + | "$no_config" \ | |
| 225 | + | "$no_peers" \ | |
| 226 | + | "$version" \ | |
| 227 | + | 2>/dev/null | |
| 228 | + | } | |
| 229 | + | ||
| 230 | + | # Helper — peer data without envelope (used by full backup) | |
| 231 | + | function cmd::export::_peer_data() { | |
| 232 | + | local name="${1:-}" | |
| 233 | + | local conf_file | |
| 234 | + | conf_file="$(ctx::clients)/${name}.conf" | |
| 235 | + | [[ ! -f "$conf_file" ]] && return 0 | |
| 236 | + | ||
| 237 | + | local conf_b64 | |
| 238 | + | conf_b64=$(base64 -w 0 < "$conf_file" 2>/dev/null || base64 < "$conf_file") | |
| 239 | + | ||
| 240 | + | local meta_file meta_json="{}" | |
| 241 | + | meta_file="$(ctx::meta)/${name}.meta" | |
| 242 | + | [[ -f "$meta_file" ]] && meta_json=$(cat "$meta_file") | |
| 243 | + | ||
| 244 | + | local public_key="" | |
| 245 | + | local key_file | |
| 246 | + | key_file="$(ctx::clients)/${name}_public.key" | |
| 247 | + | [[ -f "$key_file" ]] && public_key=$(cat "$key_file") | |
| 248 | + | ||
| 249 | + | local ip | |
| 250 | + | ip=$(peers::get_ip "$name") | |
| 251 | + | ||
| 252 | + | local peer_type | |
| 253 | + | peer_type=$(peers::get_type "$name" 2>/dev/null || echo "") | |
| 254 | + | ||
| 255 | + | local direct_rule | |
| 256 | + | direct_rule=$(peers::get_meta "$name" "rule" 2>/dev/null || echo "") | |
| 257 | + | ||
| 258 | + | local identity | |
| 259 | + | identity=$(peers::get_identity "$name" 2>/dev/null || echo "") | |
| 260 | + | ||
| 261 | + | local -a group_list=() | |
| 262 | + | while IFS= read -r g; do | |
| 263 | + | [[ -n "$g" ]] && group_list+=("\"$g\"") | |
| 264 | + | done < <(json::peer_groups "$(ctx::groups)" "$name" 2>/dev/null) | |
| 265 | + | local groups_json="[]" | |
| 266 | + | [[ ${#group_list[@]} -gt 0 ]] && \ | |
| 267 | + | groups_json="[$(printf '%s,' "${group_list[@]}" | sed 's/,$//')]" | |
| 268 | + | ||
| 269 | + | local block_file is_blocked="false" block_json="null" | |
| 270 | + | block_file="$(ctx::blocks)/${name}.block" | |
| 271 | + | if [[ -f "$block_file" ]]; then | |
| 272 | + | is_blocked="true" | |
| 273 | + | block_json="\"$(base64 -w 0 < "$block_file" 2>/dev/null || base64 < "$block_file")\"" | |
| 274 | + | fi | |
| 275 | + | ||
| 276 | + | printf \ | |
| 277 | + | '{"name":"%s","ip":"%s","type":"%s","public_key":"%s","conf":"%s","meta":%s,"identity":"%s","groups":%s,"direct_rule":"%s","blocks":{"is_blocked":%s,"block_file":%s}}' \ | |
| 278 | + | "$name" "$ip" "$peer_type" "$public_key" "$conf_b64" \ | |
| 279 | + | "$meta_json" "$identity" "$groups_json" "$direct_rule" \ | |
| 280 | + | "$is_blocked" "$block_json" | |
| 281 | + | } | |
| 282 | + | ||
| 283 | + | # ====================================================== | |
| 284 | + | # Envelope helper | |
| 285 | + | # ====================================================== | |
| 286 | + | ||
| 287 | + | function cmd::export::_envelope() { | |
| 288 | + | local export_type="${1:-}" data="${2:-}" | |
| 289 | + | local version ts | |
| 290 | + | version=$(wgctl::version 2>/dev/null || echo "unknown") | |
| 291 | + | ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| 292 | + | printf '{"wgctl_version":"%s","export_type":"%s","exported_at":"%s","data":%s}\n' \ | |
| 293 | + | "$version" "$export_type" "$ts" "$data" | |
| 294 | + | } | |
| 295 | + | ||
| 296 | + | function cmd::export::_compact_json() { | |
| 297 | + | local file="$1" | |
| 298 | + | python3 -c " | |
| 299 | + | import json, sys | |
| 300 | + | try: | |
| 301 | + | print(json.dumps(json.load(open('${file}')))) | |
| 302 | + | except Exception as e: | |
| 303 | + | print('{}', file=sys.stderr) | |
| 304 | + | " 2>/dev/null | |
| 305 | + | } | |
Plus récent
Plus ancien