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,739 @@
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
+ # frozen_string_literal: true
8
+
9
+ module Xolo
10
+
11
+ module Server
12
+
13
+ # A class for working with configuration values for the Xolo server.
14
+ #
15
+ # @seealso Xolo::Core::BaseClasses::Configuration
16
+ #
17
+ # This is a singleton class, only one instance can exist at a time.
18
+ #
19
+ # When the server loads, that instance is created, and is used to provide configuration
20
+ # values throughout the server code. It can be accessed via Xolo::Server.config.
21
+ #
22
+ # When the Xolo::Server::Configuration instance is created, the {CONF_FILENAME} file
23
+ # is examined if it exists, and the items in it are loaded into the attributes.
24
+ #
25
+ # See {KEYS} for the available attributes
26
+ #
27
+ # At any point, the attributes can read or be changed using standard Ruby getter/setter methods
28
+ # matching the name of the attribute,
29
+ # e.g.
30
+ #
31
+ # # read the current ted_server_name configuration value
32
+ # Xolo::Server.config.ted_server_name # => 'foobar.appcatalog.jamfcloud.com'
33
+ #
34
+ # # sets the ted_server_name to a new value
35
+ # Xolo::Server.config.ted_server_name = 'baz.appcatalog.jamfcloud.com'
36
+ #
37
+ #
38
+ #
39
+ class Configuration < Xolo::Core::BaseClasses::Configuration
40
+
41
+ include Singleton
42
+
43
+ # Constants
44
+ #####################################
45
+ #####################################
46
+
47
+ # Default Values
48
+ ##########
49
+
50
+ CONF_FILENAME = 'config.yaml'
51
+ BACKUP_FILE_TIMESTAMP_FORMAT = '%Y%m%d%H%M%S.%N'
52
+ BACKUP_FILE_EXPIRATION_DAYS = 30
53
+ BACKUP_FILE_EXPIRATION_SECS = BACKUP_FILE_EXPIRATION_DAYS * 24 * 60 * 60
54
+
55
+ # We don't store the ssl data in a Dir.tmpdir because those will be deleted
56
+ # out from under the server if they aren't accessed within 3 days.
57
+ SSL_DIR = Xolo::Server::Constants::DATA_DIR + 'ssl'
58
+ SSL_CERT_FILENAME = 'cert.pem'
59
+ SSL_KEY_FILENAME = 'key.pem'
60
+ SSL_CERT_FILE = SSL_DIR + SSL_CERT_FILENAME
61
+ SSL_KEY_FILE = SSL_DIR + SSL_KEY_FILENAME
62
+
63
+ DFT_SSL_VERIFY = true
64
+
65
+ PKG_SIGNING_KEYCHAIN_FILENAME = 'xolo-pkg-signing.keychain-db'
66
+ PKG_SIGNING_KEYCHAIN = Xolo::Server::Constants::DATA_DIR + PKG_SIGNING_KEYCHAIN_FILENAME
67
+
68
+ PIPE = '|'
69
+
70
+ PRIVATE = '<private>'
71
+
72
+ # if this file exists, the server is in developer mode, and some things do or don't happen
73
+ # see {#developer_mode?}
74
+ DEV_MODE_FILE = Xolo::Server::Constants::DATA_DIR + 'dev_mode'
75
+
76
+ # Attributes
77
+ #####################################
78
+
79
+ # The attribute keys we maintain, and their definitions
80
+ KEYS = {
81
+
82
+ # @!attribute ssl_cert
83
+ # @return [String] A command, path, or value for the SSL Cert.
84
+ ssl_cert: {
85
+ required: true,
86
+ load_method: :data_from_command_file_or_string,
87
+ private: true,
88
+ type: :string,
89
+ desc: <<~ENDDESC
90
+ The SSL Certificate for the https server in .pem format. When the server starts, it will be read from here, and securely stored in #{SSL_CERT_FILE}.
91
+
92
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
93
+
94
+ If the value is a path to a readable file, the file's contents are used.
95
+
96
+ Otherwise the value is used as the certificate.
97
+
98
+ Be careful of security concerns when certificates are stored in files.
99
+ ENDDESC
100
+ },
101
+
102
+ # @!attribute ssl_key
103
+ # @return [String] A command, path, or value for the SSL Cert private key.
104
+ ssl_key: {
105
+ required: true,
106
+ load_method: :data_from_command_file_or_string,
107
+ private: true,
108
+ type: :string,
109
+ desc: <<~ENDDESC
110
+ The private key for the SSL Certificate in .pem format. When the server starts, it will be read from here, and securely stored in #{SSL_KEY_FILE}/
111
+
112
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
113
+
114
+ If the value is a path to a readable file, the file's contents are used.
115
+
116
+ Otherwise the value is used as the certificate.
117
+
118
+ Be careful of security concerns when certificates are stored in files.
119
+ ENDDESC
120
+ },
121
+
122
+ # @!attribute ssl_verify
123
+ # @return [Boolean] Should the server verify SSL certs of incoming clients?
124
+ ssl_verify: {
125
+ default: DFT_SSL_VERIFY,
126
+ type: :boolean,
127
+ desc: <<~ENDDESC
128
+ Should the server verify the SSL certificates of machines it communicates with, such as the Jamf Pro server and the Title Editor server?
129
+ ENDDESC
130
+ },
131
+
132
+ # @!attribute admin_jamf_group
133
+ # @return [String] The name of a Jamf account-group containing users of 'xadm'
134
+ admin_jamf_group: {
135
+ required: true,
136
+ type: :string,
137
+ desc: <<~ENDDESC
138
+ The name of a Jamf account-group (not a User group) that allows the use of 'xadm' to create and maintain titles and versions. Users of xadm must be in this group, and provide their valid Jamf credentials.
139
+ ENDDESC
140
+ },
141
+
142
+ # @!attribute server_admin_jamf_group
143
+ # @return [String] The name of a Jamf account-group containing users of 'xadm'
144
+ server_admin_jamf_group: {
145
+ type: :string,
146
+ desc: <<~ENDDESC
147
+ The name of a Jamf account-group (not a User group) that allows the use of the server admin commands of 'xadm', including --run-server-cleanup, --update-client-data, --rotate-server-logs and --set-server-log-level.
148
+ Members of this group can also use the xadm commands that require the 'admin_jamf_group' group.
149
+ If unset, no one can use the server admin commands.
150
+ ENDDESC
151
+ },
152
+
153
+ # @!attribute log_days_to_keep
154
+ # @return [Integer] How many days worth of logs to keep
155
+ log_days_to_keep: {
156
+ default: Xolo::Server::Log::DFT_LOG_DAYS_TO_KEEP,
157
+ type: :integer,
158
+ desc: <<~ENDDESC
159
+ The server log is rotated daily. How many days of log files should be kept? All logs are kept in #{Xolo::Server::LOG_DIR}. The current file is named '#{Xolo::Server::LOG_FILE_NAME}', older files are appended with '.0' for yesterday, '.1' for the previous day, etc.
160
+ ENDDESC
161
+ },
162
+
163
+ # @!attribute log_compress_after_days
164
+ # @return [Integer] How many days worth of logs to keep
165
+ log_compress_after_days: {
166
+ default: Xolo::Server::Log::DFT_LOG_COMPRESS_AFTER_DAYS,
167
+ type: :integer,
168
+ desc: <<~ENDDESC
169
+ Once a log file is rotated, how many days before it is compressed? Compressed logs are named '#{Xolo::Server::LOG_FILE_NAME}.XX.bz2'. It can be accessed using the various bzip2 tools (bzip2, bunzip2, bzcat, bzgrep, etc). If this number is negative, or larger than log_days_to_keep, no logs will be compressed, if it is zero, all older logs will be compressed.
170
+ ENDDESC
171
+ },
172
+
173
+ # @!attribute alert_tool
174
+ # @return [String] A command and its options/args that relays messages from stdin to some means
175
+ # of alerting Xolo server admins of a problem or event that would otherwise go unnoticed.
176
+ #
177
+ alert_tool: {
178
+ type: :string,
179
+ desc: <<~ENDDESC
180
+ Server errors or other events that happen as part of xadm actions are reported to the xadm user. But sometimes such events happen outside of the scope of a xadm session. While these events will be logged, you might want them reported to a server administrator in real time.
181
+
182
+ This value is either:
183
+
184
+ - a command (path to executable plus CLI args) on the Xolo server which will accept an error or other alert message on standard input and send it somewhere where it'll be seen by an appropriate audiance, be that an email address, a Slack channel - anything you'd like.
185
+
186
+ - or a string "email:email_address" where email_address is the email address to send alerts to. In this case, the server will send an email to that address using the smtp_server and email_from configuration values.
187
+
188
+ Fictional command example:
189
+ /path/to/slackerator --sender xolo-server --channel xolo-alerts --icon dante
190
+ Fictional email example:
191
+ email:xolo-server-admins@myschool.edu
192
+ ENDDESC
193
+ },
194
+
195
+ # @!attribute pkg_signing_identity
196
+ # @return [String] The name of the package signing identity to use
197
+ pkg_signing_identity: {
198
+ required: true,
199
+ type: :string,
200
+ desc: <<~ENDDESC
201
+ Xolo needs to be able to sign at least one of the packages it maintains: the client-data pkg which installs a JSON file of title and version data on the client machines.
202
+
203
+ To sign that, or oher packages, you must install a keychain containing a valid package-signing identity on the xolo server at:
204
+ #{PKG_SIGNING_KEYCHAIN}
205
+
206
+ The 'identity' (name) of the package-signing certificate inside the keychain must be set here. It usually looks something like:
207
+ Developer ID Installer: My Company (XYZXYZYXZXYZ)
208
+
209
+ If desired, you can use this identity to sign other packages as well, see the 'sign_pkgs' config value.
210
+ ENDDESC
211
+ },
212
+
213
+ # @!attribute pkg_signing_keychain_pw
214
+ # @return [String] A command, path, or value for the password to unlock the pkg_signing_keychain
215
+ pkg_signing_keychain_pw: {
216
+ required: true,
217
+ load_method: :data_from_command_file_or_string,
218
+ private: true,
219
+ type: :string,
220
+ desc: <<~ENDDESC
221
+ The password to unlock the keychain used for package signing.
222
+
223
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
224
+
225
+ If the value is a path to a readable file, the file's contents are used.
226
+
227
+ Otherwise the value is used as the password.
228
+
229
+ Be careful of security concerns when passwords are stored in files.
230
+ ENDDESC
231
+ },
232
+
233
+ # @!attribute sign_pkgs
234
+ # @return [Boolean] Should the server sign any unsigned uploaded pkgs?
235
+ sign_pkgs: {
236
+ type: :boolean,
237
+ desc: <<~ENDDESC
238
+ When someone uses xadm to upload a .pkg, and it isn't signed, should the server sign it before uploading to Jamf's Distribution Point(s)?
239
+
240
+ If you set this to true, it will use the same keychain and identity as the 'pkg_signing_identity' config value to sign the pkg, using the keychain you installed at:
241
+ /Library/Application Support/xoloserver/xolo-pkg-signing.keychain-db
242
+
243
+ NOTE: While it may seem insecure to allow the server to sign pkgs, consider:
244
+ - Users of xadm are authenticated and authorized to use the server (see 'admin_jamf_group')
245
+ - You don't need to distribute your signing certificates to a wide group of individual developers.
246
+ - While you need to trust your xadm users not to upload a malicious pkg, this would be true
247
+ even if you deployed the certs to them, so keeping the certs on the server is more secure.
248
+ ENDDESC
249
+ },
250
+
251
+ # @!attribute release_to_all_jamf_group
252
+ # @return [String] The name of a Jamf Pro account-group that is allowed to set release_groups to 'all'
253
+ release_to_all_jamf_group: {
254
+ required: false,
255
+ type: :string,
256
+ desc: <<~ENDDESC
257
+ The name of a Jamf account-group (not a User group) whose members may set a title's release_groups to 'all'.
258
+
259
+ When this is set, and someone not in this group tries to set a title's release_groups to 'all', they will get a message telling them to contact the person or group named in 'release_to_all_contact' to get approval.
260
+
261
+ To approve the request, one of the members of this group must run 'xadm edit-title <title> --release-groups all'.
262
+
263
+ Leave this unset to allow anyone using xadm to set release_groups to 'all' without approval.
264
+ ENDDESC
265
+ },
266
+
267
+ # @!attribute release_to_all_contact
268
+ # @return [String] A string containing contact info for the release_to_all_jamf_group
269
+ release_to_all_contact: {
270
+ required: false,
271
+ type: :string,
272
+ desc: <<~ENDDESC
273
+ When release_to_all_jamf_group is set, and someone not in that group tries to set a title's release_groups to 'all', they are told to use this contact info to get approval.
274
+
275
+ This string could be an email address, a chat channel, a phone number, etc.
276
+
277
+ Examples:
278
+ - 'jamf-admins@myschool.edu'
279
+ - 'the IT deployment team in the #deployment channel on Slack'
280
+ - 'Bob Parr at 555-555-5555'
281
+
282
+ It is presented in text along the lines of:
283
+
284
+ Please contact <value> to set release_groups to 'all', letting us know why you think the title should be automatically deployed to all computers.
285
+
286
+ This value is required if release_to_all_jamf_group is set.
287
+ ENDDESC
288
+ },
289
+
290
+ # @!attribute default_min_os
291
+ # @return [String] The default minimum OS for versions
292
+ default_min_os: {
293
+ required: false,
294
+ type: :string,
295
+ desc: <<~ENDDESC
296
+ The default minimum OS version for new versions of titles. This is used when a new version is created, and no minimum OS is specified.
297
+ In Jamf Packages, this will appear in the OS limitations expanded to macOS 40, or max_os if specified for the version.
298
+ If not specified here, the default is #{Xolo::Core::BaseClasses::Version::DEFAULT_MIN_OS}.
299
+ ENDDESC
300
+ },
301
+
302
+ # @!attribute deprecated_lifetime_days
303
+ # @return [Integer] How many days after a version is deprecated to keep it
304
+ deprecated_lifetime_days: {
305
+ default: Xolo::Server::Helpers::Maintenance::DFT_DEPRECATED_LIFETIME_DAYS,
306
+ type: :integer,
307
+ desc: <<~ENDDESC
308
+ When a version is deprecated, it will be automatically deleted by the nightly cleanup this many days later. If set to 0 or less, deprecated versions will never be deleted.
309
+
310
+ Deprecated versions are those that have been released, but a newer version has been released since then.
311
+
312
+ WARNING: If you set this to 0 or less, you will need to manually delete deprecated versions. Keeping them around can cause confusion and clutter in the GUI, and use up disk space.
313
+ ENDDESC
314
+ },
315
+
316
+ # @!attribute keep_skipped_versions
317
+ # @return [Boolean] Should we keep versions that are skipped?
318
+ keep_skipped_versions: {
319
+ default: false,
320
+ type: :boolean,
321
+ desc: <<~ENDDESC
322
+ Normally, skipped versions are deleted during nightly cleanup. If you set this to true, skipped versions will be kept.
323
+
324
+ Skipped versions are those that were never released, but a newer version has been released.
325
+
326
+ WARNING: If you set this to true, you will need to manually delete skipped versions. Keeping them around can cause confusion and clutter in the GUI, and use up disk space.
327
+ ENDDESC
328
+ },
329
+
330
+ # @!attribute unreleased_pilots_notification_days
331
+ # @return [Integer] How many days after the newest pilot of a title is created to notify someone
332
+ # that it hasn't been released yet. Notification is monthly on the first.
333
+ # An email to the contact_email for the title and an alert is sent to the alert_tool, if defined.
334
+ # If set to 0 or less, no notifications will be sent.
335
+ unreleased_pilots_notification_days: {
336
+ default: Xolo::Server::Helpers::Maintenance::DFT_UNRELEASED_PILOTS_NOTIFICATION_DAYS,
337
+ type: :integer,
338
+ desc: <<~ENDDESC
339
+ If the newest pilot of a title has not been released in this many days, notify someone about it monthly, asking to release it or delete it. If set to 0 or less, these notifications are disabled.
340
+
341
+ Notifications are sent on the first of the month via email to the title's contact email address, and the alert_tool (if defined). Default is 180 days (about 6 months).
342
+
343
+ Pilot versions are those that have been added for testing, but not yet released.
344
+
345
+ This is useful to keep the Xolo server clean and up-to-date, and to avoid cluttering with unreleased versions or titles that are no longer relevant.
346
+ ENDDESC
347
+ },
348
+
349
+ # @!attribute smtp_server
350
+ # @return [String] The hostname of the SMTP server to use for sending email. This is a server that
351
+ # can recieve email for your organizaion from the xolo server. Used for sending alerts and
352
+ # notifications.
353
+ smtp_server: {
354
+ type: :string,
355
+ desc: <<~ENDDESC
356
+ The hostname of the SMTP server to use for sending email. This is a server that can recieve email for your organizaion from the xolo server. Used for sending alerts and notifications. If not set, no email notifications will be sent.
357
+ ENDDESC
358
+ },
359
+
360
+ # @!attribute email_from
361
+ # @return [String] The email address to use as the 'from' address for emails sent by xolo
362
+ email_from: {
363
+ type: :string,
364
+ desc: <<~ENDDESC
365
+ The email address to use as the 'from' address for emails sent by xolo. This should be a valid email address that can recieve replies.
366
+
367
+ Will default to '#{Xolo::Server::Helpers::Notification::DFT_EMAIL_FROM}@<hostname>' if not set. The human-readable part of the address will be 'Xolo Server on <hostname>'.
368
+ ENDDESC
369
+ },
370
+
371
+ # Jamf Pro
372
+ ####################
373
+
374
+ # @!attribute jamf_hostname
375
+ # @return [String] The hostname of the Jamf Pro server we are connecting to
376
+ jamf_hostname: {
377
+ required: true,
378
+ type: :string,
379
+ desc: <<~ENDDESC
380
+ The hostname of the Jamf Pro server used by xolo for API access.
381
+ ENDDESC
382
+ },
383
+
384
+ # @!attribute jamf_port
385
+ # @return [Integer] The port number of the Jamf Pro server we are connecting to for API access
386
+ jamf_port: {
387
+ default: Jamf::Connection::HTTPS_SSL_PORT,
388
+ type: :integer,
389
+ desc: <<~ENDDESC
390
+ The port number of the Jamf Pro server used by xolo for API access.
391
+ The default is #{Jamf::Connection::HTTPS_SSL_PORT} if the Jamf Pro hostname ends with #{Jamf::Connection::JAMFCLOUD_DOMAIN} and #{Jamf::Connection::ON_PREM_SSL_PORT} otherwise.
392
+ ENDDESC
393
+ },
394
+
395
+ # @!attribute jamf_gui_hostname
396
+ # @return [String] The hostname of the Jamf Pro server used for links to the GUI webapp
397
+ jamf_gui_hostname: {
398
+ required: true,
399
+ type: :string,
400
+ desc: <<~ENDDESC
401
+ The hostname of the Jamf Pro server used for links to the GUI webapp, if different from the jamf_hostname.
402
+ ENDDESC
403
+ },
404
+
405
+ # @!attribute jamf_gui_port
406
+ # @return [Integer] The port number of the Jamf Pro server used for links to the GUI webapp
407
+ jamf_gui_port: {
408
+ default: Jamf::Connection::HTTPS_SSL_PORT,
409
+ type: :integer,
410
+ desc: <<~ENDDESC
411
+ The port number of the Jamf Pro server used for links to the GUI webapp, if different from the jamf_port.
412
+
413
+ The default is #{Jamf::Connection::HTTPS_SSL_PORT} if the Jamf Pro hostname ends with #{Jamf::Connection::JAMFCLOUD_DOMAIN} and #{Jamf::Connection::ON_PREM_SSL_PORT} otherwise.
414
+ ENDDESC
415
+ },
416
+
417
+ # @!attribute jamf_ssl_version
418
+ # @return [String] The SSL version to use when connecting to the Jamd Pro API
419
+ jamf_ssl_version: {
420
+ default: Jamf::Connection::DFT_SSL_VERSION,
421
+ type: :string,
422
+ desc: <<~ENDDESC
423
+ The SSL version to use for the connection to the Jamf API.
424
+ ENDDESC
425
+ },
426
+
427
+ # @!attribute jamf_verify_cert
428
+ # @return [Boolean] Should we verify the SSL certificate of the Jamf Pro API?
429
+ jamf_verify_cert: {
430
+ default: true,
431
+ type: :boolean,
432
+ desc: <<~ENDDESC
433
+ Should we verify the SSL certificate used by the Jamf Pro server?
434
+ ENDDESC
435
+ },
436
+
437
+ # @!attribute jamf_open_timeout
438
+ # @return [Integer] The timeout, in seconds, for establishing http connections to the Jamf Pro API
439
+ jamf_open_timeout: {
440
+ default: Jamf::Connection::DFT_OPEN_TIMEOUT,
441
+ type: :integer,
442
+ desc: <<~ENDDESC
443
+ The timeout, in seconds, for establishing a connection to the Jamf Pro server.
444
+ The default is #{Jamf::Connection::DFT_OPEN_TIMEOUT}.
445
+ ENDDESC
446
+ },
447
+
448
+ # @!attribute jamf_timeout
449
+ # @return [Integer] The timeout, in seconds, for a response from the Jamf Pro API
450
+ jamf_timeout: {
451
+ default: Jamf::Connection::DFT_TIMEOUT,
452
+ type: :integer,
453
+ desc: <<~ENDDESC
454
+ The timeout, in seconds, for getting a response to a request made to the Jamf Pro server.
455
+ The default is #{Jamf::Connection::DFT_TIMEOUT}.
456
+ ENDDESC
457
+ },
458
+
459
+ # @!attribute jamf_api_user
460
+ # @return [String] The username to use when connecting to the Jamf Pro API
461
+ jamf_api_user: {
462
+ required: true,
463
+ type: :string,
464
+ desc: <<~ENDDESC
465
+ The username of the Jamf account for connecting to the Jamf Pro APIs.
466
+ TODO: Document the permissions needed by this account.
467
+ TODO: Allow using api-clients
468
+ ENDDESC
469
+ },
470
+
471
+ # @!attribute jamf_api_pw
472
+ # @return [String] A command, path, or value for the password for the Jamf Pro API user
473
+ jamf_api_pw: {
474
+ required: true,
475
+ load_method: :data_from_command_file_or_string,
476
+ private: true,
477
+ type: :string,
478
+ desc: <<~ENDDESC
479
+ The password for the username that connects to the Jamf Pro APIs.
480
+
481
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
482
+
483
+ If the value is a path to a readable file, the file's contents are used.
484
+
485
+ Otherwise the value is used as the password.
486
+
487
+ Be careful of security concerns when passwords are stored in files.
488
+ ENDDESC
489
+ },
490
+
491
+ # @!attribute jamf_auto_accept_xolo_eas
492
+ # @return [Boolean] should we auto-accept the Jamf patch title eas?
493
+ jamf_auto_accept_xolo_eas: {
494
+ type: :boolean,
495
+ desc: <<~ENDDESC
496
+ For titles fully maintained by Xolo, should we auto-accept the Patch Title Extension Attributes that come from the uploaded version_script from xadm?
497
+
498
+ Default is false, meaning all Title EAs must be manually accepted in the Jamf Pro Web UI.
499
+ ENDDESC
500
+ },
501
+
502
+ # @!attribute upload_tool
503
+ # @return [String] Either "api", or the path to an executable on the server that can upload
504
+ # .pkg files to your distribution points. If "api", Xolo will use the Jamf API to upload
505
+ # the package to your primary distribution point, either a cloud distribution point, or
506
+ # a fileshare distribution point. API uploads are only available in Jamf Pro 11.6 and later.
507
+ upload_tool: {
508
+ required: true,
509
+ type: :string,
510
+ desc: <<~ENDDESC
511
+ After a .pkg is uploaded to the Xolo server by someone using xadm, it must then be uploaded to the Jamf distribution point(s) to be available for installation.
512
+
513
+ If this value is 'api', and you are using Jamf Pro 11.6 or later, Xolo will use the Jamf API to upload the package to your primary distribution point. API uploads are only available in Jamf Pro 11.6 and later, and will only upload to the primary distribution point. Syncing to other distribution points is not supported by the API.
514
+
515
+ If this value is a path, it is to an executable on the xolo server that will do the upload to the distribution point(s). This tool can be anything you like, as long as it can upload a .pkg to the Jamf distribution point(s) you use.
516
+
517
+ It will be run with two arguments:
518
+ - First, The display name of the Jamf Package object the .pkg is used with
519
+ - Then the path to the .pkg file on the Xolo server, which will be uploaded
520
+ to the Jamf distribution point(s).
521
+
522
+ So if the executable is '/usr/local/bin/jamf-pkg-uploader' then when Xolo recieves a .pkg to be uploaded to Jamf, it will run something like:
523
+
524
+ /usr/local/bin/jamf-pkg-uploader 'CoolApp' '/Library/Application Support/xoloserver/tmpfiles/CoolApp.pkg'
525
+
526
+ Where 'CoolApp' is the name of the Jamf Package object that will use this .pkg, and '/Library/Application Support/xoloserver/tmpfiles/CoolApp.pkg' is the location where it was stored on the Xolo server when xadm uploaded it.
527
+
528
+ The upload tool can itself run other tools as needed, e.g. one to upload
529
+ to all fileshare distribution points, and another to upload to a Cloud dist. point.
530
+ or it can do all the things itself.
531
+
532
+ After that tool runs, the copy of the .pkg on the server ( '/Library/Application Support/xoloserver/tmpfiles/CoolApp.pkg' in the example above) will be deleted.
533
+
534
+ An external tool is used here because every Jamf Pro customer has different needs for this, e.g. various cloud and file-server distribution points. While the packages/upload endpoint of the Jamf Pro API (v11.6+) will upload to the primary distribution point, it won't upload to all the others you might have.
535
+ ENDDESC
536
+ },
537
+
538
+ # @!attribute forced_exclusion
539
+ # @return [String] The name of a single Jamf Pro computer groups that will ALWAYS be excluded
540
+ # and will never see any titles or versions in Xolo.
541
+ forced_exclusion: {
542
+ type: :string,
543
+ desc: <<~ENDDESC
544
+ If you have any jamf computers who should never even know that xolo exists, and should never have any software installed via xolo, put them into a group and put that group's name here.
545
+
546
+ An example would be a group of machines that should have a very minimalist management footprint, only enforcing basic security settings and nothing else.
547
+
548
+ This group, if defined, will be in the exclusions of all policies and patch policies maintained by Xolo.
549
+
550
+ NOTE: These machines are still managed, and software can still be installed via Jamf if desired, but outside of Xolo.
551
+ ENDDESC
552
+ },
553
+
554
+ # Title Editor
555
+ ####################
556
+
557
+ # @!attribute ted_patch_source
558
+ # @return [String] The name of the Patch Source in Jamf Pro that points at the Title Editor.
559
+ ted_patch_source: {
560
+ type: :string,
561
+ required: true,
562
+ desc: <<~ENDDESC
563
+ The name in Jamf Pro of the Title Editor as an External Patch Source.
564
+ ENDDESC
565
+ },
566
+
567
+ # @!attribute ted_hostname
568
+ # @return [String] The hostname of the Jamf Title Editor server we are connecting to
569
+ ted_hostname: {
570
+ type: :string,
571
+ required: true,
572
+ desc: <<~ENDDESC
573
+ The hostname of the Title Editor server used by xolo.
574
+ ENDDESC
575
+ },
576
+
577
+ # @!attribute ted_open_timeout
578
+ # @return [Integer] The timeout, in seconds, for establishing http connections to
579
+ # the Jamf Title Editor API
580
+ ted_open_timeout: {
581
+ default: Windoo::Connection::DFT_OPEN_TIMEOUT,
582
+ type: :integer,
583
+ desc: <<~ENDDESC
584
+ The timeout, in seconds, for establishing a connection to the Title Editor server.
585
+ ENDDESC
586
+ },
587
+
588
+ # @!attribute ted_timeout
589
+ # @return [Integer] The timeout, in seconds, for a response from the Jamf Title Editor API
590
+ ted_timeout: {
591
+ default: Windoo::Connection::DFT_TIMEOUT,
592
+ type: :integer,
593
+ desc: <<~ENDDESC
594
+ The timeout, in seconds, for getting a response to a request made to the Title Editor server.
595
+ ENDDESC
596
+ },
597
+
598
+ # @!attribute ted_api_user
599
+ # @return [String] The username to use when connecting to the Jamf Title Editor API
600
+ ted_api_user: {
601
+ required: true,
602
+ type: :string,
603
+ desc: <<~ENDDESC
604
+ The username of the Title Editor account for connecting to the Title Editor API.
605
+ TODO: Document the permissions needed by this account.
606
+ ENDDESC
607
+ },
608
+
609
+ # @!attribute ted_api_pw
610
+ # @return [String] A command, path, or value for the password for the Jamf Title Editor API user
611
+ ted_api_pw: {
612
+ required: true,
613
+ load_method: :data_from_command_file_or_string,
614
+ private: true,
615
+ type: :string,
616
+ desc: <<~ENDDESC
617
+ The password for the username that connects to the Title Editor API.
618
+
619
+ If you start this value with a vertical bar '|', everything after the bar is a command to be executed by the server at start-time. The command must return the certificate to standard output. This is useful when using a secret-storage system to manage secrets.
620
+
621
+ If the value is a path to a readable file, the file's contents are used.
622
+
623
+ Otherwise the value is used as the password.
624
+
625
+ Be careful of security concerns when passwords are stored in files.
626
+ ENDDESC
627
+ }
628
+
629
+ }.freeze
630
+
631
+ # Public Instance Methods
632
+ #####################################
633
+ #####################################
634
+
635
+ # @return [Pathname] The file that stores configuration values
636
+ #######################
637
+ def conf_file
638
+ @conf_file ||= Xolo::Server::Constants::DATA_DIR + CONF_FILENAME
639
+ end
640
+
641
+ ###############
642
+ def save_to_file(data: nil)
643
+ backup_conf_file
644
+ super
645
+ clean_old_backups
646
+ end
647
+
648
+ ################
649
+ def backup_conf_file
650
+ return unless conf_file.file?
651
+
652
+ backup_file_dir.mkpath unless backup_file_dir.directory?
653
+
654
+ backup_file_name = "#{conf_file.basename}.#{Time.now.strftime BACKUP_FILE_TIMESTAMP_FORMAT}"
655
+ backup_file = backup_file_dir + backup_file_name
656
+ conf_file.pix_cp backup_file
657
+ end
658
+
659
+ ################
660
+ def backup_file_dir
661
+ @backup_file_dir ||= Xolo::Server::BACKUPS_DIR + 'config'
662
+ end
663
+
664
+ # remove all backups older than BACKUP_FILE_EXPIRATION_DAYS, except the most recent
665
+ ################
666
+ def clean_old_backups
667
+ return unless backup_file_dir.directory?
668
+
669
+ newest_file = backup_file_dir.children.max_by(&:mtime)
670
+ oldest_ok_time = Time.now - BACKUP_FILE_EXPIRATION_SECS
671
+
672
+ backup_file_dir.each_child do |file|
673
+ next unless file.file?
674
+ next if file == newest_file
675
+
676
+ file.unlink if file.mtime < oldest_ok_time
677
+ end
678
+ end
679
+
680
+ # @return [Pathname] The directory where the Xolo server stores data
681
+ ##################
682
+ def data_dir
683
+ Xolo::Server::Constants::DATA_DIR
684
+ end
685
+
686
+ # @return [Pathname] The file where Xolo server log entries are written
687
+ ##################
688
+ def log_file
689
+ Xolo::Server::Log::LOG_FILE
690
+ end
691
+
692
+ # This file will be created based on the config value the first
693
+ # time this method is called
694
+ #
695
+ # @return [Pathname] The file where the SSL certificate[-chain] is stored
696
+ # for use by the server.
697
+ ##################
698
+ def ssl_cert_file
699
+ return @ssl_cert_file if @ssl_cert_file
700
+ raise 'ssl_cert must be set as a string in the config file' unless ssl_cert.is_a? String
701
+
702
+ SSL_CERT_FILE.pix_save data_from_command_or_file(ssl_cert)
703
+ @ssl_cert_file = SSL_CERT_FILE
704
+ end
705
+
706
+ # This file will be created based on the config value the first
707
+ # time this method is called
708
+ #
709
+ # @return [Pathname] The file where the SSL certificate private key is stored
710
+ # for use by the server.
711
+ ##################
712
+ def ssl_key_file
713
+ return @ssl_key_file if @ssl_key_file
714
+ raise 'ssl_key must be set as a string in the config file' unless ssl_key.is_a? String
715
+
716
+ SSL_CERT_FILE.pix_save data_from_command_or_file(ssl_key)
717
+ @ssl_key_file = SSL_CERT_FILE
718
+ end
719
+
720
+ # @return [Boolean] are we in developer mode? If so, some actions do or don't happen
721
+ ##################
722
+ def developer_mode?
723
+ DEV_MODE_FILE.file?
724
+ end
725
+
726
+ # @return [Hash] a hash of the configuration values, with private values replaced by '<private>'
727
+ # and server specific values added
728
+ #
729
+ def to_h
730
+ hash = super
731
+ hash[:developer_mode] = developer_mode?
732
+ hash
733
+ end
734
+
735
+ end # class Configuration
736
+
737
+ end # Server
738
+
739
+ end # module