xolo-server 2.3.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/data/client/xolo +13 -1
- 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 +44 -0
- data/lib/xolo/server/helpers/auth.rb +30 -0
- data/lib/xolo/server/helpers/autopkg.rb +138 -8
- data/lib/xolo/server/helpers/maintenance.rb +136 -38
- data/lib/xolo/server/helpers/subscriptions.rb +34 -1
- data/lib/xolo/server/mixins/title_jamf_access.rb +3 -8
- 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/routes/titles.rb +24 -0
- data/lib/xolo/server/title.rb +33 -1
- data/lib/xolo/server/version.rb +22 -19
- 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
|
data/data/client/xolo
CHANGED
|
@@ -97,6 +97,7 @@ be_verbose=
|
|
|
97
97
|
debugging_on=
|
|
98
98
|
no_versions=
|
|
99
99
|
show_xolo_version=
|
|
100
|
+
do_recon=
|
|
100
101
|
|
|
101
102
|
# arguments
|
|
102
103
|
command=
|
|
@@ -155,7 +156,9 @@ Options:
|
|
|
155
156
|
policies, etc, from test xolo server, not the production server.
|
|
156
157
|
Setting the XOLO_TEST_MODE env. variable does the same thing.
|
|
157
158
|
-r, --recon: With 'install' or 'uninstall' run a 'jamf recon' after the
|
|
158
|
-
operation completes successfully.
|
|
159
|
+
operation completes successfully. With 'update', run a 'jamf recon'
|
|
160
|
+
before the checkin. NOTE: With update, the whole process may take
|
|
161
|
+
many minutes.
|
|
159
162
|
-n, --no-versions: With the 'list-titles' and 'list-installed' commands,
|
|
160
163
|
do not show the versions available, or the version
|
|
161
164
|
installed, respectively.
|
|
@@ -190,6 +193,9 @@ Commands:
|
|
|
190
193
|
every time this computer checks in with Jamf Pro.
|
|
191
194
|
Note: this just runs the 'jamf policy' command, which will execute any pending
|
|
192
195
|
Jamf policies triggered by "recurring check-in".
|
|
196
|
+
If --recon is given, a Jamf recon will run first, followed by a 1 minute pause
|
|
197
|
+
for Jamf to process the data before doing the check-in. This helps the Mac to see
|
|
198
|
+
very recently-released versions as patch updates.
|
|
193
199
|
|
|
194
200
|
refresh, r
|
|
195
201
|
Update xolo's information about current titles and versions.
|
|
@@ -1035,6 +1041,12 @@ function run_recon() {
|
|
|
1035
1041
|
function update() {
|
|
1036
1042
|
say "Updating installed titles, or installing newly scoped ones..."
|
|
1037
1043
|
|
|
1044
|
+
if [[ -n "$do_recon" ]] ; then
|
|
1045
|
+
run_recon
|
|
1046
|
+
say "Pausing to let Jamf Pro process recon data..."
|
|
1047
|
+
sleep 60
|
|
1048
|
+
fi
|
|
1049
|
+
|
|
1038
1050
|
debug "Running jamf policy..."
|
|
1039
1051
|
if be_verbose ; then
|
|
1040
1052
|
$JAMF policy -verbose
|
|
@@ -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: {
|
|
@@ -156,6 +175,7 @@ module Xolo
|
|
|
156
175
|
|
|
157
176
|
# @!attribute server_admin_jamf_group
|
|
158
177
|
# @return [String] The name of a Jamf account-group containing users of 'xadm'
|
|
178
|
+
# who are allowed to manage the xoloserver itself
|
|
159
179
|
server_admin_jamf_group: {
|
|
160
180
|
type: :string,
|
|
161
181
|
desc: <<~ENDDESC
|
|
@@ -829,6 +849,30 @@ module Xolo
|
|
|
829
849
|
NOTE: AutoPkg recipes are always run with '-k FAIL_RECIPES_WITHOUT_TRUST_INFO=yes', and will fail if you have not 'trusted'
|
|
830
850
|
them by making an override. See the AutoPkg docs for details.
|
|
831
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
|
|
832
876
|
}
|
|
833
877
|
|
|
834
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
|
#
|
|
@@ -35,6 +35,11 @@ module Xolo
|
|
|
35
35
|
AUTOPKG_UPLOADED_BY = 'autopkg'
|
|
36
36
|
FAIL_UNTRUSTED_RECIPES_CLI_OPT = '-k FAIL_RECIPES_WITHOUT_TRUST_INFO=yes'
|
|
37
37
|
|
|
38
|
+
# when we find a .pkg after a recipe has run, it can't be older than this many
|
|
39
|
+
# seconds, or we assume it is from some other run of the recipe, not the one we
|
|
40
|
+
# just did. Currently 20 min
|
|
41
|
+
MAX_PKG_AGE = 1200
|
|
42
|
+
|
|
38
43
|
# Module Methods
|
|
39
44
|
#######################
|
|
40
45
|
#######################
|
|
@@ -89,12 +94,53 @@ module Xolo
|
|
|
89
94
|
@autopkg_enabled = false
|
|
90
95
|
end
|
|
91
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
|
+
|
|
92
135
|
# the autopkg run command for this title
|
|
93
136
|
#####################################
|
|
94
137
|
def autopkg_run_command(title_object)
|
|
95
138
|
# is autopkg enabled for the server as a whole?
|
|
96
139
|
return unless autopkg_enabled?
|
|
97
140
|
|
|
141
|
+
# is autopkg enabled for this title?
|
|
142
|
+
return unless title_object.autopkg_enabled?
|
|
143
|
+
|
|
98
144
|
[
|
|
99
145
|
'/bin/launchctl',
|
|
100
146
|
'asuser',
|
|
@@ -106,7 +152,9 @@ module Xolo
|
|
|
106
152
|
'run',
|
|
107
153
|
'--verbose',
|
|
108
154
|
title_object.autopkg_recipe,
|
|
109
|
-
FAIL_UNTRUSTED_RECIPES_CLI_OPT
|
|
155
|
+
FAIL_UNTRUSTED_RECIPES_CLI_OPT,
|
|
156
|
+
'--report-plist',
|
|
157
|
+
autopkg_report_plist_path.to_s
|
|
110
158
|
]
|
|
111
159
|
end
|
|
112
160
|
|
|
@@ -122,33 +170,115 @@ module Xolo
|
|
|
122
170
|
# is autopkg enabled for this title?
|
|
123
171
|
return unless title_object.autopkg_enabled?
|
|
124
172
|
|
|
173
|
+
# Make this available to other methods here
|
|
174
|
+
@title_id_for_autopkg = title_object.title
|
|
175
|
+
|
|
125
176
|
recipe = title_object.autopkg_recipe
|
|
126
177
|
pkgdir = Pathname.new title_object.autopkg_dir
|
|
127
178
|
clean_old_pkgs pkgdir
|
|
128
179
|
|
|
129
180
|
cmd = autopkg_run_command(title_object)
|
|
130
|
-
progress "Running AutoPkg recipe
|
|
181
|
+
progress "Running AutoPkg recipe '#{recipe}' for #{title_object.title}..."
|
|
182
|
+
log_info "Running AutoPkg recipe for #{title_object.title} via command: #{cmd.join(' ')}"
|
|
131
183
|
|
|
132
184
|
unlock_autopkg_user_keychain
|
|
133
|
-
|
|
134
185
|
souterr, status = Open3.capture2e(*cmd)
|
|
135
186
|
souterr.strip!
|
|
136
187
|
|
|
137
188
|
if status.success?
|
|
138
|
-
|
|
189
|
+
msg = "AutoPkg recipe '#{recipe}' for title '#{title_object.title}' completed successfully."
|
|
190
|
+
progress msg, log: :info, alert: true
|
|
191
|
+
|
|
139
192
|
log_debug 'AutoPkg output:'
|
|
140
193
|
souterr.lines.each { |l| log_debug "AutoPkg: #{l.chomp}" }
|
|
141
194
|
|
|
142
195
|
pkgs = pkgdir.children.select { |c| c.extname == '.pkg' }
|
|
196
|
+
# the Pathname of the newest .pkg
|
|
143
197
|
pkgs.max_by(&:mtime)
|
|
144
198
|
|
|
199
|
+
# Failure is a option
|
|
145
200
|
else
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
201
|
+
handle_failed_recipe(
|
|
202
|
+
recipe: recipe,
|
|
203
|
+
title_object: title_object,
|
|
204
|
+
status: status,
|
|
205
|
+
souterr: souterr
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Deal with a failed recipe run
|
|
211
|
+
#
|
|
212
|
+
###############################
|
|
213
|
+
def handle_failed_recipe(recipe:, title_object:, status:, souterr:)
|
|
214
|
+
errmsg = "ERROR: AutoPkg recipe '#{recipe}' for title '#{title_object.title}' failed with status #{status.exitstatus}. Details logged and emailed to '#{title_object.contact_email}'"
|
|
215
|
+
|
|
216
|
+
progress errmsg, log: :error, alert: true
|
|
149
217
|
|
|
150
|
-
|
|
218
|
+
# log the details
|
|
219
|
+
log_error 'AutoPkg output:'
|
|
220
|
+
souterr.lines.each { |l| log_error "AutoPkg: #{l.chomp}" }
|
|
221
|
+
|
|
222
|
+
# email the details to the contact addr for the title
|
|
223
|
+
email_subj = "ERROR: AutoPkg recipe FAILED for title '#{title_object.title}'"
|
|
224
|
+
email_msg = <<~ENDEMAIL
|
|
225
|
+
The autopkg recipe '#{recipe}' for title '#{title_object.title}' failed with status #{status.exitstatus}.
|
|
226
|
+
|
|
227
|
+
Please check that the recipe is up to date. If not, please update the recipe's repo by running this as the autppkg user on the xolo server:
|
|
228
|
+
|
|
229
|
+
autopkg repo-update <repo-name>
|
|
230
|
+
|
|
231
|
+
Then confirm you still trust the recipe.
|
|
232
|
+
If so, run:
|
|
233
|
+
|
|
234
|
+
autopkg update-trust-info '#{recipe}'
|
|
235
|
+
|
|
236
|
+
Then tell xolo the xolo server to re-run the recipe - the .pkg will always be assigned to the latest version.
|
|
237
|
+
|
|
238
|
+
xadm rerun-autopkg-recipe #{title_object.title}
|
|
239
|
+
|
|
240
|
+
If needed, contact your xoloserver administrator to perform the autopkg commands.
|
|
241
|
+
|
|
242
|
+
Here is the output from the failed recipe:
|
|
243
|
+
------------------------------------------
|
|
244
|
+
#{souterr}
|
|
245
|
+
ENDEMAIL
|
|
246
|
+
|
|
247
|
+
send_email to: title_object.contact_email, subject: email_subj, msg: email_msg, html: false
|
|
248
|
+
|
|
249
|
+
raise "AutoPkg recipe #{recipe} failed."
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# confirm a given autopkg package is OK to use after running a recipe
|
|
253
|
+
###########################
|
|
254
|
+
def validate_autopkg_pkg(pkg_src, desired_version)
|
|
255
|
+
if pkg_src.nil?
|
|
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.'
|
|
257
|
+
progress msg, log: :warn, alert: true
|
|
258
|
+
return false
|
|
151
259
|
end
|
|
260
|
+
|
|
261
|
+
oldest_allowed = Time.now - MAX_PKG_AGE # 20 minutes ago
|
|
262
|
+
if pkg_src.mtime < oldest_allowed
|
|
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"
|
|
277
|
+
progress msg, log: :warn, alert: true
|
|
278
|
+
return false
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
true
|
|
152
282
|
end
|
|
153
283
|
|
|
154
284
|
# remove any existing pkgs from the given autopkg output dir
|
|
@@ -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
|
-
log_debug "Maint: About to auto-release
|
|
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
|
-
log_info "Maint: Auto-released
|
|
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
|
|
|
@@ -1808,6 +1808,8 @@ module Xolo
|
|
|
1808
1808
|
pol.self_service_description = new_desc
|
|
1809
1809
|
|
|
1810
1810
|
pol.self_service_install_button_text = Xolo::Server::Title::SELF_SERVICE_INSTALL_BTN_TEXT
|
|
1811
|
+
pol.self_service_reinstall_button_text = Xolo::Server::Title::SELF_SERVICE_REINSTALL_BTN_TEXT
|
|
1812
|
+
|
|
1811
1813
|
# icon already uploaded to jamf
|
|
1812
1814
|
return if ssvc_icon_id
|
|
1813
1815
|
|
|
@@ -1823,13 +1825,6 @@ module Xolo
|
|
|
1823
1825
|
self.ssvc_icon_id = Jamf::Policy.fetch(id: pol.id, cnx: jamf_cnx).icon.id
|
|
1824
1826
|
end
|
|
1825
1827
|
|
|
1826
|
-
# run the autopkg recipe if defined
|
|
1827
|
-
# See Helpers::AutoPkg for more details
|
|
1828
|
-
############################
|
|
1829
|
-
def run_autopkg_recipe
|
|
1830
|
-
server_app_instance.run_autopkg_recipe self
|
|
1831
|
-
end
|
|
1832
|
-
|
|
1833
1828
|
# Methods used by subscription stuff
|
|
1834
1829
|
##############################
|
|
1835
1830
|
|
|
@@ -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
|
|
@@ -288,6 +288,30 @@ module Xolo
|
|
|
288
288
|
body data
|
|
289
289
|
end
|
|
290
290
|
|
|
291
|
+
# rerun the titles autopkg recipe
|
|
292
|
+
######################
|
|
293
|
+
post '/titles/:title/rerun_autopkg_recipe' do
|
|
294
|
+
halt_on_missing_title params[:title]
|
|
295
|
+
halt_on_locked_title params[:title]
|
|
296
|
+
title = instantiate_title params[:title]
|
|
297
|
+
|
|
298
|
+
unless title.autopkg_enabled?
|
|
299
|
+
msg = "Title '#{params[:title]}' is not configured to use AutoPkg"
|
|
300
|
+
log_debug "ERROR: #{msg}"
|
|
301
|
+
halt 409, { status: 409, error: msg }
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
unless title.latest_version
|
|
305
|
+
msg = "Title '#{params[:title]}' has no versions, cannot re-run the recipe"
|
|
306
|
+
log_debug "ERROR: #{msg}"
|
|
307
|
+
halt 404, { status: 404, error: msg }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
with_streaming do
|
|
311
|
+
title.rerun_autopkg_recipe
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
291
315
|
end # Titles
|
|
292
316
|
|
|
293
317
|
end # Routes
|
data/lib/xolo/server/title.rb
CHANGED
|
@@ -120,6 +120,7 @@ module Xolo
|
|
|
120
120
|
JPAPI_PATCH_REPORT_RSRC = 'patch-report'
|
|
121
121
|
|
|
122
122
|
SELF_SERVICE_INSTALL_BTN_TEXT = 'Install'
|
|
123
|
+
SELF_SERVICE_REINSTALL_BTN_TEXT = 'Re-Install'
|
|
123
124
|
|
|
124
125
|
# Class Methods
|
|
125
126
|
######################
|
|
@@ -490,7 +491,8 @@ module Xolo
|
|
|
490
491
|
# @return [Pathname] The the local file containing the self-service icon
|
|
491
492
|
#####################
|
|
492
493
|
def ssvc_icon_file
|
|
493
|
-
|
|
494
|
+
# dont cache this, read it every time
|
|
495
|
+
self.class.ssvc_icon_file title
|
|
494
496
|
end
|
|
495
497
|
|
|
496
498
|
# @return [Pathname] The the local file containing the code of the version script
|
|
@@ -984,16 +986,23 @@ module Xolo
|
|
|
984
986
|
# @return [void]
|
|
985
987
|
######################################
|
|
986
988
|
def validate_release(version_to_release)
|
|
989
|
+
# already released
|
|
987
990
|
if released_version == version_to_release
|
|
988
991
|
raise Xolo::InvalidDataError,
|
|
989
992
|
"Version '#{version_to_release}' of title '#{title}' is already released"
|
|
990
993
|
end
|
|
991
994
|
|
|
995
|
+
# no such version
|
|
992
996
|
unless versions.include? version_to_release
|
|
993
997
|
raise Xolo::NoSuchItemError,
|
|
994
998
|
"No version '#{version_to_release}' for title '#{title}'"
|
|
995
999
|
end
|
|
996
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?
|
|
997
1006
|
jamf_patch_title(refresh: true)
|
|
998
1007
|
return if jamf_patch_title.versions.key?(version_to_release)
|
|
999
1008
|
|
|
@@ -1144,6 +1153,29 @@ module Xolo
|
|
|
1144
1153
|
unlock
|
|
1145
1154
|
end
|
|
1146
1155
|
|
|
1156
|
+
# run the autopkg recipe if defined
|
|
1157
|
+
# See Helpers::AutoPkg for more details
|
|
1158
|
+
############################
|
|
1159
|
+
def run_autopkg_recipe
|
|
1160
|
+
server_app_instance.run_autopkg_recipe self
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
# Re run the autopkg recipe
|
|
1164
|
+
# and re-upload the pkg
|
|
1165
|
+
############################
|
|
1166
|
+
def rerun_autopkg_recipe
|
|
1167
|
+
log_change msg: "Re-running autopkg recipe '#{autopkg_recipe}' for latest version '#{latest_version}'"
|
|
1168
|
+
|
|
1169
|
+
pkg_src = server_app_instance.run_autopkg_recipe self
|
|
1170
|
+
|
|
1171
|
+
return unless server_app_instance.validate_autopkg_pkg pkg_src, latest_version
|
|
1172
|
+
|
|
1173
|
+
latest_version_obj = version_object latest_version
|
|
1174
|
+
latest_version_obj.pkg_is_from_autopkg = true
|
|
1175
|
+
|
|
1176
|
+
server_app_instance.process_and_upload_autopkg_pkg title, latest_version_obj, pkg_src
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1147
1179
|
# Is this title locked for updates?
|
|
1148
1180
|
#############################
|
|
1149
1181
|
def locked?
|
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,18 +762,7 @@ module Xolo
|
|
|
748
762
|
|
|
749
763
|
pkg_src = title_object.run_autopkg_recipe
|
|
750
764
|
|
|
751
|
-
|
|
752
|
-
msg = 'AutoPkg recipe is enabled for this title, but no pkg was found after running the recipe. Please check the AutoPkg recipe and the server log for details.'
|
|
753
|
-
progress msg, log: :warn, alert: true
|
|
754
|
-
return
|
|
755
|
-
end
|
|
756
|
-
|
|
757
|
-
oldest_allowed = Time.now - 1200 # 20 minutes ago
|
|
758
|
-
if pkg_src.mtime < oldest_allowed
|
|
759
|
-
msg = "AutoPkg recipe is enabled for this title, and a pkg was found after running the recipe, but it was last modified at #{pkg_src.mtime}, which is more than 20 minutes ago. To avoid accidentally uploading an old pkg, the server will not upload this pkg. Please check the AutoPkg recipe and the server log for details."
|
|
760
|
-
progress msg, log: :warn, alert: true
|
|
761
|
-
return
|
|
762
|
-
end
|
|
765
|
+
return unless server_app_instance.validate_autopkg_pkg pkg_src, version
|
|
763
766
|
|
|
764
767
|
# this lets future code know that the pkg we're working with came from autopkg
|
|
765
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
|