archlinux 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +9 -0
- data/archlinux.gemspec +1 -1
- data/lib/applications/ufw.rb +16 -0
- data/lib/archlinux.rb +6 -1
- data/lib/core.rb +17 -24
- data/lib/declarations/file.rb +80 -0
- data/lib/declarations/git.rb +22 -0
- data/lib/declarations/pacman.rb +74 -0
- data/lib/declarations/systemd.rb +100 -0
- data/lib/declarations/user.rb +38 -0
- data/lib/utils.rb +7 -4
- metadata +8 -3
- data/lib/declarations.rb +0 -243
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b2ec1f095b419f4c323bee8fa6f9f1f7215ab0e4b785c3922de9db10e5a0413
|
4
|
+
data.tar.gz: 84fe5ee8d792c03b5a62bac375114c82dcb2d1448dfe55f2a1de33a77f90a8df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c12e913857365c3390faa570f1ffee57a577628d831efb9d974c83ecf4f5b6784c4fb901b2ed09e63d5c5a1bbc025e7b636add7a7801c31e4f2c0b01c8bf68b0
|
7
|
+
data.tar.gz: c74297e0f98dd6c9518420e1d28d2193c5a56f97749ae596a1c6be398ea3f1bfc630e814d5fd889315ce39fcac2030024157902bb954a7ffa212bfd827535fa3
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Archlinux
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/archlinux)
|
4
|
+
|
3
5
|
> [!WARNING]
|
4
6
|
> this can break your system, don't use it on your running system
|
5
7
|
|
@@ -108,3 +110,10 @@ It will do the following:
|
|
108
110
|
- Make sure services and timers are running
|
109
111
|
- Do other configurations like locale, X11 keyboard settings, hostname
|
110
112
|
- Ensure users are created and in specified groups
|
113
|
+
|
114
|
+
|
115
|
+
# Concepts
|
116
|
+
|
117
|
+
## Declarations:
|
118
|
+
|
119
|
+
Functions the user will run to declare the state of the system like packages to be present, files, services, user, group...etc
|
data/archlinux.gemspec
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# @group Declarations:
|
4
|
+
|
5
|
+
# setup add ufw enable it and allow ports during configure step
|
6
|
+
def ufw(*allow)
|
7
|
+
@ufw ||= Set.new
|
8
|
+
@ufw += allow.map(&:to_s)
|
9
|
+
|
10
|
+
package :ufw
|
11
|
+
service :ufw
|
12
|
+
|
13
|
+
on_configure do
|
14
|
+
sudo "ufw allow #{@ufw.join(' ')}"
|
15
|
+
end
|
16
|
+
end
|
data/lib/archlinux.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
def require_relative_dir(dir)
|
2
|
+
Dir["#{File.dirname(__FILE__)}/#{dir}/**/*.rb"].sort.each { |f| require f }
|
3
|
+
end
|
4
|
+
|
1
5
|
require_relative 'core'
|
2
6
|
require_relative 'utils'
|
3
|
-
|
7
|
+
require_relative_dir 'declarations'
|
8
|
+
require_relative_dir 'applications'
|
4
9
|
|
5
10
|
Signal.trap("INT") { exit } # Suppress stack trace on Ctrl-C
|
data/lib/core.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
|
-
# ==============================================================
|
2
|
-
# CORE:
|
3
1
|
# State of the system It should hold all the information we need to build the
|
4
2
|
# system, packages, files, changes...etc. everything will run inside an instance
|
5
3
|
# of this class
|
6
|
-
# ==============================================================
|
7
4
|
class State
|
8
5
|
def apply(block)
|
9
6
|
instance_eval &block
|
@@ -18,21 +15,21 @@ class State
|
|
18
15
|
@prepare_steps[id] = block
|
19
16
|
end
|
20
17
|
|
21
|
-
# Same as on_prepare but for install step
|
18
|
+
# Same as {#on_prepare} but for install step
|
22
19
|
def on_install(id=nil, &block)
|
23
20
|
id ||= caller_locations(1,1).first.to_s
|
24
21
|
@install_steps ||= {}
|
25
22
|
@install_steps[id] = block
|
26
23
|
end
|
27
24
|
|
28
|
-
# Same as on_prepare but for configure step
|
25
|
+
# Same as {.on_prepare} but for configure step
|
29
26
|
def on_configure(id=nil, &block)
|
30
27
|
id ||= caller_locations(1,1).first.to_s
|
31
28
|
@configure_steps ||= {}
|
32
29
|
@configure_steps[id] = block
|
33
30
|
end
|
34
31
|
|
35
|
-
# Same as
|
32
|
+
# Same as {.on_prepare} but for configure step
|
36
33
|
def on_finalize(id=nil, &block)
|
37
34
|
id ||= caller_locations(1,1).first.to_s
|
38
35
|
@finalize_steps ||= {}
|
@@ -41,29 +38,25 @@ class State
|
|
41
38
|
|
42
39
|
# Run all registered code blocks in the following order: Prepare, Install, Configure, Finalize
|
43
40
|
def run_steps
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
run_step("Prepare", @prepare_steps)
|
42
|
+
run_step("Install", @install_steps)
|
43
|
+
run_step("Configure", @configure_steps)
|
44
|
+
run_step("Finalize", @finalize_steps)
|
45
|
+
end
|
46
|
+
end
|
48
47
|
|
49
|
-
|
50
|
-
log "=> Install"
|
51
|
-
@install_steps.each { |_, step| apply(step) }
|
52
|
-
end
|
48
|
+
private
|
53
49
|
|
54
|
-
|
55
|
-
|
56
|
-
@configure_steps.each { |_, step| apply(step) }
|
57
|
-
end
|
50
|
+
def run_step(name, step)
|
51
|
+
return unless step&.any?
|
58
52
|
|
59
|
-
|
60
|
-
|
61
|
-
@finalize_steps.each { |_, step| apply(step) }
|
62
|
-
end
|
63
|
-
end
|
53
|
+
log "=> #{name}"
|
54
|
+
step.each { |_, s| apply(s) }
|
64
55
|
end
|
65
56
|
|
66
|
-
#
|
57
|
+
# @group Core:
|
58
|
+
|
59
|
+
# passed block will run in the context of a {State} instance and then a builder
|
67
60
|
# will build this state
|
68
61
|
def linux(&block)
|
69
62
|
s = State.new
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
# @group Declarations:
|
4
|
+
|
5
|
+
# Copy src inside dest during configure step, if src/. will copy src content to dest
|
6
|
+
def copy(src, dest)
|
7
|
+
@copy ||= []
|
8
|
+
@copy << { src: src, dest: dest }
|
9
|
+
|
10
|
+
on_configure do
|
11
|
+
next unless @copy
|
12
|
+
next if @copy.empty?
|
13
|
+
|
14
|
+
@copy.each do |item|
|
15
|
+
log "Copying", item
|
16
|
+
FileUtils.cp_r item[:src], item[:dest]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Replace a regex pattern with replacement string in a file during configure step
|
22
|
+
def replace(file, pattern, replacement)
|
23
|
+
@replace ||= []
|
24
|
+
@replace << {file: file, pattern: pattern, replacement: replacement}
|
25
|
+
|
26
|
+
on_configure do
|
27
|
+
@replace.each do |params|
|
28
|
+
input = File.read(params[:file])
|
29
|
+
output = input.gsub(params[:pattern], params[:replacement])
|
30
|
+
File.write(params[:file], output)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# link file to destination
|
36
|
+
def symlink(target, link_name)
|
37
|
+
@symlink ||= Set.new
|
38
|
+
@symlink << {target: target, link_name: link_name}
|
39
|
+
|
40
|
+
on_configure do
|
41
|
+
|
42
|
+
@symlink.each do |params|
|
43
|
+
target = File.expand_path params[:target]
|
44
|
+
link_name = File.expand_path params[:link_name]
|
45
|
+
log "Linking", target: target, link_name: link_name
|
46
|
+
|
47
|
+
# make the parent if it doesn't exist
|
48
|
+
dest_dir = File.dirname(link_name)
|
49
|
+
FileUtils.mkdir_p(dest_dir) unless File.exist?(dest_dir)
|
50
|
+
|
51
|
+
# link with force
|
52
|
+
FileUtils.ln_s(target, link_name, force: true)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# on prepare make sure the directory exists
|
58
|
+
def mkdir(*path)
|
59
|
+
path.flatten!
|
60
|
+
@mkdir ||= Set.new
|
61
|
+
@mkdir += path
|
62
|
+
|
63
|
+
on_prepare do
|
64
|
+
@mkdir.each do |path|
|
65
|
+
FileUtils.mkdir_p File.expand_path(path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Write a file during configure step
|
71
|
+
def file(path, content)
|
72
|
+
@files ||= {}
|
73
|
+
@files[path] = content
|
74
|
+
|
75
|
+
on_configure do
|
76
|
+
@files.each do |path, content|
|
77
|
+
File.write(path, content)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# @group Declarations:
|
4
|
+
|
5
|
+
# on prepare make sure a git repository is cloned to directory
|
6
|
+
def git_clone(from:, to: nil)
|
7
|
+
@git_clone ||= Set.new
|
8
|
+
@git_clone << {from: from, to: to}
|
9
|
+
|
10
|
+
on_install do
|
11
|
+
@git_clone.each do |item|
|
12
|
+
from = item[:from]
|
13
|
+
to = item[:to]
|
14
|
+
system "git clone #{from} #{to}" unless File.exist?(File.expand_path(to))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# git clone for github repositories
|
20
|
+
def github_clone(from:, to: nil)
|
21
|
+
git_clone(from: "https://github.com/#{from}", to: to)
|
22
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
# @group Utilities
|
5
|
+
|
6
|
+
# Utility function, returns true of package is installed
|
7
|
+
def package?(name)
|
8
|
+
system("pacman -Qi #{name} &> /dev/null")
|
9
|
+
end
|
10
|
+
|
11
|
+
# @group Declarations:
|
12
|
+
|
13
|
+
# Install a package on install step and remove packages not registered with this
|
14
|
+
# function
|
15
|
+
def package(*names)
|
16
|
+
names.flatten!
|
17
|
+
@packages ||= Set.new
|
18
|
+
@packages += names.map(&:to_s)
|
19
|
+
|
20
|
+
# install step to install packages required and remove not required
|
21
|
+
on_install do
|
22
|
+
# install missing packages
|
23
|
+
need_install = @packages.reject { |p| package? p }
|
24
|
+
need_install_args = need_install.join(" ")
|
25
|
+
if need_install.any?
|
26
|
+
log "Installing packages", packages: need_install
|
27
|
+
sudo "pacman --noconfirm --needed -S #{need_install_args}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# expand groups to packages
|
31
|
+
packages_args = @packages.join(" ")
|
32
|
+
group_packages = Set.new(`pacman --quiet -Sg #{packages_args}`.lines.map(&:strip))
|
33
|
+
|
34
|
+
# full list of packages that should exist on the system
|
35
|
+
all = @packages + group_packages
|
36
|
+
|
37
|
+
# actual list on the system
|
38
|
+
installed = Set.new(`pacman -Q --quiet --explicit --unrequired --native`.lines.map(&:strip))
|
39
|
+
|
40
|
+
unneeded = installed - all
|
41
|
+
next if unneeded.empty?
|
42
|
+
|
43
|
+
log "Removing packages", packages: unneeded
|
44
|
+
sudo("pacman -Rs #{unneeded.join(" ")}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# aur command to install packages from aur on install step
|
49
|
+
def aur(*names)
|
50
|
+
names.flatten!
|
51
|
+
@aurs ||= Set.new
|
52
|
+
@aurs += names.map(&:to_s)
|
53
|
+
|
54
|
+
on_install do
|
55
|
+
names = @aurs || []
|
56
|
+
log "Install AUR packages", packages: names
|
57
|
+
cache = "./cache/aur"
|
58
|
+
FileUtils.mkdir_p cache
|
59
|
+
Dir.chdir cache do
|
60
|
+
names.each do |package|
|
61
|
+
system("git clone --depth 1 --shallow-submodules https://aur.archlinux.org/#{package}.git") unless Dir.exist?(package)
|
62
|
+
Dir.chdir package do
|
63
|
+
|
64
|
+
pkgbuild = File.readlines('PKGBUILD')
|
65
|
+
pkgver = pkgbuild.find { |l| l.start_with?('pkgver=') }.split('=')[1].strip.chomp('"')
|
66
|
+
package_info = `pacman -Qi #{package}`.strip.lines.map{|l| l.strip.split(/\s*:\s*/, 2) }.to_h
|
67
|
+
installed = package_info["Version"].to_s.split("-")[0] == pkgver
|
68
|
+
|
69
|
+
system("makepkg --syncdeps --install --noconfirm --needed") unless installed
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
# @group Declarations:
|
4
|
+
|
5
|
+
# set timezone and NTP settings during prepare step
|
6
|
+
def timedate(timezone: 'UTC', ntp: true)
|
7
|
+
@timedate = {timezone: timezone, ntp: ntp}
|
8
|
+
|
9
|
+
on_configure do
|
10
|
+
log "Set timedate", @timedate
|
11
|
+
sudo "timedatectl set-timezone #{@timedate[:timezone]}"
|
12
|
+
sudo "timedatectl set-ntp #{@timedate[:ntp]}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# enable system service if root or user service if not during finalize step
|
17
|
+
def service(*names)
|
18
|
+
names.flatten!
|
19
|
+
@services ||= Set.new
|
20
|
+
@services += names.map(&:to_s)
|
21
|
+
|
22
|
+
on_finalize do
|
23
|
+
log "Enable services", services: @services
|
24
|
+
user_flags = root? ? "" : "--user"
|
25
|
+
|
26
|
+
system "systemctl enable #{user_flags} #{@services.join(" ")}"
|
27
|
+
|
28
|
+
# Disable services that were enabled manually and not in the list we have
|
29
|
+
services = `systemctl list-unit-files #{user_flags} --state=enabled --type=service --no-legend --no-pager`
|
30
|
+
enabled_manually = services.lines.map{|l| l.strip.split(/\s+/) }.select{|l| (l[1] == 'enabled') && (l[2] == 'disabled')}
|
31
|
+
names_without_extension = enabled_manually.map{|l| l.first.delete_suffix(".service") }
|
32
|
+
to_disable = names_without_extension - @services.to_a
|
33
|
+
|
34
|
+
next if to_disable.empty?
|
35
|
+
|
36
|
+
log "Services to disable", packages: to_disable
|
37
|
+
# system "systemctl disable #{user_flags} #{to_disable.join(" ")}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# enable system timer if root or user timer if not during finalize step
|
42
|
+
def timer(*names)
|
43
|
+
names.flatten!
|
44
|
+
@timers ||= Set.new
|
45
|
+
@timers += names.map(&:to_s)
|
46
|
+
|
47
|
+
on_finalize do
|
48
|
+
log "Enable timers", timers: @timers
|
49
|
+
timers = @timers.map{ |t| "#{t}.timer" }.join(" ")
|
50
|
+
if root?
|
51
|
+
sudo "systemctl enable #{timers}"
|
52
|
+
else
|
53
|
+
system "systemctl enable --user #{timers}"
|
54
|
+
end
|
55
|
+
# disable all other timers
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# set keyboard settings during prepare step
|
60
|
+
def keyboard(keymap: nil, layout: nil, model: nil, variant: nil, options: nil)
|
61
|
+
@keyboard ||= {}
|
62
|
+
values = {
|
63
|
+
keymap: keymap,
|
64
|
+
layout: layout,
|
65
|
+
model: model,
|
66
|
+
variant: variant,
|
67
|
+
options: options
|
68
|
+
}.compact
|
69
|
+
@keyboard.merge!(values)
|
70
|
+
|
71
|
+
on_prepare do
|
72
|
+
next unless @keyboard[:keymap]
|
73
|
+
|
74
|
+
sudo "localectl set-keymap #{@keyboard[:keymap]}"
|
75
|
+
|
76
|
+
m = @keyboard.to_h.slice(:layout, :model, :variant, :options)
|
77
|
+
sudo "localectl set-x11-keymap \"#{m[:layout]}\" \"#{m[:model]}\" \"#{m[:variant]}\" \"#{m[:options]}\""
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Sets locale using localectl
|
82
|
+
def locale(value)
|
83
|
+
@locale = value
|
84
|
+
|
85
|
+
on_prepare do
|
86
|
+
sudo "localectl set-locale #{@locale}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Sets the machine hostname
|
91
|
+
def hostname(name)
|
92
|
+
@hostname = name
|
93
|
+
|
94
|
+
file '/etc/hostname', "#{@hostname}\n"
|
95
|
+
|
96
|
+
on_configure do
|
97
|
+
log "Setting hostname", hostname: @hostname
|
98
|
+
sudo "hostnamectl set-hostname #{@hostname}"
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'etc'
|
3
|
+
|
4
|
+
# @group Declarations:
|
5
|
+
|
6
|
+
# create a user and assign a set of group. if block is passes the block will run
|
7
|
+
# in as this user. block will run during the configure step
|
8
|
+
def user(name, groups: [], &block)
|
9
|
+
name = name.to_s
|
10
|
+
|
11
|
+
@user ||= {}
|
12
|
+
@user[name] ||= {}
|
13
|
+
@user[name][:groups] ||= []
|
14
|
+
@user[name][:groups] += groups.map(&:to_s)
|
15
|
+
@user[name][:state] = State.new
|
16
|
+
@user[name][:state].apply(block) if block_given?
|
17
|
+
|
18
|
+
on_configure do
|
19
|
+
@user.each do |name, conf|
|
20
|
+
exists = Etc.getpwnam name rescue nil
|
21
|
+
sudo "useradd #{name}" unless exists
|
22
|
+
sudo "usermod --groups #{groups.join(",")} #{name}" if groups.any?
|
23
|
+
|
24
|
+
fork do
|
25
|
+
currentuser = Etc.getpwnam(name)
|
26
|
+
Process::GID.change_privilege(currentuser.gid)
|
27
|
+
Process::UID.change_privilege(currentuser.uid)
|
28
|
+
ENV['XDG_RUNTIME_DIR'] = "/run/user/#{currentuser.uid}"
|
29
|
+
ENV['HOME'] = currentuser.dir
|
30
|
+
ENV['USER'] = currentuser.name
|
31
|
+
ENV['LOGNAME'] = currentuser.name
|
32
|
+
conf[:state].run_steps
|
33
|
+
end
|
34
|
+
|
35
|
+
Process.wait
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/utils.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'etc'
|
2
2
|
|
3
|
+
# @group Utilities: Methods for logging and small predicates
|
4
|
+
|
3
5
|
# Prints a message to the STDOUT
|
4
|
-
#
|
5
|
-
# @param [String]
|
6
|
-
#
|
7
|
-
# @param [Hash<String, Object>] args prints each key and value in separate lines after message
|
6
|
+
# @param msg [String] a log message to print
|
7
|
+
# @param args [Hash<String, Object>] prints each key and value in separate lines after message
|
8
8
|
def log(msg, args={})
|
9
9
|
puts msg
|
10
10
|
|
@@ -22,10 +22,13 @@ def log(msg, args={})
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
# Checks if current user is the root
|
26
|
+
# @return [Boolean] true if current user is root and false otherwise
|
25
27
|
def root?
|
26
28
|
Process.uid == Etc.getpwnam('root').uid
|
27
29
|
end
|
28
30
|
|
31
|
+
# Runs the command with sudo if current user is not root
|
29
32
|
def sudo(command)
|
30
33
|
root? ? system(command) : system("sudo #{command}")
|
31
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: archlinux
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emad Elsaid
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -18,9 +18,14 @@ extra_rdoc_files: []
|
|
18
18
|
files:
|
19
19
|
- README.md
|
20
20
|
- archlinux.gemspec
|
21
|
+
- lib/applications/ufw.rb
|
21
22
|
- lib/archlinux.rb
|
22
23
|
- lib/core.rb
|
23
|
-
- lib/declarations.rb
|
24
|
+
- lib/declarations/file.rb
|
25
|
+
- lib/declarations/git.rb
|
26
|
+
- lib/declarations/pacman.rb
|
27
|
+
- lib/declarations/systemd.rb
|
28
|
+
- lib/declarations/user.rb
|
24
29
|
- lib/utils.rb
|
25
30
|
homepage: https://github.com/emad-elsaid/archlinux
|
26
31
|
licenses:
|
data/lib/declarations.rb
DELETED
@@ -1,243 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
require 'fileutils'
|
3
|
-
|
4
|
-
# ==============================================================
|
5
|
-
# DECLARATIONS:
|
6
|
-
# Functions the user will run to declare the state of the system
|
7
|
-
# like packages to be present, files, services, user, group...etc
|
8
|
-
# ==============================================================
|
9
|
-
|
10
|
-
# Install a package on install step and remove packages not registered with this
|
11
|
-
# function
|
12
|
-
def package(*names)
|
13
|
-
names.flatten!
|
14
|
-
@packages ||= Set.new
|
15
|
-
@packages += names.map(&:to_s)
|
16
|
-
|
17
|
-
# install step to install packages required and remove not required
|
18
|
-
on_install do
|
19
|
-
# install packages list as is
|
20
|
-
names = @packages.join(" ")
|
21
|
-
log "Installing packages", packages: @packages
|
22
|
-
sudo "pacman --noconfirm --needed -S #{names}" unless @packages.empty?
|
23
|
-
|
24
|
-
# expand groups to packages
|
25
|
-
group_packages = Set.new(`pacman --quiet -Sg #{names}`.lines.map(&:strip))
|
26
|
-
|
27
|
-
# full list of packages that should exist on the system
|
28
|
-
all = @packages + group_packages
|
29
|
-
|
30
|
-
# actual list on the system
|
31
|
-
installed = Set.new(`pacman -Q --quiet --explicit --unrequired --native`.lines.map(&:strip))
|
32
|
-
|
33
|
-
unneeded = installed - all
|
34
|
-
next if unneeded.empty?
|
35
|
-
|
36
|
-
log "Removing packages", packages: unneeded
|
37
|
-
sudo("pacman -Rs #{unneeded.join(" ")}")
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
# aur command to install packages from aur on install step
|
43
|
-
def aur(*names)
|
44
|
-
names.flatten!
|
45
|
-
@aurs ||= Set.new
|
46
|
-
@aurs += names.map(&:to_s)
|
47
|
-
|
48
|
-
on_install do
|
49
|
-
names = @aurs || []
|
50
|
-
log "Install AUR packages", packages: names
|
51
|
-
cache = "./cache/aur"
|
52
|
-
FileUtils.mkdir_p cache
|
53
|
-
Dir.chdir cache do
|
54
|
-
names.each do |package|
|
55
|
-
system("git clone --depth 1 --shallow-submodules https://aur.archlinux.org/#{package}.git") unless Dir.exists?(package)
|
56
|
-
Dir.chdir package do
|
57
|
-
system("makepkg --syncdeps --install --noconfirm --needed")
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# set timezone and NTP settings during prepare step
|
65
|
-
def timedate(timezone: 'UTC', ntp: true)
|
66
|
-
@timedate = {timezone: timezone, ntp: ntp}
|
67
|
-
|
68
|
-
on_configure do
|
69
|
-
log "Set timedate", @timedate
|
70
|
-
sudo "timedatectl set-timezone #{@timedate[:timezone]}"
|
71
|
-
sudo "timedatectl set-ntp #{@timedate[:ntp]}"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# enable system service if root or user service if not during finalize step
|
76
|
-
def service(*names)
|
77
|
-
names.flatten!
|
78
|
-
@services ||= Set.new
|
79
|
-
@services += names.map(&:to_s)
|
80
|
-
|
81
|
-
on_finalize do
|
82
|
-
log "Enable services", services: @services
|
83
|
-
user_flags = root? ? "" : "--user"
|
84
|
-
|
85
|
-
system "systemctl enable #{user_flags} #{@services.join(" ")}"
|
86
|
-
|
87
|
-
# Disable services that were enabled manually and not in the list we have
|
88
|
-
services = `systemctl list-unit-files #{user_flags} --state=enabled --type=service --no-legend --no-pager`
|
89
|
-
enabled_manually = services.lines.map{|l| l.strip.split(/\s+/) }.select{|l| (l[1] == 'enabled') && (l[2] == 'disabled')}
|
90
|
-
names_without_extension = enabled_manually.map{|l| l.first.delete_suffix(".service") }
|
91
|
-
to_disable = names_without_extension - @services.to_a
|
92
|
-
|
93
|
-
next if to_disable.empty?
|
94
|
-
|
95
|
-
log "Services to disable", packages: to_disable
|
96
|
-
# system "systemctl disable #{user_flags} #{to_disable.join(" ")}"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# enable system timer if root or user timer if not during finalize step
|
101
|
-
def timer(*names)
|
102
|
-
names.flatten!
|
103
|
-
@timers ||= Set.new
|
104
|
-
@timers += names.map(&:to_s)
|
105
|
-
|
106
|
-
on_finalize do
|
107
|
-
log "Enable timers", timers: @timers
|
108
|
-
timers = @timers.map{ |t| "#{t}.timer" }.join(" ")
|
109
|
-
if root?
|
110
|
-
sudo "systemctl enable #{timers}"
|
111
|
-
else
|
112
|
-
system "systemctl enable --user #{timers}"
|
113
|
-
end
|
114
|
-
# disable all other timers
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# set keyboard settings during prepare step
|
119
|
-
def keyboard(keymap: nil, layout: nil, model: nil, variant: nil, options: nil)
|
120
|
-
@keyboard ||= {}
|
121
|
-
values = {
|
122
|
-
keymap: keymap,
|
123
|
-
layout: layout,
|
124
|
-
model: model,
|
125
|
-
variant: variant,
|
126
|
-
options: options
|
127
|
-
}.compact
|
128
|
-
@keyboard.merge!(values)
|
129
|
-
|
130
|
-
on_prepare do
|
131
|
-
next unless @keyboard[:keymap]
|
132
|
-
|
133
|
-
sudo "localectl set-keymap #{@keyboard[:keymap]}"
|
134
|
-
|
135
|
-
m = @keyboard.to_h.slice(:layout, :model, :variant, :options)
|
136
|
-
sudo "localectl set-x11-keymap \"#{m[:layout]}\" \"#{m[:model]}\" \"#{m[:variant]}\" \"#{m[:options]}\""
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def locale(value)
|
141
|
-
@locale = value
|
142
|
-
|
143
|
-
on_prepare do
|
144
|
-
sudo "localectl set-locale #{@locale}"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# create a user and assign a set of group. if block is passes the block will run
|
149
|
-
# in as this user. block will run during the configure step
|
150
|
-
def user(name, groups: [], &block)
|
151
|
-
name = name.to_s
|
152
|
-
|
153
|
-
@user ||= {}
|
154
|
-
@user[name] ||= {}
|
155
|
-
@user[name][:groups] ||= []
|
156
|
-
@user[name][:groups] += groups.map(&:to_s)
|
157
|
-
@user[name][:state] = State.new
|
158
|
-
@user[name][:state].apply(block) if block_given?
|
159
|
-
|
160
|
-
on_configure do
|
161
|
-
@user.each do |name, conf|
|
162
|
-
exists = Etc.getpwnam name rescue nil
|
163
|
-
sudo "useradd #{name}" if exists
|
164
|
-
sudo "usermod --groups #{groups.join(",")} #{name}" if groups.any?
|
165
|
-
|
166
|
-
fork do
|
167
|
-
currentuser = Etc.getpwnam(name)
|
168
|
-
Process::GID.change_privilege(currentuser.gid)
|
169
|
-
Process::UID.change_privilege(currentuser.uid)
|
170
|
-
ENV['XDG_RUNTIME_DIR'] = "/run/user/#{currentuser.uid}"
|
171
|
-
conf[:state].run_steps
|
172
|
-
end
|
173
|
-
|
174
|
-
Process.wait
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
# Copy src inside dest during configure step, if src/. will copy src content to dest
|
180
|
-
def copy(src, dest)
|
181
|
-
@copy ||= []
|
182
|
-
@copy << { src: src, dest: dest }
|
183
|
-
|
184
|
-
on_configure do
|
185
|
-
next unless @copy
|
186
|
-
next if @copy.empty?
|
187
|
-
|
188
|
-
@copy.each do |item|
|
189
|
-
log "Copying", item
|
190
|
-
FileUtils.cp_r item[:src], item[:dest]
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
# Replace a regex pattern with replacement string in a file during configure step
|
196
|
-
def replace(file, pattern, replacement)
|
197
|
-
@replace ||= []
|
198
|
-
@replace << {file: file, pattern: pattern, replacement: replacement}
|
199
|
-
|
200
|
-
on_configure do
|
201
|
-
@replace.each do |params|
|
202
|
-
input = File.read(params[:file])
|
203
|
-
output = input.gsub(params[:pattern], params[:replacement])
|
204
|
-
File.write(params[:file], output)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
# setup add ufw enable it and allow ports during configure step
|
210
|
-
def firewall(*allow)
|
211
|
-
@firewall ||= Set.new
|
212
|
-
@firewall += allow.map(&:to_s)
|
213
|
-
|
214
|
-
package :ufw
|
215
|
-
service :ufw
|
216
|
-
|
217
|
-
on_configure do
|
218
|
-
sudo "ufw allow #{@firewall.join(' ')}"
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Write a file during configure step
|
223
|
-
def file(path, content)
|
224
|
-
@files ||= {}
|
225
|
-
@files[path] = content
|
226
|
-
|
227
|
-
on_configure do
|
228
|
-
@files.each do |path, content|
|
229
|
-
File.write(path, content)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
def hostname(name)
|
235
|
-
@hostname = name
|
236
|
-
|
237
|
-
file '/etc/hostname', "#{@hostname}\n"
|
238
|
-
|
239
|
-
on_configure do
|
240
|
-
log "Setting hostname", hostname: @hostname
|
241
|
-
sudo "hostnamectl set-hostname #{@hostname}"
|
242
|
-
end
|
243
|
-
end
|