forked from cvquesty/openvox-gui
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.bash
More file actions
744 lines (685 loc) · 32.3 KB
/
Copy pathinstall.bash
File metadata and controls
744 lines (685 loc) · 32.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
#! /bin/bash
###############################################################################
# OpenVox agent bootstrap installer (Linux)
#
# Modeled on the Puppet Enterprise install.bash, this script downloads
# the appropriate openvox-agent package from a local OpenVox package
# repository (typically the openvox-gui server) and installs it on the
# requesting host.
#
# Typical invocation:
#
# curl -k https://<openvox-gui-server>:8140/packages/install.bash | sudo bash
#
# Optional arguments may be appended after `bash -s --` to pass
# configuration directives:
#
# curl -k https://server:8140/packages/install.bash | \
# sudo bash -s -- main:certname=web01.example.com \
# extension_requests:pp_role=webserver
#
# Supported argument forms:
#
# --server <fqdn> Puppetserver FQDN. Overrides any
# server-side render and any value
# pulled from existing puppet.conf.
# --pkg-repo-url <url> Override the package mirror base
# URL (default: https://<server>:8140/packages)
# --version <7|8> Pick OpenVox major version (default 8)
# --puppet-service-ensure <state> running | stopped (default: running)
# --puppet-service-enable <bool> true | false (default: true)
# <section>:<setting>=<value> Apply settings to puppet.conf or
# csr_attributes.yaml at install time
#
# Server-side rendering: when this script is installed at
# /opt/openvox-pkgs/install.bash by the openvox-gui server, the
# __OPENVOX_PUPPET_SERVER__ placeholder is substituted with the
# server's puppetserver FQDN, so agents that "curl ... | sudo bash"
# get a self-configuring script. If that render fails for any reason,
# the script falls back to /etc/puppetlabs/puppet/puppet.conf (helpful
# on re-install) and finally requires --server. PKG_REPO_URL is always
# *derived* from the puppetserver FQDN unless explicitly overridden --
# there is no separate PKG_REPO_URL placeholder to break.
###############################################################################
set -u
set -e
# ─── Configuration ──────────────────────────────────────────────────────────
#
# Resolution order for the puppetserver FQDN ($PUPPET_SERVER):
# 1. --server CLI arg (set during argument parsing below)
# 2. PUPPET_SERVER environment variable
# 3. The __OPENVOX_PUPPET_SERVER__ placeholder, which the openvox-gui
# server replaces with its puppetserver FQDN when this script is
# installed into /opt/openvox-pkgs/install.bash. Agents that
# `curl ... | sudo bash` always hit the rendered version, so this
# is the path that "just works" in normal operation.
# 4. The server= line in /etc/puppetlabs/puppet/puppet.conf, when the
# agent is being re-installed on a host that's already configured.
#
# $PKG_REPO_URL is *derived* from $PUPPET_SERVER unless explicitly set;
# it is not a separate placeholder. This means the agent only needs to
# know one piece of information (the server) and the URL falls out of
# that automatically.
#
# Resolution order for the agent install version ($OPENVOX_VERSION):
# 1. --version CLI arg
# 2. OPENVOX_VERSION environment variable
# 3. The __OPENVOX_DEFAULT_VERSION__ placeholder (server-side render)
# 4. Hard default of 8
PUPPET_SERVER="${PUPPET_SERVER:-__OPENVOX_PUPPET_SERVER__}"
PUPPET_SERVER_PORT="${PUPPET_SERVER_PORT:-8140}"
PKG_REPO_URL="${PKG_REPO_URL:-}"
DEFAULT_OPENVOX_VERSION="${DEFAULT_OPENVOX_VERSION:-__OPENVOX_DEFAULT_VERSION__}"
case "$DEFAULT_OPENVOX_VERSION" in
7|8) ;; # rendered, valid
*) DEFAULT_OPENVOX_VERSION="8" ;; # placeholder or junk -> default
esac
# ─── Standard agent paths (created by the openvox-agent package) ────────────
PUPPET_CONF_DIR="/etc/puppetlabs/puppet"
PUPPET_BIN_DIR="/opt/puppetlabs/bin"
PUPPET_INTERNAL_BIN_DIR="/opt/puppetlabs/puppet/bin"
# Service management defaults (override via --puppet-service-ensure / -enable)
PUPPET_SERVICE_ENSURE="running"
PUPPET_SERVICE_ENABLE="true"
# ─── Helpers ─────────────────────────────────────────────────────────────────
fail() { echo >&2 "openvox-install: $*"; exit 1; }
warn() { echo >&2 "openvox-install: WARN: $*"; }
info() { echo "openvox-install: $*"; }
cmd() { command -v "$1" >/dev/null 2>&1; }
# ─── Auto-discovery from the kernel's TCP state ─────────────────────────────
#
# When this script is run as
# curl -k https://server:8140/packages/install.bash | sudo bash
# the URL the operator typed IS the source of truth for the
# puppetserver FQDN. We extract the server from /proc/net/tcp:
# even though curl exits before bash starts executing (proven
# empirically: the script is ~17 KB, curl writes it to the pipe in
# microseconds and exits), the kernel keeps the TCP connection in
# TIME_WAIT state for ~60 seconds. The remote IP and port are
# recorded in /proc/net/tcp; we reverse-DNS the IP to recover the
# hostname.
#
# Outputs the remote IP on stdout (as dotted-decimal IPv4) and exits
# 0 on success; exits 1 on no match. Returns the LAST matching entry
# so re-runs of the install pick up the most recent connection.
discover_remote_ip_via_proc_net_tcp() {
local target_port="${1:-8140}"
local target_port_hex
target_port_hex=$(printf "%04X" "$target_port")
[ -r /proc/net/tcp ] || return 1
local found_ip="" line state rem hex_ip
while IFS= read -r line; do
# Skip the header row
[[ "$line" == *"local_address"* ]] && continue
# /proc/net/tcp columns:
# sl local_address rem_address st tx_queue:rx_queue ...
# Word splitting on whitespace is the WHOLE point here, so
# the SC2206 warning would be wrong; use read -ra instead
# to be explicit about the splitting.
local -a fields
# shellcheck disable=SC2086
read -ra fields <<< "$line"
rem="${fields[2]:-}"
state="${fields[3]:-}"
# We want client-side connections to the target port. After
# curl exits the connection passes through several states
# before fully disappearing -- on RHEL 9 we see state 08
# (CLOSE_WAIT) most often. Accept any non-LISTEN state so
# we catch the connection regardless of where it is in the
# teardown sequence.
# 01 ESTABLISHED, 02 SYN_SENT, 03 SYN_RECV, 04 FIN_WAIT1,
# 05 FIN_WAIT2, 06 TIME_WAIT, 07 CLOSE, 08 CLOSE_WAIT,
# 09 LAST_ACK, 0A LISTEN, 0B CLOSING
case "$state" in
0A) continue ;; # skip LISTEN sockets (server side)
*) ;;
esac
if [[ "$rem" == *":${target_port_hex}" ]]; then
hex_ip="${rem%:*}"
# Linux stores IPv4 in /proc/net/tcp as little-endian hex.
# 0100007F -> bytes 7F 00 00 01 -> 127.0.0.1
found_ip=$(printf "%d.%d.%d.%d" \
"0x${hex_ip:6:2}" "0x${hex_ip:4:2}" \
"0x${hex_ip:2:2}" "0x${hex_ip:0:2}")
fi
done < /proc/net/tcp
if [ -n "$found_ip" ]; then
echo "$found_ip"
return 0
fi
return 1
}
# Reverse-DNS an IP to a hostname. Tries getent first (uses NSS, so
# /etc/hosts entries win over DNS), then host(1). Outputs the FQDN
# on stdout; returns 1 if no name is found.
reverse_dns_lookup() {
local ip="$1"
[ -z "$ip" ] && return 1
local host=""
if command -v getent >/dev/null 2>&1; then
host=$(getent hosts "$ip" 2>/dev/null | awk '{print $2; exit}')
fi
if [ -z "$host" ] && command -v host >/dev/null 2>&1; then
host=$(host "$ip" 2>/dev/null \
| awk '/pointer/ {sub(/\.$/,"",$NF); print $NF; exit}')
fi
if [ -n "$host" ]; then
echo "$host"
return 0
fi
return 1
}
# Compose the two: read /proc/net/tcp, reverse-DNS the result.
# Returns the FQDN of the puppetserver the agent just curl'd, or
# empty + non-zero exit on no match.
discover_server_from_curl_socket() {
local ip
ip=$(discover_remote_ip_via_proc_net_tcp "${1:-8140}") || return 1
reverse_dns_lookup "$ip"
}
# ─── Argument parsing ────────────────────────────────────────────────────────
# Accept both flag-style overrides (--server, --version) and the
# section:setting=value directives that PE's installer accepts.
CSR_ATTR_LINES=()
CSR_EXT_LINES=()
# Don't pre-seed OPENVOX_VERSION here -- the resolution block below
# handles defaulting via "${OPENVOX_VERSION:-$DEFAULT_OPENVOX_VERSION}"
# so env vars and --version both work without being clobbered.
SECTION_REGEX='^(main|server|agent|user|custom_attributes|extension_requests):([^=]+)=(.*)$'
# We collect puppet.conf settings here so we can apply them after the
# package has been installed (and the puppet binary exists).
PUPPET_CONFIG_KV=() # entries: "section|key|value"
while [ $# -gt 0 ]; do
case "$1" in
--server)
shift
PUPPET_SERVER="${1:-}"
[ -z "$PUPPET_SERVER" ] && fail "--server requires a value"
shift
;;
--pkg-repo-url)
shift
PKG_REPO_URL="${1:-}"
[ -z "$PKG_REPO_URL" ] && fail "--pkg-repo-url requires a value"
shift
;;
--version)
shift
OPENVOX_VERSION="${1:-}"
[ -z "$OPENVOX_VERSION" ] && fail "--version requires a value"
shift
;;
--puppet-service-ensure)
shift; PUPPET_SERVICE_ENSURE="${1:-running}"; shift
;;
--puppet-service-enable)
shift; PUPPET_SERVICE_ENABLE="${1:-true}"; shift
;;
*)
if [[ "$1" =~ $SECTION_REGEX ]]; then
section="${BASH_REMATCH[1]}"
setting="${BASH_REMATCH[2]}"
value="${BASH_REMATCH[3]}"
case "$section" in
custom_attributes)
CSR_ATTR_LINES+=("${setting}: '${value}'")
;;
extension_requests)
CSR_EXT_LINES+=("${setting}: '${value}'")
;;
*)
PUPPET_CONFIG_KV+=("${section}|${setting}|${value}")
;;
esac
else
fail "Unrecognised argument: $1"
fi
shift
;;
esac
done
# ─── Resolve PUPPET_SERVER + PKG_REPO_URL ───────────────────────────────────
#
# Resolution order (highest priority first):
#
# 1. --server CLI arg / PUPPET_SERVER env var
# Already in $PUPPET_SERVER if either was provided.
#
# 2. The kernel's TCP state -- read the remote IP of the curl-to-
# port-8140 connection out of /proc/net/tcp (still in TIME_WAIT
# from the curl that just downloaded us), then reverse-DNS it.
# The URL the operator typed IS the source of truth for the
# puppetserver FQDN; this path makes the script use it directly.
#
# 3. The __OPENVOX_PUPPET_SERVER__ placeholder substituted at
# server-side render time. Belt-and-suspenders for cases where
# reverse DNS doesn't return a usable name.
#
# 4. The [main] server= line read from /etc/puppetlabs/puppet/puppet.conf
# when the agent is being re-installed on an already-configured host.
#
# Detect the unsubstituted placeholder. Critically, we build the
# marker string via runtime concatenation -- the literal
# __OPENVOX_PUPPET_SERVER__
# must NOT appear anywhere in this script outside of the actual
# default-value position above, because the server-side `sed` render
# substitutes EVERY occurrence with the puppetserver FQDN. If we wrote
# the marker as a literal here, the render would turn it into the real
# FQDN, and this check would falsely match a successful render and
# clear PUPPET_SERVER -- the exact bug we hit on production in
# 3.3.5-13. Splitting the literal across the bash concatenation
# operator keeps `sed` from matching it.
PLACEHOLDER_MARKER='__OPENVOX''_PUPPET_SERVER__'
if [[ "$PUPPET_SERVER" == *"${PLACEHOLDER_MARKER}"* ]]; then
PUPPET_SERVER=""
fi
# Path 2: discover from the kernel's TCP state.
if [ -z "$PUPPET_SERVER" ]; then
DISCOVERED_SERVER=$(discover_server_from_curl_socket "$PUPPET_SERVER_PORT" 2>/dev/null) || true
if [ -n "$DISCOVERED_SERVER" ]; then
PUPPET_SERVER="$DISCOVERED_SERVER"
info "Discovered puppetserver from /proc/net/tcp: ${PUPPET_SERVER}"
fi
fi
# Path 4: re-install on a host that already has puppet.conf. Useful
# when running install.bash from a downloaded file or against a host
# that's already been configured.
if [ -z "$PUPPET_SERVER" ] && [ -r "${PUPPET_CONF_DIR}/puppet.conf" ]; then
EXISTING_SERVER=$(awk -F= '
/^[[:space:]]*\[/ { section=$0; next }
/^[[:space:]]*server[[:space:]]*=/ {
gsub(/[[:space:]]/, "", $2); print $2; exit
}
' "${PUPPET_CONF_DIR}/puppet.conf" 2>/dev/null)
if [ -n "$EXISTING_SERVER" ]; then
PUPPET_SERVER="$EXISTING_SERVER"
info "Reusing puppetserver from existing puppet.conf: ${PUPPET_SERVER}"
fi
fi
# Final resolution check. If we still have nothing, give the operator
# an actionable error. Note: ${PLACEHOLDER_MARKER} is used in the text
# instead of the literal so that the server-side render doesn't mangle
# this message (see the comment by PLACEHOLDER_MARKER above).
if [ -z "$PUPPET_SERVER" ]; then
fail "Could not determine the puppetserver FQDN.
Tried (in order):
1. --server CLI arg / PUPPET_SERVER env var (not set)
2. /proc/net/tcp + reverse DNS of the curl connection (no
matching connection found, or reverse DNS returned nothing)
3. ${PLACEHOLDER_MARKER} placeholder substituted by the
openvox-gui server (not rendered)
4. server= line in /etc/puppetlabs/puppet/puppet.conf (not present)
Workaround: re-run passing --server explicitly:
curl -k <install-url> | sudo bash -s -- --server <puppetserver-fqdn>
Or, if running from a downloaded file:
sudo bash install.bash --server <puppetserver-fqdn>"
fi
# Default OPENVOX_VERSION if user didn't override
OPENVOX_VERSION="${OPENVOX_VERSION:-$DEFAULT_OPENVOX_VERSION}"
case "$OPENVOX_VERSION" in
7|8) ;;
*) fail "OPENVOX_VERSION must be 7 or 8 (got '${OPENVOX_VERSION}')" ;;
esac
# Derive PKG_REPO_URL from the puppetserver FQDN unless an explicit
# override has been provided (rare; only useful if the package mirror
# lives on a different host from the puppetserver).
if [ -z "$PKG_REPO_URL" ]; then
PKG_REPO_URL="https://${PUPPET_SERVER}:${PUPPET_SERVER_PORT}/packages"
fi
info "Server : ${PUPPET_SERVER}"
info "Repo URL : ${PKG_REPO_URL}"
info "OpenVox ver: ${OPENVOX_VERSION}"
# ─── Proxy bypass for the puppetserver ──────────────────────────────────────
#
# Many corporate networks set http_proxy / https_proxy globally for
# outbound internet access. Apt/yum then routes EVERY HTTPS request --
# including the one to our internal puppetserver -- through that
# proxy. The proxy typically:
# * Demands authentication the agent doesn't have, OR
# * Has no visibility into the internal network at all, OR
# * Does TLS interception with its own MITM cert that defeats the
# `Verify-Peer=false` / `sslverify=0` we pass to apt/yum (because
# it's now the proxy's cert chain, not the puppetserver's).
#
# Fix: append the puppetserver FQDN (and the standard localhost
# entries) to no_proxy and NO_PROXY for the rest of the script's
# lifetime. apt-get and dnf both honour these env vars. Long-term,
# the puppet-agent uses direct connections so this is only needed
# for the install fetch.
existing_no_proxy="${no_proxy:-${NO_PROXY:-}}"
no_proxy_extra="${PUPPET_SERVER},localhost,127.0.0.1"
if [ -n "$existing_no_proxy" ]; then
export no_proxy="${existing_no_proxy},${no_proxy_extra}"
else
export no_proxy="$no_proxy_extra"
fi
export NO_PROXY="$no_proxy"
info "no_proxy : ${no_proxy}"
# ─── Install the puppet CA into the system trust store ─────────────────────
#
# The puppetserver presents a cert signed by Puppet's internal CA.
# Without that CA in the system trust store, EVERY https request to
# the puppetserver is untrusted -- not just our install-time apt-get
# (which we could band-aid with --insecure / Verify-Peer=false), but
# also future `apt-get update`, `dnf upgrade`, manual curl, browsers,
# the puppet-agent itself, and so on. We install it once, here, so
# it's permanently trusted.
#
# The puppetserver exposes its CA cert at:
# https://<server>:8140/puppet-ca/v1/certificate/ca
# We fetch it with --insecure (we don't trust it yet) and drop the
# resulting PEM into the OS-specific trust path:
# * Debian/Ubuntu: /usr/local/share/ca-certificates/ + update-ca-certificates
# * RHEL family: /etc/pki/ca-trust/source/anchors/ + update-ca-trust extract
#
# This function is called BEFORE the repo setup so apt/yum can verify
# the puppetserver cert normally -- no Verify-Peer=false, no
# sslverify=0, no [trusted=yes] needed for the GPG fetch either.
install_puppet_ca_cert() {
local ca_url="https://${PUPPET_SERVER}:${PUPPET_SERVER_PORT}/puppet-ca/v1/certificate/ca"
local ca_pem
ca_pem=$(curl -ksLf "$ca_url" 2>/dev/null) || {
warn "Could not fetch puppet CA cert from ${ca_url}"
warn "Falling back to insecure HTTPS for the install fetch only."
return 1
}
if ! echo "$ca_pem" | grep -q "BEGIN CERTIFICATE"; then
warn "Response from ${ca_url} doesn't look like a PEM cert; skipping trust install."
return 1
fi
local ca_path
case "$PLATFORM_NAME" in
debian|ubuntu)
ca_path="/usr/local/share/ca-certificates/openvox-puppet-ca.crt"
echo "$ca_pem" > "$ca_path"
chmod 0644 "$ca_path"
update-ca-certificates >/dev/null 2>&1 || \
warn "update-ca-certificates returned non-zero (cert installed but trust store may not have refreshed)"
info "Installed puppet CA into ${ca_path}"
;;
rhel|amazon)
ca_path="/etc/pki/ca-trust/source/anchors/openvox-puppet-ca.crt"
echo "$ca_pem" > "$ca_path"
chmod 0644 "$ca_path"
(update-ca-trust extract >/dev/null 2>&1 || \
update-ca-trust >/dev/null 2>&1) || \
warn "update-ca-trust returned non-zero (cert installed but trust store may not have refreshed)"
info "Installed puppet CA into ${ca_path}"
;;
*)
warn "Don't know how to install a CA cert on platform '${PLATFORM_NAME}'"
return 1
;;
esac
return 0
}
# ─── Privilege check ─────────────────────────────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
fail "This installer must be run as root (try: sudo bash install.bash)"
fi
# ─── Platform detection ──────────────────────────────────────────────────────
# Sets PLATFORM_NAME (rhel|debian|ubuntu|...), PLATFORM_RELEASE (major
# version), PLATFORM_ARCHITECTURE (x86_64|aarch64|amd64). Logic borrowed
# from the Puppet Enterprise install.bash, simplified for the platforms
# OpenVox currently ships packages for.
sanitize_platform_name() {
case "$PLATFORM_NAME" in
redhatenterpriseserver|redhatenterpriseclient|redhatenterprisews| \
redhat|oracle|ol|rocky|almalinux|rhel|centos|scientific)
PLATFORM_NAME="rhel" ;;
amazon|amzn|amazonami)
PLATFORM_NAME="amazon" ;;
sles|sled|"suse linux"|opensuse|opensuse-leap|opensuse-tumbleweed)
PLATFORM_NAME="sles" ;;
esac
}
detect_platform() {
if [ -r /etc/os-release ]; then
# shellcheck disable=SC1091
. /etc/os-release
PLATFORM_NAME="${ID:-unknown}"
PLATFORM_RELEASE="${VERSION_ID:-}"
elif [ -r /etc/redhat-release ]; then
PLATFORM_NAME="rhel"
PLATFORM_RELEASE="$(sed -n 's/.* release \([0-9]*\).*/\1/p' /etc/redhat-release)"
else
fail "Unable to detect Linux distribution -- /etc/os-release missing"
fi
sanitize_platform_name
# Normalise release to the format the upstream voxpupuli mirror
# uses in its directory names (validated 2026-04-23):
# RHEL family -> major version only ("9", not "9.4")
# Debian -> major version only ("12", not "12.5") -> dist "debian12"
# Ubuntu -> full version ("24.04") -> dist "ubuntu24.04"
case "$PLATFORM_NAME" in
rhel|amazon|sles|debian)
PLATFORM_RELEASE="$(echo "$PLATFORM_RELEASE" | cut -d. -f1)"
;;
ubuntu)
: # leave as-is, e.g. 24.04
;;
esac
PLATFORM_ARCHITECTURE="$(uname -m)"
case "$PLATFORM_NAME:$PLATFORM_ARCHITECTURE" in
debian:x86_64|ubuntu:x86_64) PLATFORM_ARCHITECTURE="amd64" ;;
debian:aarch64|ubuntu:aarch64) PLATFORM_ARCHITECTURE="arm64" ;;
esac
}
detect_platform
info "Detected platform: ${PLATFORM_NAME} ${PLATFORM_RELEASE} (${PLATFORM_ARCHITECTURE})"
# ─── Already installed? ─────────────────────────────────────────────────────
if [ -x "${PUPPET_BIN_DIR}/puppet" ]; then
EXISTING_VERSION="$("${PUPPET_BIN_DIR}/puppet" --version 2>/dev/null || echo unknown)"
info "openvox-agent is already installed (version ${EXISTING_VERSION}). Re-running install will upgrade it if a newer package is available."
fi
# ─── Repository setup ──────────────────────────────────────────────────────
# Drop a yum/apt repository file pointing at the local OpenVox mirror,
# then install openvox-agent via the system package manager. The
# package manager handles all downstream dependencies (curl, ruby,
# openssl, etc.) and gives us free upgrade tooling.
#
# Mirror layout (validated 2026-04-23 against live voxpupuli.org):
# yum.voxpupuli.org/openvox{N}/el/{R}/{arch}/ <- packages + repodata
# apt.voxpupuli.org/dists/{numeric}/openvox{N}/binary-{arch}/Packages
# apt.voxpupuli.org/pool/openvox{N}/o/{component}/*.deb
# We mirror those into PKG_REPO_DIR/{yum,apt}/ preserving the upstream
# layout, so the agent-facing URLs are simply:
# /packages/yum/openvox{N}/el/{R}/{arch}/
# /packages/apt/ (with suite={dist}, component=openvox{N})
setup_rhel_repo() {
local repo_url="${PKG_REPO_URL%/}/yum/openvox${OPENVOX_VERSION}/el/${PLATFORM_RELEASE}/${PLATFORM_ARCHITECTURE}"
local repo_file="/etc/yum.repos.d/openvox${OPENVOX_VERSION}.repo"
local gpg_url="${PKG_REPO_URL%/}/yum/GPG-KEY-openvox.pub"
# 3.3.5-24+: sslverify=1 when the puppet CA install succeeded
# (CA_TRUSTED=true) so dnf/yum verify the puppetserver cert
# properly. Falls back to sslverify=0 only when CA install failed.
local sslverify_value=1
if [ "${CA_TRUSTED:-false}" != "true" ]; then
sslverify_value=0
fi
info "Configuring yum repository at ${repo_file}"
info " baseurl: ${repo_url}"
info " sslverify: ${sslverify_value}"
cat > "$repo_file" <<EOF
[openvox${OPENVOX_VERSION}]
name=OpenVox ${OPENVOX_VERSION} - el-${PLATFORM_RELEASE}-${PLATFORM_ARCHITECTURE} (local mirror)
baseurl=${repo_url}
enabled=1
gpgcheck=1
gpgkey=${gpg_url}
sslverify=${sslverify_value}
EOF
if cmd dnf; then
dnf -y --disablerepo='*' --enablerepo="openvox${OPENVOX_VERSION}" install openvox-agent
elif cmd yum; then
yum -y --disablerepo='*' --enablerepo="openvox${OPENVOX_VERSION}" install openvox-agent
else
fail "Neither dnf nor yum found -- can't install openvox-agent on this RHEL-family host"
fi
}
# Compute the apt suite name (matches a directory under apt/dists/).
# Upstream uses numeric forms: debian12, ubuntu24.04 -- not codenames.
apt_dist_suite() {
case "$PLATFORM_NAME" in
debian) echo "debian${PLATFORM_RELEASE}" ;;
ubuntu) echo "ubuntu${PLATFORM_RELEASE}" ;;
esac
}
setup_apt_repo() {
local apt_base="${PKG_REPO_URL%/}/apt"
local list_file="/etc/apt/sources.list.d/openvox${OPENVOX_VERSION}.list"
local trust_dir="/etc/apt/trusted.gpg.d"
local dist
dist=$(apt_dist_suite)
if [ -z "$dist" ]; then
fail "Could not determine apt dist suite for ${PLATFORM_NAME} ${PLATFORM_RELEASE}"
fi
info "Configuring apt repository at ${list_file}"
info " base : ${apt_base}"
info " suite : ${dist}"
info " comp : openvox${OPENVOX_VERSION}"
# The openvox repos are signed. The keyring is published at the
# apt-mirror root. We try to install it into trusted.gpg.d (which
# every modern apt honours) and only fall back to `[trusted=yes]`
# if the download fails -- which keeps the install working on
# disconnected internal networks.
#
# 3.3.5-24+: when the puppet CA install (CA_TRUSTED=true) succeeded
# earlier in the script, drop --insecure for the keyring fetch and
# drop Acquire::https::Verify-Peer=false from apt-get -- TLS now
# verifies properly against the just-installed CA, which is also
# what subsequent `apt-get update` / `dnf upgrade openvox-agent`
# invocations will see. When CA install failed, keep the band-aids
# so the install still completes.
local curl_tls_args=""
local apt_tls_args=()
if [ "${CA_TRUSTED:-false}" != "true" ]; then
curl_tls_args="--insecure"
apt_tls_args=(-o Acquire::https::Verify-Peer=false)
fi
local trusted_marker="[trusted=yes]"
# shellcheck disable=SC2086
if cmd curl && curl -fsSL ${curl_tls_args} \
"${apt_base}/openvox-keyring.gpg" \
-o "${trust_dir}/openvox${OPENVOX_VERSION}.gpg" 2>/dev/null; then
trusted_marker=""
info "Installed openvox keyring into ${trust_dir}/"
else
warn "Could not fetch openvox-keyring.gpg; falling back to [trusted=yes]"
fi
cat > "$list_file" <<EOF
deb ${trusted_marker} ${apt_base}/ ${dist} openvox${OPENVOX_VERSION}
EOF
apt-get update -y "${apt_tls_args[@]}" || true
DEBIAN_FRONTEND=noninteractive apt-get install -y \
"${apt_tls_args[@]}" \
openvox-agent
}
# Install the puppet CA into the system trust store BEFORE adding the
# repo so apt/yum can verify the puppetserver cert with the right CA
# chain. Without this, post-install operations (apt-get update,
# dnf upgrade openvox-agent, etc.) would fail with "certificate is
# NOT trusted" until the puppet-agent's first run installed the CA
# elsewhere -- which is too late to be useful for package management.
#
# We track success/failure in CA_TRUSTED so the repo-setup functions
# below can use proper TLS verification when the CA is trusted, and
# only fall back to --insecure / Verify-Peer=false / sslverify=0
# when the CA install actually failed (CA endpoint unreachable, no
# update-ca-certificates, etc.). 3.3.5-21 audit BUG-5 flagged that
# we were always using the band-aids -- undermining the trust install.
if install_puppet_ca_cert; then
CA_TRUSTED="true"
else
CA_TRUSTED="false"
warn "CA install failed; will fall back to insecure HTTPS for the install fetch."
fi
case "$PLATFORM_NAME" in
rhel)
setup_rhel_repo
;;
debian|ubuntu)
setup_apt_repo
;;
*)
fail "Platform ${PLATFORM_NAME} ${PLATFORM_RELEASE} is not yet supported by this installer. Currently supported: rhel/centos/rocky/alma, debian, ubuntu."
;;
esac
if [ ! -x "${PUPPET_BIN_DIR}/puppet" ]; then
fail "openvox-agent installation reported success but ${PUPPET_BIN_DIR}/puppet is not present. Check the package manager output above."
fi
INSTALLED_VERSION="$("${PUPPET_BIN_DIR}/puppet" --version 2>/dev/null || echo unknown)"
info "openvox-agent installed (version ${INSTALLED_VERSION})"
# ─── Configure puppet.conf ──────────────────────────────────────────────────
# Always set server + certname. certname is forced to lowercase because
# Puppet treats certificates as case-sensitive and lowercase FQDNs are
# the standard convention.
mkdir -p "$PUPPET_CONF_DIR"
CERTNAME="$("${PUPPET_INTERNAL_BIN_DIR}/facter" fqdn 2>/dev/null | tr '[:upper:]' '[:lower:]')"
[ -z "$CERTNAME" ] && CERTNAME="$(hostname -f | tr '[:upper:]' '[:lower:]')"
"${PUPPET_BIN_DIR}/puppet" config set server "$PUPPET_SERVER" --section main
"${PUPPET_BIN_DIR}/puppet" config set certname "$CERTNAME" --section main
# Apply any extra section:setting=value directives the user passed in.
for entry in "${PUPPET_CONFIG_KV[@]:-}"; do
[ -z "$entry" ] && continue
section="${entry%%|*}"
rest="${entry#*|}"
setting="${rest%%|*}"
value="${rest#*|}"
info "Applying puppet.conf: [${section}] ${setting}=${value}"
"${PUPPET_BIN_DIR}/puppet" config set "$setting" "$value" --section "$section"
done
# Write csr_attributes.yaml if any custom attributes / extension requests
# were specified. These appear in the CSR sent to the CA at first
# checkin and become "trusted facts" once the cert is signed.
if [ ${#CSR_ATTR_LINES[@]} -gt 0 ] || [ ${#CSR_EXT_LINES[@]} -gt 0 ]; then
csr_file="${PUPPET_CONF_DIR}/csr_attributes.yaml"
info "Writing ${csr_file}"
: > "$csr_file"
echo "---" >> "$csr_file"
if [ ${#CSR_ATTR_LINES[@]} -gt 0 ]; then
echo "custom_attributes:" >> "$csr_file"
for line in "${CSR_ATTR_LINES[@]}"; do
echo " ${line}" >> "$csr_file"
done
fi
if [ ${#CSR_EXT_LINES[@]} -gt 0 ]; then
echo "extension_requests:" >> "$csr_file"
for line in "${CSR_EXT_LINES[@]}"; do
echo " ${line}" >> "$csr_file"
done
fi
fi
# ─── Service management ────────────────────────────────────────────────────
# Use `puppet resource service` so we don't have to know whether the
# host uses systemd, upstart, sysv-init, etc. This is the same trick
# the PE installer uses.
info "Setting puppet service: ensure=${PUPPET_SERVICE_ENSURE} enable=${PUPPET_SERVICE_ENABLE}"
"${PUPPET_BIN_DIR}/puppet" resource service puppet \
"ensure=${PUPPET_SERVICE_ENSURE}" \
"enable=${PUPPET_SERVICE_ENABLE}" >/dev/null
# ─── Convenience symlinks ──────────────────────────────────────────────────
# Drop puppet/facter/hiera into /usr/local/bin so they are on PATH
# without /opt/puppetlabs/bin needing to be added explicitly.
if [ -d /usr/local/bin ] && [ -w /usr/local/bin ]; then
for tool in puppet facter hiera; do
if [ -x "${PUPPET_BIN_DIR}/${tool}" ] && [ ! -e "/usr/local/bin/${tool}" ]; then
ln -s "${PUPPET_BIN_DIR}/${tool}" "/usr/local/bin/${tool}" 2>/dev/null || true
fi
done
fi
cat <<EOF
╔══════════════════════════════════════════════════════════════════╗
║ OpenVox agent install complete ║
╚══════════════════════════════════════════════════════════════════╝
Installed version : ${INSTALLED_VERSION}
Server : ${PUPPET_SERVER}
Certname : ${CERTNAME}
Service : ${PUPPET_SERVICE_ENSURE} / enabled=${PUPPET_SERVICE_ENABLE}
Next steps:
1. On the puppetserver, sign this node's certificate:
puppetserver ca sign --certname ${CERTNAME}
2. Trigger an immediate run to verify the connection:
sudo puppet agent --test
EOF
exit 0