repo-mgr 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +75 -0
- data/bin/repo-mgr +6 -0
- data/lib/repo_mgr.rb +3 -0
- data/lib/repo_mgr/backends.rb +19 -0
- data/lib/repo_mgr/backends/deb.rb +192 -0
- data/lib/repo_mgr/backends/rpm.rb +126 -0
- data/lib/repo_mgr/cli.rb +147 -0
- data/lib/repo_mgr/config.rb +71 -0
- data/lib/repo_mgr/tools.rb +26 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1e6af886ad6d5adea7626716153f8da4e15edce28bf5300ba671e708f57e0b5a
|
4
|
+
data.tar.gz: 71f3930291d0c7f84bd1bd80507560ec0f0886bbd0f6c8972c7e1628886d7f61
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f1f43472590d082815bd16d718bac5d2dca91502789a957c7b21ee5ba87a2dbec59e80455c986bb9a208802e8adabf85e8b73d0cc7641cf8ef1dd44317a95f77
|
7
|
+
data.tar.gz: a65d9039b45515777e19a0b5c722b5b184781eafaae21d20e11f0e849c0a542d74988a9cd2fdb1f89584ee3ec15957a5180ba7da483bef6977d6a9080a2d742b
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Ștefan Rusu
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
## About
|
2
|
+
|
3
|
+
deb and rpm repository management tool. Essentially, this is a frontend for a suite of tools provided by various distribution maintainers.
|
4
|
+
|
5
|
+
repo-mgr provides a unified and consistent way for managing various repositories (deb, rpm).
|
6
|
+
|
7
|
+
Features:
|
8
|
+
|
9
|
+
* Create/update deb/rpm repositories.
|
10
|
+
* Add/remove packages to these repositories and automatically sign packages using GPG.
|
11
|
+
* Repository metadata/manifest signing using GPG.
|
12
|
+
|
13
|
+
To simplify things, aptly (which, kind of obviously, manages deb repositories) uses "stable" as distribution and "main" as component.
|
14
|
+
|
15
|
+
## Install
|
16
|
+
|
17
|
+
```bash
|
18
|
+
# RubyGems
|
19
|
+
gem install repo-mgr
|
20
|
+
|
21
|
+
# from source
|
22
|
+
rake install
|
23
|
+
```
|
24
|
+
|
25
|
+
As repo-mgr is a frontend for other tools, there's dependencies which must be installed separately.
|
26
|
+
|
27
|
+
To check which dependencies are required and their status:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
repo-mgr check-depends
|
31
|
+
+------------+--------+
|
32
|
+
| Binary | Status |
|
33
|
+
+------------+--------+
|
34
|
+
| aptly | ✔ |
|
35
|
+
| dpkg-sig | ✔ |
|
36
|
+
| createrepo | ✔ |
|
37
|
+
| rpm | ✔ |
|
38
|
+
+------------+--------+
|
39
|
+
```
|
40
|
+
|
41
|
+
For managing deb repositories:
|
42
|
+
|
43
|
+
```bash
|
44
|
+
sudo apt install aptly dpkg-sig
|
45
|
+
```
|
46
|
+
|
47
|
+
For managing rpm repositories:
|
48
|
+
|
49
|
+
```bash
|
50
|
+
sudo apt install createrepo rpm
|
51
|
+
```
|
52
|
+
|
53
|
+
n.b `createrepo` is not normally available for Debian and derrivates (including Ubuntu). This tool
|
54
|
+
has been used to bootstrap a deb repository which includes a `createrepo` build for Ubuntu 20.04,
|
55
|
+
therefore creating a dependency upon itself for setting up rpm repositories.
|
56
|
+
|
57
|
+
You can get our build of createrepo from our [deb repository](https://deb.staker.ltd/).
|
58
|
+
|
59
|
+
## How to use
|
60
|
+
|
61
|
+
```bash
|
62
|
+
# to get you started
|
63
|
+
repo-mgr help
|
64
|
+
|
65
|
+
# create repo
|
66
|
+
## --path => a local directory where the repository is published - no remote support at the moment
|
67
|
+
## GPGKEYID is expected as log keyid i.e 16 hex chars
|
68
|
+
repo-mgr upsert-repo --name foo --type deb --path path/to/foo --keyid GPGKEYID
|
69
|
+
|
70
|
+
# sign package, add to repository, and update local repo (includes sign repo release manifest)
|
71
|
+
# the local repo is exported to the path indicated in upsert-repo
|
72
|
+
repo-mgr add-pkg --repo foo --path path/to/bar_0.0.1_amd64.deb
|
73
|
+
```
|
74
|
+
|
75
|
+
You then need to sync the repo to whatever desired target. For the time being, this isn't implemented as the main use case for this tool is publishing into a git repository which is served as GitHub page.
|
data/bin/repo-mgr
ADDED
data/lib/repo_mgr.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require_relative 'backends/deb'
|
4
|
+
require_relative 'backends/rpm'
|
5
|
+
|
6
|
+
module RepoMgr
|
7
|
+
# factory loader for RepoMgr::Backend::Foo objects
|
8
|
+
class Backends
|
9
|
+
def self.load(backend, config)
|
10
|
+
@obj ||= {}
|
11
|
+
|
12
|
+
@obj[backend] ||= Object.const_get(
|
13
|
+
"RepoMgr::Backend::#{backend.capitalize}"
|
14
|
+
).new(config)
|
15
|
+
|
16
|
+
@obj[backend]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'open3'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module RepoMgr
|
8
|
+
module Backend
|
9
|
+
# deb backend handler implemented on top of aptly
|
10
|
+
class Deb
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
init_aptly_config
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_repo(name)
|
17
|
+
return if aptly_repos.include? name
|
18
|
+
|
19
|
+
cmd = "aptly -config=#{@aptly_config_file} repo create #{name}"
|
20
|
+
out, status = Open3.capture2e cmd
|
21
|
+
|
22
|
+
unless status.exitstatus.zero?
|
23
|
+
Tools.error "aptly repo create failed with:\n#{out}"
|
24
|
+
end
|
25
|
+
|
26
|
+
repo_config = @config.cfg[:repos][name]
|
27
|
+
|
28
|
+
@aptly_config['FileSystemPublishEndpoints'][name] = {
|
29
|
+
rootDir: repo_config[:path],
|
30
|
+
linkMethod: 'copy',
|
31
|
+
verifyMethod: 'md5'
|
32
|
+
}
|
33
|
+
|
34
|
+
save_aptly_config
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_pkg(repo, pkg)
|
38
|
+
sign_pkg repo, pkg
|
39
|
+
repo_add repo, pkg
|
40
|
+
repo_publish repo
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_pkg(repo, pkg)
|
44
|
+
repo_rm repo, pkg
|
45
|
+
repo_publish repo
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_sig(pkg)
|
49
|
+
out, status = Open3.capture2e "dpkg-sig --verify #{pkg}"
|
50
|
+
|
51
|
+
return out if status.exitstatus.zero?
|
52
|
+
|
53
|
+
Tools.error "unable to check package signature for #{pkg} - "\
|
54
|
+
"dpkg-sig returned:\n#{out}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def sign_pkg(repo, pkg)
|
58
|
+
signature = check_sig(pkg)
|
59
|
+
unless signature.first[-6, 5] == 'NOSIG'
|
60
|
+
return puts "-- dpkg-sig returned:\n#{signature.first}"
|
61
|
+
end
|
62
|
+
|
63
|
+
if @config.cfg[:repos][repo].nil?
|
64
|
+
Tools.error "unable to find #{repo} repository"
|
65
|
+
end
|
66
|
+
|
67
|
+
keyid = @config.cfg[:repos][repo][:keyid]
|
68
|
+
out, status = Open3.capture2e "dpkg-sig -k #{keyid} -s builder #{pkg}"
|
69
|
+
|
70
|
+
return if status.exitstatus.zero?
|
71
|
+
|
72
|
+
Tools.error "unable to sign #{pkg} - dpkg-sig returned:\n#{out}"
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def init_aptly_config
|
78
|
+
@aptly_root = "#{@config.cfg_dir}/aptly"
|
79
|
+
@aptly_config_file = "#{@config.cfg_dir}/aptly.json"
|
80
|
+
|
81
|
+
unless File.exist? @aptly_config_file
|
82
|
+
File.write @aptly_config_file, aptly_base_config.to_json
|
83
|
+
end
|
84
|
+
|
85
|
+
@aptly_config = JSON.parse File.read(@aptly_config_file)
|
86
|
+
end
|
87
|
+
|
88
|
+
# rubocop:disable Metrics/MethodLength
|
89
|
+
def aptly_base_config
|
90
|
+
FileUtils.mkdir_p @aptly_root
|
91
|
+
|
92
|
+
{
|
93
|
+
rootDir: @aptly_root,
|
94
|
+
downloadConcurrency: 4,
|
95
|
+
downloadSpeedLimit: 0,
|
96
|
+
architectures: [],
|
97
|
+
dependencyFollowSuggests: false,
|
98
|
+
dependencyFollowRecommends: false,
|
99
|
+
dependencyFollowAllVariants: false,
|
100
|
+
dependencyFollowSource: false,
|
101
|
+
dependencyVerboseResolve: false,
|
102
|
+
gpgDisableSign: false,
|
103
|
+
gpgDisableVerify: false,
|
104
|
+
# despite the binary being gpg, this must spell gpg2, otherwise aptly
|
105
|
+
# defaults to gpg1 with less than impressive results
|
106
|
+
gpgProvider: 'gpg2',
|
107
|
+
downloadSourcePackages: false,
|
108
|
+
skipLegacyPool: true,
|
109
|
+
ppaDistributorID: '',
|
110
|
+
ppaCodename: '',
|
111
|
+
skipContentsPublishing: false,
|
112
|
+
FileSystemPublishEndpoints: {},
|
113
|
+
S3PublishEndpoints: {},
|
114
|
+
SwiftPublishEndpoints: {}
|
115
|
+
}
|
116
|
+
end
|
117
|
+
# rubocop:enable Metrics/MethodLength
|
118
|
+
|
119
|
+
def aptly_repos
|
120
|
+
cmd = "aptly -raw -config=#{@aptly_config_file} repo list"
|
121
|
+
out, status = Open3.capture2e cmd
|
122
|
+
|
123
|
+
unless status.exitstatus.zero?
|
124
|
+
Tools.error "aptly repo list failed with:\n#{out}"
|
125
|
+
end
|
126
|
+
|
127
|
+
out.split("\n")
|
128
|
+
end
|
129
|
+
|
130
|
+
def aptly_published_repos
|
131
|
+
cmd = "aptly -raw -config=#{@aptly_config_file} publish list"
|
132
|
+
out, status = Open3.capture2e cmd
|
133
|
+
|
134
|
+
unless status.exitstatus.zero?
|
135
|
+
Tools.error "aptly publish list failed with:\n#{out}"
|
136
|
+
end
|
137
|
+
|
138
|
+
out.split("\n")
|
139
|
+
end
|
140
|
+
|
141
|
+
def aptly_publish_drop(repo)
|
142
|
+
cmd = "aptly -config=#{@aptly_config_file} publish drop stable "\
|
143
|
+
"filesystem:#{repo}:"
|
144
|
+
out, status = Open3.capture2e cmd
|
145
|
+
|
146
|
+
return if status.exitstatus.zero?
|
147
|
+
|
148
|
+
Tools.error "aptly publish drop failed with:\n#{out}"
|
149
|
+
end
|
150
|
+
|
151
|
+
def save_aptly_config
|
152
|
+
File.write @aptly_config_file, @aptly_config.to_json
|
153
|
+
end
|
154
|
+
|
155
|
+
def repo_add(repo, pkg)
|
156
|
+
cmd = "aptly -config=#{@aptly_config_file} repo add #{repo} #{pkg}"
|
157
|
+
out, status = Open3.capture2e cmd
|
158
|
+
|
159
|
+
return if status.exitstatus.zero?
|
160
|
+
|
161
|
+
Tools.error "aptly repo add failed with:\n#{out}"
|
162
|
+
end
|
163
|
+
|
164
|
+
def repo_rm(repo, pkg)
|
165
|
+
package = File.basename pkg, File.extname(pkg)
|
166
|
+
cmd = "aptly -config=#{@aptly_config_file} repo remove "\
|
167
|
+
"#{repo} #{package}"
|
168
|
+
out, status = Open3.capture2e cmd
|
169
|
+
|
170
|
+
return if status.exitstatus.zero?
|
171
|
+
|
172
|
+
Tools.error "aptly repo remove failed with:\n#{out}"
|
173
|
+
end
|
174
|
+
|
175
|
+
def repo_publish(repo)
|
176
|
+
if aptly_published_repos.include? "filesystem:#{repo}:. stable"
|
177
|
+
aptly_publish_drop(repo)
|
178
|
+
end
|
179
|
+
|
180
|
+
keyid = @config.cfg[:repos][repo][:keyid]
|
181
|
+
cmd = "aptly -config=#{@aptly_config_file} -distribution=stable "\
|
182
|
+
"-gpg-key=#{keyid} publish repo #{repo} filesystem:#{repo}:"
|
183
|
+
|
184
|
+
out, status = Open3.capture2e cmd
|
185
|
+
|
186
|
+
return if status.exitstatus.zero?
|
187
|
+
|
188
|
+
Tools.error "aptly publish repo failed with:\n#{out}"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'gpgme'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module RepoMgr
|
8
|
+
module Backend
|
9
|
+
# rpm backend handler
|
10
|
+
class Rpm
|
11
|
+
def initialize(config)
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
# this is managed from RepoMgr::Config
|
16
|
+
def add_repo(_name); end
|
17
|
+
|
18
|
+
def add_pkg(repo, pkg)
|
19
|
+
sign_pkg repo, pkg
|
20
|
+
|
21
|
+
arch = extract_arch pkg
|
22
|
+
dest_dir = "#{@config.cfg_dir}/rpms/#{repo}/#{arch}"
|
23
|
+
|
24
|
+
FileUtils.mkdir_p dest_dir
|
25
|
+
FileUtils.cp pkg, dest_dir
|
26
|
+
|
27
|
+
sync_repo repo
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_pkg(repo, pkg)
|
31
|
+
name = File.basename pkg
|
32
|
+
arch = extract_arch pkg
|
33
|
+
dest_dir = "#{@config.cfg_dir}/rpms/#{repo}/#{arch}"
|
34
|
+
|
35
|
+
FileUtils.rm_f "#{dest_dir}/#{name}"
|
36
|
+
|
37
|
+
sync_repo repo
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_sig(pkg)
|
41
|
+
out, status = Open3.capture2e "rpm -K #{pkg}"
|
42
|
+
|
43
|
+
return out if status.exitstatus.zero?
|
44
|
+
|
45
|
+
Tools.error "unable to check package signature for #{pkg} - "\
|
46
|
+
"rpm -K returned:\n#{out}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def sign_pkg(repo, pkg)
|
50
|
+
keyid = @config.cfg[:repos][repo][:keyid]
|
51
|
+
gpg_name = GPGME::Key.find(keyid).first.uids.first.uid
|
52
|
+
|
53
|
+
# need to deal with the %_gpg_name nonsense as adding that via CLI is
|
54
|
+
# too bloody much for ARRRRRRRRRR PM - also who in their right mind
|
55
|
+
# would target a key by name / email rather than, you know, key ID
|
56
|
+
|
57
|
+
rpm_macros = "#{ENV['HOME']}/.rpmmacros"
|
58
|
+
File.write rpm_macros, "%_gpg_name #{gpg_name}"
|
59
|
+
|
60
|
+
# gpg-agent? nah - rpm is special
|
61
|
+
cmd = "rpm --addsign #{pkg}"
|
62
|
+
|
63
|
+
out, status = Open3.capture2e cmd
|
64
|
+
|
65
|
+
FileUtils.rm_f rpm_macros
|
66
|
+
|
67
|
+
return if status.exitstatus.zero?
|
68
|
+
|
69
|
+
Tools.error "unable to sign #{pkg} - rpm --addsign returned:\n#{out}"
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def extract_arch(pkg)
|
75
|
+
pkg.split('.')[-2]
|
76
|
+
end
|
77
|
+
|
78
|
+
def sync_repo(repo)
|
79
|
+
repo_dir = @config.cfg[:repos][repo][:path]
|
80
|
+
|
81
|
+
Dir["#{@config.cfg_dir}/rpms/#{repo}/*"].each do |arch_dir|
|
82
|
+
arch = File.basename arch_dir
|
83
|
+
arch_dest = "#{repo_dir}/#{arch}"
|
84
|
+
|
85
|
+
FileUtils.rm_rf arch_dest
|
86
|
+
FileUtils.mkdir_p arch_dest
|
87
|
+
|
88
|
+
Dir["#{@config.cfg_dir}/rpms/#{repo}/#{arch}/*.rpm"].each do |rpm|
|
89
|
+
FileUtils.cp rpm, arch_dest
|
90
|
+
end
|
91
|
+
|
92
|
+
Dir.chdir arch_dest do
|
93
|
+
build_repo arch
|
94
|
+
Dir.chdir('repodata') { sign_repo repo }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_repo(arch)
|
100
|
+
cmd = 'createrepo --zck --verbose --update .'
|
101
|
+
|
102
|
+
out, status = Open3.capture2e cmd
|
103
|
+
|
104
|
+
return if status.exitstatus.zero?
|
105
|
+
|
106
|
+
Tools.error "unable to create repo for #{arch} - createrepo "\
|
107
|
+
"returned:\n#{out}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def sign_repo(repo)
|
111
|
+
keyid = @config.cfg[:repos][repo][:keyid]
|
112
|
+
|
113
|
+
data = GPGME::Data.new File.read('repomd.xml')
|
114
|
+
opt = {
|
115
|
+
armor: true,
|
116
|
+
signer: keyid,
|
117
|
+
mode: GPGME::SIG_MODE_DETACH
|
118
|
+
}
|
119
|
+
|
120
|
+
signature = GPGME::Crypto.sign data, opt
|
121
|
+
|
122
|
+
File.write 'repomd.xml.asc', signature
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/repo_mgr/cli.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'colored'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'terminal-table'
|
7
|
+
|
8
|
+
require_relative 'tools'
|
9
|
+
require_relative 'config'
|
10
|
+
require_relative 'backends'
|
11
|
+
|
12
|
+
module RepoMgr
|
13
|
+
# implements CLI interface
|
14
|
+
class CLI < Thor
|
15
|
+
def self.exit_on_failure?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.types
|
20
|
+
%w[deb rpm]
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'check-depends', 'Check dependencies'
|
24
|
+
|
25
|
+
def check_depends
|
26
|
+
rows = []
|
27
|
+
|
28
|
+
%w[aptly dpkg-sig createrepo rpm].each do |bin_dep|
|
29
|
+
rows << if Tools.which bin_dep
|
30
|
+
[bin_dep, '✔'.green]
|
31
|
+
else
|
32
|
+
[bin_dep, '✘'.red]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
puts Terminal::Table.new headings: %w[Binary Status], rows: rows
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'upsert-repo', 'Create/Update new repository'
|
40
|
+
option :name, type: :string, required: true, aliases: %w[-n],
|
41
|
+
desc: 'Name of repository to add'
|
42
|
+
option :type, type: :string, required: true, aliases: %w[-t],
|
43
|
+
enum: types, desc: 'Package type'
|
44
|
+
option :path, type: :string, required: true, aliases: %w[-p],
|
45
|
+
desc: 'Directory path where to build the repository'
|
46
|
+
option :keyid, type: :string, required: true, aliases: %w[-k],
|
47
|
+
desc: 'GPG key id used to sign the repository metadata'
|
48
|
+
|
49
|
+
def upsert_repo
|
50
|
+
FileUtils.mkdir_p options[:path]
|
51
|
+
|
52
|
+
config = Config.new
|
53
|
+
config.upsert_repo options[:name], options[:type], options[:path],
|
54
|
+
options[:keyid]
|
55
|
+
|
56
|
+
backend = Backends.load options[:type], config
|
57
|
+
backend.add_repo options[:name]
|
58
|
+
|
59
|
+
puts "-- Upserted #{options[:name]} repository"
|
60
|
+
end
|
61
|
+
|
62
|
+
desc 'list-repos', 'List existing repositories'
|
63
|
+
|
64
|
+
def list_repos
|
65
|
+
rows = []
|
66
|
+
config = Config.new
|
67
|
+
|
68
|
+
config.cfg[:repos].each do |name, repo|
|
69
|
+
rows << [name, repo[:type], repo[:path], repo[:keyid]]
|
70
|
+
end
|
71
|
+
|
72
|
+
return puts '-- No repos have been created' if rows.count.zero?
|
73
|
+
|
74
|
+
puts Terminal::Table.new headings: %w[Name Type Path KeyID], rows: rows
|
75
|
+
end
|
76
|
+
|
77
|
+
desc 'add-pkg', 'Add package to repository'
|
78
|
+
option :repo, type: :string, required: true, aliases: %w[-r],
|
79
|
+
desc: 'The repository to add the package to'
|
80
|
+
option :path, type: :string, required: true, aliases: %w[-p],
|
81
|
+
desc: 'Path to the package to add to a repo'
|
82
|
+
|
83
|
+
def add_pkg
|
84
|
+
backend, config = load_backend options[:path]
|
85
|
+
backend.add_pkg options[:repo], options[:path]
|
86
|
+
config.add_pkg options[:repo], options[:path]
|
87
|
+
|
88
|
+
puts "-- Added #{File.basename(options[:path])} to "\
|
89
|
+
"#{options[:repo]} repository"
|
90
|
+
end
|
91
|
+
|
92
|
+
desc 'list-pkgs', 'List repository packages'
|
93
|
+
option :repo, type: :string, required: true, aliases: %w[-r],
|
94
|
+
desc: 'The repository to list the packages from'
|
95
|
+
|
96
|
+
def list_pkgs
|
97
|
+
packages = Config.new.cfg[:repos][options[:repo]][:packages]
|
98
|
+
|
99
|
+
if packages.nil?
|
100
|
+
Tools.error "#{options[:repo]} repo does not have any packages"
|
101
|
+
end
|
102
|
+
|
103
|
+
rows = packages.sort.each_with_index.map { |e, i| [i + 1, e] }
|
104
|
+
|
105
|
+
puts Terminal::Table.new headings: ['#', "Packages in #{options[:repo]}"],
|
106
|
+
rows: rows
|
107
|
+
end
|
108
|
+
|
109
|
+
desc 'remove-pkg', 'Remove a package from a repository'
|
110
|
+
option :repo, type: :string, required: true, aliases: %w[-r],
|
111
|
+
desc: 'The repository to add the package to'
|
112
|
+
option :path, type: :string, required: true, aliases: %w[-p],
|
113
|
+
desc: 'Path to the package to add to a repo'
|
114
|
+
|
115
|
+
def remove_pkg
|
116
|
+
backend, config = load_backend options[:path]
|
117
|
+
backend.remove_pkg options[:repo], options[:path]
|
118
|
+
config.remove_pkg options[:repo], options[:path]
|
119
|
+
|
120
|
+
puts "-- Removed #{File.basename(options[:path])} from "\
|
121
|
+
"#{options[:repo]} repository"
|
122
|
+
end
|
123
|
+
|
124
|
+
desc 'check-sig', 'Check package signature'
|
125
|
+
option :path, type: :string, required: true, aliases: %w[-p],
|
126
|
+
desc: 'Path to the the package to check signature'
|
127
|
+
|
128
|
+
def check_sig
|
129
|
+
backend, _config = load_backend options[:path]
|
130
|
+
puts backend.check_sig options[:path]
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def load_backend(path)
|
136
|
+
type = File.extname(path).strip.downcase[1..-1]
|
137
|
+
|
138
|
+
unless CLI.types.include? type
|
139
|
+
Tools.error "unsupported package type #{type}"
|
140
|
+
end
|
141
|
+
|
142
|
+
config = Config.new
|
143
|
+
|
144
|
+
[Backends.load(type, config), config]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
require_relative 'tools'
|
7
|
+
|
8
|
+
module RepoMgr
|
9
|
+
# handles repo-mgr configuration
|
10
|
+
class Config
|
11
|
+
attr_reader :cfg, :cfg_dir
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@cfg_dir = "#{ENV['HOME']}/.repo-mgr"
|
15
|
+
FileUtils.mkdir_p @cfg_dir
|
16
|
+
|
17
|
+
@cfg_file = "#{@cfg_dir}/repo-mgr.yml"
|
18
|
+
File.write @cfg_file, { repos: {} }.to_yaml unless File.exist? @cfg_file
|
19
|
+
|
20
|
+
@cfg = YAML.load_file @cfg_file
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
File.write @cfg_file, @cfg.to_yaml
|
25
|
+
end
|
26
|
+
|
27
|
+
def upsert_repo(name, type, path, keyid)
|
28
|
+
if @cfg[:repos][name] && @cfg[:repos][name][:type] != type
|
29
|
+
Tools.error "unable to change type for #{name} repository"
|
30
|
+
end
|
31
|
+
|
32
|
+
@cfg[:repos][name] = {
|
33
|
+
type: type,
|
34
|
+
path: path,
|
35
|
+
keyid: keyid
|
36
|
+
}
|
37
|
+
|
38
|
+
save
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_pkg(repo, path)
|
42
|
+
if @cfg[:repos][repo].nil?
|
43
|
+
Tools.error "unable to add packages to #{repo} - repo does not exist"
|
44
|
+
end
|
45
|
+
|
46
|
+
@cfg[:repos][repo][:packages] ||= []
|
47
|
+
pkg = File.basename path
|
48
|
+
|
49
|
+
if @cfg[:repos][repo][:packages].include?(pkg)
|
50
|
+
Tools.error "you already have #{pkg} in your #{repo} repo"
|
51
|
+
end
|
52
|
+
|
53
|
+
@cfg[:repos][repo][:packages] << pkg
|
54
|
+
|
55
|
+
save
|
56
|
+
end
|
57
|
+
|
58
|
+
def remove_pkg(repo, path)
|
59
|
+
if @cfg[:repos][repo].nil?
|
60
|
+
Tools.error "unable to remove packages from #{repo} "\
|
61
|
+
'- repo does not exist'
|
62
|
+
end
|
63
|
+
|
64
|
+
@cfg[:repos][repo][:packages] ||= []
|
65
|
+
pkg = File.basename path
|
66
|
+
@cfg[:repos][repo][:packages].delete pkg
|
67
|
+
|
68
|
+
save
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'colored'
|
4
|
+
|
5
|
+
module RepoMgr
|
6
|
+
# Holds various tools
|
7
|
+
class Tools
|
8
|
+
def self.which(cmd)
|
9
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
10
|
+
|
11
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
12
|
+
exts.each do |ext|
|
13
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
14
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.error(msg)
|
22
|
+
warn "-- Error: #{msg}".red
|
23
|
+
Kernel.exit 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: repo-mgr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ștefan Rusu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-04-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colored
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: gpgme
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: terminal-table
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.1'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: jeweler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 13.0.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 13.0.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: deb and rpm repository manager
|
126
|
+
email: saltwaterc@gmail.com
|
127
|
+
executables:
|
128
|
+
- repo-mgr
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files:
|
131
|
+
- LICENSE
|
132
|
+
- README.md
|
133
|
+
files:
|
134
|
+
- LICENSE
|
135
|
+
- README.md
|
136
|
+
- bin/repo-mgr
|
137
|
+
- lib/repo_mgr.rb
|
138
|
+
- lib/repo_mgr/backends.rb
|
139
|
+
- lib/repo_mgr/backends/deb.rb
|
140
|
+
- lib/repo_mgr/backends/rpm.rb
|
141
|
+
- lib/repo_mgr/cli.rb
|
142
|
+
- lib/repo_mgr/config.rb
|
143
|
+
- lib/repo_mgr/tools.rb
|
144
|
+
homepage: https://github.com/mr-staker/repo-mgr
|
145
|
+
licenses:
|
146
|
+
- MIT
|
147
|
+
metadata: {}
|
148
|
+
post_install_message:
|
149
|
+
rdoc_options: []
|
150
|
+
require_paths:
|
151
|
+
- lib
|
152
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
requirements: []
|
163
|
+
rubygems_version: 3.1.4
|
164
|
+
signing_key:
|
165
|
+
specification_version: 4
|
166
|
+
summary: deb and rpm repository manager
|
167
|
+
test_files: []
|