Subversion Repositories zfs_utils

Rev

Rev 36 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
48 rodolico 1
```markdown
2
# sneakernet — Sneakernet replication script
34 rodolico 3
 
48 rodolico 4
Perl script to perform sneakernet replication of ZFS datasets between two servers using
5
an external transport drive. The script is designed for FreeBSD systems and integrates
6
with `ZFS_Utils.pm` for shared helpers (mounting by GPT label, GELI handling, logging, etc.).
34 rodolico 7
 
48 rodolico 8
Version: 1.0
9
License: Simplified BSD (FreeBSD) — see header in `sneakernet` script for full terms.
34 rodolico 10
 
11
---
12
 
48 rodolico 13
## Summary / Purpose
34 rodolico 14
 
48 rodolico 15
`sneakernet` automates ZFS snapshot export/import using a removable transport disk. On the
16
source server it creates zfs send streams (optionally encrypted) and writes them to files on
17
the transport disk. On the target server it reads those files (optionally decrypts) and
18
pipes them into `zfs receive` to update the target datasets. The script also supports
19
using GELI to protect disks on the target and can build combined GELI keys from a
20
remote binary key and a local hex key (via helpers in `ZFS_Utils.pm`).
34 rodolico 21
 
48 rodolico 22
## Usage
34 rodolico 23
 
48 rodolico 24
Run from the command line. A YAML config file is expected next to the script named
25
`$scriptname.conf.yaml` (the script will create or update it if needed).
34 rodolico 26
 
48 rodolico 27
Basic options:
34 rodolico 28
 
48 rodolico 29
- `--dryrun`, `-n`  : run without making destructive changes (no writes)
30
- `--verbose`, `-v` : increase logging verbosity (can be stacked)
31
- `--help`, `-h`    : print help and exit
32
- `--version`, `-V` : print script version and exit
34 rodolico 33
 
48 rodolico 34
Example:
34 rodolico 35
 
48 rodolico 36
```bash
37
perl sneakernet --dryrun --verbose
38
```
34 rodolico 39
 
48 rodolico 40
## Dependencies
34 rodolico 41
 
48 rodolico 42
- FreeBSD system utilities: `geli`, `zfs`, `zpool`, `geom`, `gpart`, `mount`, `/usr/sbin/sendmail`
43
- Perl core modules: `strict`, `warnings`, `FindBin`, `Getopt::Long`, `File::Basename`, `Data::Dumper`
44
- Shared module in repository: `ZFS_Utils.pm` (provides `loadConfig`, `mountDriveByLabel`, `logMsg`, etc.)
45
- Optional CPAN modules: YAML::XS or YAML::Tiny (used by `ZFS_Utils::loadConfig`)
34 rodolico 46
 
48 rodolico 47
## Configuration (YAML)
34 rodolico 48
 
48 rodolico 49
The script ships with an in-code default `$config` hash that is used as the basis for the
50
YAML configuration. The following documents the important keys, types and defaults. When you
51
run `sneakernet` it attempts to `loadConfig($scriptFullPath.conf.yaml, $config)` and will
52
create the file from the defaults if it does not exist.
34 rodolico 53
 
48 rodolico 54
Top-level keys
34 rodolico 55
 
48 rodolico 56
- `dryrun` (bool) — default: `0` — if true, actions that change state are not executed.
57
- `verbosity` (int) — default: `1` — controls logging verbosity.
58
- `status_file` (string) — path to status file used to track last replicated snapshots.
59
- `log_file` (string) — path to runtime log file.
34 rodolico 60
 
48 rodolico 61
`source` (hash)
34 rodolico 62
 
48 rodolico 63
- `hostname` (string) — hostname of the source server (used to detect running role)
64
- `poolname` (string) — zpool name on the source (default: `pool`)
65
- `report` (hash)
66
  - `email` (string) — email to send the report to
67
  - `subject` (string) — optional subject
68
  - `targetDrive` (hash)
69
    - `fstype` (string) — filesystem type of report drive (ufs/msdos)
70
    - `check_interval` (int) — polling interval (seconds)
71
    - `label` (string) — GPT label of the report drive
72
    - `mount_point` (string) — optional mount point override
34 rodolico 73
 
48 rodolico 74
`target` (hash)
34 rodolico 75
 
48 rodolico 76
- `hostname` (string) — hostname of the target server
77
- `poolname` (string) — zpool name on the target (default: `backup`)
78
- `shutdown_after_replication` (bool) — default: `0` — if true, attempt to shutdown after completion
79
- `geli` (hash) — when present, instructs the script to decrypt/mount GELI-protected pool(s):
80
  - `secureKey` (hash)
81
    - `label` (string) — GPT label of the key disk (default: `replica`)
82
    - `fstype` (string) — filesystem of key disk (default: `ufs`)
83
    - `check_interval` (int) — polling interval for key disk
84
    - `wait_timeout` (int) — how long to wait for the key disk
85
    - `keyfile` (string) — filename of the remote binary key on the key disk (default: `geli.key`)
86
  - `localKey` (string) — 64-hex-character 256-bit key string or path to file containing hex
87
  - `target` (string) — path where the combined keyfile should be written (e.g. `/media/geli.key`)
88
  - `poolname` (string) — pool name to import on target
89
  - `diskList` (array) — optional list of device names to try (e.g. `['da0','da1']`)
34 rodolico 90
 
48 rodolico 91
`transport` (hash)
34 rodolico 92
 
48 rodolico 93
- `label` (string) — GPT label of the transport drive (default: `sneakernet`)
94
- `fstype` (string) — filesystem type for mounting (default: `ufs`)
95
- `mount_point` (string) — target mount point (default in sample: `/mnt/sneakernet`)
96
- `timeout` (int) — how long to wait for the transport device to appear (seconds)
97
- `check_interval` (int) — polling interval when waiting for the device (seconds)
98
- `encryption` (hash)
99
  - `key` (string) — hex key used by `openssl enc -aes-256-cbc` for transport encryption
100
  - `IV` (string) — hex IV used by encryption (defaults to zeros in sample)
34 rodolico 101
 
48 rodolico 102
`datasets` (hash)
34 rodolico 103
 
48 rodolico 104
- Keys are logical dataset names (user-defined blocks). Each dataset object contains:
105
  - `source` — parent or root dataset on the source (string)
106
  - `target` — parent or root dataset on the target (string)
107
  - `dataset` — dataset name
34 rodolico 108
 
48 rodolico 109
Example minimal YAML snippet (derived from the script defaults):
34 rodolico 110
 
48 rodolico 111
```yaml
112
dryrun: 0
113
log_file: /path/to/sneakernet.log
114
source:
115
  hostname: source-host
