archlinux 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b2ec1f095b419f4c323bee8fa6f9f1f7215ab0e4b785c3922de9db10e5a0413
4
- data.tar.gz: 84fe5ee8d792c03b5a62bac375114c82dcb2d1448dfe55f2a1de33a77f90a8df
3
+ metadata.gz: 9a503802bfcc8eecbd6b5605d96f6cf9de4ded00319ec74c9a091d56840fd86b
4
+ data.tar.gz: 31a14e3c70aed70bad24a5a716fb3088093866ea2db7c7e46b87ca3c101aad24
5
5
  SHA512:
6
- metadata.gz: c12e913857365c3390faa570f1ffee57a577628d831efb9d974c83ecf4f5b6784c4fb901b2ed09e63d5c5a1bbc025e7b636add7a7801c31e4f2c0b01c8bf68b0
7
- data.tar.gz: c74297e0f98dd6c9518420e1d28d2193c5a56f97749ae596a1c6be398ea3f1bfc630e814d5fd889315ce39fcac2030024157902bb954a7ffa212bfd827535fa3
6
+ metadata.gz: af974b69974f277bf426b912c7619ba7fc6d87ea4fc7dfa86b5858c6abe9b2a6b862d8d32359344d566c2d4a59fd92a74be14ef3fa4bb1cbe955887c07321e80
7
+ data.tar.gz: 712fc0f6b8035b038facd41ea33df1185be0819d77f0b11caf91cc08440d3432d1765e1eb652aa83865037d6e8d1f0d464475222dcf77264d6acd5493bfe91e2
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.0
4
+
5
+ Style/StringLiterals:
6
+ Enabled: false
7
+ Style/FrozenStringLiteralComment:
8
+ Enabled: false
9
+ Style/StringLiteralsInInterpolation:
10
+ Enabled: false
11
+ Metrics/MethodLength:
12
+ Max: 100
13
+ Lint/ShadowingOuterLocalVariable:
14
+ Enabled: false
15
+ Style/RescueModifier:
16
+ Enabled: false
17
+ Metrics/AbcSize:
18
+ Enabled: false
19
+ Metrics/CyclomaticComplexity:
20
+ Enabled: false
21
+ Metrics/PerceivedComplexity:
22
+ Enabled: false
data/README.md CHANGED
@@ -116,4 +116,10 @@ It will do the following:
116
116
 
117
117
  ## Declarations:
118
118
 
119
- Functions the user will run to declare the state of the system like packages to be present, files, services, user, group...etc
119
+ Functions the user will run to declare the state of the system like packages to
120
+ be present, files, services, user, group...etc
121
+
122
+ ## Utilities:
123
+
124
+ Methods for logging and small predicates, technically any ruby method is a
125
+ utility. calling it executes the code directly instead of declaring a state.
data/archlinux.gemspec CHANGED
@@ -4,6 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.files = `git ls-files`.lines.map(&:chomp)
5
5
  s.name = 'archlinux'
6
6
  s.summary = "Archlinux DSL to manage whole system state"
7
- s.version = '0.0.1'
8
- s.licenses = ["GPL-3.0-or-later"]
7
+ s.version = '0.2.0'
8
+ s.licenses = ["GPL-3.0-or-later"]
9
+ s.metadata['rubygems_mfa_required'] = 'true'
10
+ s.required_ruby_version = '>=3.0'
9
11
  end
data/lib/archlinux.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  def require_relative_dir(dir)
2
- Dir["#{File.dirname(__FILE__)}/#{dir}/**/*.rb"].sort.each { |f| require f }
2
+ Dir["#{File.dirname(__FILE__)}/#{dir}/**/*.rb"].each { |f| require f }
3
3
  end
4
4
 
5
5
  require_relative 'core'
data/lib/core.rb CHANGED
@@ -3,35 +3,35 @@
3
3
  # of this class
4
4
  class State
5
5
  def apply(block)
6
- instance_eval &block
6
+ instance_eval(&block)
7
7
  end
8
8
 
9
9
  # Run block on prepare step. id identifies the block uniqueness in the steps.
10
10
  # registering a block with same id multiple times replaces old block by new
11
11
  # one. if id is nil the block location in source code is used as an id
