xolo-server 2.2.3 → 2.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b12ee4495ec1ef8ad6ff15963a47b8c5c960fbfc66c447355d9205a821945d5
4
- data.tar.gz: b5223966d5f2d32298cbd74fceb3bc9bd86377249f05cf7ff0cdcee7d892a0ef
3
+ metadata.gz: e78577a11f1d65aba560ec5540c3a79acdfc7d6a491060e256bb9e2da5a057d9
4
+ data.tar.gz: 9e7e776f48e8e9e260bc41a9090603bbfc41d1dad7dd62fc00396ce9914450cb
5
5
  SHA512:
6
- metadata.gz: 6891dc7666891af24efb6c3b2842154dedc0fb94652203ac7cdbce21b2f5cbf38e69dbbd54630f7c5c4f5dabdc3b717ade26c26ce1fcfbb94fc318669a2704c7
7
- data.tar.gz: faf5eb9e5a6ce4e63989070bece99e9916fe44fe14a40d6e25a04a16ddeabd5d45641cf6cce380827fa0d06901bccfc345a99ea5983b2f2509223eaf816467b6
6
+ metadata.gz: b83f99bda3666a2fd124241a26882499c393a75b3289fb503bdcb6ac994827228b5f813acc79a7a81f437f879b2e9aaca727800ddd711f17b2713d7a45a98738
7
+ data.tar.gz: 05bb86ec68010bf2258853983f093936ffa735c52144b6760c64177621a971bdf93823a46562a4b347c5a080d5206900a39ff8b6019e7fb1fa0cf0b400e4336e
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"
@@ -356,42 +359,43 @@ function debug() {
356
359
  debugging_on && echo "DEBUG: $*" 1>&2
357
360
  }
358
361
 
359
- # Gather all installed bundle ids and installed versions from lsreg
362
+ # Gather all installed bundle ids and installed versions from system_profiler
360
363
  # store them in an associative array in $installed_apps.
361
364
  # keys are bundle ids, values are app name & installed version
362
365
  # separated by a semicolon
363
366
  # e.g. 'com.apple.Safari => Safari.app;14.0.3'
364
367
  ###############################
