sgpg 0.1.0 → 1.0.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +29 -2
- data/README.md +50 -18
- data/bin/sgpg +26 -13
- data/lib/sgpg/archive.rb +34 -13
- data/lib/sgpg/cryptsetup.rb +10 -10
- data/lib/sgpg/helper.rb +28 -0
- data/lib/sgpg/incremental.rb +90 -0
- data/lib/sgpg/mount.rb +6 -6
- data/lib/sgpg/option.rb +1 -1
- data/lib/sgpg/version.rb +1 -1
- data/lib/sgpg/yaml_config.rb +3 -3
- data/lib/sgpg.rb +42 -15
- data.tar.gz.sig +0 -0
- metadata +16 -20
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2df301c36de68352a30454b4024038d9e7efa8816f6db7e332f250d40fdc607
|
|
4
|
+
data.tar.gz: 38f42188aec8bb77a2ab31f84d446427c1112c7a0a2cb829cafc6839ad71cf5a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc7c9e9530164fbab117cab1b255b59c3d20cb26b6996967f549041eabe4dc6a8c638c2ebea953ed58dcbfd540403d7fe17d0295499d7dc1f78047ddfa4c0ce6
|
|
7
|
+
data.tar.gz: 59dd16d8b4bb1327f683a887377c4ad35d63c6a0dbec57156b19766ccf6470624d18ca676aafb9f75351ff003ae0a153ea84508c70de4a6a4336ba9b618b8f1d
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## 1.0.0 - Oct. 2025
|
|
2
|
+
|
|
3
|
+
- New CLI options, `--encrypt`, `--no-encrypt`, `--export-pass`, `--import-pass`.
|
|
4
|
+
- Update the destination to `/mnt/sgpg/`. Use `/mnt/sgpg/Persistent` only if the directory exist (compatible with [Tail Linux](https://tails.net/doc/persistent_storage/index.en.html)).
|
|
5
|
+
- Create a directory `/mnt/sgpg/<key-name>/` for each key.
|
|
6
|
+
- Can save (export/import) passwords from [pass](https://www.passwordstore.org/) by incremental save
|
|
7
|
+
|
|
8
|
+
### Bug fixes
|
|
9
|
+
|
|
10
|
+
- Correct permission when move archive, we can't list keys with wrong permissions.
|
|
11
|
+
- Don't check for `*.cert` when create archives, only `*.key`
|
|
12
|
+
- Correct the last element returned from array for `--last-lesser` or `--last-master`
|
|
13
|
+
|
|
14
|
+
### Manual intervention
|
|
15
|
+
|
|
16
|
+
For those using a previous version, they need to manually import the last master key and re export keys:
|
|
17
|
+
|
|
18
|
+
sgpg --path-key /mnt/sgpg/<keymaster>.tar --edit-key
|
|
19
|
+
sgpg --export
|
|
20
|
+
|
|
21
|
+
## 0.1.0, release 09/10/24
|
|
22
|
+
|
|
23
|
+
- New `.github/workflow` to publish package on GitHub.
|
|
24
|
+
- New command line options
|
|
25
|
+
- `--disk-encrypt` if need to use `cryptsetup` or not.
|
|
26
|
+
- `--export` create master and lesser archive.
|
|
27
|
+
|
|
1
28
|
## 0.0.1, release 07/27/24
|
|
2
|
-
|
|
3
|
-
|
|
29
|
+
|
|
30
|
+
- Initial push, code release!
|
data/README.md
CHANGED
|
@@ -1,31 +1,43 @@
|
|
|
1
|
-
#
|
|
2
|
-
Short gpg, tool for manage your gpg key (backup tarball, unprivileged keys, etc)
|
|
1
|
+
# sGPG
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
<div align="center">
|
|
4
|
+
<br/>
|
|
5
|
+
|
|
6
|
+
[](https://badge.fury.io/rb/sgpg)
|
|
7
|
+
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
Short GnuPG, tool for manage your GPG key (backup tarball, unprivileged keys, etc)
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
Followed my [post](https://szorfein.github.io/gpg/build-secure-gpg-key/) to
|
|
13
|
+
create a secure GnuPG key, I need to update my keys all the 6 month on each PC.
|
|
14
|
+
It's a very annoying task without scripts so I've develop this tool in
|
|
15
|
+
Ruby to gain in time and mental sanity :).
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
To start, you always need to owm/create a GnuPG key as well.
|
|
12
18
|
|
|
13
19
|
gpg --expert --full-generate-key
|
|
14
20
|
|
|
15
|
-
## Install
|
|
21
|
+
## Install sGPG locally
|
|
22
|
+
|
|
23
|
+
gem install --user-install sgpg
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
You also need to install some dependencies:
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
- Tar
|
|
28
|
+
- Cryptsetup (if you want to encrypt/decrypt the disk)
|
|
29
|
+
- Shred (to remove the master key efficiently)
|
|
30
|
+
- And GnuPG of course.
|
|
20
31
|
|
|
21
|
-
##
|
|
22
|
-
The config is located at ~/.config/sgpg/config.yml. You can use the command line with `--save`:
|
|
32
|
+
## Optional configuration
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
The config file is located at `~/.config/sgpg/config.yml`. You can use the command line with `--save`:
|
|
35
|
+
|
|
36
|
+
sgpg --disk /dev/sdc2 --encrypt --key szorfein@protonmail.com --save
|
|
25
37
|
|
|
26
38
|
You can register the disk/by-id or disk/by-uuid if you prefer.
|
|
27
39
|
|
|
28
|
-
sgpg --disk /dev/disk/by-id/wmn-0xXXXX-part2 --
|
|
40
|
+
sgpg --disk /dev/disk/by-id/wmn-0xXXXX-part2 --encrypt --save
|
|
29
41
|
|
|
30
42
|
## Usage
|
|
31
43
|
|
|
@@ -34,17 +46,37 @@ You can register the disk/by-id or disk/by-uuid if you prefer.
|
|
|
34
46
|
When subkeys expire:
|
|
35
47
|
|
|
36
48
|
sgpg --last-master --edit-key # update expired keys, change password, etc...
|
|
37
|
-
sgpg --export
|
|
49
|
+
sgpg --export # create master and lesser archive
|
|
38
50
|
sgpg --close # unmount and close disk
|
|
39
51
|
|
|
40
|
-
Import the last
|
|
52
|
+
Import the last unprivileged key (laptop and other)
|
|
41
53
|
|
|
42
54
|
sgpg --last-lesser --edit-key # trust (555)
|
|
43
55
|
sgpg --close # unmount and close disk
|
|
44
56
|
|
|
57
|
+
Manually choose an archive
|
|
58
|
+
|
|
59
|
+
sgpg --open # mount disk
|
|
60
|
+
sgpg --path-key /mnt/sgpg/Persistent/archive.tar --edit-key
|
|
61
|
+
|
|
62
|
+
Export your passwords created with the [pass](https://www.passwordstore.org/) tool:
|
|
63
|
+
|
|
64
|
+
sgpg --open # mount disk
|
|
65
|
+
sgpg --key szorfein@protonmail.com --export-pass
|
|
66
|
+
sgpg --close
|
|
67
|
+
|
|
68
|
+
## About
|
|
69
|
+
|
|
70
|
+
About GnuPG security in brief and what's this tool help you to manage:
|
|
71
|
+
|
|
72
|
+
- You don't need a passphrase to protect your master key (if your follow all advice)
|
|
73
|
+
- Never store your master key on your computer, store it on an encrypted device.
|
|
74
|
+
- Always use an unprivileged key on your working machine.
|
|
75
|
+
- Create short live keys for Sign, Encrypt and Auth, maximum 6 month (less is better).
|
|
76
|
+
- When importing your master keys, (try to) be offline.
|
|
77
|
+
|
|
45
78
|
## Gem push
|
|
46
79
|
|
|
47
80
|
gem login
|
|
48
81
|
gem build sgpg.gemspec
|
|
49
82
|
gem push sgpg-0.0.1.gem
|
|
50
|
-
|
data/bin/sgpg
CHANGED
|
@@ -16,11 +16,15 @@ OptionParser.new do |opts|
|
|
|
16
16
|
options[:disk] = dev
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
opts.on('--
|
|
19
|
+
opts.on('--encrypt', 'If we need to use cryptsetup to open the disk.') do
|
|
20
20
|
options[:crypted] = true
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
opts.on('-
|
|
23
|
+
opts.on('--no-encrypt', 'Disable disk encryption.') do
|
|
24
|
+
options[:crypted] = false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
opts.on('-k', '--key NAME', 'Specify the GPG key to use') do |name|
|
|
24
28
|
options[:keyname] = name
|
|
25
29
|
end
|
|
26
30
|
|
|
@@ -32,11 +36,7 @@ OptionParser.new do |opts|
|
|
|
32
36
|
Sgpg.list_keys(options[:keyname])
|
|
33
37
|
end
|
|
34
38
|
|
|
35
|
-
opts.on('-
|
|
36
|
-
Sgpg.open(options[:disk], options[:crypted])
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
opts.on('-p', '--path-key PATH', 'use archive PATH') do |path|
|
|
39
|
+
opts.on('-p', '--path-key PATH', 'Use archive PATH') do |path|
|
|
40
40
|
raise "path #{path} didn't found" unless File.exist? path
|
|
41
41
|
|
|
42
42
|
options[:keypath] = path
|
|
@@ -46,15 +46,19 @@ OptionParser.new do |opts|
|
|
|
46
46
|
options[:keypath] = Sgpg.last_key(options, 'master')
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
opts.on('--last-lesser', 'Use the last archive lesser (
|
|
49
|
+
opts.on('--last-lesser', 'Use the last archive lesser (secure key with no privileges)') do
|
|
50
50
|
options[:keypath] = Sgpg.last_key(options, 'lesser')
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
opts.on('-o', '--open', "Open and mount device disk at #{Sgpg::MOUNTPOINT}.") do
|
|
54
|
+
Sgpg.open(options[:disk], options[:crypted])
|
|
55
|
+
end
|
|
56
|
+
|
|
53
57
|
opts.on('-c', '--close', 'Unmount and close disk device.') do
|
|
54
58
|
Sgpg.close(options[:disk], options[:crypted])
|
|
55
59
|
end
|
|
56
60
|
|
|
57
|
-
opts.on('-s', '--save', 'Save current
|
|
61
|
+
opts.on('-s', '--save', 'Save current cli options in the config file') do
|
|
58
62
|
config_file.save(options)
|
|
59
63
|
end
|
|
60
64
|
|
|
@@ -62,11 +66,20 @@ OptionParser.new do |opts|
|
|
|
62
66
|
Sgpg::Main.export_secret(options)
|
|
63
67
|
Sgpg::Main.lesser_keys(options)
|
|
64
68
|
end
|
|
69
|
+
|
|
70
|
+
opts.on('--export-pass', 'Export your password-store from pass.') do
|
|
71
|
+
Sgpg::Main.incremental_export(options[:keyname])
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
opts.on('--import-pass', 'Import your password-store from pass.') do
|
|
75
|
+
Sgpg::Main.incremental_import(options[:keyname])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
opts.on('-v', '--version', 'Display the current version.') do
|
|
79
|
+
puts "sgpg v#{Sgpg::VERSION}"
|
|
80
|
+
exit
|
|
81
|
+
end
|
|
65
82
|
end.parse!
|
|
66
83
|
|
|
67
84
|
raise 'no disk to use' if !options[:disk] || options[:disk] == ''
|
|
68
85
|
raise 'no key to use' if !options[:keyname] || options[:keybase] == ''
|
|
69
|
-
|
|
70
|
-
puts config_file
|
|
71
|
-
|
|
72
|
-
puts "Sgpg v.#{Sgpg::VERSION}"
|
data/lib/sgpg/archive.rb
CHANGED
|
@@ -6,14 +6,16 @@ module Sgpg
|
|
|
6
6
|
# Interact with program tar from unix
|
|
7
7
|
class Archive
|
|
8
8
|
# Code here
|
|
9
|
-
def initialize(key, name =
|
|
9
|
+
def initialize(key, name = nil)
|
|
10
|
+
raise ArgumentError, "No #{name}..." unless name
|
|
11
|
+
|
|
10
12
|
@key = key || ''
|
|
11
13
|
@name = name
|
|
12
14
|
@date = Time.now.strftime('%Y-%m-%d')
|
|
13
15
|
puts "create key #{@name}-#{@date}-master.tar"
|
|
14
16
|
FileUtils.mkdir_p Sgpg::WORKDIR
|
|
15
17
|
@gpg = Gpg.new(@name)
|
|
16
|
-
#
|
|
18
|
+
# Make it compatible with Tails Linux
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def create_master_tar
|
|
@@ -57,20 +59,42 @@ module Sgpg
|
|
|
57
59
|
mv(tar, pathdir)
|
|
58
60
|
end
|
|
59
61
|
|
|
62
|
+
# Tail Linux create an encrypted partition with a directory named Persistent
|
|
63
|
+
def move_to_disk
|
|
64
|
+
dest = Helper.search_dest
|
|
65
|
+
final_dest = "#{dest}/#{@name}" # we add the key name
|
|
66
|
+
|
|
67
|
+
Helper.mkdir(final_dest)
|
|
68
|
+
Helper.chmod('0777', final_dest)
|
|
69
|
+
|
|
70
|
+
tar = Dir.glob("#{Sgpg::WORKDIR}/*.tar")
|
|
71
|
+
raise 'No archive found.' unless tar.length >= 1
|
|
72
|
+
|
|
73
|
+
mv(tar, final_dest)
|
|
74
|
+
end
|
|
75
|
+
|
|
60
76
|
private
|
|
61
77
|
|
|
62
78
|
def import_secret(keys)
|
|
63
|
-
keys.each
|
|
79
|
+
keys.each do |k|
|
|
80
|
+
puts "importing secret #{k}..."
|
|
81
|
+
system('gpg', '-a', '--yes', '--import', k) if k.match?(/secret/)
|
|
82
|
+
end
|
|
64
83
|
end
|
|
65
84
|
|
|
66
85
|
def import_public(keys)
|
|
67
|
-
keys.each
|
|
86
|
+
keys.each do |k|
|
|
87
|
+
puts "importing public #{k}..."
|
|
88
|
+
system('gpg', '-a', '--yes', '--import', k) if k.match?(/public/)
|
|
89
|
+
end
|
|
68
90
|
end
|
|
69
91
|
|
|
70
|
-
#
|
|
92
|
+
# Suffix should be 'master' or 'lesser' (keys without privilege)
|
|
71
93
|
def create_tar(suffix = 'master')
|
|
72
94
|
if suffix == 'master'
|
|
73
|
-
|
|
95
|
+
# In theory, you can create the certificate any time as you have the master key
|
|
96
|
+
#system("tar -cf #{@name}-#{@date}-#{suffix}-keys.tar *.key *.cert")
|
|
97
|
+
system("tar -cf #{@name}-#{@date}-#{suffix}-keys.tar *.key")
|
|
74
98
|
else
|
|
75
99
|
system("tar -cf #{@name}-#{@date}-#{suffix}-keys.tar *.key")
|
|
76
100
|
end
|
|
@@ -78,13 +102,10 @@ module Sgpg
|
|
|
78
102
|
|
|
79
103
|
def mv(tar, pathdir)
|
|
80
104
|
puts "Moving archive at #{pathdir}..."
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
tar.each { |f| system('sudo', 'mv', f, "#{pathdir}/") }
|
|
86
|
-
when :doas
|
|
87
|
-
tar.each { |f| system('doas', 'mv', f, "#{pathdir}/") }
|
|
105
|
+
tar.each do |f|
|
|
106
|
+
file = File.basename(f)
|
|
107
|
+
Helper.mv(f, pathdir)
|
|
108
|
+
Helper.chmod('0644', "#{pathdir}/#{file}")
|
|
88
109
|
end
|
|
89
110
|
end
|
|
90
111
|
end
|
data/lib/sgpg/cryptsetup.rb
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
# lib/cryptsetup.rb
|
|
4
4
|
|
|
5
5
|
module Sgpg
|
|
6
|
-
#
|
|
6
|
+
# Control program cryptsetup
|
|
7
7
|
class Cryptsetup
|
|
8
8
|
class InvalidDisk < StandardError; end
|
|
9
9
|
|
|
10
10
|
def initialize(disk)
|
|
11
|
-
raise "
|
|
11
|
+
raise "No disk #{disk} specified..." unless disk
|
|
12
12
|
|
|
13
13
|
@disk = disk
|
|
14
14
|
@mapname = 'sgpg'
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
#
|
|
17
|
+
# Tails Linux make persistent volume on second partition 'disk_name'2
|
|
18
18
|
def open
|
|
19
19
|
check_disk
|
|
20
20
|
|
|
@@ -48,21 +48,21 @@ module Sgpg
|
|
|
48
48
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
|
-
def open_with(prefix =
|
|
51
|
+
def open_with(prefix = nil)
|
|
52
52
|
if prefix
|
|
53
|
-
puts "
|
|
53
|
+
puts "Opening disk #{@disk} with #{prefix}..."
|
|
54
54
|
else
|
|
55
|
-
puts "
|
|
55
|
+
puts "Opening disk #{@disk}..."
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
raise "
|
|
58
|
+
raise "Unable to open #{@disk} #{prefix}" unless
|
|
59
59
|
system(prefix, 'cryptsetup', 'open', '--type', 'luks', @disk, @mapname)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
def close_with(prefix =
|
|
63
|
-
puts "
|
|
62
|
+
def close_with(prefix = nil)
|
|
63
|
+
puts "Closing disk #{@disk}..."
|
|
64
64
|
|
|
65
|
-
raise "
|
|
65
|
+
raise "Closing disk #{@disk} failed" unless
|
|
66
66
|
system(prefix, 'cryptsetup', 'close', @mapname)
|
|
67
67
|
end
|
|
68
68
|
end
|
data/lib/sgpg/helper.rb
CHANGED
|
@@ -10,5 +10,33 @@ module Sgpg
|
|
|
10
10
|
return :doas if File.exist?('/bin/doas') || File.exist?('/sbin/doas')
|
|
11
11
|
return :sudo if File.exist?('/bin/sudo') || File.exist?('/sbin/sudo')
|
|
12
12
|
end
|
|
13
|
+
|
|
14
|
+
def self.mv(src, dest)
|
|
15
|
+
case auth?
|
|
16
|
+
when :root then FileUtils.mv(src, dest)
|
|
17
|
+
when :sudo then system('sudo', 'mv', src, dest)
|
|
18
|
+
when :doas then system('doas', 'mv', src, dest)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.mkdir(path_dir)
|
|
23
|
+
case auth?
|
|
24
|
+
when :root then FileUtils.mkdir(path_dir)
|
|
25
|
+
when :sudo then system('sudo', 'mkdir', '-p', path_dir)
|
|
26
|
+
when :doas then system('doas', 'mkdir', '-p', path_dir)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.search_dest
|
|
31
|
+
Dir.exist?("#{Sgpg::KEYDIR}/Persistent") ? "#{Sgpg::KEYDIR}/Persistent" : Sgpg::KEYDIR
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.chmod(perm, file_path)
|
|
35
|
+
case auth?
|
|
36
|
+
when :root then FileUtils.chmod perm, file_path, verbose: true
|
|
37
|
+
when :sudo then system('sudo', 'chmod', perm, file_path)
|
|
38
|
+
when :doas then system('doas', 'chmod', perm, file_path)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
13
41
|
end
|
|
14
42
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require 'time'
|
|
6
|
+
|
|
7
|
+
# Exemple: https://peerdh.com/blogs/programming-insights/creating-a-ruby-gem-for-incremental-backup-strategies
|
|
8
|
+
module Sgpg
|
|
9
|
+
# Incremental save
|
|
10
|
+
class Incremental
|
|
11
|
+
attr_accessor :source, :destination, :last_backup_time
|
|
12
|
+
|
|
13
|
+
def initialize(source, destination)
|
|
14
|
+
#raise ArgumentError, 'No dest, missed to mount stockage disk?' unless Dir.exist?(destination)
|
|
15
|
+
|
|
16
|
+
@source = source
|
|
17
|
+
@destination = destination
|
|
18
|
+
@last_backup_time = read_last_backup_time
|
|
19
|
+
@need_full_backup = check_time(@last_backup_time)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def check_time(time)
|
|
23
|
+
return false unless time
|
|
24
|
+
|
|
25
|
+
t = time
|
|
26
|
+
t.is_a?(Time)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def perform_backup
|
|
30
|
+
files = @last_backup_time ? files_to_backup : full_backup
|
|
31
|
+
puts "Incremental backup #{@need_full_backup}"
|
|
32
|
+
|
|
33
|
+
copy_directory_structure(files)
|
|
34
|
+
copy_files(files)
|
|
35
|
+
write_backup_time
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def write_backup_time
|
|
41
|
+
return unless @destination.match?(/sgpg/)
|
|
42
|
+
|
|
43
|
+
pn = Pathname.new(@destination)
|
|
44
|
+
dest = "#{pn.dirname}/last-backup"
|
|
45
|
+
File.write(dest, Time.now)
|
|
46
|
+
|
|
47
|
+
@last_backup_time = Time.now
|
|
48
|
+
puts "last backup #{@last_backup_time}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def read_last_backup_time
|
|
52
|
+
return unless @destination.match?(/sgpg/)
|
|
53
|
+
|
|
54
|
+
pn = Pathname.new(@destination)
|
|
55
|
+
dest = "#{pn.dirname}/last-backup"
|
|
56
|
+
File.exist?(dest) ? Time.parse(File.read(dest)) : nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def full_backup
|
|
60
|
+
Dir.glob(File.join(@source, '**', '*')).select do |file|
|
|
61
|
+
File.file?(file)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def files_to_backup
|
|
66
|
+
Dir.glob(File.join(@source, '**', '*')).select do |file|
|
|
67
|
+
File.file?(file) && File.mtime(file) > @last_backup_time
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Skip files, only copy directories
|
|
72
|
+
def copy_directory_structure(files)
|
|
73
|
+
files.each do |file|
|
|
74
|
+
new_dir = file.sub(@source, @destination)
|
|
75
|
+
pn = Pathname.new(new_dir)
|
|
76
|
+
puts "add dir #{pn.dirname}"
|
|
77
|
+
FileUtils.mkdir_p(pn.dirname)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def copy_files(files)
|
|
82
|
+
files.each do |file|
|
|
83
|
+
#destination_path = File.join(@destination, File.basename(file))
|
|
84
|
+
destination_path = file.sub(@source, @destination)
|
|
85
|
+
puts "cpy #{file} to dest #{destination_path}"
|
|
86
|
+
FileUtils.cp(file, destination_path, preserve: true)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/sgpg/mount.rb
CHANGED
|
@@ -38,30 +38,30 @@ module Sgpg
|
|
|
38
38
|
|
|
39
39
|
protected
|
|
40
40
|
|
|
41
|
-
def open_with(prefix =
|
|
42
|
-
prefix
|
|
41
|
+
def open_with(prefix = nil)
|
|
42
|
+
prefix ? create_with_ruby : create_with_system(prefix)
|
|
43
43
|
|
|
44
44
|
raise "Unable to mount #{@disk}" unless
|
|
45
45
|
system(prefix, 'mount', '-t', 'ext4', @disk, Sgpg::MOUNTPOINT)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def close_with(prefix =
|
|
48
|
+
def close_with(prefix = nil)
|
|
49
49
|
raise "Unable to umount #{@disk}" unless
|
|
50
50
|
system(prefix, 'umount', Sgpg::MOUNTPOINT)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
private
|
|
54
54
|
|
|
55
|
-
def
|
|
55
|
+
def create_with_ruby
|
|
56
56
|
FileUtils.mkdir_p Sgpg::MOUNTPOINT
|
|
57
57
|
FileUtils.chown @user, @user, Sgpg::MOUNTPOINT, verbose: true
|
|
58
|
-
FileUtils.chmod
|
|
58
|
+
FileUtils.chmod 0755, Sgpg::MOUNTPOINT, verbose: true
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def create_with_system(prefix)
|
|
62
62
|
system(prefix, 'mkdir', '-p', Sgpg::MOUNTPOINT)
|
|
63
63
|
system(prefix, 'chown', "#{@user}:#{@user}", Sgpg::MOUNTPOINT)
|
|
64
|
-
system(prefix, 'chmod', '
|
|
64
|
+
system(prefix, 'chmod', '0755', Sgpg::MOUNTPOINT)
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
end
|
data/lib/sgpg/option.rb
CHANGED
data/lib/sgpg/version.rb
CHANGED
data/lib/sgpg/yaml_config.rb
CHANGED
|
@@ -8,12 +8,12 @@ module Sgpg
|
|
|
8
8
|
attr_reader :config
|
|
9
9
|
|
|
10
10
|
def initialize
|
|
11
|
-
@file_dir = "#{ENV['HOME']}/.config/sgpg"
|
|
11
|
+
@file_dir = ENV['XDG_CONFIG_HOME'] ? "#{ENV['XDG_CONFIG_HOME']}/sgpg" : "#{ENV['HOME']}/.config/sgpg"
|
|
12
12
|
@filename = 'config.yml'
|
|
13
13
|
@full_path = "#{@file_dir}/#{@filename}"
|
|
14
14
|
@config = {
|
|
15
|
-
disk:
|
|
16
|
-
keyname:
|
|
15
|
+
disk: nil,
|
|
16
|
+
keyname: nil,
|
|
17
17
|
crypted: false
|
|
18
18
|
}
|
|
19
19
|
end
|
data/lib/sgpg.rb
CHANGED
|
@@ -8,10 +8,11 @@ require_relative 'sgpg/cryptsetup'
|
|
|
8
8
|
require_relative 'sgpg/mount'
|
|
9
9
|
require_relative 'sgpg/gpg'
|
|
10
10
|
require_relative 'sgpg/archive'
|
|
11
|
+
require_relative 'sgpg/incremental'
|
|
11
12
|
|
|
12
|
-
# Manage your gpg key
|
|
13
|
+
# Manage your gpg key
|
|
13
14
|
module Sgpg
|
|
14
|
-
def self.open(disk, is_crypted =
|
|
15
|
+
def self.open(disk, is_crypted = nil)
|
|
15
16
|
return if Dir.glob("#{Sgpg::MOUNTPOINT}/*").length >= 1
|
|
16
17
|
|
|
17
18
|
puts "Open device #{disk}..."
|
|
@@ -23,7 +24,7 @@ module Sgpg
|
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
def self.close(disk, is_crypted =
|
|
27
|
+
def self.close(disk, is_crypted = nil)
|
|
27
28
|
if is_crypted
|
|
28
29
|
Mount.new('/dev/mapper/sgpg').close
|
|
29
30
|
Cryptsetup.new(disk).close
|
|
@@ -32,10 +33,13 @@ module Sgpg
|
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
|
|
36
|
+
# https://www.rubyguides.com/2017/07/ruby-sort/
|
|
35
37
|
def self.list_keys(keyname)
|
|
36
|
-
|
|
38
|
+
dest = Helper.search_dest
|
|
39
|
+
dest += "/#{keyname}"
|
|
40
|
+
keys = Dir.glob("#{dest}/*.tar")
|
|
41
|
+
|
|
37
42
|
puts "Listing keys for #{keyname}..."
|
|
38
|
-
keys = Dir.glob("#{Sgpg::MOUNTPOINT}/Persistent/*.tar") if keys == [] || keys == ''
|
|
39
43
|
puts keys
|
|
40
44
|
end
|
|
41
45
|
|
|
@@ -43,19 +47,18 @@ module Sgpg
|
|
|
43
47
|
def self.last_key(opts, suffix = 'master')
|
|
44
48
|
Sgpg.open(opts[:disk], opts[:crypted])
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
dest = Helper.search_dest
|
|
51
|
+
dest += "/#{opts[:keyname]}"
|
|
52
|
+
keys = Dir.glob("#{dest}/#{opts[:keyname]}*#{suffix}*.tar").sort
|
|
53
|
+
|
|
47
54
|
raise 'No keys found' unless keys.length >= 1
|
|
48
55
|
|
|
49
|
-
keys
|
|
56
|
+
keys.last
|
|
50
57
|
end
|
|
51
58
|
|
|
52
59
|
def self.clear_keys
|
|
53
60
|
return unless Dir.glob("#{Sgpg::WORKDIR}/*.key").length >= 1
|
|
54
61
|
|
|
55
|
-
print "Clearing keys located at #{Sgpg::WORKDIR}? (y/n) "
|
|
56
|
-
choice = gets.chomp
|
|
57
|
-
return unless choice.match(/^y|^Y/)
|
|
58
|
-
|
|
59
62
|
puts "Clearing #{Sgpg::WORKDIR}..."
|
|
60
63
|
system("shred -u #{Sgpg::WORKDIR}/*.key")
|
|
61
64
|
end
|
|
@@ -71,20 +74,44 @@ module Sgpg
|
|
|
71
74
|
Sgpg.clear_keys
|
|
72
75
|
end
|
|
73
76
|
|
|
74
|
-
#
|
|
77
|
+
# Export your real keys and create an archive (tar)
|
|
75
78
|
def self.export_secret(opts)
|
|
76
79
|
archive = Archive.new(opts[:keypath], opts[:keyname])
|
|
77
80
|
archive.create_master_tar
|
|
78
|
-
archive.
|
|
81
|
+
archive.move_to_disk
|
|
79
82
|
Sgpg.clear_keys
|
|
80
83
|
end
|
|
81
84
|
|
|
82
|
-
#
|
|
85
|
+
# Create an unprivileged GnuPG key (no change can be made)
|
|
83
86
|
def self.lesser_keys(opts)
|
|
84
87
|
archive = Archive.new(opts[:keypath], opts[:keyname])
|
|
85
88
|
archive.create_lesser_tar
|
|
86
|
-
archive.
|
|
89
|
+
archive.move_to_disk
|
|
87
90
|
Sgpg.clear_keys
|
|
88
91
|
end
|
|
92
|
+
|
|
93
|
+
# Replace 'path/to/source' and 'path/to/destination' with actual paths on your machine. Run the script:
|
|
94
|
+
# pass store password at ~/.password-store by default
|
|
95
|
+
def self.incremental_export(keyname)
|
|
96
|
+
raise ArgumentError, 'incremental_export: No keyname' unless keyname
|
|
97
|
+
|
|
98
|
+
src = "#{ENV['HOME']}/.password-store"
|
|
99
|
+
dest = Helper.search_dest
|
|
100
|
+
dest += "/#{keyname}/.password-store"
|
|
101
|
+
puts "Exporting passwords from #{src} to #{dest}..."
|
|
102
|
+
backup = Incremental.new(src, dest)
|
|
103
|
+
backup.perform_backup
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.incremental_import(keyname)
|
|
107
|
+
raise ArgumentError, 'incremental_export: No keyname' unless keyname
|
|
108
|
+
|
|
109
|
+
dest = "#{ENV['HOME']}/.password-store"
|
|
110
|
+
src = Helper.search_dest
|
|
111
|
+
src += "/#{keyname}/.password-store"
|
|
112
|
+
puts "Importing passwords from #{src} to #{dest}..."
|
|
113
|
+
backup = Incremental.new(src, dest)
|
|
114
|
+
backup.perform_backup
|
|
115
|
+
end
|
|
89
116
|
end
|
|
90
117
|
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sgpg
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- szorfein
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain:
|
|
11
10
|
- |
|
|
12
11
|
-----BEGIN CERTIFICATE-----
|
|
13
|
-
|
|
12
|
+
MIIEPDCCAqSgAwIBAgIBAzANBgkqhkiG9w0BAQsFADBEMREwDwYDVQQDDAhzem9y
|
|
14
13
|
ZmVpbjEaMBgGCgmSJomT8ixkARkWCnByb3Rvbm1haWwxEzARBgoJkiaJk/IsZAEZ
|
|
15
|
-
|
|
14
|
+
FgNjb20wHhcNMjUxMDI2MTQyNTU2WhcNMjYxMDI2MTQyNTU2WjBEMREwDwYDVQQD
|
|
16
15
|
DAhzem9yZmVpbjEaMBgGCgmSJomT8ixkARkWCnByb3Rvbm1haWwxEzARBgoJkiaJ
|
|
17
16
|
k/IsZAEZFgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCqe1yx
|
|
18
17
|
EG2oM25jeHp08A8zkaDNmbI3MujjrRM/WPEYZX2dVwOxkIS20hQVuxcAsBBA4W/W
|
|
@@ -23,20 +22,18 @@ cert_chain:
|
|
|
23
22
|
xI2/BnS8zOsS6Te08iLxqZfI/lsG8wcPduekSetRI4VIOZ5QoRK54PiQjrOBhbnD
|
|
24
23
|
OQBB/XF1C80imZtRtdUqh6bK9WeWI4RYZ2/KwXL1AScEbXkBkkOECWoVrD18WgRm
|
|
25
24
|
siuX6RkNIelhtb0En7f3bizgPqlO0qPQV+wPi9TSBxdVG12C0OmjCQYMQD0CAwEA
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
urXgRIzALxd/xazPCnoLSXPzfJSI6Y77S1EBvhPd9RaSO8IyH9RhPDP9mnTvW2Kl
|
|
37
|
-
NAUnoL+txK5a
|
|
25
|
+
AaM5MDcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFOUqdDeoxQXP
|
|
26
|
+
J29lorB0/52ePa5qMA0GCSqGSIb3DQEBCwUAA4IBgQAW22kVqz92sKW7thsKrW2i
|
|
27
|
+
BwVahsopgVLIUqp7sT+nL6Jw1QWCxolDwSToWFo0XuwbNdqCuGRBM+IzhWmCcA0e
|
|
28
|
+
WoHAPGQAhKz5jCWJ9GwgWbttYNrZ/PkyzKlT+15itMVeEEKxcqvfVFkdTXvTtliP
|
|
29
|
+
h75zR66tpFPjOejy+YD0uSPWbR/Nskf4BaMFso7mEr+5nOfzj0+Emy3MikHMm7uo
|
|
30
|
+
qZEazJO4XlmQWI2O7skt/SkE6xiZDQeCADZ4NXxBNzKkxKpozBXSeBKNUi21y9zR
|
|
31
|
+
3PGUOg6fCZt5H3kSffrvtycOnAu/FRNrQUxKRHLJKez8d7zJH64sBdldm5fZJu1O
|
|
32
|
+
pzUhdmhxa/brsIYFPPsjw0PminqSEmFahOoGyph8lw1IYu6UskIlB+J1BLZxsPkJ
|
|
33
|
+
6+fmQQAYvIhUQH+pOPELkGGo+bQMpCLb4i97kyzbOjJqAFKsgqgCOtvUC4yUZRNb
|
|
34
|
+
YulAM0cmmcfbzWUxsyAoCp9mY227qQZpGv6dDgv4IbA=
|
|
38
35
|
-----END CERTIFICATE-----
|
|
39
|
-
date:
|
|
36
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
40
37
|
dependencies: []
|
|
41
38
|
description: " Short gpg, tool for manage your gpg key (backup tarball, unprivileged
|
|
42
39
|
keys, etc...)\n"
|
|
@@ -56,6 +53,7 @@ files:
|
|
|
56
53
|
- lib/sgpg/cryptsetup.rb
|
|
57
54
|
- lib/sgpg/gpg.rb
|
|
58
55
|
- lib/sgpg/helper.rb
|
|
56
|
+
- lib/sgpg/incremental.rb
|
|
59
57
|
- lib/sgpg/mount.rb
|
|
60
58
|
- lib/sgpg/option.rb
|
|
61
59
|
- lib/sgpg/version.rb
|
|
@@ -70,7 +68,6 @@ metadata:
|
|
|
70
68
|
source_code_uri: https://github.com/szorfein/sgpg
|
|
71
69
|
wiki_uri: https://github.com/szorfein/sgpg/wiki
|
|
72
70
|
funding_uri: https://patreon.com/szorfein
|
|
73
|
-
post_install_message:
|
|
74
71
|
rdoc_options: []
|
|
75
72
|
require_paths:
|
|
76
73
|
- lib
|
|
@@ -89,8 +86,7 @@ requirements:
|
|
|
89
86
|
- cryptsetup
|
|
90
87
|
- shred
|
|
91
88
|
- tar
|
|
92
|
-
rubygems_version: 3.
|
|
93
|
-
signing_key:
|
|
89
|
+
rubygems_version: 3.6.9
|
|
94
90
|
specification_version: 4
|
|
95
91
|
summary: Short gpg, tool for manage your gpg key (backup tarball, unprivileged keys,
|
|
96
92
|
etc...)
|
metadata.gz.sig
CHANGED
|
Binary file
|