116
  poolname: pool
117
target:
118
  hostname: target-host
119
  poolname: backup
120
  geli:
121
    secureKey:
122
      label: replica
123
      keyfile: geli.key
124
    localKey: e98c66...bc9c
125
    target: /media/geli.key
126
transport:
127
  label: sneakernet
128
  mount_point: /mnt/sneakernet
129
datasets:
130
  files_share:
131
    source: pool
132
    target: backup
133
    dataset: files_share
34 rodolico 134
```
135
 
48 rodolico 136
## Functions (script-level / documented)
34 rodolico 137
 
48 rodolico 138
The following functions are defined inside `sneakernet` and are documented here. Many
139
helpers used by the script are provided by `ZFS_Utils.pm` (imported at the top of the script).
34 rodolico 140
 
48 rodolico 141
- `getStatusFile($filename)`
142
  - Returns: ARRAYREF of status lines (snapshot names). Reads `$filename` if present; returns
143
    empty arrayref and logs an informational message if file missing or unreadable.
34 rodolico 144
 
48 rodolico 145
- `writeStatusFile($filename, $statusList)`
146
  - Writes the provided ARRAYREF of status lines to `$filename`.
147
  - Behavior: backs up an existing file to `$filename.bak` before writing. Dies on failure.
34 rodolico 148
 
48 rodolico 149
- `dirnameToFileName($string, $delimiter='/', $substitution='.')`
150
  - Utility to turn dataset-like strings into filename-safe strings. Example: `pool/fs/sub` -> `pool.fs.sub`.
34 rodolico 151
 
48 rodolico 152
- `doSourceReplication($config, $statusList)`
153
  - Performs replication on the source server.
154
  - Behavior: enumerates source snapshots, builds zfs send commands with `makeReplicateCommands` (from `ZFS_Utils`),
155
    optionally pipes through `openssl enc` (if transport encryption key is set), and writes send streams to files on the
156
    transport mount point. Honors `$config->{dryrun}`.
157
  - Returns: `$newStatus` ARRAYREF of updated status lines.
34 rodolico 158
 
48 rodolico 159
- `cleanup($config, $message)`
160
  - Performs final cleanup and reporting actions.
161
  - Behavior: logs disk usage and zpool list, attempts to unmount the transport drive, sends report via `sendReport`, and
162
    optionally shuts down the machine if configured. Honors `dryrun`.
34 rodolico 163
 
48 rodolico 164
- `updateTarget($config)`
165
  - Reads files from the transport disk and feeds them into `zfs receive` to update target datasets.
166
  - Behavior: detects file->dataset mapping via the filename (uses `dirnameToFileName` reversal), optionally decrypts
167
    with `openssl enc -d` if encryption was used, and calls `zfs receive -F` for each file.
34 rodolico 168
 
48 rodolico 169
## Main flow summary
34 rodolico 170
 
48 rodolico 171
1. Load YAML config using `ZFS_Utils::loadConfig` with the default config provided in the script.
172
2. Parse CLI flags (dryrun, verbose, help, version). CLI flags override config when present.
173
3. Determine whether the running host matches `source.hostname` or `target.hostname` and set `runningAs` accordingly.
174
4. Mount the transport drive (fatal error if not found) using `ZFS_Utils::mountDriveByLabel`.
175
5. If running as source:
176
   - Clean transport directory (non-recursive), produce send streams and write to files on transport drive.
177
   - Update status file with newest snapshots.
178
6. If running as target:
179
   - If `target.geli` present, attempt to decrypt/mount GELI disks (via `ZFS_Utils::mountGeli`).
180
   - Update target datasets by reading files from transport and running `zfs receive`.
181
7. Run `cleanup()` to unmount, send reports, and optionally shutdown.
34 rodolico 182
 
48 rodolico 183
## Logging & Reports
34 rodolico 184
 
48 rodolico 185
- The script uses `ZFS_Utils::logMsg` throughout. Default log path is set from `$config->{log_file}` and
186
  the module exposes `$logFileName` and `$displayLogsOnConsole` for customizing behavior at runtime.
187
- Reports can be saved to a drive (via `mountDriveByLabel`) and/or emailed via `/usr/sbin/sendmail` using
188
  `ZFS_Utils::sendReport`.
34 rodolico 189
 
48 rodolico 190
## Security notes
34 rodolico 191
 
48 rodolico 192
- GELI combined keys are created by XOR'ing a remote binary key and a local 256-bit hex key — the resulting
193
  key is written with mode `0600`. Keep these files and the key disk physically secure.
194
- Transport encryption uses `openssl enc -aes-256-cbc`; manage the encryption key material carefully.
34 rodolico 195
 
48 rodolico 196
## Example quick-run checklist
34 rodolico 197
 
48 rodolico 198
1. Edit `sneakernet.conf.yaml` (create from defaults if necessary) and confirm `transport.mount_point` and `label`.
199
2. On source: run `perl sneakernet --dryrun --verbose` to validate the planned commands.
200
3. On source: run without `--dryrun` to execute replication.
201
4. Physically move the drive to the target, insert it, and run the script on the target host.
34 rodolico 202
 
48 rodolico 203
## Troubleshooting
34 rodolico 204
 
48 rodolico 205
- If the transport drive does not mount, check that the GPT label matches `transport.label` and that the filesystem
206
  type matches `transport.fstype`.
207
- If GELI attach fails, verify the keyfile exists on the secure key disk and that the local key hex string is correct
208
  (exactly 64 hex characters) and that the combined keyfile is created at the configured `target` path.
209
- Use `--dryrun` and `--verbose` to inspect command strings before running them.
34 rodolico 210
 
48 rodolico 211
---
34 rodolico 212
 
48 rodolico 213
Document last updated: 2025-12-15
34 rodolico 214