365
368
  function get_installed_apps() {
369
+ # debug "PATH is $PATH"
366
370
  debug "Getting all installed .apps on this Mac..."
367
371
  local app_name
368
372
  local app_bundle_id
369
373
  local app_vers
374
+ local app_plist
370
375
 
371
- # loop thru lsreg output
372
- "$LSREG" -dump Bundle | while IFS= read -r line ; do
373
- # a line if ------ starts a new record
374
- if [[ "$line" =~ '^-+$' ]] ; then
375
- unset app_name
376
- unset app_bundle_id
377
- unset app_vers
378
- continue
379
- fi
376
+ json_app_data=$(/usr/sbin/system_profiler -json SPApplicationsDataType )
377
+
378
+ apps_to_look_at=$(echo "$json_app_data" | grep '"path"' | cut -d'"' -f4 | grep -v '^/System/')
379
+
380
+ for app_path in ${(f)apps_to_look_at} ; do
381
+ app_name=$(/usr/bin/basename "$app_path")
382
+ app_plist="$app_path/Contents/Info.plist"
380
383
 
381
- # $MATCH is the whole matched string, $match is the array of captures
382
- # NOTE this only works if the path is listed before the CFBundleIdentifier
383
- # which seems to be the case in the lsreg output
384
- [[ "$line" =~ '^path:[[:space:]]+/.*/(.+\.app) \(' ]] && app_name=$match[1]
385
- [[ "$line" =~ '^[[:space:]]+CFBundleIdentifier = \"(.+)\";' ]] && app_bundle_id=$match[1]
386
- [[ "$line" =~ '^[[:space:]]+CFBundleShortVersionString = \"(.+)\";' ]] && app_vers=$match[1]
384
+ if [[ -f "$app_plist" ]] ; then
385
+ app_bundle_id=$(/usr/bin/plutil -extract CFBundleIdentifier raw "$app_plist" 2>/dev/null)
386
+
387
+ app_vers=$(/usr/bin/plutil -extract CFBundleShortVersionString raw "$app_plist" 2>/dev/null)
388
+
389
+ # Use CFBundleVersion if no CFBundleShortVersionString
390
+ [[ -z "$app_vers" ]] && \
391
+ app_vers=$(/usr/bin/plutil -extract CFBundleVersion raw "$app_plist" 2>/dev/null)
387
392
 
388
- if [[ -n "$app_name" && -n "$app_bundle_id" && -n "$app_vers" ]] ; then
389
393
  installed_apps[$app_bundle_id]="$app_name;$app_vers"
390
- unset app_name
391
- unset app_bundle_id
392
- unset app_vers
394
+ else
395
+ debug "App Path '$app_path' has no Info.plist"
393
396
  fi
394
397
  done
398
+
395
399
  # installed_apps is now an associative array with bundle ids as keys and app names as values
396
400
 
397
401
  # print the installed apps - this is a long list
@@ -402,8 +406,70 @@ function get_installed_apps() {
402
406
  done
403
407
  fi
404
408
 
409
+ # debug "PATH is $PATH"
405
410
  } # end installed_apps
406
411
 
412
+ ########################################
413
+ ########################################
414
+ ########################################
415
+ ########################################
416
+ ########################################
417
+ ########################################
418
+ # Gather all installed bundle ids and installed versions from lsreg
419
+ # store them in an associative array in $installed_apps.
420
+ # keys are bundle ids, values are app name & installed version
421
+ # separated by a semicolon
422
+ # e.g. 'com.apple.Safari => Safari.app;14.0.3'
423
+ ###############################
424
+ # function get_installed_apps() {
425
+ # debug "Getting all installed .apps on this Mac..."
426
+ # local app_name
427
+ # local app_bundle_id
428
+ # local app_vers
429
+
430
+ # # loop thru lsreg output
431
+ # "$LSREG" -dump Bundle | while IFS= read -r line ; do
432
+ # # a line if ------ starts a new record
433
+ # if [[ "$line" =~ '^-+$' ]] ; then
434
+ # unset app_name
435
+ # unset app_bundle_id
436
+ # unset app_vers
437
+ # continue
438
+ # fi
439
+
440
+ # # $MATCH is the whole matched string, $match is the array of captures
441
+ # # NOTE this only works if the path is listed before the CFBundleIdentifier
442
+ # # which seems to be the case in the lsreg output
443
+ # [[ "$line" =~ '^path:[[:space:]]+/.*/(.+\.app) \(' ]] && app_name=$match[1]
444
+ # [[ "$line" =~ '^[[:space:]]+CFBundleIdentifier = \"(.+)\";' ]] && app_bundle_id=$match[1]
445
+ # [[ "$line" =~ '^[[:space:]]+CFBundleShortVersionString = \"(.+)\";' ]] && app_vers=$match[1]
446
+
447
+ # if [[ -n "$app_name" && -n "$app_bundle_id" && -n "$app_vers" ]] ; then
448
+ # installed_apps[$app_bundle_id]="$app_name;$app_vers"
449
+ # unset app_name
450
+ # unset app_bundle_id
451
+ # unset app_vers
452
+ # fi
453
+ # done
454
+ # # installed_apps is now an associative array with bundle ids as keys and app names as values
455
+
456
+ # # print the installed apps - this is a long list
457
+ # if debugging_on ; then
458
+ # debug "All installed .apps on this Mac:"
459
+ # for bundle app in ${(kv)installed_apps}; do
460
+ # debug "'$bundle' -> '$app'"
461
+ # done
462
+ # fi
463
+
464
+ # } # end installed_apps
465
+ ########################################
466
+ ########################################
467
+ ########################################
468
+ ########################################
469
+ ########################################
470
+ ########################################
471
+ ########################################
472
+
407
473
  # Extract data from the client-data.json file
408
474
  #
409
475
  # TODO: Once we only support macOS 15 (Sequoia) and higher, we
@@ -463,7 +529,7 @@ function all_xolo_titles() {
463
529
  read -r -d '' jscode <<ENDJAVASCRIPT
464
530
  result = Object.keys(parsed_data.titles).join('\n');
465
531
  ENDJAVASCRIPT
466
- extract_json_data "$jscode" | sort
532
+ extract_json_data "$jscode" | /usr/bin/sort
467
533
  }
468
534
 
469
535
  # Outputs all known titles, one per line, with versions and statuses
@@ -1020,12 +1086,15 @@ function list_installed_titles() {
1020
1086
  local ttl
1021
1087
  local app_data
1022
1088
 
1023
-
1024
1089
  verbose "Locating installed titles..."
1025
1090
 
1026
1091
  # populate the installed_apps associative array
1027
1092
  get_installed_apps
1028
1093
 
1094
+ # gather output here, display it at the end
1095
+ # rather than interspersed with debug lines
1096
+ installed_titles_report=""
1097
+
1029
1098
  while IFS= read -r ttl; do
1030
1099
  app_data=$(app_data_for_title $ttl)
1031
1100
 
@@ -1036,6 +1105,8 @@ function list_installed_titles() {
1036
1105
  display_title_if_installed_by_version_script $ttl
1037
1106
  fi
1038
1107
  done <<<"$(all_xolo_titles)"
1108
+
1109
+ say "$installed_titles_report"
1039
1110
  return 0
1040
1111
  }
1041
1112
 
@@ -1075,9 +1146,9 @@ function display_title_if_installed_by_app_data() {
1075
1146
  [[ "$inst_app_name" == "$app_name" ]] || return
1076
1147
 
1077
1148
  if [[ -n "$no_versions" ]] ; then
1078
- echo $ttl
1149
+ installed_titles_report="$installed_titles_report\n$ttl"
1079
1150
  else
1080
- echo "$ttl ($inst_app_vers)"
1151
+ installed_titles_report="$installed_titles_report\n$ttl ($inst_app_vers)"
1081
1152
  fi
1082
1153
  }
1083
1154
 
@@ -1114,9 +1185,9 @@ function display_title_if_installed_by_version_script() {
1114
1185
  [[ -n "$app_vers" ]] || return
1115
1186
 
1116
1187
  if [[ -n "$no_versions" ]] ; then
1117
- echo $ttl
1188
+ installed_titles_report="$installed_titles_report\n$ttl"
1118
1189
  else
1119
- echo "$ttl ($app_vers)"
1190
+ installed_titles_report="$installed_titles_report\n$ttl ($inst_app_vers)"
1120
1191
  fi
1121
1192
  }
1122
1193
 
@@ -33,7 +33,7 @@ module Xolo
33
33
  # Instance method #conf_file [Pathname]
34
34
  # The full expanded absolute path to the config yaml file.
35
35
  #
36
- # Constant KEYS [Hash{Symbol: Hash}]
36
+ # Constant KEYS [Hash {Symbol: Hash}]
37
37
  # The keys of this Hash are the keys that may be found in the yaml file.
38
38
  # The Hash values here define the value in the YAML file, with these keys:
39
39
  #
@@ -14,12 +14,9 @@ module Xolo
14
14
 
15
15
  module BaseClasses
16
16
 
17
- # The base class for dealing with Titles and Versions/Patches in the
18
- # Xolo Server, Admin, and Client modules.
19
- #
20
17
  # The base class for "xolo objects stored on the xolo server", i.e.
21
18
  # Titles and Versions/Patches - whether they are being used on the server,
22
- # in xadm, or in the client.
19
+ # or in xadm.
23
20
  #
24
21
  # This class holds stuff common to all no matter where or how they are used.
25
22
  #
@@ -15,12 +15,12 @@ module Xolo
15
15
  module BaseClasses
16
16
 
17
17
  # The base class for dealing with Titles in the
18
- # Xolo Server, Admin, and Client modules.
18
+ # Xolo Server & Admin modules.
19
19
  #
20
20
  # This class holds the common aspects of Xolo Titles as used
21
- # on the Xolo server, in the Xolo Admin CLI app 'xadm', and the
22
- # client app 'xolo' - most importately it defines which data they
23
- # exchange.
21
+ # on the Xolo server and in the Xolo Admin CLI app 'xadm'
22
+ # and the 'xolo' client app (as JSON data).
23
+ # - most importately it defines which data they exchange.
24
24
  #
25
25
  ############################
26
26
  class Title < Xolo::Core::BaseClasses::ServerObject
@@ -43,7 +43,7 @@ module Xolo
43
43
  #
44
44
  # This hash defines the common attributes of all Xolo titles - which should
45
45
  # be available in all subclasses, both on the server, in xadm, and via the
46
- # client.
46
+ # client (as JSON data).
47
47
  #
48
48
  # Each of those subclasses might define (and store or calculate) other
49
49
  # attributes as needed.
@@ -966,6 +966,14 @@ module Xolo
966
966
  version_order&.first
967
967
  end
968
968
 
969
+ # Does a given version exist for this title?
970
+ # @param vers [String] the version in question
971
+ # @return [Boolean]
972
+ #######################
973
+ def version_exist?(vers)
974
+ version_order.include? vers
975
+ end
976
+
969
977
  # Is this a managed title?
970
978
  # @return [Boolean]
971
979
  #######################
@@ -15,12 +15,12 @@ module Xolo
15
15
  module BaseClasses
16
16
 
17
17
  # The base class for dealing with Versions/Patches in the
18
- # Xolo Server, Admin, and Client modules.
18
+ # Xolo Server & Admin modules.
19
19
  #
20
20
  # This class holds the common aspects of Xolo Versinos as used
21
- # on the Xolo server, in the Xolo Admin CLI app 'xadm', and the
22
- # client app 'xolo' - most importately it defines which data they
23
- # exchange.
21
+ # on the Xolo server and in the Xolo Admin CLI app 'xadm'
22
+ # and the 'xolo' client app (as JSON data).
23
+ # - most importately it defines which data they exchange.
24
24
  #
25
25
  ############################
26
26
  class Version < Xolo::Core::BaseClasses::ServerObject
@@ -12,7 +12,7 @@ module Xolo
12
12
 
13
13
  module Version
14
14
 
15
- VERSION = '2.2.3'.freeze
15
+ VERSION = '2.3.0'.freeze
16
16
 
17
17
  end
18
18
 
@@ -124,6 +124,8 @@ module Xolo
124
124
  pkg_src = Pathname.new pkg_src
125
125
  orig_filename ||= pkg_src.basename.to_s
126
126
 
127
+ log_debug "Starting process_and_upload_to_jamf for #{title}, #{version}, pkg src: #{pkg_src}, orig filename: #{orig_filename}"
128
+
127
129
  version = instantiate_version(title: title, version: version) if version.is_a?(String)
128
130
 
129
131
  staged_pkg = prep_pkg_for_upload(version, pkg_src)
@@ -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.info "Maint: Not starting, it isn't time"
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
- Xolo::Server::Helpers::Maintenance.last_maint = Time.now
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]
@@ -172,13 +172,17 @@ module Xolo
172
172
  # @return [void]
173
173
  #############################
174
174
  def stream_progress(stream_file:, stream:)
175
- log_debug "About to tail: usr/bin/tail -f -c +1 #{Shellwords.escape stream_file.to_s}"
175
+ # log_debug "About to tail: usr/bin/tail -f -c +1 #{Shellwords.escape stream_file.to_s}"
176
+ log_debug "About to stream file: #{Shellwords.escape stream_file.to_s}"
177
+
178
+ # a moment for the file to exist..
179
+ until stream_file.readable?
180
+ log_debug 'Waiting for stream file to be readable'
181
+ sleep 2
182
+ end
176
183
 
177
184
  begin
178
185
  File.open(stream_file, 'r') do |file|
179
- # Go straight to the end of the file if you only want new lines
180
- file.seek(0, IO::SEEK_END)
181
-
182
186
  heartbeat_counter = 0
183
187
  loop do
184
188
  # Check if the stream was closed by the user
@@ -187,10 +191,16 @@ module Xolo
187
191
  # Try to read the next line
188
192
  # This won't block but will return nil if no line is ready
189
193
  if line = file.gets
190
- break if line.chomp == Xolo::Server::PROGRESS_COMPLETE
194
+ # log_debug "STREAMING: line is: '#{line}'"
195
+
196
+ if line.chomp == Xolo::Server::PROGRESS_COMPLETE
197
+ stream.close
198
+ break
199
+ end
191
200
 
192
201
  # Send the line to the browser immediately
193
202
  stream << line
203
+
194
204
  else
195
205
  # If no new line, sleep for a fraction of a second
196
206
  # This saves your computer's CPU from working too hard
@@ -205,8 +215,12 @@ module Xolo
205
215
  end # file open
206
216
  rescue IOError, Errno::ECONNRESET
207
217
  nil
218
+ ensure
219
+ stream.close unless stream.closed?
208
220
  end # begin
209
221
 
222
+ # the old way, using tail
223
+ ######
210
224
  # stdin, stdouterr, wait_thr = Open3.popen2e("/usr/bin/tail -f -c +1 #{Shellwords.escape stream_file.to_s}")
211
225
  # stdin.close
212
226
 
@@ -75,6 +75,11 @@ module Xolo
75
75
  subscribed_title = subscribed_title_objects.select { |tobj| tobj.jamf_patch_title_id.to_i == title_id.to_i }.first
76
76
 
77
77
  if subscribed_title
78
+ if subscribed_title.version_exist? new_version
79
+ log_info "Incoming Subscribed version '#{new_version}' of title '#{subscribed_title.title}' (#{subscribed_title.display_name}) already exists, nothing to do."
80
+ return
81
+ end
82
+
78
83
  msg = +"New version '#{new_version}' is available for subscribed title '#{subscribed_title.title}' (#{subscribed_title.display_name})."
79
84
 
80
85
  msg << " Running autopkg recipe '#{subscribed_title.autopkg_recipe}'." if subscribed_title.autopkg_enabled?
@@ -86,30 +91,16 @@ module Xolo
86
91
  new_version: new_version
87
92
  )