12
- def on_prepare(id=nil, &block)
13
- id ||= caller_locations(1,1).first.to_s
12
+ def on_prepare(id = nil, &block)
13
+ id ||= caller_locations(1, 1).first.to_s
14
14
  @prepare_steps ||= {}
15
15
  @prepare_steps[id] = block
16
16
  end
17
17
 
18
18
  # Same as {#on_prepare} but for install step
19
- def on_install(id=nil, &block)
20
- id ||= caller_locations(1,1).first.to_s
19
+ def on_install(id = nil, &block)
20
+ id ||= caller_locations(1, 1).first.to_s
21
21
  @install_steps ||= {}
22
22
  @install_steps[id] = block
23
23
  end
24
24
 
25
25
  # Same as {.on_prepare} but for configure step
26
- def on_configure(id=nil, &block)
27
- id ||= caller_locations(1,1).first.to_s
26
+ def on_configure(id = nil, &block)
27
+ id ||= caller_locations(1, 1).first.to_s
28
28
  @configure_steps ||= {}
29
29
  @configure_steps[id] = block
30
30
  end
31
31
 
32
32
  # Same as {.on_prepare} but for configure step
33
- def on_finalize(id=nil, &block)
34
- id ||= caller_locations(1,1).first.to_s
33
+ def on_finalize(id = nil, &block)
34
+ id ||= caller_locations(1, 1).first.to_s
35
35
  @finalize_steps ||= {}
36
36
  @finalize_steps[id] = block
37
37
  end
@@ -51,7 +51,7 @@ def run_step(name, step)
51
51
  return unless step&.any?
52
52
 
53
53
  log "=> #{name}"
54
- step.each { |_, s| apply(s) }
54
+ step.each_value { |s| apply(s) }
55
55
  end
56
56
 
57
57
  # @group Core:
@@ -21,7 +21,7 @@ end
21
21
  # Replace a regex pattern with replacement string in a file during configure step
22
22
  def replace(file, pattern, replacement)
23
23
  @replace ||= []
24
- @replace << {file: file, pattern: pattern, replacement: replacement}
24
+ @replace << { file: file, pattern: pattern, replacement: replacement }
25
25
 
26
26
  on_configure do
27
27
  @replace.each do |params|
@@ -35,18 +35,32 @@ end
35
35
  # link file to destination
36
36
  def symlink(target, link_name)
37
37
  @symlink ||= Set.new
38
- @symlink << {target: target, link_name: link_name}
38
+ @symlink << { target: target, link_name: link_name }
39
39
 
40
40
  on_configure do
41
-
42
41
  @symlink.each do |params|
43
42
  target = File.expand_path params[:target]
44
43
  link_name = File.expand_path params[:link_name]
44
+
45
+ if File.directory?(target)
46
+ log "Can't link directories", target: target, link_name: link_name
47
+ exit
48
+ end
49
+
50
+ # Check if file exists and is a symlink
51
+ if File.exist?(link_name) && File.symlink?(link_name)
52
+ links_to = File.readlink(link_name)
53
+ next if links_to == target # skip creating link if it's already exists
54
+
55
+ # Remove the link if it links to something else
56
+ File.unlink link_name
57
+ end
58
+
45
59
  log "Linking", target: target, link_name: link_name
46
60
 
47
61
  # make the parent if it doesn't exist
48
62
  dest_dir = File.dirname(link_name)
49
- FileUtils.mkdir_p(dest_dir) unless File.exist?(dest_dir)
63
+ FileUtils.mkdir_p(dest_dir)
50
64
 
51
65
  # link with force
52
66
  FileUtils.ln_s(target, link_name, force: true)
@@ -74,7 +88,10 @@ def file(path, content)
74
88
 
75
89
  on_configure do
76
90
  @files.each do |path, content|
91
+ FileUtils.mkdir_p File.dirname(path)
77
92
  File.write(path, content)
93
+ rescue Errno::ENOENT => e
94
+ log "Error: Can't write file", file: path, error: e
78
95
  end
79
96
  end
80
97
  end
@@ -5,7 +5,7 @@ require 'set'
5
5
  # on prepare make sure a git repository is cloned to directory
6
6
  def git_clone(from:, to: nil)
7
7
  @git_clone ||= Set.new
