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.
- checksums.yaml +7 -0
- data/LICENSE.txt +177 -0
- data/README.md +7 -0
- data/bin/xoloserver +106 -0
- data/data/com.pixar.xoloserver.plist +29 -0
- data/data/uninstall-pkgs-by-id.zsh +103 -0
- data/lib/xolo/server/app.rb +133 -0
- data/lib/xolo/server/command_line.rb +216 -0
- data/lib/xolo/server/configuration.rb +739 -0
- data/lib/xolo/server/constants.rb +70 -0
- data/lib/xolo/server/helpers/auth.rb +257 -0
- data/lib/xolo/server/helpers/client_data.rb +415 -0
- data/lib/xolo/server/helpers/file_transfers.rb +265 -0
- data/lib/xolo/server/helpers/jamf_pro.rb +156 -0
- data/lib/xolo/server/helpers/log.rb +97 -0
- data/lib/xolo/server/helpers/maintenance.rb +401 -0
- data/lib/xolo/server/helpers/notification.rb +145 -0
- data/lib/xolo/server/helpers/pkg_signing.rb +141 -0
- data/lib/xolo/server/helpers/progress_streaming.rb +252 -0
- data/lib/xolo/server/helpers/title_editor.rb +92 -0
- data/lib/xolo/server/helpers/titles.rb +145 -0
- data/lib/xolo/server/helpers/versions.rb +160 -0
- data/lib/xolo/server/log.rb +286 -0
- data/lib/xolo/server/mixins/changelog.rb +315 -0
- data/lib/xolo/server/mixins/title_jamf_access.rb +1668 -0
- data/lib/xolo/server/mixins/title_ted_access.rb +519 -0
- data/lib/xolo/server/mixins/version_jamf_access.rb +1541 -0
- data/lib/xolo/server/mixins/version_ted_access.rb +373 -0
- data/lib/xolo/server/object_locks.rb +102 -0
- data/lib/xolo/server/routes/auth.rb +89 -0
- data/lib/xolo/server/routes/jamf_pro.rb +89 -0
- data/lib/xolo/server/routes/maint.rb +174 -0
- data/lib/xolo/server/routes/title_editor.rb +71 -0
- data/lib/xolo/server/routes/titles.rb +285 -0
- data/lib/xolo/server/routes/uploads.rb +93 -0
- data/lib/xolo/server/routes/versions.rb +261 -0
- data/lib/xolo/server/routes.rb +168 -0
- data/lib/xolo/server/title.rb +1143 -0
- data/lib/xolo/server/version.rb +902 -0
- data/lib/xolo/server.rb +205 -0
- data/lib/xolo-server.rb +8 -0
- 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
|