repomate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,102 @@
1
+ # RepoMate module
2
+ module RepoMate
3
+
4
+ # Class for the component layer of the directory structure
5
+ class Component
6
+
7
+ # Init
8
+ def initialize(component, suitename, category)
9
+ @component = component
10
+ @suitename = suitename
11
+ @category = category
12
+ end
13
+
14
+ # Returns the given architecture name (eg. main, contrib, non-free)
15
+ def name
16
+ @component
17
+ end
18
+
19
+ # Returns the directory strcuture of the component including all lower layers
20
+ def directory
21
+ File.join(Cfg.rootdir, @category, @suitename, @component)
22
+ end
23
+
24
+ # Checks if the component directory exists
25
+ def exist?
26
+ Dir.exist?(directory)
27
+ end
28
+
29
+ # Checks if the component is allowed (See: configurationfile)
30
+ def is_allowed?
31
+ self.allowed.include?(@component)
32
+ end
33
+
34
+ # Checks if directory is unused
35
+ def is_unused?(dir)
36
+ status = true
37
+
38
+ path = Dir.glob(File.join(dir, "*"))
39
+ path.each do |dirorfile|
40
+ status = false if File.directory?(dirorfile)
41
+ status = false if File.basename(dirorfile) =~ /\.deb$/
42
+ end
43
+
44
+ status
45
+ end
46
+
47
+ # Creates the directory strcuture of the component including all lower layers
48
+ def create
49
+ FileUtils.mkdir_p(directory) unless exist?
50
+ end
51
+
52
+ # Deletes the components directory including all lower layers
53
+ def destroy
54
+ FileUtils.rm_r(directory) if exist?
55
+ end
56
+
57
+ # Returns a list of all debian files in the component directory
58
+ def files
59
+ Dir.glob(File.join(directory, "*.deb"))
60
+ end
61
+
62
+ # Returns a dataset including the name of the component, the fullpath recursive through all lower layers
63
+ def self.dataset(category=nil)
64
+ data = []
65
+ self.all.each do |entry|
66
+ parts = entry.split(/\//)
67
+ unless parts.length < 3
68
+ next unless parts[0].eql?(category) || category.eql?("all")
69
+ data << {
70
+ :category => parts[0],
71
+ :suitename => parts[1],
72
+ :component => parts[2],
73
+ :fullpath => File.join(Cfg.rootdir, entry)
74
+ }
75
+ end
76
+ end
77
+ data
78
+ end
79
+
80
+ # Returns all directories without @rootdir
81
+ def self.all
82
+ config = Configuration.new
83
+ suites = Suite.all
84
+ dirs = []
85
+ rootdir = Cfg.rootdir
86
+ suites.each do |suite|
87
+ components = Dir.glob(File.join(rootdir, suite, "*"))
88
+ components.each do |component|
89
+ dirs.push component.gsub(/#{rootdir}\//, '') if File.directory? component
90
+ end
91
+ end
92
+ return dirs
93
+ end
94
+
95
+ # Gets all configured architectures
96
+ def self.allowed
97
+ Cfg.components.uniq
98
+ end
99
+
100
+ end
101
+ end
102
+
@@ -0,0 +1,75 @@
1
+ require 'yaml'
2
+
3
+ # RepoMate module
4
+ module RepoMate
5
+
6
+ # Configuration class
7
+ class Configuration
8
+
9
+ # Init
10
+ def initialize
11
+ @configfile = File.join(ENV['HOME'], '.repomate')
12
+
13
+ configure(@configfile)
14
+ end
15
+
16
+ # Loads configfile
17
+ def configure(configfile)
18
+ filecontent = []
19
+
20
+ filecontent = YAML::load_file(configfile) if File.exists?(configfile)
21
+
22
+ merge(filecontent)
23
+ end
24
+
25
+ # Merges configfile content with defaults
26
+ def merge(filecontent=nil)
27
+ config = {}
28
+
29
+ defaults = {
30
+ :rootdir => '/var/lib/repomate/repository',
31
+ :logdir => '/var/log/repomate',
32
+ :dpkg => '/usr/bin/dpkg',
33
+ :suites => [ "lenny", "squeeze" ],
34
+ :components => [ "main", "contrib" ],
35
+ :architectures => [ "all", "amd64" ],
36
+ :origin => 'Repository',
37
+ :label => 'Repository',
38
+ :gpg_enable => 'yes',
39
+ :gpg_email => 'someone@example.net',
40
+ :gpg_password => 'secret',
41
+ }
42
+
43
+ if filecontent
44
+ defaults.each do |key, value|
45
+ keysymbol = key.to_sym
46
+ setter = "#{key}="
47
+
48
+ if filecontent[keysymbol]
49
+ config[keysymbol] = filecontent[keysymbol]
50
+ else
51
+ config[keysymbol] = value
52
+ end
53
+ end
54
+ else
55
+ config = defaults
56
+ end
57
+
58
+ config.each do |key, value|
59
+ setter = "#{key}="
60
+
61
+ self.class.send(:attr_accessor, key) unless respond_to?(setter)
62
+
63
+ send setter, value
64
+ end
65
+ end
66
+ end
67
+
68
+ # Returns
69
+ Cfg = Configuration.new
70
+
71
+ end
72
+
73
+
74
+
75
+
@@ -0,0 +1,30 @@
1
+ require 'sqlite3'
2
+
3
+ # RepoMate module
4
+ module RepoMate
5
+
6
+ # Class for the database
7
+ class Database
8
+
9
+ # Init
10
+ def initialize(dbfile)
11
+ @db = SQLite3::Database.new(dbfile)
12
+ end
13
+
14
+ # Checks if the database file already exists
15
+ def exists?
16
+ File.exists?(@dbfile)
17
+ end
18
+
19
+ # Executes a query
20
+ def query(sql)
21
+ @db.execute(sql)
22
+ end
23
+
24
+ # Deletes a categories directory
25
+ def destroy
26
+ FileUtils.rm_r(@dbfile) if exists?
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,137 @@
1
+ require 'erb'
2
+ require 'date'
3
+ require 'time'
4
+ require 'gpgme'
5
+ require 'digest/md5'
6
+ require 'digest/sha1'
7
+ require 'digest/sha2'
8
+
9
+ # RepoMate module
10
+ module RepoMate
11
+
12
+ # Class that can create and delete all metafiles like Packages, Packages.gz, Release and Release.gpg
13
+ class Metafile
14
+
15
+ # Init
16
+ def initialize
17
+ @repository = Repository.new
18
+ end
19
+
20
+ # Returns a lit of all existing metafiles as array
21
+ def all
22
+ rootdir = Cfg.rootdir
23
+ dirlist = ["#{rootdir}/*/*", "#{rootdir}/*/*/*/*"]
24
+ filelist = ["Packages", "Packages.gz", "Release", "Release.gpg" ]
25
+ files = []
26
+
27
+ dirlist.each do |dirs|
28
+ Dir.glob(dirs).each do |dir|
29
+ filelist.each do |file|
30
+ fullname = File.join(dir, file)
31
+ files << fullname if File.exists? fullname
32
+ end
33
+ end
34
+ end
35
+ return files
36
+ end
37
+
38
+ # Deletes all existing metafiles
39
+ def destroy
40
+ all.each { |file| FileUtils.rm_f(file) }
41
+ end
42
+
43
+ # Creates all metafiles
44
+ def create
45
+ destroy
46
+ create_packages
47
+
48
+ if Cfg.gpg_enable
49
+ if Cfg.gpg_password.nil? || Cfg.gpg_email.nil?
50
+ puts "Configure password and email for GPG!"
51
+ exit 1
52
+ else
53
+ create_release
54
+ end
55
+ end
56
+ end
57
+
58
+ # Create Packages* files
59
+ def create_packages
60
+ source_category = "dists"
61
+
62
+ packages_template = ERB.new File.new(File.join(File.dirname(__FILE__), "templates/packages.erb")).read, nil, "%"
63
+
64
+ Architecture.dataset(source_category).each do |entry|
65
+ source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category)
66
+ source.files.each do |fullname|
67
+ package = Package.new(fullname, entry[:suitename], entry[:component])
68
+
69
+ checksums = package.get_checksums
70
+
71
+ packagesfile = File.join(entry[:fullpath], "Packages")
72
+ size = File.size(fullname)
73
+ path = File.join("dists", entry[:suitename], entry[:component], entry[:architecture_dir], package.newbasename)
74
+
75
+ File.open(packagesfile, 'a') do |file|
76
+ package.controlfile.each { |key, value| file.puts "#{key}: #{value}" if key }
77
+ file.puts packages_template.result(binding)
78
+ end
79
+ raise "Could not gzip" unless system "gzip -9 -c #{packagesfile} > #{packagesfile}.gz"
80
+ end
81
+ end
82
+ end
83
+
84
+ # Create Release* files
85
+ def create_release
86
+ source_category = "dists"
87
+ suites = []
88
+
89
+ archrelease_template = ERB.new File.new(File.join(File.dirname(__FILE__), "templates/archrelease.erb")).read, nil, "%"
90
+ suiterelease_template = ERB.new File.new(File.join(File.dirname(__FILE__), "templates/suiterelease.erb")).read, nil, "%"
91
+
92
+ now = Time.new.strftime("%a, %d %b %Y %H:%M:%S %Z")
93
+
94
+ Architecture.dataset(source_category).each do |entry|
95
+ releasefile = File.join(entry[:fullpath], "Release")
96
+
97
+ suites << entry[:suitename] unless suites.include?(entry[:suitename])
98
+
99
+ File.open(releasefile, 'w') { |file| file.puts archrelease_template.result(binding) }
100
+ end
101
+
102
+ suites.each do |suite|
103
+ architecture = []
104
+ component = []
105
+
106
+ Architecture.dataset(source_category).each do |entry|
107
+ if entry[:suitename].eql?(suite)
108
+ architecture << entry[:architecture] unless architecture.include?(entry[:architecture])
109
+ component << entry[:component] unless component.include?(entry[:component])
110
+ end
111
+ end
112
+
113
+ releasefile = File.join(Cfg.rootdir, source_category, suite, "Release")
114
+
115
+ File.open(releasefile, 'w') { |file| file.puts suiterelease_template.result(binding).gsub(/^\s+\n|^\n|^\s{3}/, '') }
116
+ begin
117
+ sign(releasefile)
118
+ rescue
119
+ destroy
120
+ create_packages
121
+ puts "GPG email/password incorrect"
122
+ return
123
+ end
124
+ end
125
+ end
126
+
127
+ # Sign a file
128
+ def sign(file)
129
+ crypto = GPGME::Crypto.new :password => Cfg.gpg_password
130
+ outfile = "#{file}.gpg"
131
+ output = File.open(outfile, 'w')
132
+ crypto.sign File.open(file, 'r'), :symmetric => false, :output => output, :signer => Cfg.gpg_email, :mode => GPGME::SIG_MODE_DETACH
133
+ end
134
+
135
+ end
136
+ end
137
+
@@ -0,0 +1,116 @@
1
+ require 'digest/md5'
2
+ require 'digest/sha1'
3
+ require 'digest/sha2'
4
+ require 'tempfile'
5
+ require 'date'
6
+ require 'time'
7
+
8
+ # RepoMate module
9
+ module RepoMate
10
+
11
+ # Class for reading debian packages
12
+ class Package
13
+
14
+ attr_reader :name, :basename, :newbasename, :controlfile, :architecture, :version, :suitename, :component, :fullname
15
+
16
+ # Init
17
+ def initialize(fullname, suitename, component)
18
+ @fullname = fullname
19
+ @suitename = suitename
20
+ @component = component
21
+ @basename = File.basename(fullname)
22
+ @mtime = File.mtime(fullname)
23
+ @pkgdbfile = File.join(Cfg.rootdir, "packages.db")
24
+ @pkgdb = Database.new(@pkgdbfile)
25
+
26
+ check_package
27
+ create_table
28
+
29
+ @controlfile = read_controlfile
30
+ @name = @controlfile['Package']
31
+ @version = @controlfile['Version']
32
+ @architecture = @controlfile['Architecture']
33
+ @newbasename = "#{@name}-#{@version}_#{@architecture}.deb"
34
+ end
35
+
36
+ # Create the package table
37
+ def create_table
38
+ sql = "create table if not exists checksums (
39
+ date varchar(25),
40
+ basename varchar(70),
41
+ mtime varchar(25),
42
+ md5 varchar(32),
43
+ sha1 varchar(40),
44
+ sha256 varchar(64)
45
+ )"
46
+ @pkgdb.query(sql)
47
+ end
48
+
49
+ # Gets checksums for the given package
50
+ def get_checksums
51
+ result = []
52
+
53
+ @pkgdb.query("select md5, sha1, sha256 from checksums where basename = '#{@basename}' and mtime = '#{@mtime.iso8601}'").each do |row|
54
+ result = row
55
+ # puts "Hit: #{@basename} #{result}"
56
+ end
57
+ result
58
+ end
59
+
60
+ # Creates the checksums for a package
61
+ def create_checksums
62
+ # puts "Ins: #{@basename}"
63
+ now = DateTime.now
64
+ md5 = Digest::MD5.file(@fullname).to_s
65
+ sha1 = Digest::SHA1.file(@fullname).to_s
66
+ sha256 = Digest::SHA256.new(256).file(@fullname).to_s
67
+ @pkgdb.query("insert into checksums values ( '#{now}', '#{@basename}', '#{@mtime.iso8601}', '#{md5}', '#{sha1}', '#{sha256}' )")
68
+ end
69
+
70
+ # Gets checksums for the given package
71
+ def delete_checksums
72
+ # puts "Del: #{@basename}"
73
+ @pkgdb.query("delete from checksums where basename = '#{@basename}'")
74
+ end
75
+
76
+ protected
77
+
78
+ # Checks if the given package is a debian package
79
+ def check_package
80
+ unless `file --dereference #{@fullname}` =~ /Debian binary package/i
81
+ puts "File does not exist or is not a Debian package!"
82
+ false
83
+ end
84
+ end
85
+
86
+ # Extracts the controlfile and returns is
87
+ def read_controlfile
88
+ gzbasename = "control.tar.gz"
89
+ basename = "control"
90
+ tmpdir = File.expand_path "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
91
+ gzfullname = File.join(tmpdir, gzbasename)
92
+ fullname = File.join(tmpdir, basename)
93
+
94
+ controlfile = {}
95
+
96
+ FileUtils.mkdir_p(tmpdir)
97
+
98
+ begin
99
+ raise "Could not untar" unless system "ar -p #{@fullname} #{gzbasename} > #{gzfullname}"
100
+ raise Errno::ENOENT, "Package file does not exist" unless File.exists?(gzfullname)
101
+ raise "Could not untar" unless system "tar xfz #{gzfullname} -C #{tmpdir}"
102
+
103
+ File.open(fullname) do |file|
104
+ while(line = file.gets)
105
+ line =~ %r{([a-zA-Z]+):\s(.*)}
106
+ controlfile[$1] = $2
107
+ end
108
+ end
109
+ ensure
110
+ FileUtils.rm_rf(tmpdir)
111
+ end
112
+ controlfile
113
+ end
114
+ end
115
+ end
116
+
@@ -0,0 +1,49 @@
1
+ # RepoMate module
2
+ module RepoMate
3
+
4
+ # Class for creating the repository structure
5
+ class Repository
6
+
7
+ attr_reader :categories
8
+
9
+ # Init
10
+ def initialize
11
+ @categories = ["stage", "pool", "dists"]
12
+ end
13
+
14
+ # Creates the base structure
15
+ def create(suitename=nil, component=nil, architecture=nil)
16
+ unless Suite.allowed.include?(suitename)
17
+ STDERR.puts "Suitename (#{suitename}) is not configured"
18
+ exit 1
19
+ end
20
+
21
+ unless Component.allowed.include?(component)
22
+ STDERR.puts "Component (#{component}) is not configured"
23
+ exit 1
24
+ end
25
+
26
+ unless architecture.nil?
27
+ unless Architecture.allowed.include?(architecture)
28
+ STDERR.puts "Architecture (#{architecture}) is not configured"
29
+ exit 1
30
+ end
31
+ end
32
+
33
+ @categories.each do |category|
34
+ if category.eql?("stage")
35
+ Component.new(component, suitename, category).create
36
+ else
37
+ if architecture && component && suitename
38
+ Architecture.new(architecture, component, suitename, category).create
39
+ elsif component && suitename
40
+ Component.new(component, suitename, category).create
41
+ elsif suitename.nil?
42
+ Suite.new(suitename, category).create
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,96 @@
1
+ # RepoMate module
2
+ module RepoMate
3
+
4
+ # Class for the suite layer of the directory structure
5
+ class Suite
6
+
7
+ # Init
8
+ def initialize(suitename, category)
9
+ @suitename = suitename
10
+ @category = category
11
+ end
12
+
13
+ # Returns the given suite name (eg. lenny, squeeze)
14
+ def name
15
+ @suitename
16
+ end
17
+
18
+ # Returns the directory strcuture of the suite including all lower layers
19
+ def directory
20
+ File.join(Cfg.rootdir, @category, @suitename)
21
+ end
22
+
23
+ # Checks if the suite directory exists
24
+ def exist?
25
+ Dir.exist?(directory)
26
+ end
27
+
28
+ # Checks if the suite is allowed (See: configurationfile)
29
+ def is_allowed?
30
+ self.allowed.include?(@suitename)
31
+ end
32
+
33
+ # Checks if directory is unused
34
+ def is_unused?(dir)
35
+ status = true
36
+
37
+ path = Dir.glob(File.join(dir, "*"))
38
+ path.each do |dirorfile|
39
+ status = false if File.directory?(dirorfile)
40
+ status = false if File.basename(dirorfile) =~ /\.deb$/
41
+ end
42
+
43
+ status
44
+ end
45
+
46
+ # Creates the directory strcuture of the suite including all lower layers
47
+ def create
48
+ FileUtils.mkdir_p(directory) unless exist?
49
+ end
50
+
51
+ # Deletes the suites directory including all lower layers
52
+ def destroy
53
+ FileUtils.rm_r(directory) if exist?
54
+ end
55
+
56
+ # Returns a dataset including the name of the suite, the fullpath recursive through all lower layers
57
+ def self.dataset(category=nil)
58
+ data = []
59
+ self.all.each do |entry|
60
+ # p entry
61
+ parts = entry.split(/\//)
62
+ unless parts.length < 2
63
+ next unless parts[0].eql?(category) || category.eql?("all")
64
+
65
+ data << {
66
+ :category => parts[0],
67
+ :suitename => parts[1],
68
+ :fullpath => File.join(Cfg.rootdir, entry),
69
+ }
70
+ end
71
+ end
72
+ data
73
+ end
74
+
75
+ # Returns all directories without @rootdir
76
+ def self.all
77
+ categories = Category.all
78
+ dirs = []
79
+ rootdir = Cfg.rootdir
80
+ categories.each do |category|
81
+ suites = Dir.glob(File.join(rootdir, category, "*"))
82
+ suites.each do |suite|
83
+ dirs.push suite.gsub(/#{rootdir}\//, '') if File.directory? suite
84
+ end
85
+ end
86
+ return dirs
87
+ end
88
+
89
+ # Gets all configured architectures
90
+ def self.allowed
91
+ Cfg.suites.uniq
92
+ end
93
+
94
+ end
95
+ end
96
+
@@ -0,0 +1,6 @@
1
+ Archive: stable
2
+ Component: <%= entry[:component] %>
3
+ Origin: <%= Cfg.origin %>
4
+ Label: <%= Cfg.label %>
5
+ Architecture: <%= entry[:architecture]%>
6
+ Description: Repository for debian <%= entry[:suitename]%>
@@ -0,0 +1,6 @@
1
+ Size: <%= size %>
2
+ Filename: <%= path %>
3
+ MD5sum: <%= checksums[0] %>
4
+ SHA1: <%= checksums[1] %>
5
+ SHA256: <%= checksums[2] %>
6
+
@@ -0,0 +1,41 @@
1
+ Origin: <%= Cfg.origin %>
2
+ Label: <%= Cfg.label %>
3
+ Suite: stable
4
+ Codename: <%= suite %>
5
+ Date: <%= now %>
6
+ Architectures: <%= architecture.join(', ') %>
7
+ Components: <%= component.join(', ') %>
8
+ Description: Repository for debian
9
+ MD5Sum:
10
+ <% Architecture.dataset(source_category).each do |entry| %>
11
+ <% source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category) %>
12
+ <% Dir.glob(File.join(entry[:fullpath], "Packages*")).each do |file| %>
13
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
14
+ <%= Digest::MD5.file(file) %> <%= File.size(file) %> <%= path %>
15
+ <% end %>
16
+ <% file = File.join(entry[:fullpath], "Release") %>
17
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
18
+ <%= Digest::MD5.file(file) %> <%= File.size(file) %> <%= path %>
19
+ <% end %>
20
+ SHA1:
21
+ <% Architecture.dataset(source_category).each do |entry| %>
22
+ <% source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category) %>
23
+ <% Dir.glob(File.join(entry[:fullpath], "Packages*")).each do |file| %>
24
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
25
+ <%= Digest::SHA1.file(file) %> <%= File.size(file) %> <%= path %>
26
+ <% end %>
27
+ <% file = File.join(entry[:fullpath], "Release") %>
28
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
29
+ <%= Digest::SHA1.file(file) %> <%= File.size(file) %> <%= path %>
30
+ <% end %>
31
+ SHA256:
32
+ <% Architecture.dataset(source_category).each do |entry| %>
33
+ <% source = Architecture.new(entry[:architecture], entry[:component], entry[:suitename], source_category) %>
34
+ <% Dir.glob(File.join(entry[:fullpath], "Packages*")).each do |file| %>
35
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
36
+ <%= Digest::SHA256.new(256).file(file) %> <%= File.size(file) %> <%= path %>
37
+ <% end %>
38
+ <% file = File.join(entry[:fullpath], "Release") %>
39
+ <% path = File.join(entry[:component], entry[:architecture_dir], File.basename(file)) %>
40
+ <%= Digest::SHA256.new(256).file(file) %> <%= File.size(file) %> <%= path %>
41
+ <% end %>
data/lib/repomate.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'repomate/architecture'
4
+ require 'repomate/base'
5
+ require 'repomate/category'
6
+ require 'repomate/cli'
7
+ require 'repomate/component'
8
+ require 'repomate/configuration'
9
+ require 'repomate/database'
10
+ require 'repomate/metafile'
11
+ require 'repomate/package'
12
+ require 'repomate/repository'
13
+ require 'repomate/suite'
14
+