d-installer 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +75 -0
- data/bin/d-installer +74 -0
- data/etc/d-installer.yaml +284 -0
- data/lib/dinstaller/can_ask_question.rb +48 -0
- data/lib/dinstaller/cmdline_args.rb +80 -0
- data/lib/dinstaller/cockpit_manager.rb +176 -0
- data/lib/dinstaller/config.rb +128 -0
- data/lib/dinstaller/config_reader.rb +164 -0
- data/lib/dinstaller/dbus/base_object.rb +58 -0
- data/lib/dinstaller/dbus/clients/base.rb +71 -0
- data/lib/dinstaller/dbus/clients/language.rb +86 -0
- data/lib/dinstaller/dbus/clients/manager.rb +76 -0
- data/lib/dinstaller/dbus/clients/software.rb +185 -0
- data/lib/dinstaller/dbus/clients/users.rb +112 -0
- data/lib/dinstaller/dbus/clients/with_progress.rb +56 -0
- data/lib/dinstaller/dbus/clients/with_service_status.rb +75 -0
- data/lib/dinstaller/dbus/clients.rb +34 -0
- data/lib/dinstaller/dbus/interfaces/progress.rb +113 -0
- data/lib/dinstaller/dbus/interfaces/service_status.rb +89 -0
- data/lib/dinstaller/dbus/language.rb +93 -0
- data/lib/dinstaller/dbus/language_service.rb +92 -0
- data/lib/dinstaller/dbus/manager.rb +147 -0
- data/lib/dinstaller/dbus/manager_service.rb +132 -0
- data/lib/dinstaller/dbus/question.rb +176 -0
- data/lib/dinstaller/dbus/questions.rb +124 -0
- data/lib/dinstaller/dbus/service_runner.rb +97 -0
- data/lib/dinstaller/dbus/service_status.rb +87 -0
- data/lib/dinstaller/dbus/software/manager.rb +131 -0
- data/lib/dinstaller/dbus/software/proposal.rb +82 -0
- data/lib/dinstaller/dbus/software.rb +31 -0
- data/lib/dinstaller/dbus/software_service.rb +86 -0
- data/lib/dinstaller/dbus/storage/proposal.rb +170 -0
- data/lib/dinstaller/dbus/storage.rb +30 -0
- data/lib/dinstaller/dbus/users.rb +132 -0
- data/lib/dinstaller/dbus/users_service.rb +92 -0
- data/lib/dinstaller/dbus/with_service_status.rb +48 -0
- data/lib/dinstaller/dbus/y2dir/manager/modules/Package.rb +51 -0
- data/lib/dinstaller/dbus/y2dir/manager/modules/PackagesProposal.rb +62 -0
- data/lib/dinstaller/dbus/y2dir/modules/Autologin.rb +214 -0
- data/lib/dinstaller/dbus/y2dir/software/modules/SpaceCalculation.rb +44 -0
- data/lib/dinstaller/dbus.rb +11 -0
- data/lib/dinstaller/errors.rb +28 -0
- data/lib/dinstaller/installation_phase.rb +106 -0
- data/lib/dinstaller/language.rb +73 -0
- data/lib/dinstaller/luks_activation_question.rb +92 -0
- data/lib/dinstaller/manager.rb +261 -0
- data/lib/dinstaller/network.rb +53 -0
- data/lib/dinstaller/package_callbacks.rb +69 -0
- data/lib/dinstaller/progress.rb +149 -0
- data/lib/dinstaller/question.rb +103 -0
- data/lib/dinstaller/questions_manager.rb +145 -0
- data/lib/dinstaller/security.rb +96 -0
- data/lib/dinstaller/service_status_recorder.rb +58 -0
- data/lib/dinstaller/software.rb +232 -0
- data/lib/dinstaller/storage/actions.rb +90 -0
- data/lib/dinstaller/storage/callbacks/activate.rb +93 -0
- data/lib/dinstaller/storage/callbacks/activate_luks.rb +93 -0
- data/lib/dinstaller/storage/callbacks/activate_multipath.rb +77 -0
- data/lib/dinstaller/storage/callbacks.rb +30 -0
- data/lib/dinstaller/storage/manager.rb +104 -0
- data/lib/dinstaller/storage/proposal.rb +197 -0
- data/lib/dinstaller/storage.rb +30 -0
- data/lib/dinstaller/users.rb +156 -0
- data/lib/dinstaller/with_progress.rb +63 -0
- data/lib/dinstaller.rb +5 -0
- data/share/dbus.conf +38 -0
- data/share/dbus.service +5 -0
- data/share/systemd.service +14 -0
- metadata +295 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "yast"
|
23
|
+
require "yast2/systemd/service"
|
24
|
+
require "cfa/base_model"
|
25
|
+
require "transfer/file_from_url"
|
26
|
+
require "fileutils"
|
27
|
+
|
28
|
+
Yast.import "URL"
|
29
|
+
|
30
|
+
module DInstaller
|
31
|
+
# Cockpit configuration file representation
|
32
|
+
#
|
33
|
+
# @example Set the AllowUnencrypted option
|
34
|
+
# file = CockpitConfig.new
|
35
|
+
# file.load
|
36
|
+
# file.web_service["AllowUnencrypted"] = "true"
|
37
|
+
# file.save
|
38
|
+
class CockpitConfig < CFA::BaseModel
|
39
|
+
# Constructor
|
40
|
+
#
|
41
|
+
# @param path [String] File path
|
42
|
+
# @param file_handler [.read, .write] Object to read/write the file.
|
43
|
+
def initialize(path: DEFAULT_PATH, file_handler: nil)
|
44
|
+
super(CFA::AugeasParser.new("puppet.lns"), path, file_handler: file_handler)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the augeas tree for the "WebService" section
|
48
|
+
#
|
49
|
+
# If the given section does not exist, it returns an empty one
|
50
|
+
#
|
51
|
+
# @param name [String] section name
|
52
|
+
# @return [AugeasTree]
|
53
|
+
def web_service
|
54
|
+
data["WebService"] ||= CFA::AugeasTree.new
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Handles the Cockpit service
|
59
|
+
#
|
60
|
+
# This API offers an API to adjust Cockpit configuration and restart/reload the process. At this
|
61
|
+
# point, just a few options are allowed (@see #setup).
|
62
|
+
class CockpitManager
|
63
|
+
include Yast::Logger
|
64
|
+
include Yast::Transfer::FileFromUrl
|
65
|
+
include Yast::I18n
|
66
|
+
|
67
|
+
# Directory to store Cockpit certificates
|
68
|
+
WS_CERTS_DIR = "/etc/cockpit/ws-certs.d"
|
69
|
+
COCKPIT_SERVICE = "cockpit"
|
70
|
+
COCKPIT_CONF_PATH = "/etc/cockpit/cockpit.conf"
|
71
|
+
|
72
|
+
def initialize(logger, prefix: "/")
|
73
|
+
@prefix = prefix
|
74
|
+
@logger = logger
|
75
|
+
end
|
76
|
+
|
77
|
+
# Adjust Cockpit configuration and restart the process if needed
|
78
|
+
#
|
79
|
+
# If all arguments are nil, the configuration is not modified and the process is not restarted.
|
80
|
+
#
|
81
|
+
# @param config [Hash]
|
82
|
+
# @option ssl [Boolean,nil] SSL is enabled
|
83
|
+
# @option ssl_cert [String,nil] SSL/TLS certificate URL
|
84
|
+
# @option ssl_key [String,nil] SSL/TLS key URL
|
85
|
+
def setup(options)
|
86
|
+
return if options.values.all?(&:nil?)
|
87
|
+
|
88
|
+
enable_ssl(options["ssl"]) unless options["ssl"].nil?
|
89
|
+
if options["ssl_cert"]
|
90
|
+
copy_ssl_cert(options["ssl_cert"])
|
91
|
+
copy_ssl_key(options["ssl_key"]) unless options["ssl_key"].nil?
|
92
|
+
clear_self_signed_cert
|
93
|
+
end
|
94
|
+
|
95
|
+
restart_cockpit
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
attr_reader :prefix
|
101
|
+
|
102
|
+
# @return [Logger]
|
103
|
+
attr_reader :logger
|
104
|
+
|
105
|
+
# Enable/Disable SSL
|
106
|
+
#
|
107
|
+
# @param enabled [Boolean] Whether to enable or disable SSL
|
108
|
+
def enable_ssl(enabled)
|
109
|
+
path = File.join(prefix, COCKPIT_CONF_PATH)
|
110
|
+
config = CockpitConfig.new(path: path)
|
111
|
+
config.load if File.readable?(path)
|
112
|
+
config.web_service["AllowUnencrypted"] = (!enabled).to_s
|
113
|
+
config.save
|
114
|
+
end
|
115
|
+
|
116
|
+
# Copy the SSL certificate to Cockpit's certificates directory
|
117
|
+
#
|
118
|
+
# The certificate is renamed as `0-d-installer.cert`.
|
119
|
+
#
|
120
|
+
# @param location [String] Certificate location
|
121
|
+
def copy_ssl_cert(location)
|
122
|
+
logger.info "Retrieving SSL certificate from #{location}"
|
123
|
+
copy_file(location, File.join(prefix, WS_CERTS_DIR, "0-d-installer.cert"))
|
124
|
+
end
|
125
|
+
|
126
|
+
# Copy the SSL certificate key to Cockpit's certificates directory
|
127
|
+
#
|
128
|
+
# The certificate is renamed as `0-d-installer.key`.
|
129
|
+
#
|
130
|
+
# @param location [String] Certificate key location
|
131
|
+
def copy_ssl_key(location)
|
132
|
+
logger.info "Retrieving SSL key from #{location}"
|
133
|
+
copy_file(location, File.join(prefix, WS_CERTS_DIR, "0-d-installer.key"))
|
134
|
+
end
|
135
|
+
|
136
|
+
# Remove Cockpit's self signed certificates if they exist
|
137
|
+
def clear_self_signed_cert
|
138
|
+
self_signed = Dir[File.join(prefix, WS_CERTS_DIR, "0-self-signed.*")]
|
139
|
+
::FileUtils.rm(self_signed)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Copy a file from a potentially remote location
|
143
|
+
#
|
144
|
+
# @param location [String] File location. It might be an URL-like string (e.g.,
|
145
|
+
# "http://example.net/example.cert").
|
146
|
+
# @param target [String] Path to copy the file to.
|
147
|
+
# @return [Boolean] Whether the file was sucessfully copied or not
|
148
|
+
def copy_file(location, target)
|
149
|
+
url = Yast::URL.Parse(location)
|
150
|
+
|
151
|
+
res = get_file_from_url(
|
152
|
+
scheme: url["scheme"],
|
153
|
+
host: url["host"],
|
154
|
+
urlpath: url["path"],
|
155
|
+
localfile: target,
|
156
|
+
urltok: url,
|
157
|
+
destdir: "/"
|
158
|
+
)
|
159
|
+
# TODO: exception?
|
160
|
+
logger.error "script #{location} could not be retrieved" unless res
|
161
|
+
res
|
162
|
+
end
|
163
|
+
|
164
|
+
# Restart the Cockpit service
|
165
|
+
def restart_cockpit
|
166
|
+
logger.info "Restarting Cockpit"
|
167
|
+
service = Yast2::Systemd::Service.find(COCKPIT_SERVICE)
|
168
|
+
if service.nil?
|
169
|
+
logger.error "Could not found #{COCKPIT_SERVICE} service"
|
170
|
+
return
|
171
|
+
end
|
172
|
+
|
173
|
+
service.restart
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "yaml"
|
23
|
+
require "dinstaller/config_reader"
|
24
|
+
|
25
|
+
module DInstaller
|
26
|
+
# Class responsible for getting current configuration.
|
27
|
+
# It is smarter then just plain yaml reader as it also evaluates
|
28
|
+
# conditions in it, so it is result of all conditions in file.
|
29
|
+
# This also means that config needs to be re-evaluated if conditions
|
30
|
+
# data change, like if user pick different distro to install.
|
31
|
+
class Config
|
32
|
+
# @return [Hash] configuration data
|
33
|
+
attr_accessor :pure_data
|
34
|
+
|
35
|
+
class << self
|
36
|
+
attr_accessor :current, :base
|
37
|
+
|
38
|
+
# Loads base and current config reading configuration from the system
|
39
|
+
def load(logger = Logger.new($stdout))
|
40
|
+
@base = ConfigReader.new(logger: logger).config
|
41
|
+
@current = @base&.copy
|
42
|
+
end
|
43
|
+
|
44
|
+
# It resets the configuration internal state
|
45
|
+
def reset
|
46
|
+
@base = nil
|
47
|
+
@current = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Load the configuration from a given file
|
51
|
+
#
|
52
|
+
# @param path [String|Pathname] File path
|
53
|
+
def from_file(path)
|
54
|
+
new(YAML.safe_load(File.read(path.to_s)))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Constructor
|
59
|
+
#
|
60
|
+
# @param config_data [Hash] configuration data
|
61
|
+
def initialize(config_data = nil)
|
62
|
+
@pure_data = config_data
|
63
|
+
end
|
64
|
+
|
65
|
+
# parse loaded yaml file, so it properly applies conditions
|
66
|
+
# with default options it load file without conditions
|
67
|
+
def parse_file(_arch = nil, _distro = nil)
|
68
|
+
# TODO: move to internal only. public one should be something
|
69
|
+
# like evaluate or just setter for distro and arch
|
70
|
+
# logger.info "parse file with #{arch} and #{distro}"
|
71
|
+
# TODO: do real evaluation of conditions
|
72
|
+
data
|
73
|
+
end
|
74
|
+
|
75
|
+
def data
|
76
|
+
return @data if @data
|
77
|
+
|
78
|
+
@data = @pure_data.dup || {}
|
79
|
+
pick_product(@data["products"].keys.first) if @data["products"]
|
80
|
+
@data
|
81
|
+
end
|
82
|
+
|
83
|
+
def pick_product(product)
|
84
|
+
data.merge!(data[product])
|
85
|
+
end
|
86
|
+
|
87
|
+
# Whether there are more than one product
|
88
|
+
#
|
89
|
+
# @return [Boolean] false if there is only one product; true otherwise
|
90
|
+
def multi_product?
|
91
|
+
data["products"].size > 1
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a copy of this Object
|
95
|
+
#
|
96
|
+
# @return [Config]
|
97
|
+
def copy
|
98
|
+
Marshal.load(Marshal.dump(self))
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns a new {Config} with the merge of the given ones
|
102
|
+
#
|
103
|
+
# @params config [Config, Hash]
|
104
|
+
# @return [Config] new Configuration with the merge of the given ones
|
105
|
+
def merge(config)
|
106
|
+
Config.new(simple_merge(data, config.data))
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Simple deep merge
|
112
|
+
#
|
113
|
+
# @param a_hash [Hash] Default values
|
114
|
+
# @param another_hash [Hash] Pillar data
|
115
|
+
# @return [Hash]
|
116
|
+
def simple_merge(a_hash, another_hash)
|
117
|
+
a_hash.reduce({}) do |all, (k, v)|
|
118
|
+
next all.merge(k => v) if another_hash[k].nil?
|
119
|
+
|
120
|
+
if v.is_a?(Hash)
|
121
|
+
all.merge(k => simple_merge(a_hash[k], another_hash[k]))
|
122
|
+
else
|
123
|
+
all.merge(k => another_hash[k])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "yast"
|
23
|
+
require "yaml"
|
24
|
+
require "logger"
|
25
|
+
require "dinstaller/config"
|
26
|
+
require "dinstaller/cmdline_args"
|
27
|
+
require "transfer/file_from_url"
|
28
|
+
|
29
|
+
Yast.import "URL"
|
30
|
+
Yast.import "Directory"
|
31
|
+
|
32
|
+
module DInstaller
|
33
|
+
# This class is responsible for reading DInstaller configuration from different locations
|
34
|
+
# including kernel cmdline options
|
35
|
+
class ConfigReader
|
36
|
+
include Yast::Transfer::FileFromUrl
|
37
|
+
include Yast::I18n
|
38
|
+
|
39
|
+
# Default DInstaller configuration which should define all the possible values
|
40
|
+
SYSTEM_PATH = "/etc/d-installer.yaml"
|
41
|
+
GIT_PATH = File.expand_path("#{__dir__}/../../etc/d-installer.yaml")
|
42
|
+
REMOTE_BOOT_CONFIG = "d-installer_boot.yaml"
|
43
|
+
|
44
|
+
PATHS = [
|
45
|
+
"/usr/lib/d-installer.d",
|
46
|
+
"/etc/d-installer.d",
|
47
|
+
"/run/d-installer.d"
|
48
|
+
].freeze
|
49
|
+
|
50
|
+
attr_reader :logger
|
51
|
+
attr_reader :workdir
|
52
|
+
|
53
|
+
# Constructor
|
54
|
+
#
|
55
|
+
# @param logger [Logger]
|
56
|
+
# @param workdir [String] Root directory to read the configuration from
|
57
|
+
def initialize(logger: nil, workdir: "/")
|
58
|
+
@logger = logger || ::Logger.new($stdout)
|
59
|
+
@workdir = workdir
|
60
|
+
end
|
61
|
+
|
62
|
+
# loads correct yaml file
|
63
|
+
def config_from_file(path = nil)
|
64
|
+
raise "Missing config file at #{path}" unless File.exist?(path)
|
65
|
+
|
66
|
+
Config.from_file(path)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return an {Array} with the different {Config} objects read from the different locations
|
70
|
+
#
|
71
|
+
# TODO: handle precedence correctly
|
72
|
+
#
|
73
|
+
# @returm [Array<Config>] an array with all the configurations read from the system
|
74
|
+
def configs
|
75
|
+
return @configs if @configs
|
76
|
+
|
77
|
+
@configs = config_paths.map { |path| config_from_file(path) }
|
78
|
+
@configs << remote_config if remote_config
|
79
|
+
@configs << cmdline_config if cmdline_config
|
80
|
+
@configs
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return a {Config} oject
|
84
|
+
# @return [Config] resultant Config after merging all the configurations
|
85
|
+
def config
|
86
|
+
config = configs.first || Config.new
|
87
|
+
(configs[1..-1] || []).each { |c| config = config.merge(c) }
|
88
|
+
config
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Copy a file from a potentially remote location
|
94
|
+
#
|
95
|
+
# @param location [String] File location. It might be an URL-like string (e.g.,
|
96
|
+
# "http://example.net/example.yml").
|
97
|
+
# @param target [String] Path to copy the file to.
|
98
|
+
# @return [Boolean] Whether the file was sucessfully copied or not
|
99
|
+
def copy_file(location, target)
|
100
|
+
url = Yast::URL.Parse(location)
|
101
|
+
|
102
|
+
res = get_file_from_url(
|
103
|
+
scheme: url["scheme"],
|
104
|
+
host: url["host"],
|
105
|
+
urlpath: url["path"],
|
106
|
+
localfile: target,
|
107
|
+
urltok: url,
|
108
|
+
destdir: "/"
|
109
|
+
)
|
110
|
+
|
111
|
+
# TODO: exception?
|
112
|
+
logger.error "script #{location} could not be retrieved" unless res
|
113
|
+
res
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [CmdlineArgs]
|
117
|
+
def cmdline_args
|
118
|
+
@cmdline_args ||= CmdlineArgs.read_from(File.join(workdir, "/proc/cmdline"))
|
119
|
+
end
|
120
|
+
|
121
|
+
# return [Config]
|
122
|
+
def cmdline_config
|
123
|
+
Config.new(cmdline_args.data)
|
124
|
+
end
|
125
|
+
|
126
|
+
# return [Config]
|
127
|
+
def remote_config
|
128
|
+
return unless cmdline_args.config_url
|
129
|
+
|
130
|
+
file_path = File.join(Yast::Directory.tmpdir, REMOTE_BOOT_CONFIG)
|
131
|
+
logger.info "Copying boot config to #{file_path}"
|
132
|
+
|
133
|
+
copy_file(cmdline_args.config_url, file_path)
|
134
|
+
config_from_file(file_path)
|
135
|
+
end
|
136
|
+
|
137
|
+
def default_path
|
138
|
+
File.exist?(GIT_PATH) ? GIT_PATH : SYSTEM_PATH
|
139
|
+
end
|
140
|
+
|
141
|
+
def config_paths
|
142
|
+
paths = PATHS.each_with_object([]) do |path, all|
|
143
|
+
all.concat(file_paths_in(File.join(workdir, path)))
|
144
|
+
end
|
145
|
+
|
146
|
+
paths.uniq! { |f| File.basename(f) }
|
147
|
+
# Sort files lexicographic
|
148
|
+
paths.sort_by! { |f| File.basename(f) }
|
149
|
+
paths.prepend(default_path)
|
150
|
+
|
151
|
+
paths
|
152
|
+
end
|
153
|
+
|
154
|
+
def file_paths_in(path)
|
155
|
+
if File.file?(path)
|
156
|
+
[path]
|
157
|
+
elsif File.directory?(path)
|
158
|
+
Dir.glob("#{path}/*.{yml,yaml}")
|
159
|
+
else
|
160
|
+
[]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2021] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "dbus"
|
23
|
+
|
24
|
+
module DInstaller
|
25
|
+
module DBus
|
26
|
+
# Base class for DBus objects
|
27
|
+
class BaseObject < ::DBus::Object
|
28
|
+
# Constructor
|
29
|
+
#
|
30
|
+
# @param path [::DBus::ObjectPath]
|
31
|
+
# @param logger [Logger, nil]
|
32
|
+
def initialize(path, logger: nil)
|
33
|
+
@logger = logger || Logger.new($stdout)
|
34
|
+
super(path)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generates information about interfaces and properties of the object
|
38
|
+
#
|
39
|
+
# Returns a hash containing interfaces names as keys. Each value is the same hash that would
|
40
|
+
# be returned by the org.freedesktop.DBus.Properties.GetAll() method for that combination of
|
41
|
+
# object path and interface. If an interface has no properties, the empty hash is returned.
|
42
|
+
#
|
43
|
+
# @return [Hash]
|
44
|
+
def interfaces_and_properties
|
45
|
+
get_all_method = self.class.make_method_name("org.freedesktop.DBus.Properties", :GetAll)
|
46
|
+
|
47
|
+
intfs.keys.each_with_object({}) do |interface, hash|
|
48
|
+
hash[interface] = public_send(get_all_method, interface).first
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# @return [Logger]
|
55
|
+
attr_reader :logger
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "dbus"
|
23
|
+
require "abstract_method"
|
24
|
+
|
25
|
+
module DInstaller
|
26
|
+
module DBus
|
27
|
+
module Clients
|
28
|
+
# Base class for D-Bus clients
|
29
|
+
class Base
|
30
|
+
# @!method service_name
|
31
|
+
# Name of the D-Bus service
|
32
|
+
# @return [String]
|
33
|
+
abstract_method :service_name
|
34
|
+
|
35
|
+
# D-Bus service
|
36
|
+
#
|
37
|
+
# @return [::DBus::Service]
|
38
|
+
def service
|
39
|
+
@service ||= bus.service(service_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Registers callback to be called when the properties of the given object changes
|
45
|
+
#
|
46
|
+
# @note Signal subscription is done only once. Otherwise, the latest subscription overrides
|
47
|
+
# the previous one.
|
48
|
+
#
|
49
|
+
# @param dbus_object [::DBus::Object]
|
50
|
+
# @param block [Proc]
|
51
|
+
def on_properties_change(dbus_object, &block)
|
52
|
+
@on_properties_change_callbacks ||= {}
|
53
|
+
@on_properties_change_callbacks[dbus_object.path] ||= []
|
54
|
+
@on_properties_change_callbacks[dbus_object.path] << block
|
55
|
+
|
56
|
+
return if @on_properties_change_callbacks[dbus_object.path].size > 1
|
57
|
+
|
58
|
+
dbus_properties = dbus_object["org.freedesktop.DBus.Properties"]
|
59
|
+
dbus_properties.on_signal("PropertiesChanged") do |interface, changes, invalid|
|
60
|
+
callbacks = @on_properties_change_callbacks[dbus_object.path]
|
61
|
+
callbacks.each { |c| c.call(interface, changes, invalid) }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def bus
|
66
|
+
@bus ||= ::DBus::SystemBus.instance
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) [2022] SUSE LLC
|
4
|
+
#
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This program is free software; you can redistribute it and/or modify it
|
8
|
+
# under the terms of version 2 of the GNU General Public License as published
|
9
|
+
# by the Free Software Foundation.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
14
|
+
# more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License along
|
17
|
+
# with this program; if not, contact SUSE LLC.
|
18
|
+
#
|
19
|
+
# To contact SUSE LLC about this file by physical or electronic mail, you may
|
20
|
+
# find current contact information at www.suse.com.
|
21
|
+
|
22
|
+
require "dinstaller/dbus/clients/base"
|
23
|
+
|
24
|
+
module DInstaller
|
25
|
+
module DBus
|
26
|
+
module Clients
|
27
|
+
# D-Bus client for language configuration
|
28
|
+
class Language < Base
|
29
|
+
def initialize
|
30
|
+
super
|
31
|
+
|
32
|
+
@dbus_object = service.object("/org/opensuse/DInstaller/Language1")
|
33
|
+
@dbus_object.introspect
|
34
|
+
end
|
35
|
+
|
36
|
+
def service_name
|
37
|
+
@service_name ||= "org.opensuse.DInstaller.Language"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Available languages for the installation
|
41
|
+
#
|
42
|
+
# @return [Array<Array<String, String>>] id and name of each language
|
43
|
+
def available_languages
|
44
|
+
dbus_object["org.opensuse.DInstaller.Language1"]["AvailableLanguages"].map { |l| l[0..1] }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Languages selected to install
|
48
|
+
#
|
49
|
+
# @return [Array<String>] ids of the languages
|
50
|
+
def selected_languages
|
51
|
+
dbus_object["org.opensuse.DInstaller.Language1"]["MarkedForInstall"]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Selects the languages to install
|
55
|
+
#
|
56
|
+
# @param ids [Array<String>]
|
57
|
+
def select_languages(ids)
|
58
|
+
dbus_object.ToInstall(ids)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Finishes the language installation
|
62
|
+
def finish
|
63
|
+
dbus_object.Finish
|
64
|
+
end
|
65
|
+
|
66
|
+
# Registers a callback to run when the language changes
|
67
|
+
#
|
68
|
+
# @note Signal subscription is done only once. Otherwise, the latest subscription overrides
|
69
|
+
# the previous one.
|
70
|
+
#
|
71
|
+
# @param block [Proc] Callback to run when a language is selected
|
72
|
+
def on_language_selected(&block)
|
73
|
+
on_properties_change(dbus_object) do |_, changes, _|
|
74
|
+
languages = changes["MarkedForInstall"]
|
75
|
+
block.call(languages)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# @return [::DBus::Object]
|
82
|
+
attr_reader :dbus_object
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|