Class: AethernalAgent::App
Direct Known Subclasses
Bazarr, Cardigann, Couchpotato, Deluge, Filebrowser, Gitea, Jackett, Jdownloader, Medusa, Nextcloud, Nzbget, Olaris, Ombi, Plex, PlexDocker, Portainer, Radarr, ResilioDocker, Rutorrent, Sabnzbd, Sickchill, Sonarr, Syncthing, Tautulli, Vnc
Constant Summary
Constants included
from Systemd
Systemd::SYSTEMD_ACTIONS
Instance Attribute Summary collapse
Attributes included from Errors
#global_errors
Instance Method Summary
collapse
Methods included from Errors
#add_errors, #get_errors
Methods included from Utils
#apt_running?, #check_manifest, #container_domain, #current_xdg_runtime_dir, #docker_container_name, #get_current_uid, #get_global_config, #global_config_path, #port_is_free, #prepare_test_config, #print_system, #random_port, #random_string, #run_as, #run_command, #run_systemd_plain, #set_global_config, #systemd_text_for_service, #ubuntu_release, #wait_on_apt, #wait_on_file, #wait_on_port
Methods included from Systemd
#create_service_file, #get_systemd_status, #method_missing, #parse_systemd_status_text, #reload_systemd_config, #run_user_systemctl, #service_file_path
Methods included from Filesystem
#aethernal_agent_file, #aethernal_agent_folder, #copy, #directory, #file, #file_settings, #files_folder, #files_path, #home_folder_path, #meta_folder, #meta_path, #set_ownership, #set_permissions, #template_path, #templates_folder, #write_template
Methods included from Apt
#add_apt_ppa, #apt_package, #apt_update, included
Constructor Details
#initialize(options = {}) ⇒ App
Returns a new instance of App.
22
23
24
25
26
27
28
29
30
31
32
|
# File 'lib/aethernal_agent/app.rb', line 22
def initialize(options = {})
options = HashWithIndifferentAccess.new(options)
options[:ubuntu_version] = ubuntu_release
self.global_options = options
self.plugin_path = options[:file_path] || caller_locations(0)[1].path
self.user = options[:user]
AethernalAgent.logger.debug("initializing plugin from #{plugin_path}")
self.manifest = OpenStruct.new(YAML.load_file(File.join(File.dirname(self.plugin_path), "manifest.yml")))
end
|
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
in the class AethernalAgent::Systemd
Instance Attribute Details
#container_settings ⇒ Object
Returns the value of attribute container_settings.
20
21
22
|
# File 'lib/aethernal_agent/app.rb', line 20
def container_settings
@container_settings
end
|
#global_options ⇒ Object
Returns the value of attribute global_options.
20
21
22
|
# File 'lib/aethernal_agent/app.rb', line 20
def global_options
@global_options
end
|
#manifest ⇒ Object
Returns the value of attribute manifest.
20
21
22
|
# File 'lib/aethernal_agent/app.rb', line 20
def manifest
@manifest
end
|
#plugin_path ⇒ Object
Returns the value of attribute plugin_path.
20
21
22
|
# File 'lib/aethernal_agent/app.rb', line 20
def plugin_path
@plugin_path
end
|
#user ⇒ Object
Returns the value of attribute user.
20
21
22
|
# File 'lib/aethernal_agent/app.rb', line 20
def user
@user
end
|
Instance Method Details
#app_path(path = nil) ⇒ Object
315
316
317
318
319
320
321
322
|
# File 'lib/aethernal_agent/app.rb', line 315
def app_path(path=nil)
full_path = home_folder_path(File.join("apps", self.manifest.package['folder_name']))
if path
return File.join(full_path, path)
else
return full_path
end
end
|
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
# File 'lib/aethernal_agent/app.rb', line 134
def configure_app_user(options={})
errors = {}
opts, errors = ensure_action_options(__method__,options)
return {errors: errors} unless errors.empty?
if self.manifest.apache_configuration
apache = AethernalAgent::Apache.new(opts)
errors = apache.ensure_base_config(opts)
apache.write_app_config(template_path(self.manifest.apache_configuration), self.manifest.plain_name, opts[:port])
end
if self.is_docker_install?
begin
docker_group = Etc.getgrnam('docker')
rescue ArgumentError
self.add_errors("Docker group not found, please install Docker (https://docs.docker.com/engine/install/ubuntu/). If Docker is already installed make sure the docker group exists.") and return create_return_args(opts)
end
unless docker_group.mem.include?(self.user)
self.add_errors("#{self.user} is not a member of the Docker group. Because of the implied security implications aethernal-agent does not do this by default. Please use `usermod -aG docker #{self.user}` if you want to give this user Docker access and restart your Capsule so SystemD has access as well.") and return create_return_args(opts)
end
self.container_settings = AethernalAgent::Docker::ContainerSettings.build_from_options(self.manifest["actions"]["configure_app_user"], opts)
end
AethernalAgent.logger.debug("Running custom plugin code.")
yield opts if block_given?
AethernalAgent.logger.debug("Done running custom plugin code.")
if self.is_docker_install?
image = self.manifest.package['docker']['image']
begin
container = self.container_settings.create_container(self.docker_container_name, image)
AethernalAgent.logger.debug("Starting: #{container.start}")
rescue StandardError => e
self.add_errors(e)
return create_return_args(opts)
end
end
AethernalAgent.logger.debug("Starting service generation.")
if self.manifest.services.present?
self.manifest.services.each do |s|
options = {service_name: "#{s}.service", user: self.user, vars: opts}
service_template = template_path("#{s}.service.erb")
if File.exists?(service_template)
AethernalAgent.logger.debug("Found service template: '#{service_template}'")
options[:template] = "#{s}.service.erb"
elsif File.exists?(files_path("#{s}.service"))
AethernalAgent.logger.debug("Found service file")
options[:file] = "#{s}.service"
end
create_service_file(options)
end
AethernalAgent.logger.debug("Done with service generation.")
else
AethernalAgent.logger.debug("Skipping service generation, no service file defined.")
end
AethernalAgent.logger.debug("Done with service generation.")
directory(home_folder_path("apps"), owner: self.user)
AethernalAgent.logger.debug("Done running configure_app_user")
return create_return_args(opts)
end
|
#create_return_args(options) ⇒ Object
236
237
238
239
240
241
242
243
244
|
# File 'lib/aethernal_agent/app.rb', line 236
def create_return_args(options)
res = {options: options, errors: get_errors}
if options[:errors].present?
res[:errors] << options.slice(:errors)
end
return res
end
|
#ensure_action_options(method_name, options = {}) ⇒ Object
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
|
# File 'lib/aethernal_agent/app.rb', line 260
def ensure_action_options(method_name, options={})
options = HashWithIndifferentAccess.new(options.reverse_merge(global_options))
errors = {}
AethernalAgent.logger.debug "Running '#{method_name}' with options '#{options}'"
if self.manifest.actions.keys.include?(method_name.to_s)
opts = self.manifest.actions[method_name.to_s]
opts.each do |opt, reqs|
AethernalAgent.logger.debug "Checking option #{opt} - requirements: #{reqs}"
next if reqs.blank?
if reqs.include?("required") && reqs["required"] == true
AethernalAgent.logger.debug("#{opt} is required")
if options.has_key?(opt) && !options[opt].blank?
AethernalAgent.logger.debug("required option is supplied")
else
AethernalAgent.logger.debug("required option is not supplied")
errors[opt] ||= []
errors[opt] << "is required but has not been supplied"
next
end
end
if reqs.include?("auto_generate") && reqs["auto_generate"] == true && (!options.has_key?(opt) || options[opt].blank?)
options[opt] = self.random_string
end
if reqs.include?("auto_generate_port") && reqs["auto_generate_port"] == true && (!options.has_key?(opt) || options[opt].blank?)
min = reqs["min"] || 2000
max = reqs["max"] || 8000
options[opt] = random_port(min..max)
end
if reqs.include?("linux_user") && reqs["linux_user"] == true
AethernalAgent.logger.debug "Checking to see if '#{options[:user]}' is a local linux user."
begin
Etc.getpwnam(options[:user])
rescue ArgumentError => e
errors[opt] ||= []
errors[opt] << e.to_s
end
end
end
else
puts "Method '#{method_name}' not defined in manifest."
end
return options, errors
end
|
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
|
# File 'lib/aethernal_agent/app.rb', line 343
def (source_file, options = {})
options.reverse_merge!(extract_to_path: self.app_path, source_folder: self.app_path, auto_detect: true, extract_as: File.extname(source_file))
= File.join(options[:source_folder], source_file)
= options[:extract_to_path]
case options[:extract_as].gsub('.', '')
when "gz", "tar"
run_command("tar -C #{} -xvf #{}")
when "tgz"
run_command("tar -C #{} -zxvf #{}")
when "bz2", "tar"
run_command("tar -C #{} -xvjf #{}")
when "zip"
run_command("unzip -od #{} #{}")
else
raise "#{options[:extract_as]} not implemented yet"
end
file(, action: :delete) if options[:delete_after]
end
|
#icon_file(path = nil) ⇒ Object
332
333
334
335
336
337
338
339
340
341
|
# File 'lib/aethernal_agent/app.rb', line 332
def icon_file(path=nil)
if self.manifest.icon.present?
icon_path = meta_path(self.manifest.icon['name'])
if File.exist?(icon_path)
return Base64.encode64(File.read(icon_path))
end
end
return nil
end
|
#icon_sha ⇒ Object
324
325
326
327
328
329
330
|
# File 'lib/aethernal_agent/app.rb', line 324
def icon_sha
if self.manifest.icon.present?
return self.manifest.icon["sha256sum"]
else
return ""
end
end
|
#install_packages(options = {}) ⇒ Object
FIXME: Add error tracking
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
# File 'lib/aethernal_agent/app.rb', line 35
def install_packages(options = {})
directory(home_folder_path("/.config"), owner: self.user)
AethernalAgent.logger.info("Installing packages for #{self.manifest.name} - v#{self.manifest.version}")
if self.manifest.package.has_key?('ppa')
self.manifest.package['ppa'].each do |ppa|
AethernalAgent.logger.debug("Setting up PPA: #{ppa}")
run_command("add-apt-repository ppa:#{ppa} -y")
end
end
self.manifest.package.each_key do |package_type|
case package_type
when 'apt'
AethernalAgent.logger.debug("apt options: #{self.manifest.package['apt']} for ubuntu version #{ubuntu_release}")
options = self.manifest.package['apt'][ubuntu_release]
options = self.manifest.package['apt']["all"] if self.manifest.package['apt']["all"].present?
AethernalAgent.logger.debug("Matched options: #{options}")
packages = options['packages']
if options.has_key?('add_sources')
AethernalAgent.logger.debug("Adding custom sources")
options['add_sources'].each do |name, source|
AethernalAgent.logger.debug("Setting up custom apt source '#{name}'")
file_path = "/etc/apt/sources.list.d/#{name}.list"
unless File.exist?(file_path)
run_command("echo '#{source['source_url']}' > #{file_path}")
else
AethernalAgent.logger.debug("Custom apt source '#{name}' already present, not rewriting.")
end
run_command("curl #{source['key_url']} | sudo apt-key add -") if source['key_url'].present?
run_command("sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys #{source['key_id']}") if source['key_id'].present?
end
apt_update
end
if packages
AethernalAgent.logger.debug("Installing packages #{packages}")
apt_package(packages: packages)
end
when 'direct_download'
opts = self.manifest.package[package_type]
opts = self.manifest.package[package_type][ubuntu_release] if self.manifest.package[package_type][ubuntu_release].present?
target_file = File.join(app_path, opts["target_name"])
directory(app_path, owner: self.user)
AethernalAgent.logger.debug("Downloading file from '#{opts["url"]}' to '#{target_file}'")
File.open(target_file, "wb") do |saved_file|
open(opts["url"], "rb") do |read_file|
saved_file.write(read_file.read)
end
end
if opts["auto_unzip"]
unzip(opts["target_name"], delete_after: true)
elsif opts["auto_extract"]
(opts["target_name"], delete_after: true)
end
when 'git'
run_command("mkdir #{home_folder_path(File.join('apps'))}")
run_command("mkdir #{app_path}")
run_command("git clone #{self.manifest['package']['git']['url']} #{File.join(app_path)}")
when 'docker'
options = self.manifest.package['docker']
AethernalAgent.logger.info("Pulling Docker image #{options['image']}")
run_command("docker pull #{options['image']}")
end
end
AethernalAgent.logger.info("Done Installing packages for #{self.manifest.name} - v#{self.manifest.version}")
return create_return_args(options)
end
|
#is_docker_install? ⇒ Boolean
373
374
375
|
# File 'lib/aethernal_agent/app.rb', line 373
def is_docker_install?
self.manifest.installation_type == 'docker_image'
end
|
#is_local_install? ⇒ Boolean
369
370
371
|
# File 'lib/aethernal_agent/app.rb', line 369
def is_local_install?
self.manifest.installation_type == 'local_install'
end
|
#remove_app_user(options = {}) {|opts| ... } ⇒ Object
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
|
# File 'lib/aethernal_agent/app.rb', line 201
def remove_app_user(options={})
opts, errors = ensure_action_options(__method__,options)
return {errors: errors} unless errors.empty?
run_user_systemctl(:stop)
if self.manifest.services.present?
self.manifest.services.each do |service|
file(service_file_path("#{service}.service"), action: :delete)
end
end
if self.manifest.apache_configuration
apache = AethernalAgent::Apache.new(opts)
apache.remove_app_config(self.manifest.plain_name)
end
if self.is_docker_install?
begin
container = ::Docker::Container.get(self.docker_container_name)
container.delete(:force => true)
rescue ::Docker::Error::NotFoundError
AethernalAgent.logger.warn("Wanted to delete Docker container '#{self.docker_container_name}' but it did not exist.")
end
end
yield opts if block_given?
return create_return_args(opts)
end
|
#sha_1_hash(password, salt) ⇒ Object
377
378
379
380
381
382
383
|
# File 'lib/aethernal_agent/app.rb', line 377
def sha_1_hash(password, salt)
sha = Digest::SHA1.new
sha.update(salt)
sha.update(password)
return sha.hexdigest
end
|
#status(options = {}) ⇒ Object
246
247
248
249
250
251
252
253
254
255
256
257
258
|
# File 'lib/aethernal_agent/app.rb', line 246
def status(options = {})
if options[:service_name]
status = get_systemd_status(options[:service_name])
return status.to_h if status
else
stati = self.manifest.services.collect do |service|
status = get_systemd_status(service)
status.to_h if status
end
return stati
end
end
|
#uninstall_packages(options = {}) ⇒ Object
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
# File 'lib/aethernal_agent/app.rb', line 110
def uninstall_packages(options = {})
AethernalAgent.logger.info("Uninstalling packages for #{self.manifest.name} - v#{self.manifest.version}")
self.stop
self.disable
self.manifest.package.each_key do |package_type|
case package_type
when "apt"
options = self.manifest.package['apt'][ubuntu_release]
AethernalAgent.logger.debug("Using apt for ubuntu #{ubuntu_release} - #{options}")
packages = options['packages']
if packages
AethernalAgent.logger.debug("Removing packages #{packages}")
apt_package(packages: packages, action: :remove)
end
when "direct_download"
directory(app_path, action: :delete)
end
end
return create_return_args(options)
end
|
#unzip(source_file) ⇒ Object
365
366
367
|
# File 'lib/aethernal_agent/app.rb', line 365
def unzip(source_file)
(source_file, extract_as: "zip")
end
|