8
- @git_clone << {from: from, to: to}
8
+ @git_clone << { from: from, to: to }
9
9
 
10
10
  on_install do
11
11
  @git_clone.each do |item|
@@ -19,29 +19,28 @@ def package(*names)
19
19
 
20
20
  # install step to install packages required and remove not required
21
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
22
+ # Expand groups to packages
31
23
  packages_args = @packages.join(" ")
32
24
  group_packages = Set.new(`pacman --quiet -Sg #{packages_args}`.lines.map(&:strip))
33
25
 
34
- # full list of packages that should exist on the system
35
- all = @packages + group_packages
26
+ all = @packages + group_packages # full list of packages that should exist on the system
36
27
 
37
28
  # actual list on the system
38
29
  installed = Set.new(`pacman -Q --quiet --explicit --unrequired --native`.lines.map(&:strip))
39
30
 
40
31
  unneeded = installed - all
41
- next if unneeded.empty?
32
+ if unneeded.any?
33
+ log "Removing packages", packages: unneeded
34
+ sudo("pacman -Rsu #{unneeded.join(" ")}")
35
+ end
42
36
 
43
- log "Removing packages", packages: unneeded
44
- sudo("pacman -Rs #{unneeded.join(" ")}")
37
+ # install missing packages
38
+ need_install = @packages.reject { |p| package? p }
39
+ need_install_args = need_install.join(" ")
40
+ if need_install.any?
41
+ log "Installing packages", packages: need_install
42
+ sudo "pacman --noconfirm --needed -S #{need_install_args}"
43
+ end
45
44
  end
46
45
  end
47
46
 
@@ -52,23 +51,30 @@ def aur(*names)
52
51
  @aurs += names.map(&:to_s)
53
52
 
54
53
  on_install do
55
- names = @aurs || []
56
- log "Install AUR packages", packages: names
54
+ log "Install AUR packages", packages: @aurs
57
55
  cache = "./cache/aur"
58
56
  FileUtils.mkdir_p cache
59
57
  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)
58
+ @aurs.each do |package|
59
+ unless Dir.exist?(package)
60
+ system("git clone --depth 1 --shallow-submodules https://aur.archlinux.org/#{package}.git")
61
+ end
62
62
  Dir.chdir package do
63
-
64
63
  pkgbuild = File.readlines('PKGBUILD')
65
64
  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
65
+ package_info = `pacman -Qi #{package}`.strip.lines.to_h { |l| l.strip.split(/\s*:\s*/, 2) }
67
66
  installed = package_info["Version"].to_s.split("-")[0] == pkgver
68
67
 
69
68
  system("makepkg --syncdeps --install --noconfirm --needed") unless installed
70
69
  end
71
70
  end
72
71
  end
72
+
73
+ foreign = Set.new(`pacman -Qm`.lines.map { |l| l.split(/\s+/, 2).first })
74
+ unneeded = foreign - @aurs
75
+ next if unneeded.empty?
76
+
77
+ log "Foreign packages to remove", packages: unneeded
78
+ sudo("pacman -Rsu #{unneeded.join(" ")}")
73
79
  end
74
80
  end
@@ -4,7 +4,7 @@ require 'set'
4
4
 
5
5
  # set timezone and NTP settings during prepare step
6
6
  def timedate(timezone: 'UTC', ntp: true)
7
- @timedate = {timezone: timezone, ntp: ntp}
7
+ @timedate = { timezone: timezone, ntp: ntp }
8
8
 
9
9
  on_configure do
10
10
  log "Set timedate", @timedate
@@ -20,21 +20,28 @@ def service(*names)
20
20
  @services += names.map(&:to_s)
21
21
 
22
22
  on_finalize do
23
- log "Enable services", services: @services
24
23
  user_flags = root? ? "" : "--user"
25
24
 
26
- system "systemctl enable #{user_flags} #{@services.join(" ")}"
25
+ services = `systemctl list-unit-files #{user_flags} --state=enabled --type=service --no-legend --no-pager`
26
+ enabled = services.lines
27
+ enabled.map! { |l| l.strip.split(/\s+/) }
28
+ enabled.each { |l| l[0].delete_suffix!(".service") }
29
+
30
+ to_enable = @services - enabled.map(&:first)
31
+
32
+ if to_enable.any?
33
+ log "Enable services", services: to_enable
34
+ system "systemctl enable #{user_flags} #{to_enable.join(" ")}"
35
+ end
27
36
 
