vpsadmin-client 3.0.0.master.20240728.pre.0.dc5474cc → 3.0.0.master.202211181.pre.0.ac358990
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -4
- data/Rakefile +1 -0
- data/lib/{terminal_size.rb → terminal-size.rb} +17 -23
- data/lib/vpsadmin/cli/commands/backup_dataset.rb +98 -96
- data/lib/vpsadmin/cli/commands/backup_vps.rb +1 -1
- data/lib/vpsadmin/cli/commands/base_download.rb +6 -6
- data/lib/vpsadmin/cli/commands/network_top.rb +59 -65
- data/lib/vpsadmin/cli/commands/snapshot_download.rb +21 -20
- data/lib/vpsadmin/cli/commands/snapshot_send.rb +7 -6
- data/lib/vpsadmin/cli/commands/vps_migrate_many.rb +12 -10
- data/lib/vpsadmin/cli/commands/vps_remote_console.rb +30 -29
- data/lib/vpsadmin/cli/stream_downloader.rb +45 -38
- data/lib/vpsadmin/cli.rb +2 -2
- data/lib/vpsadmin/client/version.rb +1 -1
- data/lib/vpsadmin/client.rb +3 -2
- data/shell.nix +6 -6
- data/vpsadmin-client.gemspec +10 -7
- metadata +38 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9014c08e0f38faa0c2ef3ff240d919521ff77609f7c638c3497fdf730d76a2d9
|
4
|
+
data.tar.gz: 4639e16cc11b5b4a81ebd787e6dedf15ec6da46177920de95e442ff8bc13ad86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63cec139647a45f1b78e31189634c3f876d5531efd3abb9ab535146cb2d34d3e13cb27f9d9a1cdf0fe8c7031cd4bee87774f1d1f1387fa349f21dce5d1a87792
|
7
|
+
data.tar.gz: 5431b10cbef22f09b69e7a4968bf3b0959dae30df08e2e3f24363ca5513931bec674b98fd203a77b326abffe4f87e4377638473031db122fef50d86fa0ad4e40
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,61 +1,55 @@
|
|
1
1
|
class Terminal
|
2
|
-
class Size; VERSION = '0.0.6'
|
2
|
+
class Size; VERSION = '0.0.6' end
|
3
3
|
class << self
|
4
4
|
def size
|
5
5
|
size_via_low_level_ioctl or size_via_stty or nil
|
6
6
|
end
|
7
|
-
|
8
|
-
def size!
|
9
|
-
size or _height_width_hash_from 25, 80
|
10
|
-
end
|
7
|
+
def size!; size or _height_width_hash_from 25, 80 end
|
11
8
|
|
12
9
|
# These are experimental
|
13
|
-
def resize
|
10
|
+
def resize direction, magnitude
|
14
11
|
tmux 'resize-pane', "-#{direction}", magnitude
|
15
12
|
end
|
16
13
|
|
17
14
|
def tmux *cmd
|
18
|
-
system 'tmux', *cmd.map
|
15
|
+
system 'tmux', *(cmd.map &:to_s)
|
19
16
|
end
|
20
17
|
|
21
|
-
IOCTL_INPUT_BUF = "\x00"
|
18
|
+
IOCTL_INPUT_BUF = "\x00"*8
|
22
19
|
def size_via_low_level_ioctl
|
23
20
|
# Thanks to runpaint for the general approach to this
|
24
21
|
return unless $stdin.respond_to? :ioctl
|
25
|
-
|
26
22
|
code = tiocgwinsz_value_for RUBY_PLATFORM
|
27
23
|
return unless code
|
28
|
-
|
29
24
|
buf = IOCTL_INPUT_BUF.dup
|
30
|
-
return
|
31
|
-
return if
|
32
|
-
|
25
|
+
return unless $stdout.ioctl(code, buf).zero?
|
26
|
+
return if IOCTL_INPUT_BUF == buf
|
33
27
|
got = buf.unpack('S4')[0..1]
|
34
|
-
_height_width_hash_from
|
35
|
-
rescue
|
28
|
+
_height_width_hash_from *got
|
29
|
+
rescue
|
36
30
|
nil
|
37
31
|
end
|
38
32
|
|
39
|
-
def tiocgwinsz_value_for
|
33
|
+
def tiocgwinsz_value_for platform
|
40
34
|
# This is as reported by <sys/ioctl.h>
|
41
35
|
# Hard-coding because it seems like overkll to acutally involve C for this.
|
42
36
|
{
|
43
37
|
/linux/ => 0x5413,
|
44
|
-
/darwin/ => 0x40087468 # thanks to brandon@brandon.io for the lookup!
|
45
|
-
}.find
|
38
|
+
/darwin/ => 0x40087468, # thanks to brandon@brandon.io for the lookup!
|
39
|
+
}.find{|k,v| platform[k]}
|
46
40
|
end
|
47
41
|
|
48
42
|
def size_via_stty
|
49
|
-
ints = `stty size`.scan(/\d+/).map
|
50
|
-
_height_width_hash_from
|
51
|
-
rescue
|
43
|
+
ints = `stty size`.scan(/\d+/).map &:to_i
|
44
|
+
_height_width_hash_from *ints
|
45
|
+
rescue
|
52
46
|
nil
|
53
47
|
end
|
54
48
|
|
55
49
|
private
|
56
|
-
|
57
50
|
def _height_width_hash_from *dimensions
|
58
|
-
{ height
|
51
|
+
{ :height => dimensions[0], :width => dimensions[1] }
|
59
52
|
end
|
53
|
+
|
60
54
|
end
|
61
55
|
end
|
@@ -21,7 +21,7 @@ module VpsAdmin::CLI::Commands
|
|
21
21
|
attempts: 10,
|
22
22
|
checksum: true,
|
23
23
|
delete_after: true,
|
24
|
-
sudo: true
|
24
|
+
sudo: true,
|
25
25
|
}
|
26
26
|
|
27
27
|
opts.on('-p', '--pretend', 'Print what would the program do') do
|
@@ -93,14 +93,14 @@ module VpsAdmin::CLI::Commands
|
|
93
93
|
|
94
94
|
ds_id = read_dataset_id(fs)
|
95
95
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
96
|
+
if ds_id
|
97
|
+
ds = @api.dataset.show(ds_id)
|
98
|
+
else
|
99
|
+
ds = dataset_chooser
|
100
|
+
end
|
101
101
|
|
102
102
|
elsif args.size != 2
|
103
|
-
warn
|
103
|
+
warn "Provide DATASET_ID and FILESYSTEM arguments"
|
104
104
|
exit(false)
|
105
105
|
|
106
106
|
else
|
@@ -144,15 +144,15 @@ module VpsAdmin::CLI::Commands
|
|
144
144
|
# This is the first run within this history id, no local snapshots are
|
145
145
|
# present
|
146
146
|
if !latest_local_snapshot && @opts[:init_snapshots]
|
147
|
-
remote_state[ds.current_history_id] =
|
147
|
+
remote_state[ds.current_history_id] = \
|
148
148
|
remote_state[ds.current_history_id].last(@opts[:init_snapshots])
|
149
149
|
end
|
150
150
|
|
151
151
|
remote_state[ds.current_history_id].each do |snap|
|
152
152
|
found = false
|
153
153
|
|
154
|
-
local_state.
|
155
|
-
found =
|
154
|
+
local_state.values.each do |snapshots|
|
155
|
+
found = snapshots.detect { |s| s.name == snap.name }
|
156
156
|
break if found
|
157
157
|
end
|
158
158
|
|
@@ -170,33 +170,33 @@ module VpsAdmin::CLI::Commands
|
|
170
170
|
if for_transfer.empty?
|
171
171
|
if found_latest
|
172
172
|
exit_msg(
|
173
|
-
|
173
|
+
"Nothing to transfer: all snapshots with history id "+
|
174
174
|
"#{ds.current_history_id} are already present locally",
|
175
175
|
error: @opts[:no_snapshots_error]
|
176
176
|
)
|
177
177
|
|
178
178
|
else
|
179
|
-
exit_msg(
|
180
|
-
|
179
|
+
exit_msg(<<END
|
180
|
+
Unable to transfer: the common snapshot has not been found
|
181
181
|
|
182
|
-
|
183
|
-
|
182
|
+
This can happen when the latest local snapshot was deleted from the server,
|
183
|
+
i.e. you have not backed up this dataset for quite some time.
|
184
184
|
|
185
|
-
|
185
|
+
You can either rename or destroy the whole current history id:
|
186
186
|
|
187
|
-
|
187
|
+
zfs rename #{fs}/#{ds.current_history_id} #{fs}/#{ds.current_history_id}.old
|
188
188
|
|
189
|
-
|
189
|
+
or
|
190
190
|
|
191
|
-
|
192
|
-
|
191
|
+
zfs list -r -t all #{fs}/#{ds.current_history_id}
|
192
|
+
zfs destroy -r #{fs}/#{ds.current_history_id}
|
193
193
|
|
194
|
-
|
194
|
+
which will destroy all snapshots with this history id.
|
195
195
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
196
|
+
You can also destroy the local backup completely or backup to another dataset
|
197
|
+
and start anew.
|
198
|
+
END
|
199
|
+
)
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
@@ -226,7 +226,9 @@ module VpsAdmin::CLI::Commands
|
|
226
226
|
if shared_name
|
227
227
|
shared = remote_state[ds.current_history_id].detect { |s| s.name == shared_name }
|
228
228
|
|
229
|
-
|
229
|
+
if shared && !for_transfer.detect { |s| s.id == shared.id }
|
230
|
+
for_transfer.insert(0, shared)
|
231
|
+
end
|
230
232
|
end
|
231
233
|
|
232
234
|
write_dataset_id!(ds, fs) unless written_dataset_id?
|
@@ -236,12 +238,13 @@ module VpsAdmin::CLI::Commands
|
|
236
238
|
end
|
237
239
|
|
238
240
|
protected
|
239
|
-
|
240
241
|
def transfer(local_state, snapshots, hist_id, fs)
|
241
242
|
ds = "#{fs}/#{hist_id}"
|
242
243
|
no_local_snapshots = local_state[hist_id].nil? || local_state[hist_id].empty?
|
243
244
|
|
244
|
-
|
245
|
+
if local_state[hist_id].nil?
|
246
|
+
zfs(:create, nil, ds)
|
247
|
+
end
|
245
248
|
|
246
249
|
if no_local_snapshots
|
247
250
|
msg "Performing a full receive of @#{snapshots.first.name} to #{ds}"
|
@@ -252,59 +255,60 @@ module VpsAdmin::CLI::Commands
|
|
252
255
|
else
|
253
256
|
run_piped(zfs_cmd(:recv, '-F', ds)) do
|
254
257
|
SnapshotSend.new({}, @api).do_exec({
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
258
|
+
snapshot: snapshots.first.id,
|
259
|
+
send_mail: false,
|
260
|
+
delete_after: @opts[:delete_after],
|
261
|
+
max_rate: @opts[:max_rate],
|
262
|
+
checksum: @opts[:checksum],
|
263
|
+
quiet: @opts[:quiet],
|
264
|
+
})
|
262
265
|
end || exit_msg('Receive failed')
|
263
266
|
end
|
264
267
|
end
|
265
268
|
|
266
|
-
|
269
|
+
if !no_local_snapshots || snapshots.size > 1
|
270
|
+
msg "Performing an incremental receive of "+
|
271
|
+
"@#{snapshots.first.name} - @#{snapshots.last.name} to #{ds}"
|
267
272
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
if @opts[:safe]
|
272
|
-
safe_download(ds, snapshots.last, snapshots.first)
|
273
|
+
if @opts[:safe]
|
274
|
+
safe_download(ds, snapshots.last, snapshots.first)
|
273
275
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
276
|
+
else
|
277
|
+
run_piped(zfs_cmd(:recv, '-F', ds)) do
|
278
|
+
SnapshotSend.new({}, @api).do_exec({
|
279
|
+
snapshot: snapshots.last.id,
|
280
|
+
from_snapshot: snapshots.first.id,
|
281
|
+
send_mail: false,
|
282
|
+
delete_after: @opts[:delete_after],
|
283
|
+
max_rate: @opts[:max_rate],
|
284
|
+
checksum: @opts[:checksum],
|
285
|
+
quiet: @opts[:quiet],
|
286
|
+
})
|
287
|
+
end || exit_msg('Receive failed')
|
288
|
+
end
|
286
289
|
end
|
287
290
|
end
|
288
291
|
|
289
292
|
def safe_download(ds, snapshot, from_snapshot = nil)
|
290
293
|
part, full = snapshot_tmp_file(snapshot, from_snapshot)
|
291
294
|
|
292
|
-
|
295
|
+
if !File.exists?(full)
|
293
296
|
attempts = 0
|
294
297
|
|
295
298
|
begin
|
296
299
|
SnapshotDownload.new({}, @api).do_exec({
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
300
|
+
snapshot: snapshot.id,
|
301
|
+
from_snapshot: from_snapshot && from_snapshot.id,
|
302
|
+
format: from_snapshot ? :incremental_stream : :stream,
|
303
|
+
file: part,
|
304
|
+
max_rate: @opts[:max_rate],
|
305
|
+
checksum: @opts[:checksum],
|
306
|
+
quiet: @opts[:quiet],
|
307
|
+
resume: true,
|
308
|
+
delete_after: @opts[:delete_after],
|
309
|
+
send_mail: false,
|
310
|
+
})
|
311
|
+
|
308
312
|
rescue Errno::ECONNREFUSED,
|
309
313
|
Errno::ETIMEDOUT,
|
310
314
|
Errno::EHOSTUNREACH,
|
@@ -314,11 +318,11 @@ module VpsAdmin::CLI::Commands
|
|
314
318
|
attempts += 1
|
315
319
|
|
316
320
|
if attempts >= @opts[:attempts]
|
317
|
-
warn
|
321
|
+
warn "Run out of attempts"
|
318
322
|
exit(false)
|
319
323
|
|
320
324
|
else
|
321
|
-
warn
|
325
|
+
warn "Retry in 60 seconds"
|
322
326
|
sleep(60)
|
323
327
|
retry
|
324
328
|
end
|
@@ -335,15 +339,15 @@ module VpsAdmin::CLI::Commands
|
|
335
339
|
end
|
336
340
|
|
337
341
|
def rotate(fs, pretend: false)
|
338
|
-
msg
|
339
|
-
local_state = pretend
|
342
|
+
msg "Rotating snapshots"
|
343
|
+
local_state = pretend ? pretend : parse_tree(fs)
|
340
344
|
|
341
345
|
# Order snapshots by date of creation
|
342
346
|
snapshots = local_state.values.flatten.sort do |a, b|
|
343
347
|
a.creation <=> b.creation
|
344
348
|
end
|
345
349
|
|
346
|
-
cnt = local_state.values.inject(0) { |sum,
|
350
|
+
cnt = local_state.values.inject(0) { |sum, snapshots| sum + snapshots.count }
|
347
351
|
deleted = 0
|
348
352
|
oldest = Time.now.to_i - (@opts[:max_age] * 60 * 60 * 24)
|
349
353
|
|
@@ -359,16 +363,16 @@ module VpsAdmin::CLI::Commands
|
|
359
363
|
local_state[s.hist_id].delete(s)
|
360
364
|
|
361
365
|
msg "Destroying #{ds}@#{s.name}"
|
362
|
-
zfs(:destroy, nil, "#{ds}@#{s.name}", pretend:)
|
366
|
+
zfs(:destroy, nil, "#{ds}@#{s.name}", pretend: pretend)
|
363
367
|
end
|
364
368
|
|
365
|
-
local_state.each do |hist_id,
|
366
|
-
next unless
|
369
|
+
local_state.each do |hist_id, snapshots|
|
370
|
+
next unless snapshots.empty?
|
367
371
|
|
368
372
|
ds = "#{fs}/#{hist_id}"
|
369
373
|
|
370
374
|
msg "Destroying #{ds}"
|
371
|
-
zfs(:destroy, nil, ds, pretend:)
|
375
|
+
zfs(:destroy, nil, ds, pretend: pretend)
|
372
376
|
end
|
373
377
|
end
|
374
378
|
|
@@ -378,15 +382,15 @@ module VpsAdmin::CLI::Commands
|
|
378
382
|
# This is intentionally done by two zfs commands, because -d2 would include
|
379
383
|
# nested subdatasets, which should not be there, but the user might create
|
380
384
|
# them and it could confuse the program.
|
381
|
-
zfs(:list, '-r -d1 -tfilesystem -H -oname', fs).split("\n")[1
|
385
|
+
zfs(:list, '-r -d1 -tfilesystem -H -oname', fs).split("\n")[1..-1].each do |name|
|
382
386
|
last_name = name.split('/').last
|
383
387
|
ret[last_name.to_i] = [] if dataset?(last_name)
|
384
388
|
end
|
385
389
|
|
386
390
|
zfs(
|
387
|
-
|
388
|
-
|
389
|
-
|
391
|
+
:get,
|
392
|
+
'-Hrp -d2 -tsnapshot -oname,property,value name,creation',
|
393
|
+
fs
|
390
394
|
).split("\n").each do |line|
|
391
395
|
name, property, value = line.split
|
392
396
|
ds, snap_name = name.split('@')
|
@@ -395,7 +399,7 @@ module VpsAdmin::CLI::Commands
|
|
395
399
|
|
396
400
|
hist_id = ds_name.to_i
|
397
401
|
|
398
|
-
if
|
402
|
+
if snap = ret[hist_id].detect { |s| s.name == snap_name }
|
399
403
|
snap.send("#{property}=", value)
|
400
404
|
|
401
405
|
else
|
@@ -414,15 +418,14 @@ module VpsAdmin::CLI::Commands
|
|
414
418
|
def read_dataset_id(fs)
|
415
419
|
ds_id = zfs(:get, '-H -ovalue cz.vpsfree.vpsadmin:dataset_id', fs).strip
|
416
420
|
return nil if ds_id == '-'
|
417
|
-
|
418
421
|
@dataset_id = ds_id.to_i
|
419
422
|
end
|
420
423
|
|
421
424
|
def check_dataset_id!(ds, fs)
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
425
|
+
if @dataset_id && @dataset_id != ds.id
|
426
|
+
warn "Dataset '#{fs}' is used to backup remote dataset with id '#{@dataset_id}', not '#{ds.id}'"
|
427
|
+
exit(false)
|
428
|
+
end
|
426
429
|
end
|
427
430
|
|
428
431
|
def written_dataset_id?
|
@@ -441,13 +444,13 @@ module VpsAdmin::CLI::Commands
|
|
441
444
|
|
442
445
|
pids << Process.fork do
|
443
446
|
r.close
|
444
|
-
|
447
|
+
STDOUT.reopen(w)
|
445
448
|
block.call
|
446
449
|
end
|
447
450
|
|
448
451
|
pids << Process.fork do
|
449
452
|
w.close
|
450
|
-
|
453
|
+
STDIN.reopen(r)
|
451
454
|
Process.exec(cmd2)
|
452
455
|
end
|
453
456
|
|
@@ -497,12 +500,11 @@ module VpsAdmin::CLI::Commands
|
|
497
500
|
ds_map = {}
|
498
501
|
|
499
502
|
@api.dataset.index(user: user.id).each do |ds|
|
500
|
-
if
|
503
|
+
if vps = vps_map[ds.id]
|
501
504
|
puts "(#{i}) VPS ##{vps.id}"
|
502
505
|
|
503
506
|
else
|
504
507
|
next if vps_only
|
505
|
-
|
506
508
|
puts "(#{i}) Dataset #{ds.name}"
|
507
509
|
end
|
508
510
|
|
@@ -511,10 +513,10 @@ module VpsAdmin::CLI::Commands
|
|
511
513
|
end
|
512
514
|
|
513
515
|
loop do
|
514
|
-
|
515
|
-
|
516
|
+
STDOUT.write('Pick a dataset to backup: ')
|
517
|
+
STDOUT.flush
|
516
518
|
|
517
|
-
i =
|
519
|
+
i = STDIN.readline.strip.to_i
|
518
520
|
next if i <= 0 || ds_map[i].nil?
|
519
521
|
|
520
522
|
return ds_map[i]
|
@@ -522,12 +524,12 @@ module VpsAdmin::CLI::Commands
|
|
522
524
|
end
|
523
525
|
|
524
526
|
def snapshot_tmp_file(s, from_s = nil)
|
525
|
-
|
526
|
-
|
527
|
+
if from_s
|
528
|
+
base = ".snapshot_#{from_s.id}-#{s.id}.inc.dat.gz"
|
527
529
|
|
528
|
-
|
529
|
-
|
530
|
-
|
530
|
+
else
|
531
|
+
base = ".snapshot_#{s.id}.dat.gz"
|
532
|
+
end
|
531
533
|
|
532
534
|
["#{base}.part", base]
|
533
535
|
end
|
@@ -7,15 +7,15 @@ module VpsAdmin::CLI::Commands
|
|
7
7
|
end
|
8
8
|
|
9
9
|
protected
|
10
|
-
|
11
10
|
def find_or_create_dl(opts, do_create = true)
|
12
11
|
@api.snapshot_download.index(snapshot: opts[:snapshot]).each do |r|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
if opts[:from_snapshot] == (r.from_snapshot && r.from_snapshot_id)
|
13
|
+
if r.format != opts[:format].to_s
|
14
|
+
fail "SnapshotDownload id=#{r.id} is in unusable format '#{r.format}' (needs '#{opts[:format]}')"
|
15
|
+
end
|
17
16
|
|
18
|
-
|
17
|
+
return [r, false]
|
18
|
+
end
|
19
19
|
end
|
20
20
|
|
21
21
|
if do_create
|