Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2a08be9
fix: replace bad substitution \${} with \${NOCOLOR} in size_calculation
claude May 22, 2026
266f50d
fix: anchor grep patterns in ddpar-restore.sh to prevent multi-line m…
claude May 22, 2026
209833e
fix: use consistent ss -tln (TCP only) for remote port checks
claude May 22, 2026
16ea99e
fix: prevent port substring false-positives in ss grep patterns
claude May 22, 2026
b617f2a
fix: quote INPUT and OUTPUT path variables to handle paths with spaces
claude May 22, 2026
124a400
fix: add missing -e flag to echo in backup mode for color output
claude May 22, 2026
42b6ab0
fix: move fallocate out of restore loop to only run once
claude May 22, 2026
05c4fb3
fix: correct RANDOM port range comment and simplify to one line
claude May 22, 2026
a1fb82e
docs: add TESTING.md with example commands for all transfer scenarios
claude May 23, 2026
f099ff1
docs: add CLAUDE.md with project context for Claude Code sessions
claude May 23, 2026
a066c98
config: add .shellcheckrc for bash linting
claude May 23, 2026
a809370
docs: add ARCHITECTURE.md with design documentation
claude May 23, 2026
cb4c0bd
config: add Makefile with lint target
claude May 23, 2026
d729fb1
docs: add CHANGELOG.md documenting all bug fixes from this session
claude May 23, 2026
7366968
feat: implement local clone check (source vs destination)
claude May 23, 2026
b25ff2e
feat: add remote netcat backup (uncompressed, no checks)
claude May 23, 2026
15c5937
Merge remote-tracking branch 'origin/claude/review-ddpar-bugs-bc3la' …
claude May 23, 2026
1975ce6
docs: add remote netcat backup tests to TESTING.md
claude May 23, 2026
c0bf833
Merge remote-tracking branch 'origin/Claude/config' into claude/remot…
claude May 23, 2026
43beae7
Merge remote-tracking branch 'origin/claude/local-uncompressed-checks…
claude May 23, 2026
ad2f48a
feat: add remote netcat restore (uncompressed) to ddpar-restore.sh
claude May 23, 2026
41b44a8
feat: add remote netcat checks (uncompressed) to ddpar-check.sh
claude May 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# ShellCheck-Konfiguration für ddpar
shell=bash

# SC2034: Variable assigned but not used
# Wird für Farbvariablen (NOCOLOR, INFOCOLOR etc.) ignoriert,
# da sie nur gesetzt werden wenn das Terminal Farben unterstützt.
disable=SC2034

# SC2207: Prefer mapfile or read -a to split command output into array
# Wird vorerst ignoriert, da die betroffenen Stellen keine Arrays benötigen.
disable=SC2207
146 changes: 146 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Architektur

## Grundprinzip: Paralleles `dd`

ddpar teilt die Eingabe in `NUM_JOBS` gleichgroße Segmente auf. Jedes Segment
wird von einem eigenen `dd`-Prozess gelesen und geschrieben. Die Prozesse laufen
gleichzeitig im Hintergrund (`&`), am Ende wartet das Skript mit `wait` auf alle.

### Segmentberechnung

```
SPLIT_SIZE = INPUT_SIZE / NUM_JOBS

Segment N:
Lesen: dd if=INPUT skip=$((N * SPLIT_SIZE / BLOCKSIZEBYTES)) count=$((SPLIT_SIZE / BLOCKSIZEBYTES))
Schreiben: dd of=OUTPUT seek=$((N * SPLIT_SIZE / BLOCKSIZEBYTES))
```

`INPUT_SIZE` muss durch `NUM_JOBS × BLOCKSIZEBYTES` ganzzahlig teilbar sein.
Das Skript prüft dies in `size_calculation()` und gibt Hinweise auf alternative
Job-Zahlen oder Blockgrößen, falls die Teilung nicht aufgeht.

---