28
37
  # 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
38
+ enabled_manually = enabled.select! { |l| l[2] == 'disabled' }.map(&:first)
33
39
 
40
+ to_disable = enabled_manually - @services.to_a
34
41
  next if to_disable.empty?
35
42
 
36
43
  log "Services to disable", packages: to_disable
37
- # system "systemctl disable #{user_flags} #{to_disable.join(" ")}"
44
+ system "systemctl disable #{user_flags} #{to_disable.join(" ")}"
38
45
  end
39
46
  end
40
47
 
@@ -46,7 +53,7 @@ def timer(*names)
46
53
 
47
54
  on_finalize do
48
55
  log "Enable timers", timers: @timers
49
- timers = @timers.map{ |t| "#{t}.timer" }.join(" ")
56
+ timers = @timers.map { |t| "#{t}.timer" }.join(" ")
50
57
  if root?
51
58
  sudo "systemctl enable #{timers}"
52
59
  else
@@ -5,14 +5,15 @@ require 'etc'
5
5
 
6
6
  # create a user and assign a set of group. if block is passes the block will run
7
7
  # in as this user. block will run during the configure step
8
- def user(name, groups: [], &block)
8
+ def user(name, groups: [], autologin: nil, &block)
9
9
  name = name.to_s
10
10
 
11
11
  @user ||= {}
12
12
  @user[name] ||= {}
13
13
  @user[name][:groups] ||= []
14
14
  @user[name][:groups] += groups.map(&:to_s)
15
- @user[name][:state] = State.new
15
+ @user[name][:autologin] = autologin unless autologin.nil?
16
+ @user[name][:state] ||= State.new
16
17
  @user[name][:state].apply(block) if block_given?
17
18
 
18
19
  on_configure do
@@ -21,6 +22,15 @@ def user(name, groups: [], &block)
21
22
  sudo "useradd #{name}" unless exists
22
23
  sudo "usermod --groups #{groups.join(",")} #{name}" if groups.any?
23
24
 
25
+ if conf[:autologin]
26
+ FileUtils.mkdir_p '/etc/systemd/system/getty@tty1.service.d'
27
+ file '/etc/systemd/system/getty@tty1.service.d/autologin.conf', <<~FILE
28
+ [Service]
29
+ ExecStart=
30
+ ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin #{name} %I $TERM
31
+ FILE
32
+ end
33
+
24
34
  fork do
25
35
  currentuser = Etc.getpwnam(name)
26
36
  Process::GID.change_privilege(currentuser.gid)
data/lib/utils.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  require 'etc'
2
2
 
3
- # @group Utilities: Methods for logging and small predicates
3
+ # @group Utilities
4
4
 
5
5
  # Prints a message to the STDOUT
6
6
  # @param msg [String] a log message to print
7
7
  # @param args [Hash<String, Object>] prints each key and value in separate lines after message
8
- def log(msg, args={})
8
+ def log(msg, args = {})
9
9
  puts msg
10
10
 
11
11
  return unless args.any?
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.1
4
+ version: 0.2.0
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-03-04 00:00:00.000000000 Z
11
+ date: 2024-04-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -16,6 +16,7 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - ".rubocop.yml"
19
20
  - README.md
20
21
  - archlinux.gemspec
21
22
  - lib/applications/ufw.rb
@@ -30,7 +31,8 @@ files:
30
31
  homepage: https://github.com/emad-elsaid/archlinux
31
32
  licenses:
32
33
  - GPL-3.0-or-later
33
- metadata: {}
34
+ metadata:
35
+ rubygems_mfa_required: 'true'
34
36
  post_install_message:
35
37
  rdoc_options: []
36
38
  require_paths:
@@ -39,7 +41,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
39
41
  requirements:
40
42
  - - ">="
41
43
  - !ruby/object:Gem::Version
42
- version: '0'
44
+ version: '3.0'
43
45
  required_rubygems_version: !ruby/object:Gem::Requirement
44
46
  requirements:
45
47
  - - ">="