88
93
  else
89
- log_debug "Title '#{title_name}' ID #{title_id} is not a subscribed title in Xolo. Ignoring webhook."
94
+ log_debug "Title '#{title_name}' ID #{title_id} is not a subscribed title. Ignoring webhook."
90
95
  end
91
96
  rescue => e
92
97
  msg = "Error processing PatchSoftwareTitleUpdated webhook event: #{e.class}: #{e}"
93
98
  log_error msg
99
+ e.backtrace.each { |l| log_error "..#{l}" }
94
100
  raise e, msg
95
101
  end # thread
96
102
  end
97
103
 
98
- # TODO: Delete this method after confirming we don't want it
99
- # It wasn't in use when it was commented out.
100
- #
101
- # Subscribe to a title on a given patch source.
102
- # @param source_id [Integer] the id or name of the patch source in Jamf
103
- # @param name_id [Integer] the name_id of the title on that patch source
104
- # @param display_name [String] an display name for the title
105
- # @return [Integer] the id of the new subscribed title, or false on failure
106
- #####################################
107
- # def subscribe_to_title(source_id:, name_id:, display_name:)
108
- # new_sub = Jamf::PatchTitle.create name: display_name, source_id: source_id, name_id: name_id, cnx: jamf_cnx
109
-
110
- # new_sub.save
111
- # end
112
-
113
104
  end # Subscriptions
