xolo-server 2.2.4 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/client/xolo +110 -27
- data/lib/xolo/core/version.rb +1 -1
- data/lib/xolo/server/configuration.rb +1 -0
- data/lib/xolo/server/helpers/autopkg.rb +76 -6
- data/lib/xolo/server/helpers/maintenance.rb +55 -7
- data/lib/xolo/server/helpers/subscriptions.rb +1 -16
- data/lib/xolo/server/mixins/title_jamf_access.rb +2 -7
- data/lib/xolo/server/mixins/title_ted_access.rb +5 -5
- data/lib/xolo/server/routes/maint.rb +11 -1
- data/lib/xolo/server/routes/titles.rb +24 -0
- data/lib/xolo/server/title.rb +22 -0
- data/lib/xolo/server/version.rb +1 -12
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d27f4a3e1656ef59731865488978113bcbeb4a5ebbea8c120585371c49aad335
|
|
4
|
+
data.tar.gz: a006f5ec49ad3cfbad6ae5908e2da17b65cdc2bf8eb0c3a7133fcaa0af6db3f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c47f0964d7c5f5a42ddb909a0ee6b5d8bb01ead710b6be7fe9087e882081dcf753fc9f783be8bd0e5658b49df36d012f59f956db768026c915faabc8f5d9a4a8
|
|
7
|
+
data.tar.gz: 448ec9e58919ada9612c8651f814d168d4d6117c92b3e76faa2c5bdc74eb7747298f6bdebcc1547b7c662084d97fbbd7fb018331c8424f28b79bf1114cdacba7
|
data/data/client/xolo
CHANGED
|
@@ -27,6 +27,9 @@ zmodload zsh/zutil
|
|
|
27
27
|
###############################
|
|
28
28
|
###############################
|
|
29
29
|
|
|
30
|
+
# just in case
|
|
31
|
+
export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/usr/libexec:$PATH"
|
|
32
|
+
|
|
30
33
|
# Needed for the UTF-8 chars in the lsreg output
|
|
31
34
|
export LANG="en_US.UTF-8"
|
|
32
35
|
export LC_ALL="en_US.UTF-8"
|
|
@@ -94,6 +97,7 @@ be_verbose=
|
|
|
94
97
|
debugging_on=
|
|
95
98
|
no_versions=
|
|
96
99
|
show_xolo_version=
|
|
100
|
+
do_recon=
|
|
97
101
|
|
|
98
102
|
# arguments
|
|
99
103
|
command=
|
|
@@ -152,7 +156,9 @@ Options:
|
|
|
152
156
|
policies, etc, from test xolo server, not the production server.
|
|
153
157
|
Setting the XOLO_TEST_MODE env. variable does the same thing.
|
|
154
158
|
-r, --recon: With 'install' or 'uninstall' run a 'jamf recon' after the
|
|
155
|
-
operation completes successfully.
|
|
159
|
+
operation completes successfully. With 'update', run a 'jamf recon'
|
|
160
|
+
before the checkin. NOTE: With update, the whole process may take
|
|
161
|
+
many minutes.
|
|
156
162
|
-n, --no-versions: With the 'list-titles' and 'list-installed' commands,
|
|
157
163
|
do not show the versions available, or the version
|
|
158
164
|
installed, respectively.
|
|
@@ -187,6 +193,9 @@ Commands:
|
|
|
187
193
|
every time this computer checks in with Jamf Pro.
|
|
188
194
|
Note: this just runs the 'jamf policy' command, which will execute any pending
|
|
189
195
|
Jamf policies triggered by "recurring check-in".
|
|
196
|
+
If --recon is given, a Jamf recon will run first, followed by a 1 minute pause
|
|
197
|
+
for Jamf to process the data before doing the check-in. This helps the Mac to see
|
|
198
|
+
very recently-released versions as patch updates.
|
|
190
199
|
|
|
191
200
|
refresh, r
|
|
192
201
|
Update xolo's information about current titles and versions.
|
|
@@ -356,42 +365,43 @@ function debug() {
|
|
|
356
365
|
debugging_on && echo "DEBUG: $*" 1>&2
|
|
357
366
|
}
|
|
358
367
|
|
|
359
|
-
# Gather all installed bundle ids and installed versions from
|
|
368
|
+
# Gather all installed bundle ids and installed versions from system_profiler
|
|
360
369
|
# store them in an associative array in $installed_apps.
|
|
361
370
|
# keys are bundle ids, values are app name & installed version
|
|
362
371
|
# separated by a semicolon
|
|
363
372
|
# e.g. 'com.apple.Safari => Safari.app;14.0.3'
|
|
364
373
|
###############################
|
|
365
374
|
function get_installed_apps() {
|
|
375
|
+
# debug "PATH is $PATH"
|
|
366
376
|
debug "Getting all installed .apps on this Mac..."
|
|
367
377
|
local app_name
|
|
368
378
|
local app_bundle_id
|
|
369
379
|
local app_vers
|
|
380
|
+
local app_plist
|
|
370
381
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
382
|
+
json_app_data=$(/usr/sbin/system_profiler -json SPApplicationsDataType )
|
|
383
|
+
|
|
384
|
+
apps_to_look_at=$(echo "$json_app_data" | grep '"path"' | cut -d'"' -f4 | grep -v '^/System/')
|
|
385
|
+
|
|
386
|
+
for app_path in ${(f)apps_to_look_at} ; do
|
|
387
|
+
app_name=$(/usr/bin/basename "$app_path")
|
|
388
|
+
app_plist="$app_path/Contents/Info.plist"
|
|
389
|
+
|
|
390
|
+
if [[ -f "$app_plist" ]] ; then
|
|
391
|
+
app_bundle_id=$(/usr/bin/plutil -extract CFBundleIdentifier raw "$app_plist" 2>/dev/null)
|
|
380
392
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
[[ "$line" =~ '^[[:space:]]+CFBundleShortVersionString = \"(.+)\";' ]] && app_vers=$match[1]
|
|
393
|
+
app_vers=$(/usr/bin/plutil -extract CFBundleShortVersionString raw "$app_plist" 2>/dev/null)
|
|
394
|
+
|
|
395
|
+
# Use CFBundleVersion if no CFBundleShortVersionString
|
|
396
|
+
[[ -z "$app_vers" ]] && \
|
|
397
|
+
app_vers=$(/usr/bin/plutil -extract CFBundleVersion raw "$app_plist" 2>/dev/null)
|
|
387
398
|
|
|
388
|
-
if [[ -n "$app_name" && -n "$app_bundle_id" && -n "$app_vers" ]] ; then
|
|
389
399
|
installed_apps[$app_bundle_id]="$app_name;$app_vers"
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
unset app_vers
|
|
400
|
+
else
|
|
401
|
+
debug "App Path '$app_path' has no Info.plist"
|
|
393
402
|
fi
|
|
394
403
|
done
|
|
404
|
+
|
|
395
405
|
# installed_apps is now an associative array with bundle ids as keys and app names as values
|
|
396
406
|
|
|
397
407
|
# print the installed apps - this is a long list
|
|
@@ -402,8 +412,70 @@ function get_installed_apps() {
|
|
|
402
412
|
done
|
|
403
413
|
fi
|
|
404
414
|
|
|
415
|
+
# debug "PATH is $PATH"
|
|
405
416
|
} # end installed_apps
|
|
406
417
|
|
|
418
|
+
########################################
|
|
419
|
+
########################################
|
|
420
|
+
########################################
|
|
421
|
+
########################################
|
|
422
|
+
########################################
|
|
423
|
+
########################################
|
|
424
|
+
# Gather all installed bundle ids and installed versions from lsreg
|
|
425
|
+
# store them in an associative array in $installed_apps.
|
|
426
|
+
# keys are bundle ids, values are app name & installed version
|
|
427
|
+
# separated by a semicolon
|
|
428
|
+
# e.g. 'com.apple.Safari => Safari.app;14.0.3'
|
|
429
|
+
###############################
|
|
430
|
+
# function get_installed_apps() {
|
|
431
|
+
# debug "Getting all installed .apps on this Mac..."
|
|
432
|
+
# local app_name
|
|
433
|
+
# local app_bundle_id
|
|
434
|
+
# local app_vers
|
|
435
|
+
|
|
436
|
+
# # loop thru lsreg output
|
|
437
|
+
# "$LSREG" -dump Bundle | while IFS= read -r line ; do
|
|
438
|
+
# # a line if ------ starts a new record
|
|
439
|
+
# if [[ "$line" =~ '^-+$' ]] ; then
|
|
440
|
+
# unset app_name
|
|
441
|
+
# unset app_bundle_id
|
|
442
|
+
# unset app_vers
|
|
443
|
+
# continue
|
|
444
|
+
# fi
|
|
445
|
+
|
|
446
|
+
# # $MATCH is the whole matched string, $match is the array of captures
|
|
447
|
+
# # NOTE this only works if the path is listed before the CFBundleIdentifier
|
|
448
|
+
# # which seems to be the case in the lsreg output
|
|
449
|
+
# [[ "$line" =~ '^path:[[:space:]]+/.*/(.+\.app) \(' ]] && app_name=$match[1]
|
|
450
|
+
# [[ "$line" =~ '^[[:space:]]+CFBundleIdentifier = \"(.+)\";' ]] && app_bundle_id=$match[1]
|
|
451
|
+
# [[ "$line" =~ '^[[:space:]]+CFBundleShortVersionString = \"(.+)\";' ]] && app_vers=$match[1]
|
|
452
|
+
|
|
453
|
+
# if [[ -n "$app_name" && -n "$app_bundle_id" && -n "$app_vers" ]] ; then
|
|
454
|
+
# installed_apps[$app_bundle_id]="$app_name;$app_vers"
|
|
455
|
+
# unset app_name
|
|
456
|
+
# unset app_bundle_id
|
|
457
|
+
# unset app_vers
|
|
458
|
+
# fi
|
|
459
|
+
# done
|
|
460
|
+
# # installed_apps is now an associative array with bundle ids as keys and app names as values
|
|
461
|
+
|
|
462
|
+
# # print the installed apps - this is a long list
|
|
463
|
+
# if debugging_on ; then
|
|
464
|
+
# debug "All installed .apps on this Mac:"
|
|
465
|
+
# for bundle app in ${(kv)installed_apps}; do
|
|
466
|
+
# debug "'$bundle' -> '$app'"
|
|
467
|
+
# done
|
|
468
|
+
# fi
|
|
469
|
+
|
|
470
|
+
# } # end installed_apps
|
|
471
|
+
########################################
|
|
472
|
+
########################################
|
|
473
|
+
########################################
|
|
474
|
+
########################################
|
|
475
|
+
########################################
|
|
476
|
+
########################################
|
|
477
|
+
########################################
|
|
478
|
+
|
|
407
479
|
# Extract data from the client-data.json file
|
|
408
480
|
#
|
|
409
481
|
# TODO: Once we only support macOS 15 (Sequoia) and higher, we
|
|
@@ -463,7 +535,7 @@ function all_xolo_titles() {
|
|
|
463
535
|
read -r -d '' jscode <<ENDJAVASCRIPT
|
|
464
536
|
result = Object.keys(parsed_data.titles).join('\n');
|
|
465
537
|
ENDJAVASCRIPT
|
|
466
|
-
extract_json_data "$jscode" | sort
|
|
538
|
+
extract_json_data "$jscode" | /usr/bin/sort
|
|
467
539
|
}
|
|
468
540
|
|
|
469
541
|
# Outputs all known titles, one per line, with versions and statuses
|
|
@@ -969,6 +1041,12 @@ function run_recon() {
|
|
|
969
1041
|
function update() {
|
|
970
1042
|
say "Updating installed titles, or installing newly scoped ones..."
|
|
971
1043
|
|
|
1044
|
+
if [[ -n "$do_recon" ]] ; then
|
|
1045
|
+
run_recon
|
|
1046
|
+
say "Pausing to let Jamf Pro process recon data..."
|
|
1047
|
+
sleep 60
|
|
1048
|
+
fi
|
|
1049
|
+
|
|
972
1050
|
debug "Running jamf policy..."
|
|
973
1051
|
if be_verbose ; then
|
|
974
1052
|
$JAMF policy -verbose
|
|
@@ -1020,12 +1098,15 @@ function list_installed_titles() {
|
|
|
1020
1098
|
local ttl
|
|
1021
1099
|
local app_data
|
|
1022
1100
|
|
|
1023
|
-
|
|
1024
1101
|
verbose "Locating installed titles..."
|
|
1025
1102
|
|
|
1026
1103
|
# populate the installed_apps associative array
|
|
1027
1104
|
get_installed_apps
|
|
1028
1105
|
|
|
1106
|
+
# gather output here, display it at the end
|
|
1107
|
+
# rather than interspersed with debug lines
|
|
1108
|
+
installed_titles_report=""
|
|
1109
|
+
|
|
1029
1110
|
while IFS= read -r ttl; do
|
|
1030
1111
|
app_data=$(app_data_for_title $ttl)
|
|
1031
1112
|
|
|
@@ -1036,6 +1117,8 @@ function list_installed_titles() {
|
|
|
1036
1117
|
display_title_if_installed_by_version_script $ttl
|
|
1037
1118
|
fi
|
|
1038
1119
|
done <<<"$(all_xolo_titles)"
|
|
1120
|
+
|
|
1121
|
+
say "$installed_titles_report"
|
|
1039
1122
|
return 0
|
|
1040
1123
|
}
|
|
1041
1124
|
|
|
@@ -1075,9 +1158,9 @@ function display_title_if_installed_by_app_data() {
|
|
|
1075
1158
|
[[ "$inst_app_name" == "$app_name" ]] || return
|
|
1076
1159
|
|
|
1077
1160
|
if [[ -n "$no_versions" ]] ; then
|
|
1078
|
-
|
|
1161
|
+
installed_titles_report="$installed_titles_report\n$ttl"
|
|
1079
1162
|
else
|
|
1080
|
-
|
|
1163
|
+
installed_titles_report="$installed_titles_report\n$ttl ($inst_app_vers)"
|
|
1081
1164
|
fi
|
|
1082
1165
|
}
|
|
1083
1166
|
|
|
@@ -1114,9 +1197,9 @@ function display_title_if_installed_by_version_script() {
|
|
|
1114
1197
|
[[ -n "$app_vers" ]] || return
|
|
1115
1198
|
|
|
1116
1199
|
if [[ -n "$no_versions" ]] ; then
|
|
1117
|
-
|
|
1200
|
+
installed_titles_report="$installed_titles_report\n$ttl"
|
|
1118
1201
|
else
|
|
1119
|
-
|
|
1202
|
+
installed_titles_report="$installed_titles_report\n$ttl ($inst_app_vers)"
|
|
1120
1203
|
fi
|
|
1121
1204
|
}
|
|
1122
1205
|
|
data/lib/xolo/core/version.rb
CHANGED
|
@@ -156,6 +156,7 @@ module Xolo
|
|
|
156
156
|
|
|
157
157
|
# @!attribute server_admin_jamf_group
|
|
158
158
|
# @return [String] The name of a Jamf account-group containing users of 'xadm'
|
|
159
|
+
# who are allowed to manage the xoloserver itself
|
|
159
160
|
server_admin_jamf_group: {
|
|
160
161
|
type: :string,
|
|
161
162
|
desc: <<~ENDDESC
|
|
@@ -35,6 +35,11 @@ module Xolo
|
|
|
35
35
|
AUTOPKG_UPLOADED_BY = 'autopkg'
|
|
36
36
|
FAIL_UNTRUSTED_RECIPES_CLI_OPT = '-k FAIL_RECIPES_WITHOUT_TRUST_INFO=yes'
|
|
37
37
|
|
|
38
|
+
# when we find a .pkg after a recipe has run, it can't be older than this many
|
|
39
|
+
# seconds, or we assume it is from some other run of the recipe, not the one we
|
|
40
|
+
# just did. Currently 20 min
|
|
41
|
+
MAX_PKG_AGE = 1200
|
|
42
|
+
|
|
38
43
|
# Module Methods
|
|
39
44
|
#######################
|
|
40
45
|
#######################
|
|
@@ -127,7 +132,9 @@ module Xolo
|
|
|
127
132
|
clean_old_pkgs pkgdir
|
|
128
133
|
|
|
129
134
|
cmd = autopkg_run_command(title_object)
|
|
130
|
-
progress "Running AutoPkg recipe
|
|
135
|
+
progress "Running AutoPkg recipe '#{recipe}' for #{title_object.title}..."
|
|
136
|
+
|
|
137
|
+
log_info "Running AutoPkg recipe for #{title_object.title} via command: #{cmd.join(' ')}"
|
|
131
138
|
|
|
132
139
|
unlock_autopkg_user_keychain
|
|
133
140
|
|
|
@@ -135,20 +142,83 @@ module Xolo
|
|
|
135
142
|
souterr.strip!
|
|
136
143
|
|
|
137
144
|
if status.success?
|
|
138
|
-
progress "AutoPkg recipe #{recipe} for title '#{title_object.title}' completed successfully.", log: :info, alert: true
|
|
145
|
+
progress "AutoPkg recipe '#{recipe}' for title '#{title_object.title}' completed successfully.", log: :info, alert: true
|
|
139
146
|
log_debug 'AutoPkg output:'
|
|
140
147
|
souterr.lines.each { |l| log_debug "AutoPkg: #{l.chomp}" }
|
|
141
148
|
|
|
142
149
|
pkgs = pkgdir.children.select { |c| c.extname == '.pkg' }
|
|
150
|
+
# the Pathname of the newest .pkg
|
|
143
151
|
pkgs.max_by(&:mtime)
|
|
144
152
|
|
|
153
|
+
# Failure is a option
|
|
145
154
|
else
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
handle_failed_recipe(
|
|
156
|
+
recipe: recipe,
|
|
157
|
+
title_object: title_object,
|
|
158
|
+
status: status,
|
|
159
|
+
souterr: souterr
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Deal with a failed recipe run
|
|
165
|
+
#
|
|
166
|
+
###############################
|
|
167
|
+
def handle_failed_recipe(recipe:, title_object:, status:, souterr:)
|
|
168
|
+
errmsg = "ERROR: AutoPkg recipe '#{recipe}' for title '#{title_object.title}' failed with status #{status.exitstatus}. Details logged and emailed to '#{title_object.contact_email}'"
|
|
169
|
+
|
|
170
|
+
progress errmsg, log: :error, alert: true
|
|
171
|
+
|
|
172
|
+
# log the details
|
|
173
|
+
log_error 'AutoPkg output:'
|
|
174
|
+
souterr.lines.each { |l| log_error "AutoPkg: #{l.chomp}" }
|
|
175
|
+
|
|
176
|
+
# email the details to the contact addr for the title
|
|
177
|
+
email_subj = "ERROR: AutoPkg recipe FAILED for title '#{title_object.title}'"
|
|
178
|
+
email_msg = <<~ENDEMAIL
|
|
179
|
+
The autopkg recipe '#{recipe}' for title '#{title_object.title}' failed with status #{status.exitstatus}.
|
|
180
|
+
|
|
181
|
+
Please check that the recipe is up to date. If not, please update the recipe's repo by running this as the autppkg user on the xolo server:
|
|
182
|
+
|
|
183
|
+
autopkg repo-update <repo-name>
|
|
184
|
+
|
|
185
|
+
Then confirm you still trust the recipe.
|
|
186
|
+
If so, run:
|
|
187
|
+
|
|
188
|
+
autopkg update-trust-info '#{recipe}'
|
|
189
|
+
|
|
190
|
+
Then tell xolo the xolo server to re-run the recipe - the .pkg will always be assigned to the latest version.
|
|
191
|
+
|
|
192
|
+
xadm rerun-autopkg-recipe #{title_object.title}
|
|
193
|
+
|
|
194
|
+
If needed, contact your xoloserver administrator to perform the autopkg commands.
|
|
195
|
+
|
|
196
|
+
Here is the output from the failed recipe:
|
|
197
|
+
------------------------------------------
|
|
198
|
+
#{souterr}
|
|
199
|
+
ENDEMAIL
|
|
200
|
+
|
|
201
|
+
send_email to: title_object.contact_email, subject: email_subj, msg: email_msg, html: false
|
|
202
|
+
|
|
203
|
+
raise "AutoPkg recipe #{recipe} failed."
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# confirm a given autopkg package is OK to use after running a recipe
|
|
207
|
+
###########################
|
|
208
|
+
def validate_autopkg_pkg(pkg_src)
|
|
209
|
+
if pkg_src.nil?
|
|
210
|
+
msg = 'AutoPkg recipe is enabled for this title, but no pkg was found after running the recipe. Please check the AutoPkg recipe and the server log for details.'
|
|
211
|
+
progress msg, log: :warn, alert: true
|
|
212
|
+
return false
|
|
213
|
+
end
|
|
149
214
|
|
|
150
|
-
|
|
215
|
+
oldest_allowed = Time.now - MAX_PKG_AGE # 20 minutes ago
|
|
216
|
+
if pkg_src.mtime < oldest_allowed
|
|
217
|
+
msg = "AutoPkg recipe is enabled for this title, and a pkg was found after running the recipe, but it was last modified at #{pkg_src.mtime}, which is more than 20 minutes ago. To avoid accidentally uploading an old pkg, the server will not upload this pkg. Please check the AutoPkg recipe and the server log for details."
|
|
218
|
+
progress msg, log: :warn, alert: true
|
|
219
|
+
return false
|
|
151
220
|
end
|
|
221
|
+
true
|
|
152
222
|
end
|
|
153
223
|
|
|
154
224
|
# remove any existing pkgs from the given autopkg output dir
|
|
@@ -108,14 +108,14 @@ module Xolo
|
|
|
108
108
|
@maint_timer_task
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
# When was our last maint?
|
|
111
|
+
# When was our last automated maint?
|
|
112
112
|
# @return [Time] the time of the last maint, or the epoch if never
|
|
113
113
|
######################################
|
|
114
114
|
def self.last_maint
|
|
115
115
|
@last_maint ||= Time.at(0)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
# Set the time of the last maint
|
|
118
|
+
# Set the time of the last maint, if it was automated
|
|
119
119
|
# @param time [Time] the time of the last maint
|
|
120
120
|
# @return [Time] the time of the last maint
|
|
121
121
|
######################################
|
|
@@ -153,7 +153,7 @@ module Xolo
|
|
|
153
153
|
Xolo::Server.logger.info 'Maint: Starting nightly run now'
|
|
154
154
|
|
|
155
155
|
else
|
|
156
|
-
Xolo::Server.logger.
|
|
156
|
+
Xolo::Server.logger.debug "Maint: Not starting, it isn't time"
|
|
157
157
|
return
|
|
158
158
|
end
|
|
159
159
|
|
|
@@ -195,13 +195,16 @@ module Xolo
|
|
|
195
195
|
|
|
196
196
|
# add new maintenance tasks/methods here
|
|
197
197
|
######
|
|
198
|
+
update_ssl_cert_and_key
|
|
198
199
|
accept_title_editor_eas title_objects_for_maint
|
|
199
200
|
auto_release_versions title_objects_for_maint
|
|
200
201
|
cleanup_versions title_objects_for_maint
|
|
201
202
|
notify_admins_of_unreleased_pilots title_objects_for_maint
|
|
202
203
|
|
|
203
|
-
# make note of the time.
|
|
204
|
-
|
|
204
|
+
# make note of the time, but only for automated runs.
|
|
205
|
+
# Manual runs don't affect this.
|
|
206
|
+
Xolo::Server::Helpers::Maintenance.last_maint = Time.now unless @manual_maintenance
|
|
207
|
+
|
|
205
208
|
log_info 'Maint: complete'
|
|
206
209
|
|
|
207
210
|
log_debug 'Maint: running update_client_data after all tasks'
|
|
@@ -210,6 +213,51 @@ module Xolo
|
|
|
210
213
|
mutex&.unlock
|
|
211
214
|
end
|
|
212
215
|
|
|
216
|
+
# Re-fetch the SSL certificate and key, as defined in the server config,
|
|
217
|
+
# and compare to the current values. If they are different, save and activate
|
|
218
|
+
# the new ones.
|
|
219
|
+
#
|
|
220
|
+
# NOTE, to get the current values we read the active files from disk,
|
|
221
|
+
# not the value in memory in the Xolo::Server.config object.
|
|
222
|
+
# In other words we use this:
|
|
223
|
+
# curr_crt = Xolo::Server::Configuration::SSL_CERT_FILE.read.strip
|
|
224
|
+
# Not this:
|
|
225
|
+
# curr_crt = Xolo::Server.config.ssl_cert
|
|
226
|
+
# This is because the file contents are what get sent to the https clients,
|
|
227
|
+
# so that's what matters.
|
|
228
|
+
# We do however update the in-memory value if an update is needed, just in case.
|
|
229
|
+
######################################
|
|
230
|
+
def update_ssl_cert_and_key
|
|
231
|
+
log_info 'Maint: Checking if SSL Certificate & Key need updating.'
|
|
232
|
+
update = false
|
|
233
|
+
|
|
234
|
+
# check the cert
|
|
235
|
+
new_crt_src = Xolo::Server.config.raw_data[:ssl_cert]
|
|
236
|
+
log_debug "Maint: Re-fetching SSL Certificate from: #{new_crt_src}"
|
|
237
|
+
new_crt = Xolo::Server.config.data_from_command_file_or_string(new_crt_src).strip
|
|
238
|
+
curr_crt = Xolo::Server::Configuration::SSL_CERT_FILE.read.strip
|
|
239
|
+
update = true unless new_crt == curr_crt
|
|
240
|
+
|
|
241
|
+
# check the key
|
|
242
|
+
new_key_src = Xolo::Server.config.raw_data[:ssl_key]
|
|
243
|
+
log_debug "Maint: Re-fetching SSL Private Key from: #{new_key_src}"
|
|
244
|
+
new_key = Xolo::Server.config.data_from_command_file_or_string(new_key_src).strip
|
|
245
|
+
curr_key = Xolo::Server::Configuration::SSL_KEY_FILE.read.strip
|
|
246
|
+
update = true unless new_key == curr_key
|
|
247
|
+
|
|
248
|
+
if update
|
|
249
|
+
log_info 'Maint: SSL Certificate or Key has been updated, installing...'
|
|
250
|
+
Xolo::Server.config.ssl_cert = new_crt
|
|
251
|
+
Xolo::Server.config.ssl_key = new_key
|
|
252
|
+
Xolo::Server::Configuration::SSL_CERT_FILE.pix_save new_crt
|
|
253
|
+
Xolo::Server::Configuration::SSL_KEY_FILE.pix_save new_key
|
|
254
|
+
else
|
|
255
|
+
log_debug 'Maint: SSL Certificate and Private Key are unchanged.'
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
log_info 'Maint: Done Checking if SSL Certificate & Key.'
|
|
259
|
+
end
|
|
260
|
+
|
|
213
261
|
# look for any titles that need their Title Editor EA's accepted,
|
|
214
262
|
# and auto accept them if we need to
|
|
215
263
|
# @return [void]
|
|
@@ -286,13 +334,13 @@ module Xolo
|
|
|
286
334
|
next unless vobj_to_release
|
|
287
335
|
|
|
288
336
|
# release it
|
|
289
|
-
log_debug "Maint: About to auto-release
|
|
337
|
+
log_debug "Maint: About to auto-release '#{title}' version '#{vobj_to_release.version}' which came out #{vobj_to_release.creation_date}"
|
|
290
338
|
|
|
291
339
|
# releasing a version is done via the title obj, since it affects the status
|
|
292
340
|
# of all versions.
|
|
293
341
|
title_obj.release vobj_to_release.version
|
|
294
342
|
|
|
295
|
-
log_info "Maint: Auto-released
|
|
343
|
+
log_info "Maint: Auto-released '#{title}' version '#{vobj_to_release.version}' which came out #{vobj_to_release.creation_date}", alert: true
|
|
296
344
|
|
|
297
345
|
# pause for server to catch up
|
|
298
346
|
sleep 60
|
|
@@ -91,7 +91,7 @@ module Xolo
|
|
|
91
91
|
new_version: new_version
|
|
92
92
|
)
|
|
93
93
|
else
|
|
94
|
-
log_debug "Title '#{title_name}' ID #{title_id} is not a subscribed title
|
|
94
|
+
log_debug "Title '#{title_name}' ID #{title_id} is not a subscribed title. Ignoring webhook."
|
|
95
95
|
end
|
|
96
96
|
rescue => e
|
|
97
97
|
msg = "Error processing PatchSoftwareTitleUpdated webhook event: #{e.class}: #{e}"
|
|
@@ -101,21 +101,6 @@ module Xolo
|
|
|
101
101
|
end # thread
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
# TODO: Delete this method after confirming we don't want it
|
|
105
|
-
# It wasn't in use when it was commented out.
|
|
106
|
-
#
|
|
107
|
-
# Subscribe to a title on a given patch source.
|
|
108
|
-
# @param source_id [Integer] the id or name of the patch source in Jamf
|
|
109
|
-
# @param name_id [Integer] the name_id of the title on that patch source
|
|
110
|
-
# @param display_name [String] an display name for the title
|
|
111
|
-
# @return [Integer] the id of the new subscribed title, or false on failure
|
|
112
|
-
#####################################
|
|
113
|
-
# def subscribe_to_title(source_id:, name_id:, display_name:)
|
|
114
|
-
# new_sub = Jamf::PatchTitle.create name: display_name, source_id: source_id, name_id: name_id, cnx: jamf_cnx
|
|
115
|
-
|
|
116
|
-
# new_sub.save
|
|
117
|
-
# end
|
|
118
|
-
|
|
119
104
|
end # Subscriptions
|
|
120
105
|
|
|
121
106
|
end # Helpers
|
|
@@ -1808,6 +1808,8 @@ module Xolo
|
|
|
1808
1808
|
pol.self_service_description = new_desc
|
|
1809
1809
|
|
|
1810
1810
|
pol.self_service_install_button_text = Xolo::Server::Title::SELF_SERVICE_INSTALL_BTN_TEXT
|
|
1811
|
+
pol.self_service_reinstall_button_text = Xolo::Server::Title::SELF_SERVICE_REINSTALL_BTN_TEXT
|
|
1812
|
+
|
|
1811
1813
|
# icon already uploaded to jamf
|
|
1812
1814
|
return if ssvc_icon_id
|
|
1813
1815
|
|
|
@@ -1823,13 +1825,6 @@ module Xolo
|
|
|
1823
1825
|
self.ssvc_icon_id = Jamf::Policy.fetch(id: pol.id, cnx: jamf_cnx).icon.id
|
|
1824
1826
|
end
|
|
1825
1827
|
|
|
1826
|
-
# run the autopkg recipe if defined
|
|
1827
|
-
# See Helpers::AutoPkg for more details
|
|
1828
|
-
############################
|
|
1829
|
-
def run_autopkg_recipe
|
|
1830
|
-
server_app_instance.run_autopkg_recipe self
|
|
1831
|
-
end
|
|
1832
|
-
|
|
1833
1828
|
# Methods used by subscription stuff
|
|
1834
1829
|
##############################
|
|
1835
1830
|
|
|
@@ -119,17 +119,17 @@ module Xolo
|
|
|
119
119
|
|
|
120
120
|
# the capabilites
|
|
121
121
|
stub_patch.capabilities.add_criterion(
|
|
122
|
-
|
|
123
|
-
# avoiding a warning message in the TEd gui.
|
|
124
|
-
# name: Xolo::Server::Version::STUB_PATCH_CAPABILITY_CRITERION_NAME,
|
|
125
|
-
name: title,
|
|
122
|
+
name: Xolo::Server::Version::STUB_PATCH_CAPABILITY_CRITERION_NAME,
|
|
126
123
|
operator: Xolo::Server::Version::STUB_PATCH_CAPABILITY_CRITERION_OPERATOR,
|
|
127
124
|
value: Xolo::Server::Version::STUB_PATCH_CAPABILITY_CRITERION_VALUE
|
|
128
125
|
)
|
|
129
126
|
|
|
130
127
|
# the component
|
|
131
128
|
stub_patch.add_component(
|
|
132
|
-
name
|
|
129
|
+
# Use the title as the name, so it matches all the future versions/patches
|
|
130
|
+
# avoiding a warning message in the TEd gui.
|
|
131
|
+
# name: Xolo::Server::Version::STUB_PATCH_COMPONENT_NAME,
|
|
132
|
+
name: title,
|
|
133
133
|
version: Xolo::Server::Version::STUB_PATCH_VERSION
|
|
134
134
|
)
|
|
135
135
|
|
|
@@ -51,7 +51,7 @@ module Xolo
|
|
|
51
51
|
start_time: Xolo::Server.start_time,
|
|
52
52
|
uptime: uptime_secs.pix_humanize_secs,
|
|
53
53
|
uptime_secs: uptime_secs,
|
|
54
|
-
|
|
54
|
+
last_automated_maint: Xolo::Server::Helpers::Maintenance.last_maint,
|
|
55
55
|
app_env: Xolo::Server.app_env,
|
|
56
56
|
test_server: Xolo::Server.test_server?,
|
|
57
57
|
data_dir: Xolo::Server::DATA_DIR,
|
|
@@ -71,6 +71,14 @@ module Xolo
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
if params[:extended]
|
|
74
|
+
ssl_cert_data ||= OpenSSL::X509::Certificate.new(Xolo::Server::Configuration::SSL_CERT_FILE.read)
|
|
75
|
+
state[:ssl_cert_subject] = ssl_cert_data.subject
|
|
76
|
+
state[:ssl_cert_issuer] = ssl_cert_data.issuer
|
|
77
|
+
state[:ssl_cert_valid_from] = ssl_cert_data.not_before.localtime
|
|
78
|
+
state[:ssl_cert_expires] = ssl_cert_data.not_after.localtime
|
|
79
|
+
state[:ssl_cert_serial] = ssl_cert_data.serial
|
|
80
|
+
state[:ssl_cert_signature_algorithm] = ssl_cert_data.signature_algorithm
|
|
81
|
+
|
|
74
82
|
state[:gem_path] = Gem.paths.path
|
|
75
83
|
state[:load_path] = $LOAD_PATH
|
|
76
84
|
state[:object_locks] = Xolo::Server.object_locks
|
|
@@ -98,6 +106,8 @@ module Xolo
|
|
|
98
106
|
# run the maintenance process manually from a server admin via xadm
|
|
99
107
|
################
|
|
100
108
|
post '/maint/maint-start' do
|
|
109
|
+
@manual_maintenance = true
|
|
110
|
+
|
|
101
111
|
log_info "Starting manual server maintenance by #{session[:admin]}"
|
|
102
112
|
|
|
103
113
|
session[:admin] = "Maintenance by #{session[:admin]}"
|
|
@@ -288,6 +288,30 @@ module Xolo
|
|
|
288
288
|
body data
|
|
289
289
|
end
|
|
290
290
|
|
|
291
|
+
# rerun the titles autopkg recipe
|
|
292
|
+
######################
|
|
293
|
+
post '/titles/:title/rerun_autopkg_recipe' do
|
|
294
|
+
halt_on_missing_title params[:title]
|
|
295
|
+
halt_on_locked_title params[:title]
|
|
296
|
+
title = instantiate_title params[:title]
|
|
297
|
+
|
|
298
|
+
unless title.autopkg_enabled?
|
|
299
|
+
msg = "Title '#{params[:title]}' is not configured to use AutoPkg"
|
|
300
|
+
log_debug "ERROR: #{msg}"
|
|
301
|
+
halt 409, { status: 409, error: msg }
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
unless title.latest_version
|
|
305
|
+
msg = "Title '#{params[:title]}' has no versions, cannot re-run the recipe"
|
|
306
|
+
log_debug "ERROR: #{msg}"
|
|
307
|
+
halt 404, { status: 404, error: msg }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
with_streaming do
|
|
311
|
+
title.rerun_autopkg_recipe
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
291
315
|
end # Titles
|
|
292
316
|
|
|
293
317
|
end # Routes
|
data/lib/xolo/server/title.rb
CHANGED
|
@@ -120,6 +120,7 @@ module Xolo
|
|
|
120
120
|
JPAPI_PATCH_REPORT_RSRC = 'patch-report'
|
|
121
121
|
|
|
122
122
|
SELF_SERVICE_INSTALL_BTN_TEXT = 'Install'
|
|
123
|
+
SELF_SERVICE_REINSTALL_BTN_TEXT = 'Re-Install'
|
|
123
124
|
|
|
124
125
|
# Class Methods
|
|
125
126
|
######################
|
|
@@ -1144,6 +1145,27 @@ module Xolo
|
|
|
1144
1145
|
unlock
|
|
1145
1146
|
end
|
|
1146
1147
|
|
|
1148
|
+
# run the autopkg recipe if defined
|
|
1149
|
+
# See Helpers::AutoPkg for more details
|
|
1150
|
+
############################
|
|
1151
|
+
def run_autopkg_recipe
|
|
1152
|
+
server_app_instance.run_autopkg_recipe self
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
# Re run the autopkg recipe
|
|
1156
|
+
# and re-upload the pkg
|
|
1157
|
+
############################
|
|
1158
|
+
def rerun_autopkg_recipe
|
|
1159
|
+
pkg_src = server_app_instance.run_autopkg_recipe self
|
|
1160
|
+
|
|
1161
|
+
return unless server_app_instance.validate_autopkg_pkg pkg_src
|
|
1162
|
+
|
|
1163
|
+
latest_version_obj = version_object latest_version
|
|
1164
|
+
latest_version_obj.pkg_is_from_autopkg = true
|
|
1165
|
+
|
|
1166
|
+
server_app_instance.process_and_upload_autopkg_pkg title, latest_version_obj, pkg_src
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1147
1169
|
# Is this title locked for updates?
|
|
1148
1170
|
#############################
|
|
1149
1171
|
def locked?
|
data/lib/xolo/server/version.rb
CHANGED
|
@@ -748,18 +748,7 @@ module Xolo
|
|
|
748
748
|
|
|
749
749
|
pkg_src = title_object.run_autopkg_recipe
|
|
750
750
|
|
|
751
|
-
|
|
752
|
-
msg = 'AutoPkg recipe is enabled for this title, but no pkg was found after running the recipe. Please check the AutoPkg recipe and the server log for details.'
|
|
753
|
-
progress msg, log: :warn, alert: true
|
|
754
|
-
return
|
|
755
|
-
end
|
|
756
|
-
|
|
757
|
-
oldest_allowed = Time.now - 1200 # 20 minutes ago
|
|
758
|
-
if pkg_src.mtime < oldest_allowed
|
|
759
|
-
msg = "AutoPkg recipe is enabled for this title, and a pkg was found after running the recipe, but it was last modified at #{pkg_src.mtime}, which is more than 20 minutes ago. To avoid accidentally uploading an old pkg, the server will not upload this pkg. Please check the AutoPkg recipe and the server log for details."
|
|
760
|
-
progress msg, log: :warn, alert: true
|
|
761
|
-
return
|
|
762
|
-
end
|
|
751
|
+
return unless server_app_instance.validate_autopkg_pkg pkg_src
|
|
763
752
|
|
|
764
753
|
# this lets future code know that the pkg we're working with came from autopkg
|
|
765
754
|
self.pkg_is_from_autopkg = true
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xolo-server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Lasell
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|