xolo-server 1.0.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +177 -0
  3. data/README.md +7 -0
  4. data/bin/xoloserver +106 -0
  5. data/data/com.pixar.xoloserver.plist +29 -0
  6. data/data/uninstall-pkgs-by-id.zsh +103 -0
  7. data/lib/xolo/server/app.rb +133 -0
  8. data/lib/xolo/server/command_line.rb +216 -0
  9. data/lib/xolo/server/configuration.rb +739 -0
  10. data/lib/xolo/server/constants.rb +70 -0
  11. data/lib/xolo/server/helpers/auth.rb +257 -0
  12. data/lib/xolo/server/helpers/client_data.rb +415 -0
  13. data/lib/xolo/server/helpers/file_transfers.rb +265 -0
  14. data/lib/xolo/server/helpers/jamf_pro.rb +156 -0
  15. data/lib/xolo/server/helpers/log.rb +97 -0
  16. data/lib/xolo/server/helpers/maintenance.rb +401 -0
  17. data/lib/xolo/server/helpers/notification.rb +145 -0
  18. data/lib/xolo/server/helpers/pkg_signing.rb +141 -0
  19. data/lib/xolo/server/helpers/progress_streaming.rb +252 -0
  20. data/lib/xolo/server/helpers/title_editor.rb +92 -0
  21. data/lib/xolo/server/helpers/titles.rb +145 -0
  22. data/lib/xolo/server/helpers/versions.rb +160 -0
  23. data/lib/xolo/server/log.rb +286 -0
  24. data/lib/xolo/server/mixins/changelog.rb +315 -0
  25. data/lib/xolo/server/mixins/title_jamf_access.rb +1668 -0
  26. data/lib/xolo/server/mixins/title_ted_access.rb +519 -0
  27. data/lib/xolo/server/mixins/version_jamf_access.rb +1541 -0
  28. data/lib/xolo/server/mixins/version_ted_access.rb +373 -0
  29. data/lib/xolo/server/object_locks.rb +102 -0
  30. data/lib/xolo/server/routes/auth.rb +89 -0
  31. data/lib/xolo/server/routes/jamf_pro.rb +89 -0
  32. data/lib/xolo/server/routes/maint.rb +174 -0
  33. data/lib/xolo/server/routes/title_editor.rb +71 -0
  34. data/lib/xolo/server/routes/titles.rb +285 -0
  35. data/lib/xolo/server/routes/uploads.rb +93 -0
  36. data/lib/xolo/server/routes/versions.rb +261 -0
  37. data/lib/xolo/server/routes.rb +168 -0
  38. data/lib/xolo/server/title.rb +1143 -0
  39. data/lib/xolo/server/version.rb +902 -0
  40. data/lib/xolo/server.rb +205 -0
  41. data/lib/xolo-server.rb +8 -0
  42. metadata +243 -0