## Pipe-Architektur

Je nach aktivierten Optionen wird die Pipe dynamisch zusammengebaut in `$FULL_CMD`
und dann per `eval "${FULL_CMD}"` ausgeführt.

### Backup lokal, unkomprimiert

```
dd if=INPUT ... | dd of=OUTPUT_PART ... &
```

### Backup lokal, mit Checksumme

```
dd if=INPUT ... | tee >(sha256sum > PART.sha256) | dd of=OUTPUT_PART ... &
```

### Backup lokal, mit gzip-Kompression

```
dd if=INPUT ... | gzip -LEVEL > OUTPUT_PART.gz &
```

### Backup lokal, mit Checksumme + gzip

```
dd if=INPUT ... | tee >(sha256sum > PART.sha256) | gzip -LEVEL > OUTPUT_PART.gz &
```

### Remote Clone (Netcat)

**Lokal:**
```
dd if=INPUT ... | nc REMOTE_HOST PORT &
```

**Remote (startet zuerst als Listener):**
```
nc -N -l PORT | dd of=OUTPUT ... & (läuft im Hintergrund via nohup + SSH)
```

Das Skript prüft nach dem Start des Remote-Listeners per `ss -tln`, ob der Port
tatsächlich belegt ist (bis zu 3 Versuche mit 1 s Pause) bevor der lokale
`nc`-Client verbindet.

---

## Metadaten-Datei

Beim Backup schreibt `ddpar.sh` eine Metadaten-Datei `<BASE>-metadata.txt`,
die alle Parameter enthält, die `ddpar-restore.sh` und `ddpar-check.sh` für
die Rekonstruktion benötigen:

```
NUM_JOBS=4
FILE_NAME=sdb
BLOCKSIZEBYTES=1048576
INPUT_SIZE=68719476736
INPUT_FILE_NAME=sdb
FILE_TYPE=block special (8/16)
SPLIT_SIZE=17179869184
COMPRESSION=1 # nur wenn komprimiert
COMPRESSION_LEVEL=6 # nur wenn komprimiert
```

**Wichtig:** Alle `grep`-Zugriffe auf diese Datei müssen mit `^`-Anker arbeiten
(`grep "^KEY="`), da sonst Präfix-Matches auftreten können (z.B. `COMPRESSION`
matcht auch `COMPRESSION_LEVEL`).

---

## Remote-Konzept

### SSH-Multiplexing

`ddpar.sh` öffnet eine persistente SSH-Master-Verbindung über einen Unix-Socket
(`SSH_SOCKET_PATH`). Alle weiteren SSH-Aufrufe (Befehle, Hintergrundprozesse)
laufen über diesen Kontroll-Socket, ohne erneute Authentifizierung.

```
ssh -o ControlMaster=auto -o ControlPersist=yes -S SOCKET HOST true
```

### Port-Auswahl

Für jeden Job wird ein Port aus dem Bereich **10000–42767** zufällig gewählt
(`REMOTE_PORT + PART_NUM`). Vor der Nutzung prüft `check_remote_port_availability()`
via `ss -tln`, ob der Port auf dem Remote-Host frei ist. Bei Kollision wird
ein neuer Port generiert.

### Remote-Modi (`-r`)

| Flag | Bedeutung | Implementierungsstatus |
|---|---|---|
| `n` | Netcat ohne Datenverschlüsselung | ✅ |
| `l` | Vollständig über SSH (verschlüsselt) | ⚙️ teilweise |
| `c` | Kompression auf der Remote-Seite | ⚙️ teilweise |

---

## Funktionsübersicht (`ddpar.sh`)

