xolo-server 2.4.0 → 2.5.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/lib/xolo/core/base_classes/title.rb +6 -2
- data/lib/xolo/core/constants.rb +3 -0
- data/lib/xolo/core/version.rb +1 -1
- data/lib/xolo/server/configuration.rb +43 -0
- data/lib/xolo/server/helpers/auth.rb +30 -0
- data/lib/xolo/server/helpers/autopkg.rb +67 -7
- data/lib/xolo/server/helpers/maintenance.rb +134 -36
- data/lib/xolo/server/helpers/subscriptions.rb +34 -1
- data/lib/xolo/server/mixins/title_jamf_access.rb +1 -1
- data/lib/xolo/server/mixins/version_jamf_access.rb +14 -6
- data/lib/xolo/server/routes/auth.rb +6 -3
- data/lib/xolo/server/routes/subscriptions.rb +15 -0
- data/lib/xolo/server/title.rb +12 -2
- data/lib/xolo/server/version.rb +22 -8
- 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: 7f515836aab0143c5343395e240de91947be9aef7cc70b266d4e1a84eb5fe714
|
|
4
|
+
data.tar.gz: 7f438c26d4974cb273241ec6bd72a9521eef562283cea9f64ba5f4521c34cf40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b2cc84027b9a9af0fc4619d1ba5d564700043207e5ee96be50a69f248b5b4644605b5de569cb9906ec6b71c3d7ee322041bfe4395187cdcce06bcacf9b2d099
|
|
7
|
+
data.tar.gz: 344224658c124b01d6caa0b175ef64e3e200618335224119efb2d5430a38e178d15bba1878282ee16908c51c9979d5dbadbcd02da8aa293812ac22a1a4d32598
|
|
@@ -459,15 +459,17 @@ module Xolo
|
|
|
459
459
|
},
|
|
460
460
|
|
|
461
461
|
# @!attribute auto_release_delay
|
|
462
|
-
# @return [String] 'none' or an integer in string.
|
|
462
|
+
# @return [String] 'none', 'monthly', or an integer in string.
|
|
463
|
+
# How many days before new versions are automatically released?
|
|
463
464
|
# Applies only to subscribed titles with autopkg recipes.
|
|
464
465
|
auto_release_delay: {
|
|
465
466
|
label: 'Automatic Release Delay',
|
|
466
467
|
cli: :A,
|
|
467
468
|
type: :string,
|
|
468
469
|
walkthru_na: :auto_release_delay_na,
|
|
470
|
+
validate: true,
|
|
469
471
|
changelog: true,
|
|
470
|
-
invalid_msg: "Must be a non-negative integer, or '#{Xolo::NONE}'.",
|
|
472
|
+
invalid_msg: "Must be a non-negative integer, '#{Xolo::MONTHLY}', or '#{Xolo::NONE}'.",
|
|
471
473
|
desc: <<~ENDDESC
|
|
472
474
|
Specify how many days after a new version appears before it is automatically released from pilot.
|
|
473
475
|
|
|
@@ -477,6 +479,8 @@ module Xolo
|
|
|
477
479
|
|
|
478
480
|
Setting it to zero means that new versions will be released at the next nightly auto-release task. *** USE WITH EXTREME CAUTION ***
|
|
479
481
|
|
|
482
|
+
Setting it to '#{Xolo::MONTHLY}' will release it on whatever day of the month is defined in the Xolo server's 'auto_release_monthly' setting, or on the first day of the month if the server isn't set. The server may be set to a specific date, like '15' or to an ordinal-weekday, like '3rd Mon'.
|
|
483
|
+
|
|
480
484
|
To unset, meaning versions are never auto-released, use '#{Xolo::NONE}'
|
|
481
485
|
|
|
482
486
|
Auto-release happens during the server's nightly maintenance between 2-3am.
|
data/lib/xolo/core/constants.rb
CHANGED
data/lib/xolo/core/version.rb
CHANGED
|
@@ -143,6 +143,25 @@ module Xolo
|
|
|
143
143
|
ENDDESC
|
|
144
144
|
},
|
|
145
145
|
|
|
146
|
+
# @!attribute xadm_authenticator
|
|
147
|
+
# @return [String] How to authenticate users of 'xadm', either 'use-jamf' or a path to an authentication tool.
|
|
148
|
+
xadm_authenticator: {
|
|
149
|
+
required: true,
|
|
150
|
+
type: :string,
|
|
151
|
+
desc: <<~ENDDESC
|
|
152
|
+
The method for authenticating users of 'xadm'. The value 'use-jamf' will authenticate the user's name and password by trying
|
|
153
|
+
to connect to the Jamf Pro API. This may not work for Jamf Cloud instances with an identity provider or other more complex
|
|
154
|
+
authentication processes. There are plans to address this in a future version of xolo.
|
|
155
|
+
|
|
156
|
+
Alternatively, provide the full path to a CLI tool installed on the xolo server. This tool must take two lines on its standard-input:
|
|
157
|
+
the username, then the password. It must return 'OK' to its standard-output, and exit with status 0, for the credentials to be
|
|
158
|
+
considered correct.
|
|
159
|
+
|
|
160
|
+
After authentication, the username provided must be a member of the Jamf account-group defined in 'admin_jamf_group', and/or
|
|
161
|
+
that defined in 'server_admin_jamf_group'.
|
|
162
|
+
ENDDESC
|
|
163
|
+
},
|
|
164
|
+
|
|
146
165
|
# @!attribute admin_jamf_group
|
|
147
166
|
# @return [String] The name of a Jamf account-group containing users of 'xadm'
|
|
148
167
|
admin_jamf_group: {
|
|
@@ -830,6 +849,30 @@ module Xolo
|
|
|
830
849
|
NOTE: AutoPkg recipes are always run with '-k FAIL_RECIPES_WITHOUT_TRUST_INFO=yes', and will fail if you have not 'trusted'
|
|
831
850
|
them by making an override. See the AutoPkg docs for details.
|
|
832
851
|
ENDDESC
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
# @!attribute auto_release_monthly
|
|
855
|
+
# @return [String] Which day of the month should auto-releases happen?
|
|
856
|
+
auto_release_monthly: {
|
|
857
|
+
type: :string,
|
|
858
|
+
desc: <<~ENDDESC
|
|
859
|
+
If a title has an --auto-release-delay set to '#{Xolo::MONTHLY}', this defines which day of the month the release happens. Releases happen during nightly maintenence, between 2 and 3am, which may affect the value you put here. Monthly releases will only release the newest version, if it's status is 'pilot'.
|
|
860
|
+
|
|
861
|
+
Setting this string to an integer will cause the release to happen on that day of the month, so '15' makes it happen on the 15th of the month. If you set the value to beyond the end of the month, it will happen on the last day of the month, so set it to '31' (or higher) for the last day of every month.
|
|
862
|
+
|
|
863
|
+
Alternatively you can set it to a string specifying an 'ordinal-weekday', e.g. '2nd Tue', which will cause the release to happen on the second Tuesday of every month.
|
|
864
|
+
|
|
865
|
+
Ordinal weekday strings must be two space-separated values: First one of '1st', '2nd', '3rd', '4th', or 'last', then a 3-letter English weekday, one of 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', or 'Sat'. All of these are case-insensitive.
|
|
866
|
+
|
|
867
|
+
Examples: '2nd Sun', '4th Fri', 'Last Wed'
|
|
868
|
+
|
|
869
|
+
If a title is set to 'monthly', and the server has no valid value here, releases happen on the first day of every month, regardless of weekday.
|
|
870
|
+
|
|
871
|
+
Setting monthly auto-release can help avoid update-fatigue when you have lots of titles auto-releasing frequently. Users can expect all their updates at around the same time once a month.
|
|
872
|
+
|
|
873
|
+
Note that new versions of titles can always be manually released at any time once they are added to xolo.
|
|
874
|
+
So if an important security update comes out and you don't want to wait up to a month to release it, you can do so manually with 'xadm release <title> <version>'
|
|
875
|
+
ENDDESC
|
|
833
876
|
}
|
|
834
877
|
|
|
835
878
|
}.freeze
|
|
@@ -59,6 +59,9 @@ module Xolo
|
|
|
59
59
|
# The loopback address for IPV4, aka 'localhost'
|
|
60
60
|
IPV4_LOOPBACK = '127.0.0.1'
|
|
61
61
|
|
|
62
|
+
# This is a possible value for xadm_authenticator
|
|
63
|
+
USE_JAMF = 'use-jamf'
|
|
64
|
+
|
|
62
65
|
# Module methods
|
|
63
66
|
#####################
|
|
64
67
|
#####################
|
|
@@ -239,6 +242,33 @@ module Xolo
|
|
|
239
242
|
false
|
|
240
243
|
end
|
|
241
244
|
|
|
245
|
+
# authenticate via an external xadm authenticator tool.
|
|
246
|
+
# it must exit with status 0, and send 'OK' to its stdout
|
|
247
|
+
#
|
|
248
|
+
# @param admin [String] The jamf acct name of the person seeking access
|
|
249
|
+
#
|
|
250
|
+
# @param pw [String] The password for the jamf acct
|
|
251
|
+
#
|
|
252
|
+
# @return [Boolean] Did the password work for the user?
|
|
253
|
+
####################
|
|
254
|
+
def authenticated_via_xadm_authenticator?(admin, pw)
|
|
255
|
+
upw = "#{admin}\n#{pw}"
|
|
256
|
+
out = nil
|
|
257
|
+
err = nil
|
|
258
|
+
success = false
|
|
259
|
+
|
|
260
|
+
Open3.popen3(Xolo::Server.config.xadm_authenticator) do |inp, outp, serr, wthr|
|
|
261
|
+
inp.print upw
|
|
262
|
+
inp.close
|
|
263
|
+
|
|
264
|
+
out = outp.read.chomp
|
|
265
|
+
err = serr.read.chomp
|
|
266
|
+
success = wthr.value.success?
|
|
267
|
+
end
|
|
268
|
+
log_warn "xadm authenticator failed for admin '#{admin}': #{err} " unless success
|
|
269
|
+
success && out == Xolo::OK
|
|
270
|
+
end
|
|
271
|
+
|
|
242
272
|
# Try to authenticate the jamf user trying to log in to xolo.
|
|
243
273
|
# This must be a regular Jamf user acct, not an API client.
|
|
244
274
|
#
|
|
@@ -94,12 +94,53 @@ module Xolo
|
|
|
94
94
|
@autopkg_enabled = false
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
# the autopkg report-plist path, based on the
|
|
98
|
+
# title name & timestamp w/nanosecs
|
|
99
|
+
# @return [Pathname]
|
|
100
|
+
##############################
|
|
101
|
+
def autopkg_report_plist_path
|
|
102
|
+
@autopkg_report_plist_path ||= Pathname.new "/tmp/xolo-autopkg-report-#{@title_id_for_autopkg}-#{Time.now.strftime('%s%N')}.plist"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Given a Hash or Array, recursively look for a
|
|
106
|
+
# Hash Key :version and return the value.
|
|
107
|
+
# Returns the first such key it finds.
|
|
108
|
+
#
|
|
109
|
+
# @param collection [Hash, Array] the data to search
|
|
110
|
+
#
|
|
111
|
+
# @return [String, nil] the first 'version' value found, or nil if not found
|
|
112
|
+
###########################
|
|
113
|
+
def find_version(collection)
|
|
114
|
+
version = nil
|
|
115
|
+
|
|
116
|
+
if collection.is_a? Hash
|
|
117
|
+
collection.each do |key, value|
|
|
118
|
+
if key == :version
|
|
119
|
+
version = value
|
|
120
|
+
break
|
|
121
|
+
end
|
|
122
|
+
version = find_version(value) if value.is_a?(Hash) || value.is_a?(Array)
|
|
123
|
+
break if version
|
|
124
|
+
end
|
|
125
|
+
elsif collection.is_a? Array
|
|
126
|
+
collection.each do |value|
|
|
127
|
+
version = find_version(value) if value.is_a?(Hash) || value.is_a?(Array)
|
|
128
|
+
break if version
|
|
129
|
+
end
|
|
130
|
+
end # if collection.is_a
|
|
131
|
+
|
|
132
|
+
version
|
|
133
|
+
end # def
|
|
134
|
+
|
|
97
135
|
# the autopkg run command for this title
|
|
98
136
|
#####################################
|
|
99
137
|
def autopkg_run_command(title_object)
|
|
100
138
|
# is autopkg enabled for the server as a whole?
|
|
101
139
|
return unless autopkg_enabled?
|
|
102
140
|
|
|
141
|
+
# is autopkg enabled for this title?
|
|
142
|
+
return unless title_object.autopkg_enabled?
|
|
143
|
+
|
|
103
144
|
[
|
|
104
145
|
'/bin/launchctl',
|
|
105
146
|
'asuser',
|
|
@@ -111,7 +152,9 @@ module Xolo
|
|
|
111
152
|
'run',
|
|
112
153
|
'--verbose',
|
|
113
154
|
title_object.autopkg_recipe,
|
|
114
|
-
FAIL_UNTRUSTED_RECIPES_CLI_OPT
|
|
155
|
+
FAIL_UNTRUSTED_RECIPES_CLI_OPT,
|
|
156
|
+
'--report-plist',
|
|
157
|
+
autopkg_report_plist_path.to_s
|
|
115
158
|
]
|
|
116
159
|
end
|
|
117
160
|
|
|
@@ -127,22 +170,25 @@ module Xolo
|
|
|
127
170
|
# is autopkg enabled for this title?
|
|
128
171
|
return unless title_object.autopkg_enabled?
|
|
129
172
|
|
|
173
|
+
# Make this available to other methods here
|
|
174
|
+
@title_id_for_autopkg = title_object.title
|
|
175
|
+
|
|
130
176
|
recipe = title_object.autopkg_recipe
|
|
131
177
|
pkgdir = Pathname.new title_object.autopkg_dir
|
|
132
178
|
clean_old_pkgs pkgdir
|
|
133
179
|
|
|
134
180
|
cmd = autopkg_run_command(title_object)
|
|
135
181
|
progress "Running AutoPkg recipe '#{recipe}' for #{title_object.title}..."
|
|
136
|
-
|
|
137
182
|
log_info "Running AutoPkg recipe for #{title_object.title} via command: #{cmd.join(' ')}"
|
|
138
183
|
|
|
139
184
|
unlock_autopkg_user_keychain
|
|
140
|
-
|
|
141
185
|
souterr, status = Open3.capture2e(*cmd)
|
|
142
186
|
souterr.strip!
|
|
143
187
|
|
|
144
188
|
if status.success?
|
|
145
|
-
|
|
189
|
+
msg = "AutoPkg recipe '#{recipe}' for title '#{title_object.title}' completed successfully."
|
|
190
|
+
progress msg, log: :info, alert: true
|
|
191
|
+
|
|
146
192
|
log_debug 'AutoPkg output:'
|
|
147
193
|
souterr.lines.each { |l| log_debug "AutoPkg: #{l.chomp}" }
|
|
148
194
|
|
|
@@ -205,19 +251,33 @@ module Xolo
|
|
|
205
251
|
|
|
206
252
|
# confirm a given autopkg package is OK to use after running a recipe
|
|
207
253
|
###########################
|
|
208
|
-
def validate_autopkg_pkg(pkg_src)
|
|
254
|
+
def validate_autopkg_pkg(pkg_src, desired_version)
|
|
209
255
|
if pkg_src.nil?
|
|
210
|
-
msg = '
|
|
256
|
+
msg = 'No pkg was found after running the autopkg recipe. Please check the AutoPkg recipe and the server log for details. Make sure the --autopkg-dir is correct. Nothing to upload.'
|
|
211
257
|
progress msg, log: :warn, alert: true
|
|
212
258
|
return false
|
|
213
259
|
end
|
|
214
260
|
|
|
215
261
|
oldest_allowed = Time.now - MAX_PKG_AGE # 20 minutes ago
|
|
216
262
|
if pkg_src.mtime < oldest_allowed
|
|
217
|
-
msg = "
|
|
263
|
+
msg = "A pkg was found after running the autopkg 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. Nothing to upload"
|
|
264
|
+
progress msg, log: :warn, alert: true
|
|
265
|
+
return false
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
fetched_version =
|
|
269
|
+
if autopkg_report_plist_path.file?
|
|
270
|
+
json_report = `/usr/bin/plutil -convert json #{autopkg_report_plist_path.pix_shellescape} -o -`
|
|
271
|
+
report = JSON.parse json_report, symbolize_names: true
|
|
272
|
+
find_version report[:summary_results]
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
if fetched_version != desired_version
|
|
276
|
+
msg = "A pkg was found after running the recipe, but the version that was fetched was '#{fetched_version}', and we were expecting version '#{desired_version}'. Nothing to upload"
|
|
218
277
|
progress msg, log: :warn, alert: true
|
|
219
278
|
return false
|
|
220
279
|
end
|
|
280
|
+
|
|
221
281
|
true
|
|
222
282
|
end
|
|
223
283
|
|
|
@@ -43,14 +43,15 @@ module Xolo
|
|
|
43
43
|
# MAINT_CHECK_INTERVAL = 600
|
|
44
44
|
|
|
45
45
|
# At what hour should the nightly maintenance run?
|
|
46
|
+
# default to 2am
|
|
46
47
|
MAINT_HOUR = 2
|
|
47
48
|
# MAINT_HOUR = 10
|
|
48
49
|
|
|
49
|
-
#
|
|
50
|
+
# Automated maint won't run unless the last run was at least this many
|
|
50
51
|
# seconds ago.
|
|
51
52
|
# 23 hrs ago means that even if we check twice within the
|
|
52
53
|
# MAINT_HOUR, only the first one will run.
|
|
53
|
-
|
|
54
|
+
AUTO_MAINT_MAX_FREQ_SECS = 23 * 3600
|
|
54
55
|
|
|
55
56
|
# the route we POST to, to start the nightly process.
|
|
56
57
|
TIMED_MAINT_TRIGGER_ROUTE = '/maint/maint-internal'
|
|
@@ -145,11 +146,11 @@ module Xolo
|
|
|
145
146
|
now = Time.now
|
|
146
147
|
|
|
147
148
|
# only run the maintenance if it's during the MAINT_HOUR
|
|
148
|
-
# and the last one was more than
|
|
149
|
+
# and the last one was more than AUTO_MAINT_MAX_FREQ_SECS ago
|
|
149
150
|
if force
|
|
150
151
|
Xolo::Server.logger.info 'Maint: Starting now due to force'
|
|
151
152
|
|
|
152
|
-
elsif now.hour == MAINT_HOUR && (now - last_maint) >
|
|
153
|
+
elsif now.hour == MAINT_HOUR && (now - last_maint) > AUTO_MAINT_MAX_FREQ_SECS
|
|
153
154
|
Xolo::Server.logger.info 'Maint: Starting nightly run now'
|
|
154
155
|
|
|
155
156
|
else
|
|
@@ -295,60 +296,157 @@ module Xolo
|
|
|
295
296
|
# Loop thru the titles
|
|
296
297
|
title_objects.each do |title_obj|
|
|
297
298
|
title = title_obj.title
|
|
299
|
+
vobj_to_release = nil
|
|
298
300
|
|
|
299
|
-
# nothing to do if
|
|
301
|
+
# nothing to do if auto_release_delay is nil or 'none'
|
|
300
302
|
next if title_obj.auto_release_delay.nil? || title_obj.auto_release_delay == Xolo::NONE
|
|
301
303
|
|
|
302
304
|
# title must be subscribed and autopkg
|
|
303
|
-
# (enforced in xadm)
|
|
305
|
+
# (enforced in xadm also)
|
|
304
306
|
next unless title_obj.subscribed? && !title_obj.autopkg_recipe.pix_empty?
|
|
305
307
|
|
|
306
|
-
# title
|
|
307
|
-
#
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
#
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
308
|
+
# if the title is set for monthly release, is today the
|
|
309
|
+
# right day of the month?
|
|
310
|
+
if title_obj.auto_release_delay == Xolo::MONTHLY
|
|
311
|
+
unless monthly_release_today?
|
|
312
|
+
log_debug "Title '#{title}' is set to monthly, but today isn't '#{Xolo::Server.config.auto_release_monthly}'"
|
|
313
|
+
next
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# For Monthlies, we only ever auto-release the newest version,
|
|
317
|
+
# and only if it is in pilot
|
|
318
|
+
latest_vobj = title_obj.version_objects.first
|
|
319
|
+
unless latest_vobj&.pilot?
|
|
320
|
+
log_debug "Title '#{title}' is set to monthly, but the latest version isn't in pilot"
|
|
321
|
+
next
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
vobj_to_release = latest_vobj
|
|
325
|
+
|
|
326
|
+
# Look for a version to be released today.
|
|
327
|
+
# The delay value must be a non-negative integer
|
|
328
|
+
elsif title_obj.auto_release_delay.pix_integer?
|
|
329
|
+
delay = title_obj.auto_release_delay.to_i
|
|
330
|
+
if delay.negative?
|
|
331
|
+
log_debug "Title '#{title}': invalid auto_release_delay: #{delay}"
|
|
332
|
+
next
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Given the title's delay, what creation date means
|
|
336
|
+
# we should release today?
|
|
337
|
+
# So if the auto_release_delay is 7, its today - 7 days.
|
|
338
|
+
# if the auto_release_delay is 0, its today - 0 days, aka today
|
|
339
|
+
creation_date_to_be_released_today = today - delay
|
|
340
|
+
|
|
341
|
+
# Find the newest pilot version whose creation date is
|
|
342
|
+
# creation_date_to_be_released_today or earlier
|
|
343
|
+
# That one should be released now.
|
|
344
|
+
# version_objects are in order of newest to oldest
|
|
345
|
+
title_obj.version_objects.each do |vobj|
|
|
346
|
+
# gotta be in pilot
|
|
347
|
+
next unless vobj.pilot?
|
|
348
|
+
|
|
349
|
+
# creation date has to be appropriate
|
|
350
|
+
next unless vobj.creation_date.to_date <= creation_date_to_be_released_today
|
|
351
|
+
|
|
352
|
+
vobj_to_release = vobj
|
|
353
|
+
break
|
|
354
|
+
end # title_obj.version_objects.each
|
|
355
|
+
end # if title_obj.auto_release_delay == Xolo::MONTHLY
|
|
328
356
|
|
|
329
|
-
|
|
330
|
-
|
|
357
|
+
# no versions to release today
|
|
358
|
+
unless vobj_to_release
|
|
359
|
+
log_debug "Title '#{title}': nothing to auto-release"
|
|
360
|
+
next
|
|
331
361
|
end
|
|
332
362
|
|
|
333
|
-
#
|
|
334
|
-
|
|
363
|
+
# must have a jamf pkg file uploaded
|
|
364
|
+
# (won't if autopkg recipe failed)
|
|
365
|
+
next if vobj_to_release.jamf_pkg_file.pix_empty?
|
|
335
366
|
|
|
336
367
|
# release it
|
|
337
368
|
log_debug "Maint: About to auto-release '#{title}' version '#{vobj_to_release.version}' which came out #{vobj_to_release.creation_date}"
|
|
338
369
|
|
|
339
|
-
# releasing a version is done via the title obj,
|
|
340
|
-
# of all versions.
|
|
370
|
+
# releasing a version is done via the title obj,
|
|
371
|
+
# since it affects the status of all versions.
|
|
341
372
|
title_obj.release vobj_to_release.version
|
|
342
373
|
|
|
343
374
|
log_info "Maint: Auto-released '#{title}' version '#{vobj_to_release.version}' which came out #{vobj_to_release.creation_date}", alert: true
|
|
344
375
|
|
|
345
376
|
# pause for server to catch up
|
|
346
|
-
sleep
|
|
347
|
-
end # each
|
|
377
|
+
sleep 30
|
|
378
|
+
end # title_objects.each do |title_obj|
|
|
348
379
|
|
|
349
380
|
log_debug 'Maint: Done auto-releasing'
|
|
350
381
|
end
|
|
351
382
|
|
|
383
|
+
# Is today the day for a monthly release?
|
|
384
|
+
# The order we check:
|
|
385
|
+
# - If the server is set to an ordinal weekday, like '3rd Thu'
|
|
386
|
+
# - If the server is set to the last weekday, like 'Last Thu'
|
|
387
|
+
# - If the server is set to a specific day of the month, which may be beyond the last day
|
|
388
|
+
# - All other cases, the first of the month.
|
|
389
|
+
#
|
|
390
|
+
# @return [Boolean] is today the day for a monthly release?
|
|
391
|
+
#################################
|
|
392
|
+
def monthly_release_today?
|
|
393
|
+
return @monthly_release_today if defined? @monthly_release_today
|
|
394
|
+
|
|
395
|
+
today = Date.today
|
|
396
|
+
last_day_of_this_month = Date.new(today.year, today.month, -1)
|
|
397
|
+
|
|
398
|
+
# downcased string...
|
|
399
|
+
server_setting = Xolo::Server.config.auto_release_monthly.to_s.downcase
|
|
400
|
+
|
|
401
|
+
@monthly_release_today =
|
|
402
|
+
# matches, e.g. '2nd Mon'
|
|
403
|
+
##########
|
|
404
|
+
if today.to_time.pix_ordinal_weekday.casecmp? server_setting
|
|
405
|
+
true
|
|
406
|
+
|
|
407
|
+
# matches 'last <wkday>' e.g. 'last mon'
|
|
408
|
+
##########
|
|
409
|
+
elsif server_setting.start_with? 'last '
|
|
410
|
+
# e.g. 'Mmon'
|
|
411
|
+
release_weekday = server_setting.split(' ').last
|
|
412
|
+
|
|
413
|
+
# is it the right weekday?
|
|
414
|
+
if today.strftime('%a').casecmp? release_weekday
|
|
415
|
+
# yes... so is it the last one? Make a Range and see it today is in it.
|
|
416
|
+
last_week_of_the_month = (last_day_of_this_month - 6)..last_day_of_month
|
|
417
|
+
last_week_of_the_month.include? today
|
|
418
|
+
|
|
419
|
+
# it is not the right weekday
|
|
420
|
+
else
|
|
421
|
+
false
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# matches an integer day of the month, or the last day of the month
|
|
425
|
+
# if server_setting is greater than the number of days in the month
|
|
426
|
+
##########
|
|
427
|
+
elsif server_setting.pix_integer? && server_setting.to_i.positive?
|
|
428
|
+
# convert string to integer
|
|
429
|
+
server_setting = server_setting.to_i
|
|
430
|
+
|
|
431
|
+
# integer for the last day of the month
|
|
432
|
+
last_day = last_day_of_this_month.day
|
|
433
|
+
|
|
434
|
+
# use the last day of this month if the server is set beyond it
|
|
435
|
+
server_setting = last_day if server_setting >= last_day
|
|
436
|
+
|
|
437
|
+
# is it today?
|
|
438
|
+
server_setting == today.day
|
|
439
|
+
|
|
440
|
+
# All other cases, e.g. bad value or unset on the server,
|
|
441
|
+
# ... the first of the month.
|
|
442
|
+
##########
|
|
443
|
+
else
|
|
444
|
+
today.day == 1
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
@monthly_release_today
|
|
448
|
+
end
|
|
449
|
+
|
|
352
450
|
# Cleanup versions.
|
|
353
451
|
# @return [void]
|
|
354
452
|
################################
|
|
@@ -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. Ignoring webhook."
|
|
94
|
+
log_debug "Title '#{title_name}' ID #{title_id} is not a subscribed title on this Xolo server. Ignoring webhook."
|
|
95
95
|
end
|
|
96
96
|
rescue => e
|
|
97
97
|
msg = "Error processing PatchSoftwareTitleUpdated webhook event: #{e.class}: #{e}"
|
|
@@ -101,6 +101,39 @@ module Xolo
|
|
|
101
101
|
end # thread
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# Manually re-trigger a the latest version,
|
|
105
|
+
# if the subscription update failed.
|
|
106
|
+
#
|
|
107
|
+
# Will delete and re-create the latest version, which
|
|
108
|
+
# must be pilot or pending.
|
|
109
|
+
def rerun_subscription_update(title)
|
|
110
|
+
log_debug "Re-running subscription update for latest version of #{title}"
|
|
111
|
+
|
|
112
|
+
title_obj = instantiate_title title
|
|
113
|
+
raise Xolo::UnsupportedError, "Title #{title} is not a subscribed title." unless title_obj.subscribed?
|
|
114
|
+
|
|
115
|
+
version = title_obj.latest_version
|
|
116
|
+
vobj = instantiate_version title: title, version: version
|
|
117
|
+
unless %w[pilot pending].include? vobj.status
|
|
118
|
+
raise Xolo::UnsupportedError,
|
|
119
|
+
"Latest version must be 'pending' or 'pilot' to rerun subscription updates. Version '#{version}' of title '#{title}' is '#{vobj.status}' "
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
title_obj.log_change msg: "Re-running subscription update for latest version' #{version}'"
|
|
123
|
+
|
|
124
|
+
progress "Deleting existing version '#{version}'...", log: :info
|
|
125
|
+
vobj.delete
|
|
126
|
+
progress 'Pausing for package deletion (sorry for delay)...'
|
|
127
|
+
|
|
128
|
+
sleep 60
|
|
129
|
+
|
|
130
|
+
progress "Re-adding version '#{version}' via subscription...", log: :info
|
|
131
|
+
Xolo::Server::Version.add_version_via_subscription(
|
|
132
|
+
title_object: title_obj,
|
|
133
|
+
new_version: version
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
104
137
|
end # Subscriptions
|
|
105
138
|
|
|
106
139
|
end # Helpers
|
|
@@ -156,7 +156,6 @@ module Xolo
|
|
|
156
156
|
|
|
157
157
|
update_description_in_jamf
|
|
158
158
|
update_ssvc
|
|
159
|
-
# TODO: deal with icon changes: if changes_for_update&.key? :self_service_icon
|
|
160
159
|
|
|
161
160
|
# are there any versions yet? if no, not active
|
|
162
161
|
if jamf_title_active?
|
|
@@ -234,6 +233,7 @@ module Xolo
|
|
|
234
233
|
|
|
235
234
|
vers_obj.update_jamf_package_notes(ttl_obj: self) if need_to_update_description?
|
|
236
235
|
vers_obj.update_patch_policy_deployment(ttl_obj: self) if changes_for_update&.key? :self_service_updates
|
|
236
|
+
vers_obj.set_patch_policy_ssvc_icon if self_service_updates?
|
|
237
237
|
end
|
|
238
238
|
end
|
|
239
239
|
|
|
@@ -454,7 +454,7 @@ module Xolo
|
|
|
454
454
|
msg = "Jamf: Starting deletion of Package '#{jamf_pkg_name}' id #{jamf_pkg_id} at #{Time.now.strftime '%F %T'}"
|
|
455
455
|
progress msg, log: :info
|
|
456
456
|
|
|
457
|
-
warning = "IMPORTANT: Package deletion can be slow. If you plan to re-add this version, '#{version}', please
|
|
457
|
+
warning = "IMPORTANT: Package deletion can be slow. If you plan to re-add this version, '#{version}', please wait a few minutes before doing so."
|
|
458
458
|
progress warning
|
|
459
459
|
|
|
460
460
|
self.class.pkg_deletion_pool.post do
|
|
@@ -978,7 +978,8 @@ module Xolo
|
|
|
978
978
|
#
|
|
979
979
|
# @param ttl_obj [Xolo::Server::Title] the title object that is being changed.
|
|
980
980
|
###############################
|
|
981
|
-
def update_patch_policy_deployment(ttl_obj:)
|
|
981
|
+
def update_patch_policy_deployment(ttl_obj: nil)
|
|
982
|
+
ttl_obj ||= title_object
|
|
982
983
|
return unless ttl_obj.changes_for_update[:self_service_updates]
|
|
983
984
|
|
|
984
985
|
ppol = jamf_patch_policy
|
|
@@ -1033,6 +1034,7 @@ module Xolo
|
|
|
1033
1034
|
ppol.self_service_reminder_frequency = PATCH_POL_SSVC_REMINDER_FREQ_HRS
|
|
1034
1035
|
ppol.deadline = PATCH_POL_SSVC_DEADLINE_DAYS
|
|
1035
1036
|
|
|
1037
|
+
set_patch_policy_ssvc_icon ppol
|
|
1036
1038
|
# Automatic deployment
|
|
1037
1039
|
else
|
|
1038
1040
|
progress "Jamf: Configuring Patch Policy for version '#{version}' of title '#{title}' for automatic deployment", log: :info
|
|
@@ -1052,23 +1054,27 @@ module Xolo
|
|
|
1052
1054
|
# @param icon_id [Integer, String] The id of the existing icon to use.
|
|
1053
1055
|
# @return [Void]
|
|
1054
1056
|
##########################
|
|
1055
|
-
def set_patch_policy_ssvc_icon(ppol)
|
|
1057
|
+
def set_patch_policy_ssvc_icon(ppol = nil)
|
|
1056
1058
|
return unless use_ssvc_updates?
|
|
1057
1059
|
|
|
1060
|
+
ppol ||= jamf_patch_policy
|
|
1061
|
+
|
|
1058
1062
|
if title_object.ssvc_icon_id
|
|
1059
1063
|
icon_id = title_object.ssvc_icon_id
|
|
1060
1064
|
log_debug "Jamf: ssvc_icon_id from title object is: #{icon_id}"
|
|
1061
1065
|
else
|
|
1062
1066
|
icon_id = title_object.jamf_manual_install_released_policy.self_service_icon.id
|
|
1063
1067
|
log_debug "Jamf: ssvc_icon_id from manual install policy is: #{icon_id}"
|
|
1064
|
-
|
|
1068
|
+
title_object.ssvc_icon_id = icon_id
|
|
1065
1069
|
end
|
|
1066
1070
|
|
|
1067
1071
|
if icon_id.pix_empty?
|
|
1068
|
-
|
|
1072
|
+
progress "Jamf: could not find ssvc icon id for title '#{title}', not setting icon for patch policy of version '#{version}'.", log: :info
|
|
1069
1073
|
return
|
|
1070
1074
|
end
|
|
1071
1075
|
|
|
1076
|
+
progress "Jamf: Setting icon for Self Service Updates of version '#{version}' of title '#{title}'", log: :info
|
|
1077
|
+
|
|
1072
1078
|
# this str can't be frozen for now
|
|
1073
1079
|
icon_xml = +<<~ENDXML
|
|
1074
1080
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -1081,12 +1087,14 @@ module Xolo
|
|
|
1081
1087
|
</patch_policy>
|
|
1082
1088
|
ENDXML
|
|
1083
1089
|
|
|
1084
|
-
log_debug "Jamf: Adding existing SSvc icon to patch policy for version '#{version}' of title '#{title}'"
|
|
1090
|
+
log_debug "Jamf: Adding existing SSvc icon id '#{icon_id}' to patch policy '#{ppol.id}' for version '#{version}' of title '#{title}'"
|
|
1085
1091
|
|
|
1086
1092
|
# TODO: in ruby-jss, update c_put and related methods to
|
|
1087
1093
|
# use unfrozen dups of strings for #gsub
|
|
1088
1094
|
|
|
1089
1095
|
jamf_cnx.c_put "patchpolicies/id/#{ppol.id}", icon_xml
|
|
1096
|
+
rescue Jamf::NoSuchItemError => e
|
|
1097
|
+
log_error "Couldn't set SSvc icon id '#{icon_id}' to patch policy '#{ppol.id}' for version '#{version}' of title '#{title}': Jamf::NoSuchItemError: #{e}"
|
|
1090
1098
|
end
|
|
1091
1099
|
|
|
1092
1100
|
# @return [String] the URL for the Jamf Pro Patch Policy that updates to this version
|
|
@@ -42,9 +42,12 @@ module Xolo
|
|
|
42
42
|
pw = payload[:password]
|
|
43
43
|
|
|
44
44
|
err =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
("'#{admin}' is not allowed to use the Xolo server" unless member_of_admin_jamf_group?(admin))
|
|
46
|
+
|
|
47
|
+
err ||=
|
|
48
|
+
if Xolo::Server.config.xadm_authenticator == Xolo::Server::Helpers::Auth::USE_JAMF
|
|
49
|
+
"Incorrect xolo admin username or password for '#{admin}'" unless authenticated_via_jamf?(admin, pw)
|
|
50
|
+
elsif !authenticated_via_xadm_authenticator?(admin, pw)
|
|
48
51
|
"Incorrect xolo admin username or password for '#{admin}'"
|
|
49
52
|
end
|
|
50
53
|
|
|
@@ -117,6 +117,21 @@ module Xolo
|
|
|
117
117
|
body resp
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
+
# Manually re-trigger a previous, failed subscription update
|
|
121
|
+
# Will delete and re-create the existing version, which
|
|
122
|
+
# must be pilot or pending.
|
|
123
|
+
###############
|
|
124
|
+
post '/titles/:title/rerun-subscription' do
|
|
125
|
+
title = params[:title]
|
|
126
|
+
|
|
127
|
+
halt_on_missing_title title
|
|
128
|
+
halt_on_locked_title title
|
|
129
|
+
|
|
130
|
+
with_streaming do
|
|
131
|
+
rerun_subscription_update(title)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
120
135
|
end # Module
|
|
121
136
|
|
|
122
137
|
end # Routes
|
data/lib/xolo/server/title.rb
CHANGED
|
@@ -491,7 +491,8 @@ module Xolo
|
|
|
491
491
|
# @return [Pathname] The the local file containing the self-service icon
|
|
492
492
|
#####################
|
|
493
493
|
def ssvc_icon_file
|
|
494
|
-
|
|
494
|
+
# dont cache this, read it every time
|
|
495
|
+
self.class.ssvc_icon_file title
|
|
495
496
|
end
|
|
496
497
|
|
|
497
498
|
# @return [Pathname] The the local file containing the code of the version script
|
|
@@ -985,16 +986,23 @@ module Xolo
|
|
|
985
986
|
# @return [void]
|
|
986
987
|
######################################
|
|
987
988
|
def validate_release(version_to_release)
|
|
989
|
+
# already released
|
|
988
990
|
if released_version == version_to_release
|
|
989
991
|
raise Xolo::InvalidDataError,
|
|
990
992
|
"Version '#{version_to_release}' of title '#{title}' is already released"
|
|
991
993
|
end
|
|
992
994
|
|
|
995
|
+
# no such version
|
|
993
996
|
unless versions.include? version_to_release
|
|
994
997
|
raise Xolo::NoSuchItemError,
|
|
995
998
|
"No version '#{version_to_release}' for title '#{title}'"
|
|
996
999
|
end
|
|
997
1000
|
|
|
1001
|
+
# missing .pkg file
|
|
1002
|
+
vobj = version_object version_to_release
|
|
1003
|
+
raise Xolo::MissingDataError, "Version '#{version_to_release}' of title '#{title}' has no uploaded .pkg" if vobj.jamf_pkg_file.pix_empty?
|
|
1004
|
+
|
|
1005
|
+
# visible to jamf?
|
|
998
1006
|
jamf_patch_title(refresh: true)
|
|
999
1007
|
return if jamf_patch_title.versions.key?(version_to_release)
|
|
1000
1008
|
|
|
@@ -1156,9 +1164,11 @@ module Xolo
|
|
|
1156
1164
|
# and re-upload the pkg
|
|
1157
1165
|
############################
|
|
1158
1166
|
def rerun_autopkg_recipe
|
|
1167
|
+
log_change msg: "Re-running autopkg recipe '#{autopkg_recipe}' for latest version '#{latest_version}'"
|
|
1168
|
+
|
|
1159
1169
|
pkg_src = server_app_instance.run_autopkg_recipe self
|
|
1160
1170
|
|
|
1161
|
-
return unless server_app_instance.validate_autopkg_pkg pkg_src
|
|
1171
|
+
return unless server_app_instance.validate_autopkg_pkg pkg_src, latest_version
|
|
1162
1172
|
|
|
1163
1173
|
latest_version_obj = version_object latest_version
|
|
1164
1174
|
latest_version_obj.pkg_is_from_autopkg = true
|
data/lib/xolo/server/version.rb
CHANGED
|
@@ -307,8 +307,22 @@ module Xolo
|
|
|
307
307
|
|
|
308
308
|
auto_release_delay = title_object.auto_release_delay.to_i
|
|
309
309
|
|
|
310
|
-
# is auto_release_delay
|
|
311
|
-
if title_object.auto_release_delay
|
|
310
|
+
# is auto_release_delay monthly?
|
|
311
|
+
if title_object.auto_release_delay == Xolo::MONTHLY
|
|
312
|
+
monthly = Xolo::Server.config.auto_release_monthly
|
|
313
|
+
day =
|
|
314
|
+
if monthly.pix_integer?
|
|
315
|
+
monthly.to_i.pix_ordinalize
|
|
316
|
+
elsif %w[sun mon tue wed thu fri sat].include? monthly[-3..-1].downcase
|
|
317
|
+
monthly
|
|
318
|
+
else
|
|
319
|
+
'1st'
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
msg = "New pilot for subscribed title '#{title}', version '#{new_version}' has been created in Xolo via subscription.\nIt will be released during nightly maintenance on the next #{day} of the month"
|
|
323
|
+
|
|
324
|
+
# is auto_release_delay an integer?
|
|
325
|
+
elsif title_object.auto_release_delay&.pix_integer? && !auto_release_delay.negative?
|
|
312
326
|
how_soon =
|
|
313
327
|
if auto_release_delay.zero?
|
|
314
328
|
'tonight'
|
|
@@ -696,8 +710,12 @@ module Xolo
|
|
|
696
710
|
progress 'Saving version data to Xolo server'
|
|
697
711
|
save_local_data
|
|
698
712
|
|
|
699
|
-
|
|
713
|
+
# prepend our version to the version_order array of the title once
|
|
714
|
+
# our version data has been saved at least once.
|
|
715
|
+
progress "Updating title version_order, prepending '#{version}'", log: :info
|
|
716
|
+
title_object.prepend_version(version)
|
|
700
717
|
|
|
718
|
+
create_patch_in_ted unless subscribed?
|
|
701
719
|
create_in_jamf
|
|
702
720
|
|
|
703
721
|
self.status = STATUS_PILOT
|
|
@@ -706,10 +724,6 @@ module Xolo
|
|
|
706
724
|
# add some data
|
|
707
725
|
save_local_data
|
|
708
726
|
|
|
709
|
-
# prepend our version to the version_order array of the title
|
|
710
|
-
progress "Updating title version_order, prepending '#{version}'", log: :info
|
|
711
|
-
title_object.prepend_version(version)
|
|
712
|
-
|
|
713
727
|
log_change msg: 'Version Created'
|
|
714
728
|
|
|
715
729
|
progress "Version '#{version}' of Title '#{title}' has been created in Xolo.", log: :info
|
|
@@ -748,7 +762,7 @@ module Xolo
|
|
|
748
762
|
|
|
749
763
|
pkg_src = title_object.run_autopkg_recipe
|
|
750
764
|
|
|
751
|
-
return unless server_app_instance.validate_autopkg_pkg pkg_src
|
|
765
|
+
return unless server_app_instance.validate_autopkg_pkg pkg_src, version
|
|
752
766
|
|
|
753
767
|
# this lets future code know that the pkg we're working with came from autopkg
|
|
754
768
|
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.5.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-10-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|