Skip to content

Commit 61d0873

Browse files
committed
Publish latest code
1 parent 7352ad7 commit 61d0873

11 files changed

Lines changed: 1383 additions & 0 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.idea/
2+
snapper.iml
3+
snapper/
4+
config.json
5+
__pycache__
6+
/*.sh

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# :turtle: Snapper
2+
3+
Snapper is a simple python script that executes [SnapRAID](https://github.com/amadvance/snapraid) in order to sync and scrub the array. Inspired by the great [snapraid-aio-script](https://github.com/auanasgheps/snapraid-aio-script) with a limited feature set.
4+
5+
The reason I created this is that I wanted more granular control of how my setup worked, which consequently means, this script is opinionated.
6+
7+
## Features
8+
9+
- Sanity checks the array
10+
- Runs `touch` if necessary
11+
- Runs `diff` before attempting to `sync`
12+
- Allows you to pre-hash before syncing
13+
- Allows you to automatically re-run `sync` if snapraid recommends it
14+
- Allows you to run snapraid with a lower priority to keep server and drives responsive
15+
- Allows you to abort execution if configurable thresholds are broken
16+
- Allows you to `scrub` after `sync`
17+
- Logs the raw snapraid output as well as formatted text
18+
- Creates a nicely formatted report and sends it via email or discord
19+
- Provides live insight into the sync/scrub process in Discord
20+
- Spin down selected hard drives after script completion
21+
22+
**This project is a work in progress, and can change at any time.**
23+
24+
I welcome bugfixes and contributions, but be aware that I will not merge PRs that I do not feel do not fit the usage of this tool.
25+
26+
## How to use
27+
28+
- Ensure you have Python 3.7 or later installed
29+
- Install the necessary dependencies by running `pip3 install -r requirements.txt`
30+
- Download the [latest release](https://github.com/firasdib/snapper/releases/latest) of this project, or clone the git project.
31+
- Copy or rename `config.json.example` to `config.json`
32+
- Run the script via `python3 snapper.py`
33+
34+
You may run the script with the `--force` flag to force a sync/scrub and ignore any thresholds or sanity checks.
35+
36+
## Configuration
37+
38+
A `config.json` file is required and expected to be in the same root as this script.
39+
40+
Please read through the [json schema](config.schema.json) to understand the exact details of each property. If you're not fluent in json schema (I don't blame you), you could use something like [this](https://json-schema.app/view/%23?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffirasdib%2Fsnapper%2Fmain%2Fconfig.schema.json) to get a better idea of the different options.

config.json.example

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"snapraid": {
3+
"binary": "/usr/bin/snapraid",
4+
"config": "/etc/snapraid.conf",
5+
"nice": 10,
6+
"diff": {
7+
"thresholds": {
8+
"added": 500,
9+
"removed": 500
10+
}
11+
},
12+
"sync": {
13+
"pre_hash": true,
14+
"auto_sync": {
15+
"enabled": false,
16+
"max_attempts": 3
17+
}
18+
},
19+
"scrub": {
20+
"enabled": true,
21+
"check_percent": 3,
22+
"min_age": 30,
23+
"scrub_new": true
24+
}
25+
},
26+
"notifications": {
27+
"email": {
28+
"enabled": true,
29+
"binary": "/usr/bin/mailx",
30+
"from_email": "from@email.com",
31+
"to_email": "to@email.com"
32+
},
33+
"discord": {
34+
"enabled": false,
35+
"webhook_id": "",
36+
"webhook_token": ""
37+
}
38+
},
39+
"logs": {
40+
"dir": "/var/log/snapper",
41+
"max_count": 14
42+
},
43+
"spindown": {
44+
"enabled": true,
45+
"binary": "/usr/sbin/hdparm",
46+
"drives": "parity"
47+
}
48+
}

config.schema.json

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"snapraid": {
5+
"type": "object",
6+
"properties": {
7+
"binary": {
8+
"type": "string",
9+
"examples": [
10+
"/usr/bin/snapraid"
11+
],
12+
"description": "The location of your snapraid executable."
13+
},
14+
"config": {
15+
"type": "string",
16+
"examples": [
17+
"/etc/snapraid.conf"
18+
],
19+
"description": "Location of the snapraid config file. Necessary for sanity checks."
20+
},
21+
"nice": {
22+
"type": "number",
23+
"examples": [
24+
10
25+
],
26+
"description": "Run snapraid at a given `nice`. By default processes run at `0`. Lower values mean higher priority. Ranges between -20 to +19.",
27+
"minimum": -20,
28+
"maximum": 19
29+
},
30+
"diff": {
31+
"type": "object",
32+
"properties": {
33+
"thresholds": {
34+
"type": "object",
35+
"properties": {
36+
"added": {
37+
"type": "number",
38+
"examples": [
39+
500
40+
],
41+
"description": "If more files than the threshold amount have been added, don't execute jobs. Set to `0` to disable.",
42+
"minimum": 0
43+
},
44+
"removed": {
45+
"type": "number",
46+
"examples": [
47+
500
48+
],
49+
"description": "If more files than the threshold amount have been removed, don't execute jobs. Set to `0` to disable.",
50+
"minimum": 0
51+
}
52+
},
53+
"additionalProperties": false,
54+
"required": ["added", "removed"]
55+
}
56+
},
57+
"additionalProperties": false,
58+
"required": ["thresholds"]
59+
},
60+
"sync": {
61+
"type": "object",
62+
"properties": {
63+
"pre_hash": {
64+
"type": "boolean",
65+
"examples": [
66+
true
67+
],
68+
"description": "Whether to pre-hash changed blocks before syncing."
69+
},
70+
"auto_sync": {
71+
"type": "object",
72+
"properties": {
73+
"enabled": {
74+
"type": "boolean",
75+
"examples": [
76+
false
77+
],
78+
"description": "Whether or not to re-run the sync command if snapraid recommends it."
79+
},
80+
"max_attempts": {
81+
"type": "number",
82+
"examples": [3],
83+
"description": "The max amount of attempts to `sync` the array before bailing.",
84+
"minimum": 0
85+
}
86+
},
87+
"additionalProperties": false,
88+
"required": ["enabled", "max_attempts"]
89+
}
90+
},
91+
"additionalProperties": false,
92+
"required": ["pre_hash", "auto_sync"]
93+
},
94+
"scrub": {
95+
"type": "object",
96+
"properties": {
97+
"enabled": {
98+
"type": "boolean",
99+
"examples": [
100+
true
101+
],
102+
"description": "Whether or not to scrub the array."
103+
},
104+
"check_percent": {
105+
"type": "number",
106+
"examples": [
107+
3
108+
],
109+
"description": "How many percent of the array to scrub each time. Set to `0` to disable scrubbing.",
110+
"minimum": 0,
111+
"maximum": 100
112+
},
113+
"min_age": {
114+
"type": "number",
115+
"examples": [
116+
30
117+
],
118+
"description": "How old the blocks have to be before considered for scrub, in days.",
119+
"minimum": 1
120+
},
121+
"scrub_new": {
122+
"type": "boolean",
123+
"examples": [
124+
true
125+
],
126+
"description": "Whether to scrub newly synced blocks or not."
127+
}
128+
},
129+
"additionalProperties": false,
130+
"required": ["enabled", "check_percent", "min_age", "scrub_new"]
131+
}
132+
},
133+
"additionalProperties": false,
134+
"required": ["binary", "config", "nice", "diff", "sync", "scrub"]
135+
},
136+
"notifications": {
137+
"type": "object",
138+
"properties": {
139+
"email": {
140+
"type": "object",
141+
"properties": {
142+
"enabled": {
143+
"type": "boolean",
144+
"examples": [
145+
true
146+
],
147+
"description": "Whether or not to send notifications and reports to the defined email."
148+
},
149+
"binary": {
150+
"type": "string",
151+
"examples": [
152+
"/usr/bin/mailx"
153+
],
154+
"description": "The location of `mailx`."
155+
},
156+
"from_email": {
157+
"type": "string",
158+
"examples": [
159+
"from@email.com"
160+
],
161+
"description": "The senders email."
162+
},
163+
"to_email": {
164+
"type": "string",
165+
"examples": [
166+
"to@email.com"
167+
],
168+
"description": "The recipients email."
169+
}
170+
},
171+
"additionalProperties": false,
172+
"required": ["enabled", "binary", "from_email", "to_email"]
173+
},
174+
"discord": {
175+
"type": "object",
176+
"properties": {
177+
"enabled": {
178+
"type": "boolean",
179+
"examples": [
180+
true
181+
],
182+
"description": "Whether or not to send notifications and reports to Discord."
183+
},
184+
"webhook_id": {
185+
"type": "string",
186+
"examples": ["1234567890"],
187+
"description": "Discord webhook id."
188+
},
189+
"webhook_token": {
190+
"type": "string",
191+
"examples": ["abc123"],
192+
"description": "Discord webhook token."
193+
}
194+
},
195+
"additionalProperties": false,
196+
"required": ["enabled", "webhook_id", "webhook_token"]
197+
}
198+
},
199+
"additionalProperties": false,
200+
"required": ["email", "discord"]
201+
},
202+
"logs": {
203+
"type": "object",
204+
"properties": {
205+
"dir": {
206+
"type": "string",
207+
"examples": [
208+
"/var/log/snapper"
209+
],
210+
"description": "The directory in which to save logs. Will be created if it does not exist."
211+
},
212+
"max_count": {
213+
"type": "number",
214+
"examples": [
215+
14
216+
],
217+
"description": "How many historic logs to keep. A new log is generated on each run, and the previous ones are rotated and gzipped.",
218+
"minimum": 1
219+
}
220+
},
221+
"additionalProperties": false,
222+
"required": ["dir", "max_count"]
223+
},
224+
"spindown": {
225+
"type": "object",
226+
"properties": {
227+
"enabled": {
228+
"type": "boolean",
229+
"examples": [true],
230+
"description": "Whether to spin down hard drives after script execution or not"
231+
},
232+
"binary": {
233+
"type": "string",
234+
"examples": [
235+
"/usr/sbin/hdparm"
236+
],
237+
"description": "The location of the `hdparm` executable."
238+
},
239+
"drives": {
240+
"type": "string",
241+
"enum": ["parity", "all"],
242+
"examples": ["parity"],
243+
"description": "Which drives to spin down after script execution is complete"
244+
}
245+
},
246+
"additionalProperties": false,
247+
"required": ["enabled", "binary", "drives"]
248+
}
249+
},
250+
"additionalProperties": false,
251+
"required": ["snapraid", "notifications", "logs", "spindown"]
252+
}

reports/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)