| Funktion | Aufgabe |
|---|---|
| `option_analysis` | Kommandozeilenparameter parsen und validieren |
| `set_colors` | ANSI-Farbvariablen setzen (nur wenn Terminal Farben unterstützt) |
| `establish_ssh_connection` | SSH-Verbindung aufbauen (mit oder ohne Passwort via sshpass) |
| `connect_ssh` | SSH-Verbindung prüfen und ggf. aufbauen |
| `is_ssh_socket_alive` | Prüft ob der SSH-Kontroll-Socket noch aktiv ist |
| `execute_command` | Befehl lokal oder remote ausführen |
| `execute_remote_command` | Befehl immer remote via SSH ausführen |
| `execute_remote_background_command` | Befehl remote im Hintergrund starten (nohup) |
| `close_ssh_connection` | SSH-Multiplexing-Verbindung schließen |
| `check_commands_availability` | Prüft ob benötigte Tools lokal vorhanden sind |
| `check_remote_commands_availability` | Prüft ob benötigte Tools remote vorhanden sind |
| `input_analysis` | Typ und Größe der Eingabe bestimmen |
| `output_analysis` | Typ und Größe des Ziels bestimmen |
| `remote_port_generation` | Zufälligen Port im Bereich 10000–42767 generieren |
| `check_remote_port_availability` | Prüft ob ein Port auf dem Remote-Host frei ist |
| `size_calculation` | SPLIT_SIZE berechnen, Teilbarkeit prüfen |
| `clone_file` | Paralleler Clone einer regulären Datei |
| `clone_block` | Paralleler Clone eines Block-Devices |
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Changelog

## [Unreleased] – branch claude/review-ddpar-bugs-bc3la

### Hinzugefügt
- `TESTING.md` mit Beispielkommandos für alle Übertragungsvarianten

### Behoben
- **ddpar.sh:** `${}` Bad-Substitution in `size_calculation()` — der Hinweis auf
die nächsthöhere Thread-Zahl wurde nie ausgegeben, stattdessen ein Shell-Fehler
produziert (`2a08be9`)
- **ddpar-restore.sh:** `grep "COMPRESSION"` matchte auch `COMPRESSION_LEVEL=`
und `grep "FILE_NAME"` matchte auch `INPUT_FILE_NAME=`; Muster mit `^`-Anker
verankert (`266f50d`)
- **ddpar.sh:** `ss -tln` vs. `ss -tuln` Inkonsistenz — alle Port-Prüfungen
nutzen jetzt einheitlich `ss -tln` (nur TCP, da Netcat TCP verwendet) (`209833e`)
- **ddpar.sh:** Port-Substring-Matching — `grep -q ":PORT"` matchte z.B.
Port `100` in `:10000`; ersetzt durch `grep -qE ":PORT[^0-9]"` (`16ea99e`)
- **ddpar.sh:** Unquotierte Pfadvariablen `${INPUT}` und `${OUTPUT}` in `file`,
`stat`, `blockdev`-Aufrufen; Tippfehler `${INPUT_SIZE=}` → `${INPUT_SIZE}`
(`b617f2a`)
- **ddpar.sh:** Fehlendes `-e` bei `echo` im Backup-Modus — ANSI-Farbcodes
wurden als Klartext ausgegeben (`124a400`)
- **ddpar-restore.sh:** `fallocate` wurde innerhalb der Job-Schleife für jeden
Job aufgerufen statt einmal vor der Schleife (`42b6ab0`)
- **ddpar.sh:** `RANDOM % 55001` ist identisch zu `RANDOM` da `RANDOM` max.
32767 liefert; vereinfacht zu `REMOTE_PORT=$(( RANDOM + 10000 ))`, Kommentar
korrigiert auf effektiven Bereich 10000–42767 (`05c4fb3`)

---

## [Merged] – PR #1 (branch claude/review-ddpar-bugs-bc3la → main)