@@ -0,0 +1,519 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+ #
7
+
8
+ # frozen_string_literal: true
9
+
10
+ # main module
11
+ module Xolo
12
+
13
+ module Server
14
+
15
+ module Mixins
16
+
17
+ # This is mixed in to Xolo::Server::Title
18
+ # to define Title-related access to the Title Editor server
19
+ #
20
+ module TitleTedAccess
21
+
22
+ # Module methods
23
+ #
24
+ # These are available as module methods but not as 'helper'
25
+ # methods in sinatra routes & views.
26
+ #
27
+ ##############################
28
+ ##############################
29
+
30
+ # when this module is included
31
+ ##############################
32
+ def self.included(includer)
33
+ Xolo.verbose_include includer, self
34
+ end
35
+
36
+ # Instance methods
37
+ #
38
+ # These are available directly in sinatra routes and views
39
+ #
40
+ ##############################
41
+ ##############################
42
+
43
+ # @return [Windoo::SoftwareTitle] The Windoo::SoftwareTitle object that represents
44
+ # this title in the title editor
45
+ #############################
46
+ def ted_title(refresh: false)
47
+ @ted_title = nil if refresh
48
+ return @ted_title if @ted_title
49
+
50
+ @ted_title =
51
+ if Windoo::SoftwareTitle.all_ids(cnx: ted_cnx).include? title
52
+ Windoo::SoftwareTitle.fetch id: title, cnx: ted_cnx
53
+ else
54
+ return if deleting?
55
+
56
+ create_title_in_ted
57
+ end
58
+
59
+ @ted_title
60
+ end
61
+
62
+ # Create this title in the title editor, along with a stub patch that allows us to enable it
63
+ # so we can activate it in Jamf and accept an EA if needed
64
+ #
65
+ # @return [Windoo::SoftwareTitle]
66
+ ##########################
67
+ def create_title_in_ted
68
+ # delete an old one if its there
69
+ ted_title&.delete if Windoo::SoftwareTitle.all_ids(cnx: ted_cnx).include? title
70
+
71
+ new_title = Windoo::SoftwareTitle.create(
72
+ id: title,
73
+ name: display_name,
74
+ publisher: publisher,
75
+ appName: app_name,
76
+ bundleId: app_bundle_id,
77
+ currentVersion: Xolo::Server::Title::NEW_TITLE_CURRENT_VERSION,
78
+ cnx: ted_cnx
79
+ )
80
+
81
+ progress "Title Editor: Creating SoftwareTitle '#{title}'", log: :info
82
+ create_ted_title_requirements
83
+
84
+ create_and_enable_stub_patch_in_ted(new_title)
85
+
86
+ sleep 2
87
+
88
+ # re-fetch the title from ted and enable it
89
+ ted_title(refresh: true).enable
90
+
91
+ self.ted_id_number = ted_title.softwareTitleId
92
+ end
93
+
94
+ # Create and enable the stub patch, so that the title
95
+ # can be enabled in Jamf, and if needed, any EA accepted
96
+ # @param new_title [Windoo::SoftwareTitle] The newly created title
97
+ # @return [void]
98
+ ####################################
99
+ def create_and_enable_stub_patch_in_ted(new_title)
100
+ progress(
101
+ "Title Editor: Creating Stub version #{Xolo::Server::Version::STUB_PATCH_VERSION} for title '#{title}', to allow activation in Jamf",
102
+ log: :info
103
+ )
104
+
105
+ # the patch
106
+ new_title.patches.add_patch(
107
+ version: Xolo::Server::Version::STUB_PATCH_VERSION,
108
+ minimumOperatingSystem: Xolo::Server::Version::DEFAULT_MIN_OS,
109
+ releaseDate: Time.now
110
+ )
111
+ stub_patch = new_title.patches.first
112
+
113
+ # the capabilites
114
+ stub_patch.capabilities.add_criterion(
115
+ name: Xolo::Server::Version::STUB_PATCH_CAPABILITY_CRITERION_NAME,
116
+ operator: Xolo::Server::Version::STUB_PATCH_CAPABILITY_CRITERION_OPERATOR,
117
+ value: Xolo::Server::Version::STUB_PATCH_CAPABILITY_CRITERION_VALUE
118
+ )
119
+
120
+ # the component
121
+ stub_patch.add_component(
122
+ name: Xolo::Server::Version::STUB_PATCH_COMPONENT_NAME,
123
+ version: Xolo::Server::Version::STUB_PATCH_VERSION
124
+ )
125
+
126
+ comp = stub_patch.component
127
+ comp.criteria.add_criterion(
128
+ name: Xolo::Server::Version::STUB_PATCH_COMPONENT_CRITERION_NAME,
129
+ operator: Xolo::Server::Version::STUB_PATCH_COMPONENT_CRITERION_OPERATOR,
130
+ value: Xolo::Server::Version::STUB_PATCH_COMPONENT_CRITERION_VALUE
131
+ )
132
+
133
+ # enable the patch
134
+ stub_patch.enable
135
+ end
136
+
137
+ # Create the requirements for a new title in the Title Editor
138
+ # Either app-based or EA-based, depending on the data in the Xolo Title.
139
+ #
140
+ # Requirements are criteria indicating that this title (any version)
141
+ # is installed on a client machine.
142
+ #
143
+ # If the Xolo Title has app_name and app_bundle_id defined,
144
+ # they are used as the requirement criteria and the Patch component criteria.
145
+ #
146
+ # If the Xolo Title as a version_script defined, it returns
147
+ # either an empty value, or the version installed on the client
148
+ # It is added to the Title Editor title as the Ext Attr and used both as the
149
+ # requirement criterion (the value is not empty) and as a Patch Component
150
+ # criterion for versions (the value contains the version).
151
+ #
152
+ # @return [void]
153
+ ################################
154
+ def create_ted_title_requirements
155
+ # if we have app-based requirements, set them
156
+ if app_name && app_bundle_id
157
+ set_ted_title_requirement app_name: app_name, app_bundle_id: app_bundle_id
158
+ elsif version_script
159
+ # if we have a version_script, set it
160
+ create_ted_ea version_script_contents
161
+ set_ted_title_requirement ea_name: ted_ea_key
162
+ else
163
+ raise Xolo::MissingDataError,
164
+ 'Cannot create Title Editor Title Requirements without app_name & app_bundle_id, or version_script'
165
+ end
166
+ end
167
+
168
+ # Update title in the title editor
169
+ #
170
+ # @param new_data [Hash] The new data sent from xadm
171
+ # @return [void]
172
+ ##########################
173
+ def update_title_in_ted
174
+ return unless changes_for_update
175
+
176
+ unless any_ted_changes?
177
+ progress "Title Editor: No changes to make for SoftwareTitle '#{title}'", log: :info
178
+ return
179
+ end
180
+
181
+ progress "Title Editor: Updating SoftwareTitle '#{title}'", log: :info
182
+
183
+ # loop through the attributes that are in the Title Editor
184
+ Xolo::Server::Title::ATTRIBUTES.each do |attr, deets|
185
+ ted_attribute = deets[:ted_attribute]
186
+ next unless ted_attribute
187
+ next unless changes_for_update&.key? attr
188
+
189
+ new_val = changes_for_update[attr][:new]
190
+ old_val = changes_for_update[attr][:old]
191
+
192
+ progress "Title Editor: Updating title attribute '#{ted_attribute}': #{old_val} -> #{new_val}", log: :info
193
+
194
+ ted_title.send "#{ted_attribute}=", new_val
195
+ end # Xolo::Server::Title::ATTRIBUTES.each
196
+
197
+ # This will also apply the changes to all patch component criteria
198
+ apply_requirement_changes
199
+
200
+ # mucking with the patches often disables the title, make sure its enabled.
201
+ enable_ted_title
202
+
203
+ self.ted_id_number ||= ted_title.softwareTitleId
204
+ end
205
+
206
+ # Apply changes to the Title Editor Title Requirements
207
+ # and patch component criteria for all versions
208
+ #
209
+ # @return [void]
210
+ ##############################
211
+ def apply_requirement_changes
212
+ req_change = requirement_change
213
+ return unless req_change
214
+
215
+ new_app_name = changes_for_update.dig :app_name, :new
216
+ new_app_bundle_id = changes_for_update.dig :app_bundle_id, :new
217
+ new_ea_script = changes_for_update.dig :version_script, :new
218
+
219
+ case req_change
220
+ when :app_to_ea
221
+ # create the ea
222
+ create_ted_ea new_ea_script
223
+ # set the requirement to use the ea
224
+ set_ted_title_requirement ea_name: ted_ea_key
225
+ # for all versions, update the patch compotent criteria to use the ea
226
+ set_ted_patch_component_criteria_after_update ea_name: ted_ea_key
227
+
228
+ when :ea_to_app
229
+ # set the requirement to use the app data
230
+ set_ted_title_requirement app_name: new_app_name, app_bundle_id: new_app_bundle_id
231
+ # for all versions, update the patch compotent criteria to use the app data
232
+ set_ted_patch_component_criteria_after_update app_name: new_app_name, app_bundle_id: new_app_bundle_id
233
+ # delete the ea
234
+ delete_ted_ea
235
+
236
+ when :update_app
237
+ # set the requirement to use the new app data
238
+ set_ted_title_requirement app_name: new_app_name, app_bundle_id: new_app_bundle_id
239
+ # for all versions, update the patch compotent criteria to use the new app data
240
+ set_ted_patch_component_criteria_after_update app_name: new_app_name, app_bundle_id: new_app_bundle_id
241
+
242
+ when :update_ea
243
+ # update the ea script
244
+ update_ted_ea new_ea_script
245
+
246
+ end
247
+ end
248
+
249
+ # @return [Boolean] are there any changes to make in the Title Editor?
250
+ ##########################
251
+ def any_ted_changes?
252
+ ted_attrs = Xolo::Server::Title::ATTRIBUTES.select { |_attr, deets| deets[:ted_attribute] }.keys
253
+ # version scripts are handled differently and are not marked as
254
+ # ted_attributes, so we need to add it here
255
+ ted_attrs << :version_script
256
+
257
+ (changes_for_update.keys & ted_attrs).empty? ? false : true
258
+ end
259
+
260
+ # Are we changing any requirements, and if so, how?
261
+ # @return [Symbol] :app_to_ea, :ea_to_app, :update_app, :update_ea
262
+ ######################
263
+ def requirement_change
264
+ # we have to change the version script, but are we just updating it,
265
+ # or switching to it from app data?
266
+ if changes_for_update[:version_script]
267
+
268
+ # if we have no old value, we are switching to ea, from app data
269
+ if changes_for_update[:version_script][:old].pix_empty?
270
+ :app_to_ea
271
+
272
+ # if we have no new value, we are switching from ea, to app data
273
+ elsif changes_for_update[:version_script][:new].pix_empty?
274
+ :ea_to_app
275
+
276
+ # if we are here, we have both, so we are just updating the ea script
277
+ else
278
+ :update_ea
279
+ end
280
+
281
+ # if we are here, we aren't changing the ea at all, but we might be
282
+ # updating the app data
283
+ elsif changes_for_update[:app_name] || changes_for_update[:app_bundle_id]
284
+ :update_app
285
+
286
+ # and if none of that is true, we'll return nil
287
+ end
288
+ end
289
+
290
+ # @return [String] The key and display name of a version script stored
291
+ # in the title editor as the ExtAttr for this title
292
+ #####################
293
+ def ted_ea_key
294
+ @ted_ea_key ||= self.class.ted_ea_key title
295
+ end
296
+
297
+ # Create the EA in the Title Editor
298
+ #
299
+ # @return [void]
300
+ ##############################
301
+ def create_ted_ea(script)
302
+ # delete and recreate the EA
303
+ progress "Title Editor: Creating Extension Attribute from version_script for title '#{title}'", log: :info
304
+
305
+ ted_title.delete_extensionAttribute
306
+
307
+ ted_title.add_extensionAttribute(
308
+ key: ted_ea_key,
309
+ displayName: ted_ea_key,
310
+ script: script
311
+ )
312
+ @need_to_accept_jamf_patch_ea = true
313
+ end
314
+
315
+ # Update the EA in the Title Editor
316
+ # the only thing we update is the script
317
+ #
318
+ # @return [void]
319
+ ##############################
320
+ def update_ted_ea(script)
321
+ progress "Title Editor: Updating Extension Attribute from version_script for title '#{title}'", log: :info
322
+
323
+ if ted_title.extensionAttribute
324
+ ted_title.extensionAttribute.script = script
325
+ @need_to_accept_jamf_patch_ea = true
326
+ else
327
+ create_ted_ea(script)
328
+ end
329
+ end
330
+
331
+ # Delete the extension attribute from the title editor
332
+ # @return [void]
333
+ ##############################
334
+ def delete_ted_ea
335
+ progress "Title Editor: Deleting Extension Attribute (version_script) for title '#{title}'", log: :info
336
+ ted_title.delete_extensionAttribute
337
+ end
338
+
339
+ # Set the requirements for a title in the Title Editor
340
+ #
341
+ # If the title has an EA script, it is used as the requirement criterion
342
+ # (the EA should already be created in the Title Editor)
343
+ #
344
+ # If the title has app_name and app_bundle_id, they are used as the requirement criteria
345
+ #
346
+ # @param app_name [String] the name of the app to use in app-based requirements,
347
+ # must be used with app_bundle_id, cannot be used with ea_name
348
+ #
349
+ # @param app_bundle_id [String] the bundle id of the app to use in app-based requirements
350
+ # must be used with app_name, cannot be used with ea_name
351
+ #
352
+ # @param ea_name [String] the name of the EA to use in EA-based requirements (the ted_ea_key)
353
+ # Cannot be used with app_name or app_bundle_id
354
+ #
355
+ # @return [void]
356
+ ##############################
357
+ def set_ted_title_requirement(app_name: nil, app_bundle_id: nil, ea_name: nil)
358
+ raise Xolo::MissingDataError, 'Must provide either ea_name or app_name & app_bundle_id' unless (app_name && app_bundle_id) || ea_name
359
+
360
+ type = ea_name ? 'Extension Attribute (version_script)' : 'App'
361
+
362
+ progress "Title Editor: Setting #{type}-based Requirement for SoftwareTitle '#{title}'", log: :info
363
+
364
+ # delete any already there
365
+ ted_title.requirements.delete_all_criteria
366
+
367
+ ea_name ? set_ea_requirement(ea_name) : set_app_requirement(app_name, app_bundle_id)
368
+ end
369
+
370
+ # @return [String] the progress/log message
371
+ ##############################
372
+ def set_ea_requirement(ea_name)
373
+ # add criteria for the ea name
374
+ ted_title.requirements.add_criterion(
375
+ type: 'extensionAttribute',
376
+ name: ea_name,
377
+ operator: 'is not',
378
+ value: Xolo::BLANK
379
+ )
380
+ end
381
+
382
+ # @return [String] the progress/log message
383
+ ##############################
384
+ def set_app_requirement(app_name, app_bundle_id)
385
+ # add criteria for the app name and bundle id.
386
+ ted_title.requirements.add_criterion(
387
+ name: 'Application Title',
388
+ operator: 'is',
389
+ value: app_name
390
+ )
391
+
392
+ ted_title.requirements.add_criterion(
393
+ name: 'Application Bundle ID',
394
+ operator: 'is',
395
+ value: app_bundle_id
396
+ )
397
+ end
398
+
399
+ # update the patch compotent criteria for all versions
400
+ # to match changes in the title requirements
401
+ #
402
+ # @return [void]
403
+ ##############################
404
+ def set_ted_patch_component_criteria_after_update(app_name: nil, app_bundle_id: nil, ea_name: nil)
405
+ version_objects.each do |vers_obj|
406
+ vers_obj.set_ted_patch_component_criteria(
407
+ app_name: app_name,
408
+ app_bundle_id: app_bundle_id,
409
+ ea_name: ea_name
410
+ )
411
+ vers_obj.enable_ted_patch
412
+ end
413
+ end
414
+
415
+ # Enable the title in the title editor when at least one patch is enabled
416
+ #
417
+ # Re-enable the title in ted after updating any patches
418
+ #
419
+ # @return [void]
420
+ ##############################
421
+ def enable_ted_title
422
+ # Nothing to re-enabled unless we have at least one enabled patch
423
+ return unless ted_title.patches.to_a.any?(&:enabled?)
424
+
425
+ # re-enable the title itself, we should have at least one enabled version
426
+ progress "Title Editor: (Re-)Enabling SoftwareTitle '#{title}'", log: :debug
427
+
428
+ # loop until the enablement goes thru
429
+ breaktime = Time.now + Xolo::Server::Constants::MAX_JAMF_WAIT_FOR_TITLE_EDITOR
430
+ loop do
431
+ raise Xolo::TimeoutError, "Title Editor: Timed out waiting for SoftwareTitle '#{title}' to enable" if Time.now > breaktime
432
+
433
+ sleep 5
434
+ ted_title(refresh: true).enable
435
+ break
436
+ rescue Windoo::MissingDataError => e
437
+ log_debug "Title Editor: Looping up to #{Xolo::Server::Constants::MAX_JAMF_WAIT_FOR_TITLE_EDITOR} secs while re-enabling SoftwareTitle '#{title}': #{e}"
438
+
439
+ # make sure all patches are enabled, even tho at least one should have been
440
+ # enabled at the start of this method
441
+ begin
442
+ log_debug "Title Editor: Force-enablng all patches for title #{title}"
443
+ ted_title.patches.each(&:enable)
444
+ rescue
445
+ nil
446
+ end
447
+
448
+ nil
449
+ end
450
+ end
451
+
452
+ # Delete from the title editor
453
+ # @return [Integer] title editor id number
454
+ ###########################
455
+ def delete_title_from_ted
456
+ progress "Title Editor: Deleting SoftwareTitle '#{title}'", log: :info
457
+
458
+ ted_title&.delete if Windoo::SoftwareTitle.all_ids(cnx: ted_cnx).include? title
459
+ rescue Windoo::NoSuchItemError
460
+ ted_id_number
461
+ end
462
+
463
+ # @return [String] the URL for the title in the Title Editor
464
+ #####################
465
+ def ted_title_url
466
+ "https://#{Xolo::Server.config.ted_hostname}/softwaretitles/#{ted_id_number}"
467
+ end
468
+
469
+ # repair this title in the title editor
470
+ # - display name
471
+ # - publisher
472
+ # - requirements, EA or App Data
473
+ # - stub version if needed
474
+ # - enabled
475
+ # @return [void]
476
+ ############################
477
+ def repair_ted_title
478
+ progress "Title Editor: Repairing SoftwareTitle '#{title}'", log: :info
479
+
480
+ # TODO: version order??
481
+
482
+ # loop through the attributes that are in the Title Editor
483
+ Xolo::Server::Title::ATTRIBUTES.each do |attr, deets|
484
+ ted_attribute = deets[:ted_attribute]
485
+ next unless ted_attribute
486
+
487
+ ted_val = ted_title.send ted_attribute
488
+ real_val = send attr
489
+ next if ted_val == real_val
490
+
491
+ progress "Title Editor: Repairing title attribute '#{ted_attribute}': #{ted_val} -> #{real_val}", log: :info
492
+
493
+ ted_title.send "#{ted_attribute}=", real_val
494
+ end # Xolo::Server::Title::ATTRIBUTES.each
495
+
496
+ # requirements
497
+ create_ted_title_requirements
498
+
499
+ # make sure there's at least one patch
500
+ if ted_title.patches.empty?
501
+ create_and_enable_stub_patch_in_ted(ted_title)
502
+ else
503
+ # make sure at least one patch is enabled
504
+ unless ted_title.patches.to_a.any?(&:enabled?)
505
+ progress "Title Editor: Enabling at least the first patch for title '#{title}'", log: :info
506
+ ted_title.patches.first.enable
507
+ end
508
+ end
509
+
510
+ ted_title.enable unless ted_title(refresh: true).enabled?
511
+ end
512
+
513
+ end # TitleEditorTitle
514
+
515
+ end # Mixins
516
+
517
+ end # Server
518
+
519
+ end # module Xolo