archlinux 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/archlinux.svg)](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
|