### Behoben
- **ddpar.sh:** Off-by-One in Post-Loop-Bedingung — Erfolg beim letzten Versuch
(ATTEMPT == MAX_ATTEMPTS) wurde fälschlich als Fehler gewertet (`-ge` → `-gt`)
(`075de3c`)
- **ddpar.sh, ddpar-restore.sh:** Unquotiertes `eval ${FULL_CMD}` — durch
Word-Splitting und Glob-Expansion konnte es zu unvorhersehbarer Befehlsausführung
kommen; ersetzt durch `eval "${FULL_CMD}"` (`927b3a8`)
- **ddpar-check.sh:** `[ ! -z $SOURCE ]` und `[ ! -z $DESTINATION ]` ohne Quotes
— scheitert bei leeren Variablen; Quotes ergänzt (`bf1a62f`)
- **ddpar-restore.sh:** Größenprüfung verglich `BLOCKSIZEBYTES` (~1 MB) mit
`OUTPUT_SIZE` statt `INPUT_SIZE` (Backup-Größe) — sinnloser Vergleich, der nie
anschlug (`57b6fce`)
- **ddpar.sh:** Unquotiertes `[ -z ${REMOTE_PORT} ]` — Quotes ergänzt (`323c254`)
68 changes: 68 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# CLAUDE.md – Projektkontext für Claude Code

## Projektübersicht

**ddpar** (dd parallel) ist eine Sammlung von Bash-Skripten zum parallelen Klonen,
Sichern und Wiederherstellen von Block-Devices und Dateien mittels `dd`.
Die Parallelisierung erfolgt durch Aufteilung der Eingabe in gleichgroße Segmente,
die gleichzeitig über separate `dd`-Prozesse verarbeitet werden.

## Dateien

| Datei | Aufgabe |
|---|---|
| `ddpar.sh` | Hauptskript: Clone- und Backup-Modus, lokal und remote |
| `ddpar-restore.sh` | Wiederherstellen eines mit `ddpar.sh -m backup` erstellten Backups |
| `ddpar-check.sh` | Prüfung der Integrität via SHA256 (Quelle↔Backup oder Backup↔Ziel) |
| `TESTING.md` | Manuelle Testszenarien mit Beispielkommandos |
| `ARCHITECTURE.md` | Design-Dokumentation: Parallelisierung, Pipes, Remote-Konzept |
| `CHANGELOG.md` | Versionshistorie und Bug-Fix-Dokumentation |

## Coding-Konventionen

- **Shell:** Bash (`#!/bin/bash`), keine POSIX-only-Syntax erforderlich
- **Linter:** ShellCheck (siehe `.shellcheckrc`). Vor jedem Commit ausführen: `make lint`
- **Variablen:** Immer in doppelten Anführungszeichen, wenn sie Pfade enthalten können
- **`eval`:** Immer `eval "${VAR}"` mit Quotes — nie `eval $VAR`
- **Tests:** `[ -z "$VAR" ]` mit Quotes — nie `[ -z $VAR ]`
- **grep in Metadaten:** Immer mit `^`-Anker, z.B. `grep "^KEY="`, um Prefix-Matches zu vermeiden
- **Farb-Echo:** Immer `echo -e` wenn ANSI-Codes (`${INFOCOLOR}` etc.) ausgegeben werden
- **Port-Prüfung:** `grep -qE ":PORT[^0-9]"` — niemals `:PORT` ohne Suffix (Substring-Matching)

## Metadaten-Format

Backup-Metadatei `<BACKUP_BASE>-metadata.txt` — zeilenweise `KEY=VALUE`:

```
NUM_JOBS=4
FILE_NAME=sdb
BLOCKSIZEBYTES=1048576
INPUT_SIZE=68719476736
INPUT_FILE_NAME=sdb
FILE_TYPE=block special (8/16)
SPLIT_SIZE=17179869184
COMPRESSION=1 # nur vorhanden wenn komprimiert
COMPRESSION_LEVEL=6 # nur vorhanden wenn komprimiert
```

## Änderungen validieren

```bash
make lint # ShellCheck auf alle .sh-Dateien
```

Manuelle Tests: siehe `TESTING.md`.

