xolo-server 2.1.0 → 2.2.2

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.
@@ -51,6 +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
55
  app_env: Xolo::Server.app_env,
55
56
  test_server: Xolo::Server.test_server?,
56
57
  data_dir: Xolo::Server::DATA_DIR,
@@ -80,29 +81,29 @@ module Xolo
80
81
  body state
81
82
  end
82
83
 
83
- # run the cleanup process from the internal timer task
84
+ # run the maint process from the internal timer task
84
85
  # The before filter will ensure the request came from the server itself.
85
86
  # with a valid internal auth token.
86
87
  ################
87
- post '/maint/cleanup-internal' do
88
- log_info 'Starting internal cleanup'
89
- session[:admin] = 'Automated Cleanup'
88
+ post '/maint/maint-internal' do
89
+ log_info 'Starting internal maint'
90
+ session[:admin] = 'Automated Maintenance'
90
91
 
91
- thr = Thread.new { run_cleanup }
92
- thr.name = 'Internal Cleanup Thread'
93
- result = { result: 'Internal Cleanup Underway' }
92
+ thr = Thread.new { run_maint }
93
+ thr.name = 'Internal Maintenance Thread'
94
+ result = { result: 'Internal Maintenance Underway' }
94
95
  body result
95
96
  end
96
97
 
97
- # run the cleanup process manually from a server admin via xadm
98
+ # run the maintenance process manually from a server admin via xadm
98
99
  ################
99
- post '/maint/cleanup' do
100
- log_info "Starting manual server cleanup by #{session[:admin]}"
100
+ post '/maint/maint-start' do
101
+ log_info "Starting manual server maintenance by #{session[:admin]}"
101
102
 
102
- session[:admin] = "Cleanup by #{session[:admin]}"
103
- thr = Thread.new { run_cleanup }
104
- thr.name = 'Manual Cleanup Thread'
105
- result = { result: 'Manual Cleanup Underway' }
103
+ session[:admin] = "Maintenance by #{session[:admin]}"
104
+ thr = Thread.new { run_maint }
105
+ thr.name = 'Manual Maintenance Thread'
106
+ result = { result: 'Manual Maintenance Underway' }
106
107
  body result
107
108
  end
108
109
 
@@ -146,7 +147,7 @@ module Xolo
146
147
  # Shutdown the server gracefully
147
148
  # stop accepting new requests
148
149
  # wait for all queues and threads to finish, including:
149
- # the cleanup timer task & mutex
150
+ # the maintenance timer task & mutex
150
151
  # the log rotation timer task & mutex
151
152
  # the pkg deletion pool
152
153
  # the object locks
@@ -263,6 +263,9 @@ module Xolo
263
263
  if title.jamf_title_active?
264
264
  data[:jamf_patch_title_url] = title.jamf_patch_title_url unless title.jamf_patch_title_id.pix_empty?
265
265
  data[:jamf_patch_ea_url] = title.jamf_patch_ea_url if title.version_script
266
+
267
+ data[:jamf_target_groups_exclusion_group_url] = title.jamf_target_groups_exclusion_group_url if title.jamf_target_groups_exclusion_group_exist?
268
+
266
269
  end
267
270
 
268
271
  body data
@@ -127,7 +127,7 @@ module Xolo
127
127
  @no_json = true
128
128
  stream_file = Pathname.new params[:stream_file]
129
129
 
130
- stream do |stream_out|
130
+ stream :keep_open do |stream_out|
131
131
  stream_progress(stream_file: stream_file, stream: stream_out)
132
132
  rescue => e
133
133
  stream_out << "ERROR DURING PROGRESS STREAM: #{e.class}: #{e}"
@@ -77,6 +77,7 @@ module Xolo
77
77
  JAMF_MANUAL_INSTALL_RELEASED_POL_SUFFIX = 'install'
78
78
  JAMF_UNINSTALL_SUFFIX = 'uninstall'
79
79
  JAMF_EXPIRE_SUFFIX = 'expire'
80
+ JAMF_TGT_GRP_EXCL_GRP_NAME_SUFFIX = 'target-group-exclusions'
80
81
 
81
82
  # the expire policy will run this client command,
82
83
  # appending the title