114
105
 
115
106
  end # Helpers
@@ -50,6 +50,77 @@ module Xolo
50
50
  Xolo.verbose_include includer, self
51
51
  end
52
52
 
53
+ # Module Methods
54
+ # These must be called using Xolo::Server::Helpers::TitleEditor.<method>
55
+ ###############################
56
+ ##############################
57
+
58
+ # Given a Windoo::Patch object, and an
59
+ # app_name: & app_bundle_id: or an ea_name:
60
+ # Set the component criteria for the patch to
61
+ # be based either on the app data, or ea
62
+ #
63
+ # @param patch [Windoo::Patch] the patch to set
64
+ #
65
+ # @param title [String] the title for this patch
66
+ # @param app_name [String] the name of the app to use in app-based requirements,
67
+ # must be used with app_bundle_id, cannot be used with ea_name
68
+ #
69
+ # @param app_bundle_id [String] the bundle id of the app to use in app-based requirements
70
+ # must be used with app_name, cannot be used with ea_name
71
+ #
72
+ # @param ea_name [String] the name of the EA to use in EA-based requirements (the ted_ea_key)
73
+ # Cannot be used with app_name or app_bundle_id
74
+ #
75
+ # @return [void]
76
+ ################################
77
+ def self.set_patch_component_criteria(ted_patch, app_name: nil, app_bundle_id: nil, ea_name: nil)
78
+ # delete any already there and make a new one
79
+ ted_patch.delete_component
80
+ ted_patch.add_component name: ted_patch.container.id, version: ted_patch.version
81
+
82
+ comp = ted_patch.component
83
+
84
+ if ea_name
85
+ set_ea_component(comp, ea_name, ted_patch.version)
86
+ else
87
+ set_app_component(comp, app_name, app_bundle_id, ted_patch.version)
88
+ end
89
+ end
90
+
91
+ # @return [void]
92
+ ##############################
93
+ def self.set_ea_component(comp, ea_name, version)
94
+ comp.criteria.add_criterion(
95
+ type: 'extensionAttribute',
96
+ name: ea_name,
97
+ operator: 'is',
98
+ value: version
99
+ )
100
+ end
101
+
102
+ # @return [void]
103
+ ##############################
104
+ def self.set_app_component(comp, app_name, app_bundle_id, version)
105
+ comp.criteria.add_criterion(
106
+ name: 'Application Title',
107
+ operator: 'is',
108
+ value: app_name
109
+ )
110
+
111
+ comp.criteria.add_criterion(
112
+ name: 'Application Bundle ID',
113
+ operator: 'is',
114
+ value: app_bundle_id
115
+ )
116
+
117
+ comp.criteria.add_criterion(
118
+ name: 'Application Version',
119
+ operator: 'is',
120
+ value: version
121
+ )
122
+ end
123
+
53
124
  # Instance methods