## Bekannte Einschränkungen / offene Baustellen

- Remote Backup, Remote Restore und Remote Checks (netcat, unkomprimiert) sind implementiert; komprimierter Remote-Transfer fehlt noch (🛑 in README)
- Remote Checks (`ddpar-check.sh -r`) vergleichen nur SHA256-Hashes je Segment (lokal vs. per SSH) — kein netcat-Datentransfer nötig
- `RANDOM` in Bash liefert nur 0–32767 → Remote-Ports werden aus dem Bereich 10000–42767 gewählt
- `fallocate` funktioniert nicht auf Block-Devices (wird korrekt übersprungen)
- Eingabegröße muss durch `NUM_JOBS × BLOCKSIZEBYTES` ganzzahlig teilbar sein
- Remote-Modus-Flags (`-r l/n/c`) sind noch nicht vollständig implementiert

## Branch-Strategie

- `main` — stabiler Stand
- `claude/*` — von Claude Code erstellte Feature-/Fix-Branches
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
SCRIPTS := ddpar.sh ddpar-restore.sh ddpar-check.sh

.PHONY: lint install-deps help

help:
@echo "Verfügbare Targets:"
@echo " make lint ShellCheck auf alle .sh-Dateien ausführen"
@echo " make install-deps ShellCheck installieren (Debian/Ubuntu)"

lint:
@command -v shellcheck > /dev/null 2>&1 || \
{ echo "shellcheck nicht gefunden. Installation: make install-deps"; exit 1; }
shellcheck $(SCRIPTS)

install-deps:
sudo apt-get install -y shellcheck
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Attempting scripted parallel dd execution.

| | clone (check) || backup (check) | restore (check) |
|----------|----------|-|----------|----------|
| block dev | :heavy_check_mark: (:stop_sign:) || :heavy_check_mark: (:heavy_check_mark:) | :heavy_check_mark: (:heavy_check_mark:) |
| file | :heavy_check_mark: (:stop_sign:) || :heavy_check_mark: (:heavy_check_mark:) | :heavy_check_mark: (:heavy_check_mark:) |
| block dev | :heavy_check_mark: (:heavy_check_mark:) || :heavy_check_mark: (:heavy_check_mark:) | :heavy_check_mark: (:heavy_check_mark:) |
| file | :heavy_check_mark: (:heavy_check_mark:) || :heavy_check_mark: (:heavy_check_mark:) | :heavy_check_mark: (:heavy_check_mark:) |

#### compressed
| | backup gzip (check) | restore gzip (check) |
Expand All @@ -32,7 +32,8 @@ Attempting scripted parallel dd execution.
| check_remote_port_availability | :heavy_check_mark: |
| output_analysis | :heavy_check_mark: |
| remote_cloning_commands | :gear: |
| remote_backup_commands | :stop_sign: |
| remote_backup_commands | :heavy_check_mark: |
| remote_restore_commands | :heavy_check_mark: |



Expand All @@ -42,8 +43,8 @@ Attempting scripted parallel dd execution.
#### uncompressed
| | clone (check) | | backup (check) | restore (check) |
|-|----------|-|----------|----------|
| block dev | :heavy_check_mark: (:stop_sign:) | | :stop_sign: (:stop_sign:) | :stop_sign: (:stop_sign:) |
| file | :heavy_check_mark: (:stop_sign:) | | :stop_sign: (:stop_sign:) | :stop_sign: (:stop_sign:) |
| block dev | :heavy_check_mark: (:heavy_check_mark:) | | :heavy_check_mark: (:heavy_check_mark:) | :heavy_check_mark: (:heavy_check_mark:) |
| file | :heavy_check_mark: (:heavy_check_mark:) | | :heavy_check_mark: (:heavy_check_mark:) | :heavy_check_mark: (:heavy_check_mark:) |

#### local [de]compression
| | backup gzip (check) | restore gzip (check) |
Expand Down
Loading