@@ -257,6 +258,20 @@ module Xolo
257
258
  # @return [String] the name of the smart group
258
259
  attr_reader :jamf_frozen_group_name
259
260
 
261
+ # If a title has 'target_groups', they act as the opposite of
262
+ # excluded groups - macs NOT in the target groups can't see the title.
263
+ #
264
+ # We implement this by maintaining a smart group
265
+ # named 'xolo-<title>-target-group-exclusions', which has the criteria:
266
+ # computerGroup...not a member of...<target-group>
267
+ # ANDed together for each target-group
268
+ # and then we use that smart group as an exclusion.
269
+ #
270
+ # This is the name of that smart group.
271
+ #
272
+ # @return [String] the name of the smart group
273
+ attr_reader :jamf_target_groups_exclusion_group_name
274
+
260
275
  # The name of the policy that does initial manual or self-service
261
276
  # installs of the currently-released version of this title.
262
277
  # It will be named 'xolo-<title>-install'
@@ -354,6 +369,7 @@ module Xolo
354
369
  @jamf_obj_name_pfx = "#{jamf_obj_name_pfx_base}#{data_hash[:title]}"
355
370
  @jamf_installed_group_name = "#{jamf_obj_name_pfx}-#{JAMF_INSTALLED_GROUP_NAME_SUFFIX}"
356
371
  @jamf_frozen_group_name = "#{jamf_obj_name_pfx}-#{JAMF_FROZEN_GROUP_NAME_SUFFIX}"
372
+ @jamf_target_groups_exclusion_group_name = "#{jamf_obj_name_pfx}-#{JAMF_TGT_GRP_EXCL_GRP_NAME_SUFFIX}"
357
373
 
358
374
  @jamf_manual_install_released_policy_name = "#{jamf_obj_name_pfx}-#{JAMF_MANUAL_INSTALL_RELEASED_POL_SUFFIX}"
359
375
 
@@ -419,7 +435,7 @@ module Xolo
419
435
  def jamf_patch_title_id
420
436
  return @jamf_patch_title_id if @jamf_patch_title_id
421
437
 
422
- log_debug "Getting jamf_patch_title_id for title '#{title}'"
438
+ log_debug "Jamf: Getting jamf_patch_title_id for title '#{title}'"
423
439
 
424
440
  self.jamf_patch_title_id =
425
441
  if managed?
@@ -615,7 +631,7 @@ module Xolo
615
631
  end
616
632
 
617
633
  # @return [Array<Xolo::Server::Version>] An array of all current version objects
618
- # NOTE: This might not be wise if hundreds of versions, but automated cleanup should
634
+ # NOTE: This might not be wise if hundreds of versions, but automated maintenance should
619
635
  # help with that.
620
636
  ########################
621
637
  def version_objects(refresh: false)
@@ -973,10 +989,16 @@ module Xolo
973
989
  "Version '#{version_to_release}' of title '#{title}' is already released"
974
990
  end
975
991
 
976
- return if versions.include? version_to_release
992
+ unless versions.include? version_to_release
993
+ raise Xolo::NoSuchItemError,
994
+ "No version '#{version_to_release}' for title '#{title}'"
995
+ end
996
+
997
+ jamf_patch_title(refresh: true)
998
+ return if jamf_patch_title.versions.key?(version_to_release)
977
999
 
978
- raise Xolo::NoSuchItemError,
979
- "No version '#{version_to_release}' for title '#{title}'"
1000
+ raise Xolo::TooSoonError,
1001
+ "Version '#{version_to_release}' of title '#{title}' is not yet visible to Jamf Pro. Please wait at least 10 minutes after adding it before releasing it. An alert notification will be sent when it is ready."
980
1002
  end
981
1003
 
982
1004
  # Update all versions when releasing one
@@ -1106,6 +1128,7 @@ module Xolo
1106
1128
 
1107
1129
  progress "Starting repair of title '#{title}'"
1108
1130
  repair_ted_title
1131
+ set_subscribed_display_name_and_publisher
1109
1132
  repair_jamf_title_objects
1110
1133
  save_local_data
1111
1134
  return unless repair_versions
@@ -234,14 +234,38 @@ module Xolo
234
234
  def self.add_version_via_subscription(title_object:, new_version:)
235
235
  title_object.log_info "Adding new version '#{new_version}' for subscribed title '#{title_object.title}'"
236
236
 
237
- # get more details about this version from the JPAPI
238
- patch_version_data = title_object.patch_versions(version: new_version).first
239
- unless patch_version_data
237
+ vobj_data = new_subscribed_version_data(title_object: title_object, new_version: new_version)
238
+
239
+ unless vobj_data
240
240
  msg = "Could not get patch version data from JPAPI for version '#{new_version}' of subscribed title '#{title_object.title}'. Cannot create new version in Xolo without this data. Aborting."
241
241
  title_object.log_error msg, alert: true
242
242
  return
243
243
  end
244
244
 
245
+ # instantiate the version object
246
+ title_object.log_debug "Instantiating version via subscription '#{new_version}' of title '#{title_object.title}' (#{title_object.class}) with data: #{vobj_data}"
247
+
248
+ vobj = title_object.server_app_instance.instantiate_version(
249
+ title: title_object,
250
+ version: new_version,
251
+ **vobj_data
252
+ )
253
+
254
+ # create it in xolo
255
+ vobj.create
256
+
257
+ # tell someone
258
+ send_alerts_about_new_subscribed_version(title_object: title_object, vobj: vobj)
259
+ end
260
+
261
+ # Gather the data needed to create a new xolo version from a subscription
262
+ # @return [Hash] the data needed to create the version object
263
+ ################
264
+ def self.new_subscribed_version_data(title_object:, new_version:)
265
+ patch_version_data = title_object.patch_versions(version: new_version).first
266
+ # return nil if not found - will send error alert
267
+ return unless patch_version_data
268
+
245
269
  title_object.log_debug "Got patch version data from JPAPI for version '#{new_version}': #{patch_version_data}"
246
270
 
247
271
  # put the data into a hash for creating a new version object
@@ -269,34 +293,47 @@ module Xolo
269
293
  end
270
294
  end
271
295
 
272
- # instantiate the version object
273
- title_object.log_debug "Instantiating version via subscription '#{new_version}' of title '#{title_object.title}' (#{title_object.class}) with data: #{vobj_data}"
274
-
275
- vobj = title_object.server_app_instance.instantiate_version(
276
- title: title_object,
277
- version: new_version,
278
- **vobj_data
279
- )
280
-
281
- # create it in xolo
282
- vobj.create
283
-
284
- # moved to vobj.create:
285
- # vobj.server_app_instance.update_client_data
296
+ vobj_data
297
+ end
286
298
 
287
- # tell someone
288
- msg = "ACTION REQUIRED: New pilot for subscribed title '#{title_object.title}', version '#{new_version}' has been created in Xolo via subscription."
299
+ # Handle alerts for newly-created subscribed version
300
+ ##################
301
+ def self.send_alerts_about_new_subscribed_version(title_object:, vobj:)
302
+ title = title_object.title
303
+ new_version = vobj.version
304
+
305
+ # if here, and auto_release_delay is zero, release it immediately
306
+ if title_object.autopkg_enabled?
307
+
308
+ auto_release_delay = title_object.auto_release_delay.to_i
309
+
310
+ # is auto_release_delay an integer?
311
+ if title_object.auto_release_delay&.pix_integer? && !auto_release_delay.negative?
312
+ how_soon =
313
+ if auto_release_delay.zero?
314
+ 'tonight'
315
+ elsif auto_release_delay == 1
316
+ 'tomorrow night'
317
+ else
318
+ "in #{auto_release_delayy} days"
319
+ end
320
+ msg = "New pilot for subscribed title '#{title}', version '#{new_version}' has been created in Xolo via subscription.\nIt will be released during nightly maintenance #{how_soon}"
321
+
322
+ # nil, empty, 'none', or negative = no auto release
323
+ else
324
+ msg = "ACTION REQUIRED: New pilot for subscribed title '#{title}', version '#{new_version}' has been created in Xolo via subscription.\nRequires manual release."
325
+ end
289
326
 
290
327
  # if not autopkg enabled, we need to tell someone to upload a pkg for this new version
291
- unless title_object.autopkg_enabled?
328
+ else
292
329
  # update general alert msg