54
125
  #
55
126
  # These are available directly in sinatra routes and views
@@ -126,7 +126,10 @@ module Xolo
126
126
 
127
127
  # the component
128
128
  stub_patch.add_component(
129
- name: Xolo::Server::Version::STUB_PATCH_COMPONENT_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,
130
133
  version: Xolo::Server::Version::STUB_PATCH_VERSION
131
134
  )
132
135
 
@@ -424,17 +427,36 @@ module Xolo
424
427
  # update the patch compotent criteria for all versions
425
428
  # to match changes in the title requirements
426
429
  #
430
+ # NOTE: we do all versions in the title editor, not just the ones known to
431
+ # xolo.
432
+ #
433
+ #
427
434
  # @return [void]
428
435
  ##############################
429
436
  def set_ted_patch_component_criteria_after_update(app_name: nil, app_bundle_id: nil, ea_name: nil)
430
- version_objects.each do |vers_obj|
431
- vers_obj.set_ted_patch_component_criteria(
437
+ ted_title.patches.each do |ted_patch|
438
+ Xolo::Server::Helpers::TitleEditor.set_patch_component_criteria(
439
+ ted_patch,
432
440
  app_name: app_name,
433
441
  app_bundle_id: app_bundle_id,
434
442
  ea_name: ea_name
435
443
  )
436
- vers_obj.enable_ted_patch
437
- end
444
+ # ensure its enabled
445
+ ted_patch.enable
446
+ end # ted_title.patches.each
447
+
448
+ # # this only dealt with versions currently known to xolo
449
+ # # but the title editor keeps all versions ever known
450
+ # # even if deleted from xolo.
451
+ #
452
+ # version_objects.each do |vers_obj|
453
+ # vers_obj.set_ted_patch_component_criteria(
454
+ # app_name: app_name,
455
+ # app_bundle_id: app_bundle_id,
456
+ # ea_name: ea_name
457
+ # )
458
+ # vers_obj.enable_ted_patch
459
+ # end
438
460
  end
439
461
 
440
462
  # Enable the title in the title editor when at least one patch is enabled
@@ -538,18 +560,36 @@ module Xolo
538
560
  version_objects.each do |vobj|
539
561
  vobj.repair_ted_patch
540
562
  end
563
+
564
+ # also update the component criteria for patches not known to xolo anymore.
565
+ ted_title.patches.each do |ted_patch|
566
+ # we've already done the known version objects, above
567
+ next if version_order.include? ted_patch.version
568
+
569
+ Xolo::Server::Helpers::TitleEditor.set_patch_component_criteria(
570
+ ted_patch,
571
+ app_name: app_name,
572
+ app_bundle_id: app_bundle_id,
573
+ ea_name: ted_ea_key
574
+ )
575
+ ted_patch.enable
576
+ end
541
577
  end
542
578
 
543
579
  ted_title.enable unless ted_title(refresh: true).enabled?
544
580
  end
545
581
 
546
582
  # Re-enable all patches in the title editor
583
+ # not just those known to xolo
584
+ #
547
585
  # @return [void]
548
586
  ###########################
549
587
  def reenable_all_ted_patches
550
588
  # re-enable all patches, after any change, which might have disabled them
551
589
  progress "Title Editor: Re-enabling all patches for '#{title}'", log: :info
552
- version_objects.each { |vobj| vobj.enable_ted_patch }
590
+
591
+ # version_objects.each { |vobj| vobj.enable_ted_patch }
592
+ ted_title.patches.each { |ted_patch| ted_patch.enable }
553
593
  end
554
594
 
555
595
  end # TitleEditorTitle
@@ -438,8 +438,10 @@ module Xolo
438
438
  end
439
439
 
440
440
  # Delete the package for this version from Jamf Pro.
441
- # Package deletion takes a long time, so we do it in a threadpool
441
+ # Package deletion in some versions of Jamf Pro takes a long time,
442
+ # especially when you have a lot of pkgs. so we do it in a threadpool
442
443
  # and tell the admin to check the Alert Tool for completion
444
+ #
443
445
  # (if we have an alert tool in place) or to wait at least 5 min before
444
446
  # re-adding the same version.
445
447
  #
@@ -452,16 +454,8 @@ module Xolo
452
454
  msg = "Jamf: Starting deletion of Package '#{jamf_pkg_name}' id #{jamf_pkg_id} at #{Time.now.strftime '%F %T'}"
453
455
  progress msg, log: :info
454
456
 
455
- warning = +"IMPORTANT: Package deletion can be slow. If you plan to re-add this version, '#{version}', please\n "
456
- warning <<
457
- if Xolo::Server.config.alert_tool
458
- 'check your Xolo alerts for completion, which can take up to 5 minutes,'
459
- else
460
- 'wait at least 5 minutes'
461
- end
462
- warning << ' before re-adding this version.'
463
-
464
- progress warning, log: nil
457
+ warning = "IMPORTANT: Package deletion can be slow. If you plan to re-add this version, '#{version}', please\nwait at least 5 minutes before doing so."
458
+ progress warning
465
459
 
466
460
  self.class.pkg_deletion_pool.post do
467
461
  start = Time.now
@@ -470,7 +464,7 @@ module Xolo
470
464
  Jamf::JPackage.delete pkg_id, cnx: jamf_cnx
471
465
  finish = Time.now
472
466
  duration = (finish - start).to_i.pix_humanize_secs
473
- log_info "Jamf: Deleted Package '#{jamf_pkg_name}' id #{jamf_pkg_id} in #{duration}", alert: true
467
+ log_info "Jamf: Deleted Package '#{jamf_pkg_name}' id #{jamf_pkg_id} in #{duration}"
474
468
  rescue => e
475
469
  log_error "Package Deletion thread: #{e.class}: #{e}"
476
470
  e.backtrace.each { |l| log_error "..#{l}" }
@@ -887,14 +881,19 @@ module Xolo
887
881
 
888
882
  configure_jamf_patch_policy(ppol)
889
883
 
890
- # This is always false to start will be set to true as needed if
891
- # a rollback is being done
884
+ # This is always false to start, which is why we set it here in the create method
885
+ # will be set to true as needed if a rollback is being done
892
886
  ppol.allow_downgrade = false
893
887
 
894
888
  ppol.save
895
889
 
896
890
  # refetch it rather than using the one we just created
897
- Jamf::PatchPolicy.fetch(name: jamf_patch_policy_name, cnx: jamf_cnx)
891
+ ppol = Jamf::PatchPolicy.fetch(name: jamf_patch_policy_name, cnx: jamf_cnx)
892
+
893
+ # set the ssvc icon, must happen after the policy is saved
894
+ set_patch_policy_ssvc_icon ppol
895
+
896
+ ppol
898
897
  end
899
898
 
900
899
  # repair the patch policy only
@@ -908,6 +907,9 @@ module Xolo
908
907
  configure_jamf_patch_policy(ppol)
909
908
 
910
909
  ppol.save
910
+
911
+ # set the ssvc icon, must happen after the policy is saved
912
+ set_patch_policy_ssvc_icon ppol
911
913
  end
912
914
 
913
915
  # update the :patch_unknown for the patch policy
@@ -982,10 +984,24 @@ module Xolo
982
984
  ppol = jamf_patch_policy
983
985
  configure_patch_pol_deployment ttl_obj: ttl_obj, ppol: ppol
984
986
  ppol.save
987
+ # set the ssvc icon, must happen after the policy is saved
988
+ set_patch_policy_ssvc_icon ppol
989
+ end
990
+
991
+ # @return [Boolean] should this version use ssvc updates?
992
+ ##################
993
+ def use_ssvc_updates?(ttl_obj = nil)
994
+ ttl_obj ||= title_object
995
+
996
+ if ttl_obj.changes_for_update&.dig :self_service_updates
997
+ ttl_obj.changes_for_update[:self_service_updates][:new]
998
+ else
999
+ ttl_obj.self_service_updates
1000
+ end
985
1001
  end
986
1002
 
987
1003
  # Configure the patch policy for self service or automatic deployment
988
- # NOTE: Does NOT save the policy changes!
1004
+ # NOTE: this does not save changes to the policy - the caller must
989
1005
  # @param ppol [Jamf::PatchPolicy] The patch policy to configure
990
1006
  # @return [void]
991
1007
  # ###############################
@@ -993,13 +1009,6 @@ module Xolo
993
1009
  ppol ||= jamf_patch_policy
994
1010
  ttl_obj ||= title_object
995
1011
 
996
- use_ssvc_updates =
997
- if ttl_obj.changes_for_update&.dig :self_service_updates
998
- ttl_obj.changes_for_update[:self_service_updates][:new]
999
- else
1000
- ttl_obj.self_service_updates
1001
- end
1002
-
1003
1012
  # always set the grace period regardless of deployment type
1004
1013
  progress "Jamf: Configuring Patch Policy grace period for version '#{version}' of title '#{title}' ", log: :info
1005
1014
  ppol.grace_period = PATCH_POL_GRACE_PERIOD_MINUTES
@@ -1007,7 +1016,7 @@ module Xolo
1007
1016
  ppol.grace_period_message = PATCH_POL_GRACE_PERIOD_MSG
1008
1017
 
1009
1018
  # Self Service deployment
1010
- if use_ssvc_updates
1019
+ if use_ssvc_updates?
1011
1020
  progress "Jamf: Configuring Patch Policy for version '#{version}' of title '#{title}' for Self Service", log: :info
1012
1021
 
1013
1022
  ppol.add_to_self_service
@@ -1024,12 +1033,6 @@ module Xolo
1024
1033
  ppol.self_service_reminder_frequency = PATCH_POL_SSVC_REMINDER_FREQ_HRS
1025
1034
  ppol.deadline = PATCH_POL_SSVC_DEADLINE_DAYS
1026
1035
 
1027
- icon_id =
1028
- title_object.ssvc_icon_id || title_object.jamf_manual_install_released_policy.self_service_icon.id
1029
- return unless icon_id
1030
-
1031
- set_patch_policy_ssvc_icon ppol, icon_id
1032
-
1033
1036
  # Automatic deployment
1034
1037
  else
1035
1038
  progress "Jamf: Configuring Patch Policy for version '#{version}' of title '#{title}' for automatic deployment", log: :info
@@ -1049,7 +1052,23 @@ module Xolo
1049
1052
  # @param icon_id [Integer, String] The id of the existing icon to use.
1050
1053
  # @return [Void]
1051
1054
  ##########################
1052
- def set_patch_policy_ssvc_icon(ppol, icon_id)
1055
+ def set_patch_policy_ssvc_icon(ppol)
1056
+ return unless use_ssvc_updates?
1057
+
1058
+ if title_object.ssvc_icon_id
1059
+ icon_id = title_object.ssvc_icon_id
1060
+ log_debug "Jamf: ssvc_icon_id from title object is: #{icon_id}"
1061
+ else
1062
+ icon_id = title_object.jamf_manual_install_released_policy.self_service_icon.id
1063
+ log_debug "Jamf: ssvc_icon_id from manual install policy is: #{icon_id}"
1064
+
1065
+ end
1066
+
1067
+ if icon_id.pix_empty?
1068
+ log_debug 'Jamf: could not find ssvc icon id, not setting icon for patch policy.'
1069
+ return
1070
+ end
1071
+
1053
1072
  # this str can't be frozen for now
1054
1073
  icon_xml = +<<~ENDXML
1055
1074
  <?xml version="1.0" encoding="UTF-8"?>
@@ -1063,9 +1082,11 @@ module Xolo
1063
1082
  ENDXML
1064
1083
 
1065
1084
  log_debug "Jamf: Adding existing SSvc icon to patch policy for version '#{version}' of title '#{title}'"
1085
+
1066
1086
  # TODO: in ruby-jss, update c_put and related methods to
1067
1087
  # use unfrozen dups of strings for #gsub
1068
- jamf_cnx.c_put ppol.rest_rsrc, icon_xml
1088
+
1089
+ jamf_cnx.c_put "patchpolicies/id/#{ppol.id}", icon_xml
1069
1090
  end
1070
1091
 
1071
1092
  # @return [String] the URL for the Jamf Pro Patch Policy that updates to this version
@@ -253,12 +253,12 @@ module Xolo
253
253
  msg = "Title Editor: Setting #{type}-based component criteria for Patch '#{version}' of SoftwareTitle '#{title}'"
254
254
  progress msg, log: :info
255
255
 
256
- # delete any already there and make a new one
257
- ted_patch.delete_component
258
- ted_patch.add_component name: title, version: version
259
- comp = ted_patch.component
260
-
261
- ea_name ? set_ea_component(comp, ea_name) : set_app_component(comp, app_name, app_bundle_id)
256
+ Xolo::Server::Helpers::TitleEditor.set_patch_component_criteria(
257
+ ted_patch,
258
+ app_name: app_name,
259
+ app_bundle_id: app_bundle_id,
260
+ ea_name: ea_name
261
+ )
262
262
  end
263
263
 
264
264
  # get the param values for patch component criteria from the title object,
@@ -287,39 +287,6 @@ module Xolo
287
287
  [app_name, app_bundle_id, ea_name]
288
288
  end
289
289
 
290
- # @return [void]
291
- ##############################
292
- def set_ea_component(comp, ea_name)
293
- comp.criteria.add_criterion(
294
- type: 'extensionAttribute',
295
- name: ea_name,
296
- operator: 'is',
297
- value: version
298
- )
299
- end
300
-
301
- # @return [void]
302
- ##############################
303
- def set_app_component(comp, app_name, app_bundle_id)
304
- comp.criteria.add_criterion(
305
- name: 'Application Title',
306
- operator: 'is',
307
- value: app_name
308
- )
309
-
310
- comp.criteria.add_criterion(
311
- name: 'Application Bundle ID',
312
- operator: 'is',
313
- value: app_bundle_id
314
- )
315
-
316
- comp.criteria.add_criterion(
317
- name: 'Application Version',
318
- operator: 'is',
319
- value: version
320
- )
321
- end
322
-
323
290
  # For a patch to be enabled in the Title Editor, it needs at least a component criterion
324
291
  # and one capability. Xolo enforces those when the patch is created, so from the title
325
292
  # editor's view it should be OK from the start.
@@ -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
- last_maint: Xolo::Server::Helpers::Maintenance.last_maint,
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]}"
@@ -144,8 +144,6 @@ module Xolo
144
144
 
145
145
  with_streaming do
146
146
  title.repair repair_versions: do_versions
147
- # moved to title.repair
148
- # update_client_data
149
147
  end
150
148
  end
151
149
 
@@ -159,8 +157,6 @@ module Xolo
159
157
 
160
158
  with_streaming do
161
159
  title.delete
162
- # moved to title.delete
163
- # update_client_data
164
160
  end
165
161
  end
166
162
 
@@ -127,8 +127,6 @@ module Xolo
127
127
 
128
128
  with_streaming do
129
129
  vers.update new_data
130
- # moved to vers.update
131
- # update_client_data
132
130
  end
133
131
  end
134
132
 
@@ -160,7 +158,10 @@ module Xolo
160
158
  # param with the uploaded file must be :file
161
159
  ######################
162
160
  post '/titles/:title/versions/:version/pkg' do
161
+ @pkg_upload_happening = true
162
+ log_debug "Starting POST to upload pkg for #{params[:title]}, #{params[:version]}"
163
163
  process_and_upload_uploaded_pkg
164
+ @pkg_upload_happening = false
164
165
  body({ result: :uploaded })
165
166
  end
166
167
 
@@ -85,7 +85,7 @@ module Xolo
85
85
  STUB_PATCH_CAPABILITY_CRITERION_VALUE = '10.0'
86
86
 
87
87
  # machines that have this version installed
88
- STUB_PATCH_COMPONENT_NAME = 'Xolo Stub'
88
+ # STUB_PATCH_COMPONENT_NAME = 'Xolo Stub'
89
89
  STUB_PATCH_COMPONENT_CRITERION_NAME = 'Application Title'
90
90
  STUB_PATCH_COMPONENT_CRITERION_OPERATOR = 'is'
91
91
  STUB_PATCH_COMPONENT_CRITERION_VALUE = 'XoloStub-DoesNotExist.app'
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.2.3
4
+ version: 2.3.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-08-17 00:00:00.000000000 Z
11
+ date: 2026-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday