imap-backup 13.3.0 → 14.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +36 -59
- data/docs/development.md +10 -1
- data/docs/performance.md +53 -0
- data/lib/imap/backup/account/backup_folders.rb +1 -1
- data/lib/imap/backup/account/folder.rb +12 -2
- data/lib/imap/backup/cli/helpers.rb +18 -2
- data/lib/imap/backup/cli/local.rb +1 -0
- data/lib/imap/backup/cli/single/backup.rb +131 -0
- data/lib/imap/backup/cli/single.rb +179 -0
- data/lib/imap/backup/cli.rb +12 -11
- data/lib/imap/backup/configuration.rb +23 -4
- data/lib/imap/backup/downloader.rb +2 -0
- data/lib/imap/backup/logger.rb +9 -4
- data/lib/imap/backup/setup/account/header.rb +1 -1
- data/lib/imap/backup/setup/connection_tester.rb +2 -0
- data/lib/imap/backup/setup/folder_chooser.rb +5 -5
- data/lib/imap/backup/setup/global_options.rb +1 -1
- data/lib/imap/backup/setup.rb +1 -1
- data/lib/imap/backup/version.rb +2 -2
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c98349e8780fc3170e7ca735e6469fb38c9eeb3b7f023eec8d5f7d1e80e7258b
|
4
|
+
data.tar.gz: 3ebb575e74b72ac190f52b192cf8a5af54302799f4feec7d707cb438b4e5f542
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11a1f328334d32eaf54ec2b013bf444f9434704c9e218bf51e34798a675b33be1569a0293ddacd521d923528e8dbb534c836877e302af11ad5fd8be0ceb764a3
|
7
|
+
data.tar.gz: 8f40f2ed79c32716b366a678da2c065b1df83a206901411bb3667ee31183779e668bd2619dd252cbaee46683a65ab9c5c3374afa30fa73cc0dc5b03aa83efa43
|
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
[![Build Status](https://github.com/joeyates/imap-backup/actions/workflows/main.yml/badge.svg)][CI Status]
|
3
3
|
![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/joeyates/b54fe758bfb405c04bef72dad293d707/raw/coverage.json)
|
4
4
|
![License](https://img.shields.io/github/license/joeyates/imap-backup?color=brightgreen&label=License)
|
5
|
+
[![Stars](https://img.shields.io/github/stars/joeyates/imap-backup?style=social)][GitHub Stars]
|
5
6
|
|
6
7
|
# imap-backup
|
7
8
|
|
@@ -11,7 +12,7 @@ Backup, restore and migrate email accounts.
|
|
11
12
|
|
12
13
|
```sh
|
13
14
|
brew install imap-backup # for macOS
|
14
|
-
gem install imap-backup # for Linux
|
15
|
+
gem install imap-backup --no-document # for Linux
|
15
16
|
imap-backup setup
|
16
17
|
imap-backup
|
17
18
|
```
|
@@ -35,6 +36,8 @@ See below for a [full list of commands](#Commands).
|
|
35
36
|
|
36
37
|
## Homebrew (macOS)
|
37
38
|
|
39
|
+
![Homebrew installs](https://img.shields.io/homebrew/installs/dm/imap-backup?label=Homebrew%20installs)
|
40
|
+
|
38
41
|
If you have [Homebrew](https://brew.sh/), do this:
|
39
42
|
|
40
43
|
```sh
|
@@ -44,7 +47,7 @@ brew install imap-backup
|
|
44
47
|
## As a Ruby Gem
|
45
48
|
|
46
49
|
```sh
|
47
|
-
gem install imap-backup
|
50
|
+
gem install imap-backup --no-document
|
48
51
|
```
|
49
52
|
|
50
53
|
If that doesn't work, see the [detailed installation instructions](docs/installation/rubygem.md).
|
@@ -55,8 +58,10 @@ If you want to use imap-backup directly from the source code, see [here](docs/in
|
|
55
58
|
|
56
59
|
# Setup
|
57
60
|
|
58
|
-
|
59
|
-
|
61
|
+
Normally you will want to backup a number of email accounts.
|
62
|
+
Doing so requires the creation of a config file.
|
63
|
+
|
64
|
+
You do this via a menu-driven command line program:
|
60
65
|
|
61
66
|
Run:
|
62
67
|
|
@@ -64,6 +69,10 @@ Run:
|
|
64
69
|
imap-backup setup
|
65
70
|
```
|
66
71
|
|
72
|
+
As an alternative, if you only want to backup a single account,
|
73
|
+
you can pass all the necessary parameters directly to the `single backup` command
|
74
|
+
(see the [`single backup`](docs/commands/single-backup.md) docs).
|
75
|
+
|
67
76
|
## GMail
|
68
77
|
|
69
78
|
To use imap-backup with GMail, Office 365 and other services that require
|
@@ -80,24 +89,25 @@ imap-backup
|
|
80
89
|
|
81
90
|
Alternatively, add it to your crontab.
|
82
91
|
|
83
|
-
Backups can also be inspected, for example via [local show](docs/commands/local-show.md)
|
84
|
-
and exported via [utils export-to-thunderbird](docs/commands/utils-export-to-thunderbird.md).
|
92
|
+
Backups can also be inspected, for example via [`local show`](docs/commands/local-show.md)
|
93
|
+
and exported via [`utils export-to-thunderbird`](docs/commands/utils-export-to-thunderbird.md).
|
85
94
|
|
86
95
|
# Commands
|
87
96
|
|
88
|
-
* [backup](docs/commands/backup.md)
|
89
|
-
* [local accounts](docs/commands/local-accounts.md)
|
90
|
-
* [local check](docs/commands/local-check.md)
|
91
|
-
* [local folders](docs/commands/local-folders.md)
|
92
|
-
* [local list](docs/commands/local-list.md)
|
93
|
-
* [local show](docs/commands/local-show.md)
|
94
|
-
* [migrate](docs/commands/migrate.md)
|
95
|
-
* [mirror](docs/commands/mirror.md)
|
96
|
-
* [remote folders](docs/commands/remote-folders.md)
|
97
|
-
* [restore](docs/commands/restore.md)
|
98
|
-
* [setup](docs/commands/setup.md)
|
99
|
-
* [
|
100
|
-
* [utils
|
97
|
+
* [`backup`](docs/commands/backup.md)
|
98
|
+
* [`local accounts`](docs/commands/local-accounts.md)
|
99
|
+
* [`local check`](docs/commands/local-check.md)
|
100
|
+
* [`local folders`](docs/commands/local-folders.md)
|
101
|
+
* [`local list`](docs/commands/local-list.md)
|
102
|
+
* [`local show`](docs/commands/local-show.md)
|
103
|
+
* [`migrate`](docs/commands/migrate.md)
|
104
|
+
* [`mirror`](docs/commands/mirror.md)
|
105
|
+
* [`remote folders`](docs/commands/remote-folders.md)
|
106
|
+
* [`restore`](docs/commands/restore.md)
|
107
|
+
* [`setup`](docs/commands/setup.md)
|
108
|
+
* [`single backup`](docs/commands/single-backup.md)
|
109
|
+
* [`utils export-to-thunderbird`](docs/commands/utils-export-to-thunderbird.md)
|
110
|
+
* [`utils ignore-history`](docs/commands/utils-ignore-history.md)
|
101
111
|
|
102
112
|
For a full list of available commands, run
|
103
113
|
|
@@ -118,46 +128,10 @@ to improve backup speed.
|
|
118
128
|
|
119
129
|
These are activated via two settings:
|
120
130
|
|
121
|
-
* Global setting "Delay download writes"
|
122
|
-
* Account setting "Multi-fetch size"
|
123
|
-
|
124
|
-
As with all performance tweaks, there are trade-offs.
|
125
|
-
If you are using a small virtual server or Raspberry Pi
|
126
|
-
to run your backups, you will probably want to leave
|
127
|
-
the default settings.
|
128
|
-
If, on the other hand, you are using a computer with a
|
129
|
-
fair bit of RAM, and you are dealing with a *lot* of email,
|
130
|
-
then changing these settings may be worthwhile.
|
131
|
-
|
132
|
-
## Delay download writes
|
133
|
-
|
134
|
-
This setting affects all account backups.
|
135
|
-
|
136
|
-
By default, `imap-backup` uses the "delay metadata" strategy.
|
137
|
-
As messages are being backed-up, the message *text*
|
138
|
-
is written to disk, while the related metadata is stored in memory.
|
131
|
+
* Global setting "Delay download writes",
|
132
|
+
* Account setting "Multi-fetch size".
|
139
133
|
|
140
|
-
|
141
|
-
file for every message, speeding things up and reducing disk wear.
|
142
|
-
|
143
|
-
The alternative strategy, called "direct", writes everything to disk
|
144
|
-
as it is received. This method is slower, but has the advantage
|
145
|
-
of using slightly less memory, which may be important on very
|
146
|
-
resource-limited systems, like Raspberry Pis.
|
147
|
-
|
148
|
-
## Multi-fetch Size
|
149
|
-
|
150
|
-
By default, during backup, each message is downloaded one-by-one.
|
151
|
-
|
152
|
-
Using this setting, you can download chunks of emails at a time,
|
153
|
-
potentially speeding up the process.
|
154
|
-
|
155
|
-
Using multi-fetch *will* mean that the backup process will use
|
156
|
-
more memory - equivalent to the size of the groups of messages
|
157
|
-
that are downloaded.
|
158
|
-
|
159
|
-
This behaviour may also exceed the rate limits on your email provider,
|
160
|
-
so it's best to check before cranking it up!
|
134
|
+
See [the performance document](docs/performance.md) for more information.
|
161
135
|
|
162
136
|
# Troubleshooting
|
163
137
|
|
@@ -168,10 +142,12 @@ If you have problems:
|
|
168
142
|
|
169
143
|
# Development
|
170
144
|
|
145
|
+
![Activity](https://img.shields.io/github/last-commit/joeyates/imap-backup/main)
|
146
|
+
|
171
147
|
See the [Development documentation](./docs/development.md) for notes
|
172
148
|
on development and testing.
|
173
149
|
|
174
|
-
See [the CHANGELOG](./CHANGELOG.md)
|
150
|
+
See [the CHANGELOG](./CHANGELOG.md) for a list of changes that have been
|
175
151
|
made in each release.
|
176
152
|
|
177
153
|
* [Source Code]
|
@@ -180,6 +156,7 @@ made in each release.
|
|
180
156
|
* [CI Status]
|
181
157
|
|
182
158
|
[Source Code]: https://github.com/joeyates/imap-backup "Source code at GitHub"
|
159
|
+
[GitHub Stars]: https://github.com/joeyates/imap-backup/stargazers "GitHub Stars"
|
183
160
|
[Code Documentation]: https://rubydoc.info/gems/imap-backup/frames "Code Documentation at Rubydoc.info"
|
184
161
|
[Rubygem]: https://rubygems.org/gems/imap-backup "Ruby gem at rubygems.org"
|
185
162
|
[CI Status]: https://github.com/joeyates/imap-backup/actions/workflows/main.yml
|
data/docs/development.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
|
+
# Repository
|
2
|
+
|
3
|
+
After cloning the repo, run the following command to get
|
4
|
+
better `git blame` output:
|
5
|
+
|
6
|
+
```sh
|
7
|
+
git config --local blame.ignoreRevsFile .git-blame-ignore-revs
|
8
|
+
```
|
9
|
+
|
1
10
|
# Design Goals
|
2
11
|
|
3
|
-
* Secure - use a local file protected by permissions
|
12
|
+
* Secure - use a local configuration file protected by permissions
|
4
13
|
* Restartable - calculate start point based on already downloaded messages
|
5
14
|
* Standalone - do not rely on an email client or MTA
|
6
15
|
|
data/docs/performance.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Performace
|
2
|
+
|
3
|
+
The two performance-related settings are "Download strategy",
|
4
|
+
which is a global setting,
|
5
|
+
and "Multi-fetch size", which is an Account-level setting.
|
6
|
+
|
7
|
+
As with all performance tweaks, there are trade-offs.
|
8
|
+
|
9
|
+
# Overview
|
10
|
+
|
11
|
+
The defaults, which suit most machines and plays nice with servers is:
|
12
|
+
|
13
|
+
* Download strategy: "delay writing metadata",
|
14
|
+
* Multi-fetch size: 1.
|
15
|
+
|
16
|
+
If you are using a resource-limited machine like
|
17
|
+
a small virtual server or Raspberry Pi
|
18
|
+
to run your backups, you can change "Download strategy".
|
19
|
+
|
20
|
+
If your email provider supports it,
|
21
|
+
and you don't have tight memeory limits,
|
22
|
+
increase "Multi-fetch size" for faster backups.
|
23
|
+
|
24
|
+
# Delay download writes
|
25
|
+
|
26
|
+
This is a global setting, affecting all account backups.
|
27
|
+
|
28
|
+
By default, `imap-backup` uses the "delay writing metadata" strategy.
|
29
|
+
As messages are being backed-up, the message *text*
|
30
|
+
is written to disk, while the related metadata is stored in memory.
|
31
|
+
|
32
|
+
While this uses a *little* more memory, it avoids rewiting a growing JSON
|
33
|
+
file for every message, speeding things up and reducing disk wear.
|
34
|
+
|
35
|
+
The alternative strategy, called "write straight to disk",
|
36
|
+
writes everything to disk as it is received.
|
37
|
+
This method is slower, but has the advantage
|
38
|
+
of using slightly less memory, which may be important on very
|
39
|
+
resource-limited systems, like Raspberry Pis.
|
40
|
+
|
41
|
+
# Multi-fetch Size
|
42
|
+
|
43
|
+
By default, during backup, each message is downloaded one-by-one.
|
44
|
+
|
45
|
+
Using this setting, you can download chunks of emails at a time,
|
46
|
+
potentially speeding up the process.
|
47
|
+
|
48
|
+
Using multi-fetch *will* mean that the backup process will use
|
49
|
+
more memory - equivalent to the size of the groups of messages
|
50
|
+
that are downloaded.
|
51
|
+
|
52
|
+
This behaviour may also exceed the rate limits on your email provider,
|
53
|
+
so it's best to check before cranking it up!
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require "forwardable"
|
2
|
+
require "logger"
|
2
3
|
require "net/imap"
|
3
4
|
|
5
|
+
require "imap/backup/logger"
|
4
6
|
require "retry_on_error"
|
5
7
|
|
6
8
|
module Imap; end
|
@@ -31,12 +33,19 @@ module Imap::Backup
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def exist?
|
36
|
+
previous_level = Imap::Backup::Logger.logger.level
|
37
|
+
previous_debug = Net::IMAP.debug
|
38
|
+
Imap::Backup::Logger.logger.level = ::Logger::Severity::UNKNOWN
|
39
|
+
Net::IMAP.debug = false
|
34
40
|
retry_on_error(errors: EXAMINE_RETRY_CLASSES) do
|
35
41
|
examine
|
36
42
|
end
|
37
43
|
true
|
38
44
|
rescue FolderNotFound
|
39
45
|
false
|
46
|
+
ensure
|
47
|
+
Imap::Backup::Logger.logger.level = previous_level
|
48
|
+
Net::IMAP.debug = previous_debug
|
40
49
|
end
|
41
50
|
|
42
51
|
def create
|
@@ -68,7 +77,7 @@ module Imap::Backup
|
|
68
77
|
'This is caused by `@responses["SEARCH"] being unset/undefined. ' \
|
69
78
|
"Among others, Apple Mail servers send empty responses when " \
|
70
79
|
"folders are empty, causing this error."
|
71
|
-
Logger.logger.warn message
|
80
|
+
Imap::Backup::Logger.logger.warn message
|
72
81
|
[]
|
73
82
|
end
|
74
83
|
|
@@ -150,7 +159,8 @@ module Imap::Backup
|
|
150
159
|
def examine
|
151
160
|
client.examine(utf7_encoded_name)
|
152
161
|
rescue Net::IMAP::NoResponseError
|
153
|
-
Logger.logger.warn "Folder '#{name}' does not exist on server"
|
162
|
+
Imap::Backup::Logger.logger.warn "Folder '#{name}' does not exist on server"
|
163
|
+
Imap::Backup::Logger.logger.warn caller.join("\n")
|
154
164
|
raise FolderNotFound, "Folder '#{name}' does not exist on server"
|
155
165
|
end
|
156
166
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require "thor"
|
2
2
|
|
3
3
|
require "imap/backup/configuration"
|
4
|
+
require "imap/backup/configuration_not_found"
|
4
5
|
|
5
6
|
module Imap; end
|
6
7
|
|
@@ -46,12 +47,27 @@ module Imap::Backup
|
|
46
47
|
)
|
47
48
|
end
|
48
49
|
|
50
|
+
def self.refresh_option
|
51
|
+
method_option(
|
52
|
+
"refresh",
|
53
|
+
type: :boolean,
|
54
|
+
desc: "in the default 'keep all emails' mode, " \
|
55
|
+
"updates flags for messages that are already downloaded",
|
56
|
+
aliases: ["-r"]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
49
60
|
def self.verbose_option
|
50
61
|
method_option(
|
51
62
|
"verbose",
|
52
63
|
type: :boolean,
|
53
|
-
desc:
|
54
|
-
|
64
|
+
desc:
|
65
|
+
"increase the amount of logging. " \
|
66
|
+
"Without this option, the program gives minimal output. " \
|
67
|
+
"Using this option once gives more detailed output. " \
|
68
|
+
"Whereas, using this option twice also shows all IMAP network calls",
|
69
|
+
aliases: ["-v"],
|
70
|
+
repeatable: true
|
55
71
|
)
|
56
72
|
end
|
57
73
|
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
require "imap/backup/account"
|
4
|
+
require "imap/backup/account/backup"
|
5
|
+
require "imap/backup/configuration"
|
6
|
+
|
7
|
+
module Imap; end
|
8
|
+
|
9
|
+
module Imap::Backup
|
10
|
+
class CLI < Thor; end
|
11
|
+
class CLI::Single < Thor; end
|
12
|
+
|
13
|
+
class CLI::Single::Backup
|
14
|
+
attr_reader :options
|
15
|
+
attr_reader :password
|
16
|
+
|
17
|
+
def initialize(options)
|
18
|
+
@options = options
|
19
|
+
@password = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
process_options!
|
24
|
+
account = Account.new(
|
25
|
+
username: email,
|
26
|
+
password: password,
|
27
|
+
server: server,
|
28
|
+
download_strategy: download_strategy,
|
29
|
+
folder_blacklist: folder_blacklist,
|
30
|
+
local_path: local_path,
|
31
|
+
mirror: mirror,
|
32
|
+
reset_seen_flags_after_fetch: reset_seen_flags_after_fetch
|
33
|
+
)
|
34
|
+
account.connection_options = connection_options if connection_options
|
35
|
+
account.folders = folders if folders.any?
|
36
|
+
account.multi_fetch_size = multi_fetch_size if multi_fetch_size
|
37
|
+
backup = Account::Backup.new(account: account, refresh: refresh)
|
38
|
+
backup.run
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def process_options!
|
44
|
+
if !email
|
45
|
+
raise Thor::RequiredArgumentMissingError,
|
46
|
+
"No value provided for required options '--email'"
|
47
|
+
end
|
48
|
+
if !server
|
49
|
+
raise Thor::RequiredArgumentMissingError,
|
50
|
+
"No value provided for required options '--server'"
|
51
|
+
end
|
52
|
+
handle_password_options!
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_password_options!
|
56
|
+
plain = options[:password]
|
57
|
+
env = options[:password_environment_variable]
|
58
|
+
file = options[:password_file]
|
59
|
+
case [plain, env, file]
|
60
|
+
when [nil, nil, nil]
|
61
|
+
raise Thor::RequiredArgumentMissingError,
|
62
|
+
"Supply one of the --password... parameters"
|
63
|
+
when [plain, nil, nil]
|
64
|
+
@password = plain
|
65
|
+
when [nil, env, nil]
|
66
|
+
@password = ENV.fetch(env)
|
67
|
+
when [nil, nil, file]
|
68
|
+
@password = File.read(file).gsub(/\n$/, "")
|
69
|
+
else
|
70
|
+
raise ArgumentError, "Supply only one of the --password... parameters"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def connection_options
|
75
|
+
options[:connection_options]
|
76
|
+
end
|
77
|
+
|
78
|
+
def download_strategy
|
79
|
+
@download_strategy =
|
80
|
+
case options[:download_strategy]
|
81
|
+
when nil
|
82
|
+
Configuration::DEFAULT_STRATEGY
|
83
|
+
when "delay"
|
84
|
+
"delay_metadata"
|
85
|
+
when "direct"
|
86
|
+
"direct"
|
87
|
+
else
|
88
|
+
raise ArgumentError, "Unknown download_strategy: '#{options[:download_strategy]}'"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def email
|
93
|
+
options[:email]
|
94
|
+
end
|
95
|
+
|
96
|
+
def folder_blacklist
|
97
|
+
options[:folder_blacklist] ? true : false
|
98
|
+
end
|
99
|
+
|
100
|
+
def folders
|
101
|
+
@folders ||= options[:folder] || []
|
102
|
+
end
|
103
|
+
|
104
|
+
def local_path
|
105
|
+
return options[:path] if options.key?(:path)
|
106
|
+
|
107
|
+
for_account = email.tr("@", "_")
|
108
|
+
File.join(Dir.pwd, for_account)
|
109
|
+
end
|
110
|
+
|
111
|
+
def mirror
|
112
|
+
options[:mirror] ? true : false
|
113
|
+
end
|
114
|
+
|
115
|
+
def multi_fetch_size
|
116
|
+
options[:multi_fetch_size]
|
117
|
+
end
|
118
|
+
|
119
|
+
def refresh
|
120
|
+
options[:refresh] ? true : false
|
121
|
+
end
|
122
|
+
|
123
|
+
def reset_seen_flags_after_fetch
|
124
|
+
options[:reset_seen_flags_after_fetch] ? true : false
|
125
|
+
end
|
126
|
+
|
127
|
+
def server
|
128
|
+
options[:server]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
require "imap/backup/logger"
|
4
|
+
require "imap/backup/cli/helpers"
|
5
|
+
require "imap/backup/cli/single/backup"
|
6
|
+
|
7
|
+
module Imap; end
|
8
|
+
|
9
|
+
module Imap::Backup
|
10
|
+
class CLI < Thor; end
|
11
|
+
|
12
|
+
class CLI::Single < Thor
|
13
|
+
include CLI::Helpers
|
14
|
+
|
15
|
+
desc "backup", "Backup a single email account based on command-line parameters"
|
16
|
+
long_desc <<~DESC
|
17
|
+
This is a "stand-alone" backup command that doesn't require
|
18
|
+
a configuration file.
|
19
|
+
|
20
|
+
At a minimum, you need to supply the email, the server and the
|
21
|
+
password. (There are three ways of specifying the password)
|
22
|
+
|
23
|
+
$ imap-backup single backup
|
24
|
+
--email me@example.com
|
25
|
+
--password MyS3kr1t
|
26
|
+
--server imap.example.com
|
27
|
+
|
28
|
+
Instead of supplying the password directly on the command line,
|
29
|
+
there are two alternatives.
|
30
|
+
You can set an environment variable (with any name) to your
|
31
|
+
password, then pass the name of the environment variable.
|
32
|
+
|
33
|
+
For example, if MY_IMAP_PASSWORD is set to your password,
|
34
|
+
|
35
|
+
$ imap-backup single backup
|
36
|
+
--email me@example.com
|
37
|
+
--password-environment-variable MY_IMAP_PASSWORD
|
38
|
+
--server imap.example.com
|
39
|
+
|
40
|
+
Alternatively, you can supply the name of a file that contains
|
41
|
+
the password.
|
42
|
+
|
43
|
+
For example, in `~/imap-password`:
|
44
|
+
|
45
|
+
`MyS3kr1t`
|
46
|
+
|
47
|
+
$ imap-backup single backup
|
48
|
+
--email me@example.com
|
49
|
+
--password-file ~/imap-password
|
50
|
+
--server imap.example.com
|
51
|
+
|
52
|
+
If you need to use an insecure connection (this normally happens
|
53
|
+
when running an OAuth2 proxy), you can specify server connection options
|
54
|
+
in JSON:
|
55
|
+
|
56
|
+
$ imap-backup single backup
|
57
|
+
--email me@example.com
|
58
|
+
--password MyS3kr1t
|
59
|
+
--server imap.example.com
|
60
|
+
--connection-options '{"ssl":{"verify_mode":0}}'
|
61
|
+
DESC
|
62
|
+
method_option(
|
63
|
+
"email",
|
64
|
+
type: :string,
|
65
|
+
desc: "the email address",
|
66
|
+
aliases: ["-e"]
|
67
|
+
)
|
68
|
+
method_option(
|
69
|
+
"server",
|
70
|
+
type: :string,
|
71
|
+
desc: "the address of the IMAP server",
|
72
|
+
aliases: ["-s"]
|
73
|
+
)
|
74
|
+
method_option(
|
75
|
+
"password",
|
76
|
+
type: :string,
|
77
|
+
desc: "your password. " \
|
78
|
+
"As an alternative, use the --password-environment-variable " \
|
79
|
+
"or --password-file parameter. " \
|
80
|
+
"You need to pass exactly one of these parameters. " \
|
81
|
+
"If you pass more than one of the --password... parameters together " \
|
82
|
+
"you will get an error.",
|
83
|
+
aliases: ["-p"]
|
84
|
+
)
|
85
|
+
method_option(
|
86
|
+
"password-environment-variable",
|
87
|
+
type: :string,
|
88
|
+
desc: "an environment variable that is set to your password",
|
89
|
+
aliases: ["-e"]
|
90
|
+
)
|
91
|
+
method_option(
|
92
|
+
"password-file",
|
93
|
+
type: :string,
|
94
|
+
desc: "a file containing your password. " \
|
95
|
+
"Note that to make it easier to create such files, " \
|
96
|
+
"trailing newlines will be removed. " \
|
97
|
+
"If you happen to have a password that ends in a newline (!), " \
|
98
|
+
"you can't use this parameter.",
|
99
|
+
aliases: ["-W"]
|
100
|
+
)
|
101
|
+
method_option(
|
102
|
+
"path",
|
103
|
+
type: "string",
|
104
|
+
desc: "the path of the directory where backups are to be saved. " \
|
105
|
+
"If the directory does not exists, it will be created. " \
|
106
|
+
"If not set, this is set to a diretory under the current path " \
|
107
|
+
"which is derived from the username, by replacing '@' with '_'.",
|
108
|
+
aliases: ["-P"]
|
109
|
+
)
|
110
|
+
method_option(
|
111
|
+
"folder",
|
112
|
+
type: :string,
|
113
|
+
desc: "a folder (this option can be given any number of times). " \
|
114
|
+
"By default, all of an account's folders are backed up. " \
|
115
|
+
"If you supply any --folder parameters, " \
|
116
|
+
"only **those** folders are backed up. " \
|
117
|
+
"See also --folder-blacklist.",
|
118
|
+
repeatable: true,
|
119
|
+
aliases: ["-F"]
|
120
|
+
)
|
121
|
+
method_option(
|
122
|
+
"folder-blacklist",
|
123
|
+
type: :boolean,
|
124
|
+
desc: "if this option is given, the list of --folders specified " \
|
125
|
+
"will treated as a blacklist - " \
|
126
|
+
"those folders will be skipped and " \
|
127
|
+
"all others will be backed up.",
|
128
|
+
default: false,
|
129
|
+
aliases: ["-b"]
|
130
|
+
)
|
131
|
+
method_option(
|
132
|
+
"mirror",
|
133
|
+
type: :boolean,
|
134
|
+
desc: "if this option is given, " \
|
135
|
+
"emails that are removed from the server " \
|
136
|
+
"will be removed from the local backup.",
|
137
|
+
aliases: ["-m"]
|
138
|
+
)
|
139
|
+
method_option(
|
140
|
+
"multi-fetch-size",
|
141
|
+
type: :numeric,
|
142
|
+
desc: "the number of emails to download at a time",
|
143
|
+
default: 1,
|
144
|
+
aliases: ["-n"]
|
145
|
+
)
|
146
|
+
method_option(
|
147
|
+
"connection-options",
|
148
|
+
type: :string,
|
149
|
+
desc: "an optional JSON string with options for the IMAP connection",
|
150
|
+
aliases: ["-o"]
|
151
|
+
)
|
152
|
+
method_option(
|
153
|
+
"download-strategy",
|
154
|
+
type: :string,
|
155
|
+
desc: "the download strategy to adopt. " \
|
156
|
+
"For details, see the documentation for this setting " \
|
157
|
+
"in the setup program.",
|
158
|
+
enum: %w(delay direct),
|
159
|
+
default: "delay",
|
160
|
+
aliases: ["-S"]
|
161
|
+
)
|
162
|
+
refresh_option
|
163
|
+
method_option(
|
164
|
+
"reset-seen-flags-after-fetch",
|
165
|
+
type: :boolean,
|
166
|
+
desc: "reset 'Seen' flags after backup. " \
|
167
|
+
"For details, see the documentation for this setting " \
|
168
|
+
"in the setup program.",
|
169
|
+
aliases: ["-R"]
|
170
|
+
)
|
171
|
+
quiet_option
|
172
|
+
verbose_option
|
173
|
+
def backup
|
174
|
+
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
175
|
+
direct = Backup.new(non_logging_options)
|
176
|
+
direct.run
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/imap/backup/cli.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "thor"
|
2
2
|
|
3
3
|
require "imap/backup/logger"
|
4
|
+
require "imap/backup/version"
|
4
5
|
|
5
6
|
module Imap; end
|
6
7
|
|
@@ -13,14 +14,13 @@ module Imap::Backup
|
|
13
14
|
autoload :Remote, "imap/backup/cli/remote"
|
14
15
|
autoload :Restore, "imap/backup/cli/restore"
|
15
16
|
autoload :Setup, "imap/backup/cli/setup"
|
17
|
+
autoload :Single, "imap/backup/cli/single"
|
16
18
|
autoload :Stats, "imap/backup/cli/stats"
|
17
19
|
autoload :Transfer, "imap/backup/cli/transfer"
|
18
20
|
autoload :Utils, "imap/backup/cli/utils"
|
19
21
|
|
20
22
|
include Helpers
|
21
23
|
|
22
|
-
VERSION_ARGUMENTS = %w(-v --version).freeze
|
23
|
-
|
24
24
|
NAMESPACE_CONFIGURATION_DESCRIPTION = <<~DESC.freeze
|
25
25
|
Some IMAP servers use namespaces (i.e. prefixes like "INBOX"),
|
26
26
|
while others, while others concatenate the names of subfolders
|
@@ -44,9 +44,8 @@ module Imap::Backup
|
|
44
44
|
|
45
45
|
default_task :backup
|
46
46
|
|
47
|
-
def self.start(
|
48
|
-
|
49
|
-
if version_argument.any?
|
47
|
+
def self.start(args)
|
48
|
+
if args.include?("--version")
|
50
49
|
new.version
|
51
50
|
exit 0
|
52
51
|
end
|
@@ -80,13 +79,8 @@ module Imap::Backup
|
|
80
79
|
accounts_option
|
81
80
|
config_option
|
82
81
|
quiet_option
|
82
|
+
refresh_option
|
83
83
|
verbose_option
|
84
|
-
method_option(
|
85
|
-
"refresh",
|
86
|
-
type: :boolean,
|
87
|
-
desc: "in 'keep all emails' mode, update flags for messages that are already downloaded",
|
88
|
-
aliases: ["-r"]
|
89
|
-
)
|
90
84
|
def backup
|
91
85
|
non_logging_options = Imap::Backup::Logger.setup_logging(options)
|
92
86
|
Backup.new(non_logging_options).run
|
@@ -171,6 +165,10 @@ module Imap::Backup
|
|
171
165
|
a new file is created alongside the normal backup files (.imap and .mbox)
|
172
166
|
This file has a '.mirror' extension. This file contains a mapping of
|
173
167
|
the known UIDs on the source account to those on the destination account.
|
168
|
+
|
169
|
+
Some configuration may be necessary, as follows:
|
170
|
+
|
171
|
+
#{NAMESPACE_CONFIGURATION_DESCRIPTION}
|
174
172
|
DESC
|
175
173
|
config_option
|
176
174
|
quiet_option
|
@@ -237,6 +235,9 @@ module Imap::Backup
|
|
237
235
|
CLI::Setup.new(non_logging_options).run
|
238
236
|
end
|
239
237
|
|
238
|
+
desc "single SUBCOMMAND [OPTIONS]", "Run actions on a single account"
|
239
|
+
subcommand "single", Single
|
240
|
+
|
240
241
|
desc "stats EMAIL [OPTIONS]", "Print stats for each account folder"
|
241
242
|
long_desc <<~DESC
|
242
243
|
For each account folder, lists three counts of emails:
|
@@ -11,7 +11,8 @@ module Imap; end
|
|
11
11
|
module Imap::Backup
|
12
12
|
class Configuration
|
13
13
|
CONFIGURATION_DIRECTORY = File.expand_path("~/.imap-backup")
|
14
|
-
|
14
|
+
VERSION_2_1 = "2.1".freeze
|
15
|
+
VERSION = "2.2".freeze
|
15
16
|
DEFAULT_STRATEGY = "delay_metadata".freeze
|
16
17
|
DOWNLOAD_STRATEGIES = [
|
17
18
|
{key: "direct", description: "write straight to disk"},
|
@@ -81,7 +82,7 @@ module Imap::Backup
|
|
81
82
|
inject_global_attributes(accounts)
|
82
83
|
end
|
83
84
|
|
84
|
-
def download_strategy_modified
|
85
|
+
def download_strategy_modified?
|
85
86
|
ensure_loaded!
|
86
87
|
|
87
88
|
@download_strategy_modified
|
@@ -90,7 +91,7 @@ module Imap::Backup
|
|
90
91
|
def modified?
|
91
92
|
ensure_loaded!
|
92
93
|
|
93
|
-
return true if download_strategy_modified
|
94
|
+
return true if download_strategy_modified?
|
94
95
|
|
95
96
|
accounts.any? { |a| a.modified? || a.marked_for_deletion? }
|
96
97
|
end
|
@@ -121,12 +122,30 @@ module Imap::Backup
|
|
121
122
|
else
|
122
123
|
DEFAULT_STRATEGY
|
123
124
|
end
|
124
|
-
data
|
125
|
+
if data[:version] == VERSION_2_1
|
126
|
+
dehashify_folders(data)
|
127
|
+
else
|
128
|
+
data
|
129
|
+
end
|
125
130
|
else
|
126
131
|
{accounts: [], download_strategy: DEFAULT_STRATEGY}
|
127
132
|
end
|
128
133
|
end
|
129
134
|
|
135
|
+
def dehashify_folders(data)
|
136
|
+
data[:version] = VERSION
|
137
|
+
|
138
|
+
data[:accounts].each do |account|
|
139
|
+
next if !account.key?(:folders)
|
140
|
+
|
141
|
+
folders = account[:folders]
|
142
|
+
names = folders.map { |f| f[:name] }
|
143
|
+
account[:folders] = names
|
144
|
+
end
|
145
|
+
|
146
|
+
data
|
147
|
+
end
|
148
|
+
|
130
149
|
def remove_modified_flags
|
131
150
|
@download_strategy_modified = false
|
132
151
|
accounts.each(&:clear_changes)
|
data/lib/imap/backup/logger.rb
CHANGED
@@ -16,19 +16,20 @@ module Imap::Backup
|
|
16
16
|
def self.setup_logging(options = {})
|
17
17
|
copy = options.clone
|
18
18
|
quiet = copy.delete(:quiet)
|
19
|
-
verbose = copy.delete(:verbose)
|
19
|
+
verbose = copy.delete(:verbose) || []
|
20
|
+
verbose_count = count(verbose)
|
20
21
|
level =
|
21
22
|
case
|
22
23
|
when quiet
|
23
24
|
::Logger::Severity::UNKNOWN
|
24
|
-
when
|
25
|
+
when verbose_count >= 2
|
25
26
|
::Logger::Severity::DEBUG
|
26
27
|
else
|
27
28
|
::Logger::Severity::INFO
|
28
29
|
end
|
29
30
|
logger.level = level
|
30
|
-
|
31
|
-
Net::IMAP.debug =
|
31
|
+
|
32
|
+
Net::IMAP.debug = (verbose_count >= 3)
|
32
33
|
|
33
34
|
copy
|
34
35
|
end
|
@@ -43,6 +44,10 @@ module Imap::Backup
|
|
43
44
|
$stderr = previous_stderr
|
44
45
|
end
|
45
46
|
|
47
|
+
def self.count(verbose)
|
48
|
+
verbose.reduce(1) { |acc, v| acc + (v ? 1 : -1) }
|
49
|
+
end
|
50
|
+
|
46
51
|
attr_reader :logger
|
47
52
|
|
48
53
|
def initialize
|
@@ -63,18 +63,18 @@ module Imap::Backup
|
|
63
63
|
config_folders = account.folders
|
64
64
|
return false if config_folders.nil?
|
65
65
|
|
66
|
-
config_folders.find { |f| f
|
66
|
+
config_folders.find { |f| f == folder_name }
|
67
67
|
end
|
68
68
|
|
69
69
|
def remove_missing
|
70
70
|
removed = []
|
71
71
|
config_folders = []
|
72
72
|
account.folders.each do |f|
|
73
|
-
found = folder_names.find { |folder| folder == f
|
73
|
+
found = folder_names.find { |folder| folder == f }
|
74
74
|
if found
|
75
75
|
config_folders << f
|
76
76
|
else
|
77
|
-
removed << f
|
77
|
+
removed << f
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
@@ -91,11 +91,11 @@ module Imap::Backup
|
|
91
91
|
|
92
92
|
def toggle_selection(folder_name)
|
93
93
|
if selected?(folder_name)
|
94
|
-
new_list = account.folders.reject { |f| f
|
94
|
+
new_list = account.folders.reject { |f| f == folder_name }
|
95
95
|
account.folders = new_list
|
96
96
|
else
|
97
97
|
existing = account.folders || []
|
98
|
-
account.folders = existing
|
98
|
+
account.folders = existing << folder_name
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
@@ -42,7 +42,7 @@ module Imap::Backup
|
|
42
42
|
def change_download_strategy(menu)
|
43
43
|
strategies = Imap::Backup::Configuration::DOWNLOAD_STRATEGIES
|
44
44
|
current = strategies.find { |s| s[:key] == config.download_strategy }
|
45
|
-
changed = config.download_strategy_modified ? " *" : ""
|
45
|
+
changed = config.download_strategy_modified? ? " *" : ""
|
46
46
|
menu.choice("change download strategy (currently: '#{current[:description]}')#{changed}") do
|
47
47
|
DownloadStrategyChooser.new(config: config).run
|
48
48
|
end
|
data/lib/imap/backup/setup.rb
CHANGED
@@ -76,7 +76,7 @@ module Imap::Backup
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def modify_global_options(menu)
|
79
|
-
changed = config.
|
79
|
+
changed = config.download_strategy_modified? ? " *" : ""
|
80
80
|
menu.choice("modify global options#{changed}") do
|
81
81
|
GlobalOptions.new(config: config).run
|
82
82
|
end
|
data/lib/imap/backup/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imap-backup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 14.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Yates
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-10-
|
11
|
+
date: 2023-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- docs/delimiters-and-prefixes.md
|
137
137
|
- docs/development.md
|
138
138
|
- docs/migrate-server-keep-address.md
|
139
|
+
- docs/performance.md
|
139
140
|
- imap-backup.gemspec
|
140
141
|
- lib/cli_coverage.rb
|
141
142
|
- lib/email/mboxrd/message.rb
|
@@ -165,6 +166,8 @@ files:
|
|
165
166
|
- lib/imap/backup/cli/remote.rb
|
166
167
|
- lib/imap/backup/cli/restore.rb
|
167
168
|
- lib/imap/backup/cli/setup.rb
|
169
|
+
- lib/imap/backup/cli/single.rb
|
170
|
+
- lib/imap/backup/cli/single/backup.rb
|
168
171
|
- lib/imap/backup/cli/stats.rb
|
169
172
|
- lib/imap/backup/cli/transfer.rb
|
170
173
|
- lib/imap/backup/cli/utils.rb
|