293
- msg = "#{msg}\nPlease upload a .pkg for it ASAP using this command:\n xadm edit-version #{title_object.title} #{new_version} --pkg-to-upload /path/to/installer.pkg"
330
+ msg = "ACTION REQUIRED: New pilot for subscribed title '#{title}', version '#{new_version}' has been created in Xolo via subscription.\nPlease upload a .pkg for it ASAP using this command:\n xadm edit-version #{title} #{new_version} --pkg-to-upload /path/to/installer.pkg"
294
331
 
295
332
  # email to title contact
296
333
  vobj.server_app_instance.send_email to: title_object.contact_email, subject: 'Need manual upload of xolo pkg', msg: msg
297
334
  end # if title_object.autopkg_enabled?
298
335
 
299
- # send general alert
336
+ # send alert
300
337
  vobj.log_info msg, alert: true
301
338
  end
302
339
 
@@ -488,6 +525,7 @@ module Xolo
488
525
  # this title.
489
526
  #
490
527
  # Excluded groups are defined in the title, applying to all versions, and may be augmented by:
528
+ # - A smart group of computers NOT in the target_groups, if any target groups are defined.
491
529
  # - Xolo::Server.config.forced_exclusion, a group excluded from ALL of xolo, defined
492
530
  # in the server config.
493
531
  # - The title's jamf_frozen_group_name, if it exists, containing computers that have been
@@ -506,14 +544,17 @@ module Xolo
506
544
  return @excluded_groups_to_use if @excluded_groups_to_use
507
545
 
508
546
  ttl_obj ||= title_object
509
- # get the excluded groups from the title
510
- # Use .dup so we don't modify the original
511
- @excluded_groups_to_use = ttl_obj.changes_for_update&.key?(:excluded_groups) ? ttl_obj.changes_for_update[:excluded_groups][:new].dup : ttl_obj.excluded_groups.dup
512
547
 
513
548
  # always exclude the frozen static group
514
549
  # calling ttl_obj.jamf_frozen_group will create the group if needed
515
- @excluded_groups_to_use << ttl_obj.jamf_frozen_group.name
516
- log_debug "Appended '#{ttl_obj.jamf_frozen_group_name}' to @excluded_groups_to_use"
550
+ @excluded_groups_to_use = [ttl_obj.jamf_frozen_group.name]
551
+ log_debug "Added '#{ttl_obj.jamf_frozen_group_name}' to @excluded_groups_to_use"
552
+
553
+ # get the excluded groups from the title
554
+ @excluded_groups_to_use += ttl_obj.changes_for_update&.key?(:excluded_groups) ? ttl_obj.changes_for_update[:excluded_groups][:new] : ttl_obj.excluded_groups
555
+
556
+ # add the jamf_target_groups_exclusion_group - it must have been created before now
557
+ @excluded_groups_to_use << ttl_obj.jamf_target_groups_exclusion_group_name if ttl_obj.use_jamf_target_groups_exclusion_group?
517
558
 
518
559
  # always exclude Xolo::Server.config.forced_exclusion if defined
519
560
  @excluded_groups_to_use << valid_forced_exclusion_group_name if valid_forced_exclusion_group_name
@@ -858,6 +899,7 @@ module Xolo
858
899
  msg = "Jamf: Version '#{version}': Setting scope targets of patch policy to all eligible computers"
859
900
  progress msg, log: :info
860
901
  ppol = jamf_patch_policy
902
+ ppol.enable
861
903
  ppol.scope.set_all_targets
862
904
 
863
905
  # if rollback, make sure the patch policy is set to 'allow downgrade'
@@ -1017,7 +1059,8 @@ module Xolo
1017
1059
  log_change msg: 'Version Deleted'
1018
1060
 
1019
1061
  progress "Version '#{version}' of Title '#{title}' has been deleted from Xolo.", log: :info
1020
- server_app_instance.update_client_data
1062
+
1063
+ server_app_instance.update_client_data unless deleting_title
1021
1064
  ensure
1022
1065
  unlock
1023
1066
  end
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.1.0
4
+ version: 2.2.2
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-05 00:00:00.000000000 Z
11
+ date: 2026-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday