schulsync 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schulsync-0.1.0/.github/workflows/ci.yml +46 -0
- schulsync-0.1.0/.github/workflows/release.yml +64 -0
- schulsync-0.1.0/.gitignore +23 -0
- schulsync-0.1.0/CHANGELOG.md +21 -0
- schulsync-0.1.0/LICENSE +21 -0
- schulsync-0.1.0/PKG-INFO +229 -0
- schulsync-0.1.0/README.md +193 -0
- schulsync-0.1.0/docs/beispiel-report.html +1248 -0
- schulsync-0.1.0/docs/dsgvo.md +85 -0
- schulsync-0.1.0/docs/funktionsweise.md +126 -0
- schulsync-0.1.0/docs/images/apply-2026.svg +442 -0
- schulsync-0.1.0/docs/images/plan-2026.svg +414 -0
- schulsync-0.1.0/docs/images/plan-idempotent.svg +121 -0
- schulsync-0.1.0/docs/images/report-2026.png +0 -0
- schulsync-0.1.0/examples/lehrkraefte.csv +6 -0
- schulsync-0.1.0/examples/schild-export-2025.csv +150 -0
- schulsync-0.1.0/examples/schild-export-2026-nachzuegler.csv +151 -0
- schulsync-0.1.0/examples/schild-export-2026.csv +150 -0
- schulsync-0.1.0/examples/schild-export-legacy-cp1252.csv +6 -0
- schulsync-0.1.0/examples/schulsync.example.yaml +42 -0
- schulsync-0.1.0/lab/Dockerfile +23 -0
- schulsync-0.1.0/lab/README.md +28 -0
- schulsync-0.1.0/lab/docker-compose.yml +18 -0
- schulsync-0.1.0/lab/entrypoint.sh +25 -0
- schulsync-0.1.0/lab/schulsync.lab.yaml +25 -0
- schulsync-0.1.0/pyproject.toml +81 -0
- schulsync-0.1.0/scripts/erzeuge_beispieldaten.py +161 -0
- schulsync-0.1.0/scripts/erzeuge_readme_bilder.py +108 -0
- schulsync-0.1.0/src/schulsync/__init__.py +7 -0
- schulsync-0.1.0/src/schulsync/applier.py +185 -0
- schulsync-0.1.0/src/schulsync/cli.py +423 -0
- schulsync-0.1.0/src/schulsync/config.py +141 -0
- schulsync-0.1.0/src/schulsync/csvsource.py +194 -0
- schulsync-0.1.0/src/schulsync/ldapclient.py +392 -0
- schulsync-0.1.0/src/schulsync/models.py +167 -0
- schulsync-0.1.0/src/schulsync/passwords.py +42 -0
- schulsync-0.1.0/src/schulsync/planner.py +172 -0
- schulsync-0.1.0/src/schulsync/report.py +94 -0
- schulsync-0.1.0/src/schulsync/templates/briefe.html.j2 +56 -0
- schulsync-0.1.0/src/schulsync/templates/report.html.j2 +206 -0
- schulsync-0.1.0/src/schulsync/usernames.py +91 -0
- schulsync-0.1.0/tests/integration/conftest.py +59 -0
- schulsync-0.1.0/tests/integration/test_schuljahreswechsel.py +212 -0
- schulsync-0.1.0/tests/test_csvsource.py +115 -0
- schulsync-0.1.0/tests/test_planner.py +153 -0
- schulsync-0.1.0/tests/test_usernames.py +72 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint-und-unit:
|
|
10
|
+
name: Lint & Unit-Tests (Python ${{ matrix.python }})
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python: ["3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
- uses: actions/setup-python@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python }}
|
|
20
|
+
- name: Installieren
|
|
21
|
+
run: pip install -e ".[dev]"
|
|
22
|
+
- name: Ruff (Lint)
|
|
23
|
+
run: ruff check src tests scripts
|
|
24
|
+
- name: Unit-Tests
|
|
25
|
+
run: pytest -q
|
|
26
|
+
|
|
27
|
+
integration:
|
|
28
|
+
name: Integration gegen echten Samba-AD-DC
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
needs: lint-und-unit
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v7
|
|
33
|
+
- uses: actions/setup-python@v6
|
|
34
|
+
with:
|
|
35
|
+
python-version: "3.12"
|
|
36
|
+
- name: Installieren
|
|
37
|
+
run: pip install -e ".[dev]"
|
|
38
|
+
- name: Lab-DC starten (Samba AD, Domäne SCHULE.LOCAL)
|
|
39
|
+
run: docker compose -f lab/docker-compose.yml up -d --build --wait
|
|
40
|
+
- name: Integrationstests
|
|
41
|
+
env:
|
|
42
|
+
SCHULSYNC_LDAP_PASSWORT: Lab-Kennwort-2026
|
|
43
|
+
run: pytest -m integration -o addopts='' -q
|
|
44
|
+
- name: Lab-Logs bei Fehlern
|
|
45
|
+
if: failure()
|
|
46
|
+
run: docker logs schulsync-lab-ad --tail 100
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Auslöser: ein Versions-Tag (v0.1.0, v0.2.0, …).
|
|
4
|
+
# Veröffentlicht auf PyPI via Trusted Publishing (OIDC, keine Tokens)
|
|
5
|
+
# und legt ein GitHub-Release mit den Build-Artefakten an.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Paket bauen
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
- uses: actions/setup-python@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- name: Bauen (sdist + wheel)
|
|
21
|
+
run: |
|
|
22
|
+
pip install build twine
|
|
23
|
+
python -m build
|
|
24
|
+
twine check dist/*
|
|
25
|
+
- uses: actions/upload-artifact@v7
|
|
26
|
+
with:
|
|
27
|
+
name: dist
|
|
28
|
+
path: dist/
|
|
29
|
+
|
|
30
|
+
pypi:
|
|
31
|
+
name: Auf PyPI veröffentlichen
|
|
32
|
+
needs: build
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
environment:
|
|
35
|
+
name: pypi
|
|
36
|
+
url: https://pypi.org/p/schulsync
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write # OIDC für PyPI Trusted Publishing
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/download-artifact@v8
|
|
41
|
+
with:
|
|
42
|
+
name: dist
|
|
43
|
+
path: dist/
|
|
44
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
45
|
+
|
|
46
|
+
github-release:
|
|
47
|
+
name: GitHub-Release anlegen
|
|
48
|
+
needs: build
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
permissions:
|
|
51
|
+
contents: write
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v7
|
|
54
|
+
- uses: actions/download-artifact@v8
|
|
55
|
+
with:
|
|
56
|
+
name: dist
|
|
57
|
+
path: dist/
|
|
58
|
+
- name: Release erstellen
|
|
59
|
+
env:
|
|
60
|
+
GH_TOKEN: ${{ github.token }}
|
|
61
|
+
run: |
|
|
62
|
+
gh release create "$GITHUB_REF_NAME" dist/* \
|
|
63
|
+
--title "SchulSync $GITHUB_REF_NAME" \
|
|
64
|
+
--notes "Änderungen: siehe [CHANGELOG.md](https://github.com/${GITHUB_REPOSITORY}/blob/main/CHANGELOG.md)"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
htmlcov/
|
|
12
|
+
|
|
13
|
+
# SchulSync – erzeugte Dateien mit personenbezogenen Daten NIEMALS committen!
|
|
14
|
+
zugangsdaten-*.csv
|
|
15
|
+
briefe/
|
|
16
|
+
report-*.html
|
|
17
|
+
schulsync.yaml
|
|
18
|
+
!examples/schulsync.example.yaml
|
|
19
|
+
|
|
20
|
+
# OS / IDE
|
|
21
|
+
.DS_Store
|
|
22
|
+
.idea/
|
|
23
|
+
.vscode/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Änderungen
|
|
2
|
+
|
|
3
|
+
## 0.1.0 – 2026-07
|
|
4
|
+
|
|
5
|
+
Erste Veröffentlichung.
|
|
6
|
+
|
|
7
|
+
* Deklarativer Soll/Ist-Abgleich (plan/apply) zwischen Schulverwaltungs-CSV
|
|
8
|
+
und Active Directory: Anlegen, Klassenwechsel, Umbenennen, Reaktivieren,
|
|
9
|
+
Deaktivieren.
|
|
10
|
+
* CSV-Profile für SchILD-NRW und ASV-BW, generisches Profil mit freier
|
|
11
|
+
Spaltenzuordnung; automatische Erkennung von Kodierung (UTF-8/Windows-1252)
|
|
12
|
+
und Trennzeichen.
|
|
13
|
+
* Benutzernamen mit Transliteration (Umlaute, Türkisch, Polnisch …),
|
|
14
|
+
sAMAccountName-20-Zeichen-Grenze, domänenweiter Kollisionsprüfung.
|
|
15
|
+
* Sicherheitsnetze: Dry-Run als Standard, Notbremse gegen unvollständige
|
|
16
|
+
Exporte, Konflikte blockieren den Lauf, TLS-Zwang.
|
|
17
|
+
* Löschkonzept: Abgänger-OU mit Frist-Stempel, `cleanup` löscht erst nach
|
|
18
|
+
Ablauf der Aufbewahrungsfrist.
|
|
19
|
+
* HTML-Diff-Report und druckfertige Zugangsdaten-Briefe pro Klasse.
|
|
20
|
+
* Docker-Lab mit echtem Samba-AD-DC; Integrationstests decken den
|
|
21
|
+
kompletten Konten-Lebenszyklus ab und laufen in der CI.
|
schulsync-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Haydar Kozat
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
schulsync-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: schulsync
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Schuljahreswechsel ohne Handarbeit: deklarative Synchronisation von Schülerkonten aus der Schulverwaltung (SchILD-NRW, ASV-BW, CSV) ins Active Directory.
|
|
5
|
+
Project-URL: Homepage, https://github.com/haydarkozat/schulsync
|
|
6
|
+
Project-URL: Repository, https://github.com/haydarkozat/schulsync
|
|
7
|
+
Project-URL: Issues, https://github.com/haydarkozat/schulsync/issues
|
|
8
|
+
Author-email: Haydar Kozat <haydarkozat@hotmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: active-directory,asv-bw,dsgvo,ldap,samba,schild-nrw,schule,schuljahreswechsel,schulverwaltung
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Natural Language :: German
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Education
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: jinja2>=3.1
|
|
26
|
+
Requires-Dist: ldap3>=2.9.1
|
|
27
|
+
Requires-Dist: pydantic>=2.7
|
|
28
|
+
Requires-Dist: pyyaml>=6.0
|
|
29
|
+
Requires-Dist: rich>=13.7
|
|
30
|
+
Requires-Dist: typer>=0.12
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# SchulSync
|
|
38
|
+
|
|
39
|
+
**Schuljahreswechsel ohne Handarbeit: Schulverwaltung → Active Directory.**
|
|
40
|
+
|
|
41
|
+
[](https://github.com/haydarkozat/schulsync/actions/workflows/ci.yml)
|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
Jeden September dasselbe Ritual: Der neue Export aus der Schulverwaltung
|
|
47
|
+
liegt vor, und irgendjemand aus der Schul-IT klickt sich durch hunderte
|
|
48
|
+
Konten – neue Fünftklässler anlegen, alle Klassen eine Stufe
|
|
49
|
+
weiterschieben, Abgänger deaktivieren, Passwortzettel drucken. Zwei Tage
|
|
50
|
+
Handarbeit, und ein Tippfehler bedeutet, dass ein Kind am ersten Schultag
|
|
51
|
+
nicht ins WLAN kommt.
|
|
52
|
+
|
|
53
|
+
SchulSync macht daraus **einen Befehl mit Vorschau**:
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
## Das Prinzip: Terraform für Schülerkonten
|
|
58
|
+
|
|
59
|
+
Der CSV-Export der Schulverwaltung (SchILD-NRW, ASV-BW oder beliebige
|
|
60
|
+
Spalten) beschreibt den **Soll-Zustand**. Das Active Directory ist der
|
|
61
|
+
**Ist-Zustand**. SchulSync berechnet die Differenz – und führt sie erst
|
|
62
|
+
aus, wenn ein Mensch den Plan gesehen und bestätigt hat.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
schulsync plan export.csv # Was würde passieren? Ändert garantiert nichts.
|
|
66
|
+
schulsync apply export.csv # Plan ausführen – nach Bestätigung.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
* **Idempotent:** derselbe Export zweimal ⇒ beim zweiten Mal „nichts zu
|
|
70
|
+
tun". Abgebrochene Läufe repariert der nächste Lauf von selbst.
|
|
71
|
+
* **Ein Modell für alles:** Schuljahreswechsel, Zuzug im Februar,
|
|
72
|
+
Namensänderung nach Heirat – immer derselbe Soll/Ist-Vergleich.
|
|
73
|
+
* **Nachvollziehbar:** jeder Lauf auf Wunsch als HTML-Report für die
|
|
74
|
+
Ablage (Verfahrensnachweis).
|
|
75
|
+
|
|
76
|
+
## Was SchulSync erledigt
|
|
77
|
+
|
|
78
|
+
| | |
|
|
79
|
+
|---|---|
|
|
80
|
+
| ➕ **Anlegen** | Konto in der Klassen-OU, Klassen- & Sammelgruppen, Home-Verzeichnis-Attribute, merkbares Initialpasswort (`Tiger-Wolke-47`), Passwortwechsel bei erster Anmeldung erzwungen |
|
|
81
|
+
| ⇄ **Klassenwechsel** | OU-Umzug + Gruppentausch (`Klasse-5A` → `Klasse-6A`) |
|
|
82
|
+
| ✎ **Umbenennen** | Namensänderung ohne neuen Benutzernamen – niemand verliert Profil oder Dateien |
|
|
83
|
+
| ↻ **Reaktivieren** | Rückkehrer bekommen ihr altes Konto wieder statt eines Duplikats |
|
|
84
|
+
| ⏻ **Deaktivieren** | Abgänger: gesperrt, aus allen Gruppen entfernt, in die Abgänger-OU, mit Frist-Stempel |
|
|
85
|
+
| 🗑 **Löschen** | `schulsync cleanup` löscht erst nach Ablauf der Aufbewahrungsfrist – dokumentiertes Löschkonzept statt Karteileichen |
|
|
86
|
+
| ✉ **Briefe** | druckfertige Zugangsdaten-Briefe pro Klasse aus einem Befehl |
|
|
87
|
+
|
|
88
|
+
Dazu die Details, die man erst nach ein paar hundert Schülerkonten zu
|
|
89
|
+
schätzen weiß: Umlaut- und Sonderzeichen-Transliteration
|
|
90
|
+
(`Yılmaz → yilmaz`, `Đorđević → dordevic`), die 20-Zeichen-Grenze von
|
|
91
|
+
`sAMAccountName`, Kollisionszähler gegen **alle** Konten der Domäne
|
|
92
|
+
(`emma.fischer2`), automatische Erkennung von Kodierung (UTF-8 ↔
|
|
93
|
+
Windows-1252) und Trennzeichen.
|
|
94
|
+
|
|
95
|
+
## Eingebaute Sicherheitsnetze
|
|
96
|
+
|
|
97
|
+
* **Dry-Run zuerst:** `plan` hat keinerlei Schreibzugriff – Vorschau ist
|
|
98
|
+
der Normalfall, nicht das Extra.
|
|
99
|
+
* **Notbremse:** Ein Export, der mehr als die Hälfte der aktiven Konten
|
|
100
|
+
deaktivieren würde (typisch: halber Export, falsche Berichtsvorlage),
|
|
101
|
+
wird als Konflikt blockiert – bevor er Schaden anrichtet.
|
|
102
|
+
* **Konflikt heißt Stopp:** doppelte IDs, Konten ohne Quell-ID → Meldung
|
|
103
|
+
statt Automatik-Raten. Exit-Code 2, nichts wurde geändert.
|
|
104
|
+
* **Nur die eigene OU:** Konten außerhalb von `OU=SchulSync` werden nie
|
|
105
|
+
angefasst. Das Dienstkonto braucht keine Domain-Admin-Rechte, nur
|
|
106
|
+
Delegierung auf diese OU.
|
|
107
|
+
* **Verschlüsselung erzwungen:** LDAPS bzw. STARTTLS – unverschlüsselt
|
|
108
|
+
verweigert SchulSync den Dienst. Passwörter stehen nie in Dateien,
|
|
109
|
+
nur in `SCHULSYNC_LDAP_PASSWORT`.
|
|
110
|
+
|
|
111
|
+

|
|
112
|
+
|
|
113
|
+
## In 5 Minuten ausprobieren – mit echtem AD
|
|
114
|
+
|
|
115
|
+
Das Repo bringt ein Docker-Lab mit: ein wegwerfbarer **Samba Active
|
|
116
|
+
Directory Domain Controller** (Domäne `SCHULE.LOCAL`) plus fiktive
|
|
117
|
+
Beispieldaten für zwei Schuljahre.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone https://github.com/haydarkozat/schulsync && cd schulsync
|
|
121
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
122
|
+
pip install -e .
|
|
123
|
+
|
|
124
|
+
# 1) Lab-AD hochfahren (einmalig ~1 Minute)
|
|
125
|
+
docker compose -f lab/docker-compose.yml up -d --build --wait
|
|
126
|
+
export SCHULSYNC_LDAP_PASSWORT="Lab-Kennwort-2026"
|
|
127
|
+
|
|
128
|
+
# 2) Schuljahr 2025/26 einspielen: 149 Schüler:innen + 5 Lehrkräfte
|
|
129
|
+
schulsync apply examples/schild-export-2025.csv \
|
|
130
|
+
--lehrkraefte examples/lehrkraefte.csv --ja \
|
|
131
|
+
--config lab/schulsync.lab.yaml
|
|
132
|
+
|
|
133
|
+
# 3) Der Schuljahreswechsel: erst ansehen …
|
|
134
|
+
schulsync plan examples/schild-export-2026.csv \
|
|
135
|
+
--lehrkraefte examples/lehrkraefte.csv \
|
|
136
|
+
--report report.html --config lab/schulsync.lab.yaml
|
|
137
|
+
|
|
138
|
+
# … dann ausführen: 24 anlegen, 125 aufrücken, 24 Abgänger, 2 Umbenennungen
|
|
139
|
+
schulsync apply examples/schild-export-2026.csv \
|
|
140
|
+
--lehrkraefte examples/lehrkraefte.csv --ja \
|
|
141
|
+
--config lab/schulsync.lab.yaml
|
|
142
|
+
|
|
143
|
+
# 4) Zugangsdaten-Briefe für die neuen 5er drucken
|
|
144
|
+
schulsync briefe zugangsdaten-*.csv --loeschen --config lab/schulsync.lab.yaml
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Danach lohnt ein Blick in den HTML-Report ([Beispiel](docs/beispiel-report.html)):
|
|
148
|
+
|
|
149
|
+
[](docs/beispiel-report.html)
|
|
150
|
+
|
|
151
|
+
Und weil SchulSync idempotent ist, sagt derselbe Befehl direkt danach
|
|
152
|
+
schlicht die Wahrheit:
|
|
153
|
+
|
|
154
|
+

|
|
155
|
+
|
|
156
|
+
## Im echten Schulnetz
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
cp examples/schulsync.example.yaml schulsync.yaml # anpassen: Server, Basis-DN, Schema
|
|
160
|
+
export SCHULSYNC_LDAP_PASSWORT='…' # Bind-Passwort des Dienstkontos
|
|
161
|
+
schulsync check # Verbindung & Konfiguration prüfen
|
|
162
|
+
schulsync validate export.csv # Export prüfen (Kodierung, Dubletten)
|
|
163
|
+
schulsync plan export.csv --report plan.html # Vorschau für die Ablage
|
|
164
|
+
schulsync apply export.csv # mit Rückfrage ausführen
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Funktioniert gegen **Windows Server AD** und **Samba AD** (ab Werk wird
|
|
168
|
+
gegen einen echten Samba-DC integrationsgetestet, s. u.). Für die
|
|
169
|
+
Drift-Überwachung: `schulsync plan export.csv --check` liefert Exit-Code 3,
|
|
170
|
+
sobald AD und Schulverwaltung auseinanderlaufen – fertig ist der
|
|
171
|
+
Nightly-Check im Monitoring.
|
|
172
|
+
|
|
173
|
+
| Kommando | Zweck |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `schulsync check` | Konfiguration & AD-Verbindung prüfen (nur lesend) |
|
|
176
|
+
| `schulsync validate <csv>` | Export prüfen: Kodierung, Spalten, Dubletten |
|
|
177
|
+
| `schulsync plan <csv>` | Soll/Ist-Vergleich als Vorschau (Dry-Run) |
|
|
178
|
+
| `schulsync apply <csv>` | Plan ausführen (mit Bestätigung bzw. `--ja`) |
|
|
179
|
+
| `schulsync briefe <csv>` | Zugangsdaten-Briefe je Klasse als druckfertiges HTML |
|
|
180
|
+
| `schulsync cleanup` | Abgänger nach Ablauf der Aufbewahrungsfrist endgültig löschen |
|
|
181
|
+
|
|
182
|
+
## Warum nicht …?
|
|
183
|
+
|
|
184
|
+
* **… Microsoft School Data Sync?** Synct in die Cloud (Entra ID/Teams),
|
|
185
|
+
nicht ins on-prem AD, und setzt M365-Schulverträge voraus. SchulSync
|
|
186
|
+
läuft dort, wo viele Schulträger ihre Konten wirklich verwalten: im
|
|
187
|
+
lokalen Active Directory – ohne dass Schülerdaten das Haus verlassen.
|
|
188
|
+
* **… UCS@school?** Stark, aber ein kompletter Plattformwechsel samt
|
|
189
|
+
eigenem Ökosystem. SchulSync ist ein Werkzeug, keine Plattform: es
|
|
190
|
+
arbeitet mit dem AD, das schon da ist.
|
|
191
|
+
* **… das gewachsene PowerShell-Skript?** Kennt jede Schul-IT. Meist ohne
|
|
192
|
+
Dry-Run, ohne Idempotenz, ohne Notbremse, ohne Löschkonzept – und der
|
|
193
|
+
Autor ist nicht mehr an der Schule. Genau diese Lücken schließt
|
|
194
|
+
SchulSync, mit Tests statt Hoffnung.
|
|
195
|
+
|
|
196
|
+
## Qualität
|
|
197
|
+
|
|
198
|
+
* **60+ Tests**, darunter eine Integrationssuite, die in der CI einen
|
|
199
|
+
**echten Samba-AD-DC im Docker-Container** hochfährt und den kompletten
|
|
200
|
+
Lebenszyklus durchspielt: Erstbefüllung → Idempotenz →
|
|
201
|
+
Schuljahreswechsel → Rückkehrer-Reaktivierung → Notbremse →
|
|
202
|
+
Fristablauf & Löschung.
|
|
203
|
+
* Kein Mock spielt AD – was hier grün ist, lief gegen LDAP, TLS und
|
|
204
|
+
`unicodePwd`-Realität.
|
|
205
|
+
* [docs/funktionsweise.md](docs/funktionsweise.md) erklärt die
|
|
206
|
+
Designentscheidungen (warum `CN = Benutzername`, warum `employeeNumber`
|
|
207
|
+
als Schlüssel, warum diese Ausführungsreihenfolge).
|
|
208
|
+
* [docs/dsgvo.md](docs/dsgvo.md): Datenminimierung, Löschkonzept,
|
|
209
|
+
TLS-Zwang – Datenschutz als Designgrundlage, nicht als Fußnote.
|
|
210
|
+
|
|
211
|
+
> **Hinweis zu den Beispieldaten:** Alle Namen in `examples/` sind fiktiv
|
|
212
|
+
> und werden von `scripts/erzeuge_beispieldaten.py` deterministisch
|
|
213
|
+
> erzeugt. Es sind keine echten Schülerdaten – und es sollten auch nie
|
|
214
|
+
> welche ins Repo gelangen (`.gitignore` hilft nach).
|
|
215
|
+
|
|
216
|
+
## Über dieses Projekt
|
|
217
|
+
|
|
218
|
+
Ich habe 16 Jahre lang Schul-IT betrieben – zuletzt als
|
|
219
|
+
IT-Systemadministrator im türkischen FATİH-Programm (dem Pendant zum
|
|
220
|
+
DigitalPakt Schule) mit rund 5.000 Nutzerkonten. Den Schuljahreswechsel
|
|
221
|
+
habe ich oft genug von Hand gemacht, um zu wissen, welche Fehler um
|
|
222
|
+
3 Uhr nachts passieren. SchulSync ist das Werkzeug, das ich mir damals
|
|
223
|
+
gewünscht hätte.
|
|
224
|
+
|
|
225
|
+
**Haydar Kozat** ·
|
|
226
|
+
[GitHub](https://github.com/haydarkozat) ·
|
|
227
|
+
[LinkedIn](https://linkedin.com/in/haydar-kozat)
|
|
228
|
+
|
|
229
|
+
Lizenz: [MIT](LICENSE)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# SchulSync
|
|
2
|
+
|
|
3
|
+
**Schuljahreswechsel ohne Handarbeit: Schulverwaltung → Active Directory.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/haydarkozat/schulsync/actions/workflows/ci.yml)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
Jeden September dasselbe Ritual: Der neue Export aus der Schulverwaltung
|
|
11
|
+
liegt vor, und irgendjemand aus der Schul-IT klickt sich durch hunderte
|
|
12
|
+
Konten – neue Fünftklässler anlegen, alle Klassen eine Stufe
|
|
13
|
+
weiterschieben, Abgänger deaktivieren, Passwortzettel drucken. Zwei Tage
|
|
14
|
+
Handarbeit, und ein Tippfehler bedeutet, dass ein Kind am ersten Schultag
|
|
15
|
+
nicht ins WLAN kommt.
|
|
16
|
+
|
|
17
|
+
SchulSync macht daraus **einen Befehl mit Vorschau**:
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Das Prinzip: Terraform für Schülerkonten
|
|
22
|
+
|
|
23
|
+
Der CSV-Export der Schulverwaltung (SchILD-NRW, ASV-BW oder beliebige
|
|
24
|
+
Spalten) beschreibt den **Soll-Zustand**. Das Active Directory ist der
|
|
25
|
+
**Ist-Zustand**. SchulSync berechnet die Differenz – und führt sie erst
|
|
26
|
+
aus, wenn ein Mensch den Plan gesehen und bestätigt hat.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
schulsync plan export.csv # Was würde passieren? Ändert garantiert nichts.
|
|
30
|
+
schulsync apply export.csv # Plan ausführen – nach Bestätigung.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
* **Idempotent:** derselbe Export zweimal ⇒ beim zweiten Mal „nichts zu
|
|
34
|
+
tun". Abgebrochene Läufe repariert der nächste Lauf von selbst.
|
|
35
|
+
* **Ein Modell für alles:** Schuljahreswechsel, Zuzug im Februar,
|
|
36
|
+
Namensänderung nach Heirat – immer derselbe Soll/Ist-Vergleich.
|
|
37
|
+
* **Nachvollziehbar:** jeder Lauf auf Wunsch als HTML-Report für die
|
|
38
|
+
Ablage (Verfahrensnachweis).
|
|
39
|
+
|
|
40
|
+
## Was SchulSync erledigt
|
|
41
|
+
|
|
42
|
+
| | |
|
|
43
|
+
|---|---|
|
|
44
|
+
| ➕ **Anlegen** | Konto in der Klassen-OU, Klassen- & Sammelgruppen, Home-Verzeichnis-Attribute, merkbares Initialpasswort (`Tiger-Wolke-47`), Passwortwechsel bei erster Anmeldung erzwungen |
|
|
45
|
+
| ⇄ **Klassenwechsel** | OU-Umzug + Gruppentausch (`Klasse-5A` → `Klasse-6A`) |
|
|
46
|
+
| ✎ **Umbenennen** | Namensänderung ohne neuen Benutzernamen – niemand verliert Profil oder Dateien |
|
|
47
|
+
| ↻ **Reaktivieren** | Rückkehrer bekommen ihr altes Konto wieder statt eines Duplikats |
|
|
48
|
+
| ⏻ **Deaktivieren** | Abgänger: gesperrt, aus allen Gruppen entfernt, in die Abgänger-OU, mit Frist-Stempel |
|
|
49
|
+
| 🗑 **Löschen** | `schulsync cleanup` löscht erst nach Ablauf der Aufbewahrungsfrist – dokumentiertes Löschkonzept statt Karteileichen |
|
|
50
|
+
| ✉ **Briefe** | druckfertige Zugangsdaten-Briefe pro Klasse aus einem Befehl |
|
|
51
|
+
|
|
52
|
+
Dazu die Details, die man erst nach ein paar hundert Schülerkonten zu
|
|
53
|
+
schätzen weiß: Umlaut- und Sonderzeichen-Transliteration
|
|
54
|
+
(`Yılmaz → yilmaz`, `Đorđević → dordevic`), die 20-Zeichen-Grenze von
|
|
55
|
+
`sAMAccountName`, Kollisionszähler gegen **alle** Konten der Domäne
|
|
56
|
+
(`emma.fischer2`), automatische Erkennung von Kodierung (UTF-8 ↔
|
|
57
|
+
Windows-1252) und Trennzeichen.
|
|
58
|
+
|
|
59
|
+
## Eingebaute Sicherheitsnetze
|
|
60
|
+
|
|
61
|
+
* **Dry-Run zuerst:** `plan` hat keinerlei Schreibzugriff – Vorschau ist
|
|
62
|
+
der Normalfall, nicht das Extra.
|
|
63
|
+
* **Notbremse:** Ein Export, der mehr als die Hälfte der aktiven Konten
|
|
64
|
+
deaktivieren würde (typisch: halber Export, falsche Berichtsvorlage),
|
|
65
|
+
wird als Konflikt blockiert – bevor er Schaden anrichtet.
|
|
66
|
+
* **Konflikt heißt Stopp:** doppelte IDs, Konten ohne Quell-ID → Meldung
|
|
67
|
+
statt Automatik-Raten. Exit-Code 2, nichts wurde geändert.
|
|
68
|
+
* **Nur die eigene OU:** Konten außerhalb von `OU=SchulSync` werden nie
|
|
69
|
+
angefasst. Das Dienstkonto braucht keine Domain-Admin-Rechte, nur
|
|
70
|
+
Delegierung auf diese OU.
|
|
71
|
+
* **Verschlüsselung erzwungen:** LDAPS bzw. STARTTLS – unverschlüsselt
|
|
72
|
+
verweigert SchulSync den Dienst. Passwörter stehen nie in Dateien,
|
|
73
|
+
nur in `SCHULSYNC_LDAP_PASSWORT`.
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
## In 5 Minuten ausprobieren – mit echtem AD
|
|
78
|
+
|
|
79
|
+
Das Repo bringt ein Docker-Lab mit: ein wegwerfbarer **Samba Active
|
|
80
|
+
Directory Domain Controller** (Domäne `SCHULE.LOCAL`) plus fiktive
|
|
81
|
+
Beispieldaten für zwei Schuljahre.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/haydarkozat/schulsync && cd schulsync
|
|
85
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
86
|
+
pip install -e .
|
|
87
|
+
|
|
88
|
+
# 1) Lab-AD hochfahren (einmalig ~1 Minute)
|
|
89
|
+
docker compose -f lab/docker-compose.yml up -d --build --wait
|
|
90
|
+
export SCHULSYNC_LDAP_PASSWORT="Lab-Kennwort-2026"
|
|
91
|
+
|
|
92
|
+
# 2) Schuljahr 2025/26 einspielen: 149 Schüler:innen + 5 Lehrkräfte
|
|
93
|
+
schulsync apply examples/schild-export-2025.csv \
|
|
94
|
+
--lehrkraefte examples/lehrkraefte.csv --ja \
|
|
95
|
+
--config lab/schulsync.lab.yaml
|
|
96
|
+
|
|
97
|
+
# 3) Der Schuljahreswechsel: erst ansehen …
|
|
98
|
+
schulsync plan examples/schild-export-2026.csv \
|
|
99
|
+
--lehrkraefte examples/lehrkraefte.csv \
|
|
100
|
+
--report report.html --config lab/schulsync.lab.yaml
|
|
101
|
+
|
|
102
|
+
# … dann ausführen: 24 anlegen, 125 aufrücken, 24 Abgänger, 2 Umbenennungen
|
|
103
|
+
schulsync apply examples/schild-export-2026.csv \
|
|
104
|
+
--lehrkraefte examples/lehrkraefte.csv --ja \
|
|
105
|
+
--config lab/schulsync.lab.yaml
|
|
106
|
+
|
|
107
|
+
# 4) Zugangsdaten-Briefe für die neuen 5er drucken
|
|
108
|
+
schulsync briefe zugangsdaten-*.csv --loeschen --config lab/schulsync.lab.yaml
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Danach lohnt ein Blick in den HTML-Report ([Beispiel](docs/beispiel-report.html)):
|
|
112
|
+
|
|
113
|
+
[](docs/beispiel-report.html)
|
|
114
|
+
|
|
115
|
+
Und weil SchulSync idempotent ist, sagt derselbe Befehl direkt danach
|
|
116
|
+
schlicht die Wahrheit:
|
|
117
|
+
|
|
118
|
+

|
|
119
|
+
|
|
120
|
+
## Im echten Schulnetz
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cp examples/schulsync.example.yaml schulsync.yaml # anpassen: Server, Basis-DN, Schema
|
|
124
|
+
export SCHULSYNC_LDAP_PASSWORT='…' # Bind-Passwort des Dienstkontos
|
|
125
|
+
schulsync check # Verbindung & Konfiguration prüfen
|
|
126
|
+
schulsync validate export.csv # Export prüfen (Kodierung, Dubletten)
|
|
127
|
+
schulsync plan export.csv --report plan.html # Vorschau für die Ablage
|
|
128
|
+
schulsync apply export.csv # mit Rückfrage ausführen
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Funktioniert gegen **Windows Server AD** und **Samba AD** (ab Werk wird
|
|
132
|
+
gegen einen echten Samba-DC integrationsgetestet, s. u.). Für die
|
|
133
|
+
Drift-Überwachung: `schulsync plan export.csv --check` liefert Exit-Code 3,
|
|
134
|
+
sobald AD und Schulverwaltung auseinanderlaufen – fertig ist der
|
|
135
|
+
Nightly-Check im Monitoring.
|
|
136
|
+
|
|
137
|
+
| Kommando | Zweck |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `schulsync check` | Konfiguration & AD-Verbindung prüfen (nur lesend) |
|
|
140
|
+
| `schulsync validate <csv>` | Export prüfen: Kodierung, Spalten, Dubletten |
|
|
141
|
+
| `schulsync plan <csv>` | Soll/Ist-Vergleich als Vorschau (Dry-Run) |
|
|
142
|
+
| `schulsync apply <csv>` | Plan ausführen (mit Bestätigung bzw. `--ja`) |
|
|
143
|
+
| `schulsync briefe <csv>` | Zugangsdaten-Briefe je Klasse als druckfertiges HTML |
|
|
144
|
+
| `schulsync cleanup` | Abgänger nach Ablauf der Aufbewahrungsfrist endgültig löschen |
|
|
145
|
+
|
|
146
|
+
## Warum nicht …?
|
|
147
|
+
|
|
148
|
+
* **… Microsoft School Data Sync?** Synct in die Cloud (Entra ID/Teams),
|
|
149
|
+
nicht ins on-prem AD, und setzt M365-Schulverträge voraus. SchulSync
|
|
150
|
+
läuft dort, wo viele Schulträger ihre Konten wirklich verwalten: im
|
|
151
|
+
lokalen Active Directory – ohne dass Schülerdaten das Haus verlassen.
|
|
152
|
+
* **… UCS@school?** Stark, aber ein kompletter Plattformwechsel samt
|
|
153
|
+
eigenem Ökosystem. SchulSync ist ein Werkzeug, keine Plattform: es
|
|
154
|
+
arbeitet mit dem AD, das schon da ist.
|
|
155
|
+
* **… das gewachsene PowerShell-Skript?** Kennt jede Schul-IT. Meist ohne
|
|
156
|
+
Dry-Run, ohne Idempotenz, ohne Notbremse, ohne Löschkonzept – und der
|
|
157
|
+
Autor ist nicht mehr an der Schule. Genau diese Lücken schließt
|
|
158
|
+
SchulSync, mit Tests statt Hoffnung.
|
|
159
|
+
|
|
160
|
+
## Qualität
|
|
161
|
+
|
|
162
|
+
* **60+ Tests**, darunter eine Integrationssuite, die in der CI einen
|
|
163
|
+
**echten Samba-AD-DC im Docker-Container** hochfährt und den kompletten
|
|
164
|
+
Lebenszyklus durchspielt: Erstbefüllung → Idempotenz →
|
|
165
|
+
Schuljahreswechsel → Rückkehrer-Reaktivierung → Notbremse →
|
|
166
|
+
Fristablauf & Löschung.
|
|
167
|
+
* Kein Mock spielt AD – was hier grün ist, lief gegen LDAP, TLS und
|
|
168
|
+
`unicodePwd`-Realität.
|
|
169
|
+
* [docs/funktionsweise.md](docs/funktionsweise.md) erklärt die
|
|
170
|
+
Designentscheidungen (warum `CN = Benutzername`, warum `employeeNumber`
|
|
171
|
+
als Schlüssel, warum diese Ausführungsreihenfolge).
|
|
172
|
+
* [docs/dsgvo.md](docs/dsgvo.md): Datenminimierung, Löschkonzept,
|
|
173
|
+
TLS-Zwang – Datenschutz als Designgrundlage, nicht als Fußnote.
|
|
174
|
+
|
|
175
|
+
> **Hinweis zu den Beispieldaten:** Alle Namen in `examples/` sind fiktiv
|
|
176
|
+
> und werden von `scripts/erzeuge_beispieldaten.py` deterministisch
|
|
177
|
+
> erzeugt. Es sind keine echten Schülerdaten – und es sollten auch nie
|
|
178
|
+
> welche ins Repo gelangen (`.gitignore` hilft nach).
|
|
179
|
+
|
|
180
|
+
## Über dieses Projekt
|
|
181
|
+
|
|
182
|
+
Ich habe 16 Jahre lang Schul-IT betrieben – zuletzt als
|
|
183
|
+
IT-Systemadministrator im türkischen FATİH-Programm (dem Pendant zum
|
|
184
|
+
DigitalPakt Schule) mit rund 5.000 Nutzerkonten. Den Schuljahreswechsel
|
|
185
|
+
habe ich oft genug von Hand gemacht, um zu wissen, welche Fehler um
|
|
186
|
+
3 Uhr nachts passieren. SchulSync ist das Werkzeug, das ich mir damals
|
|
187
|
+
gewünscht hätte.
|
|
188
|
+
|
|
189
|
+
**Haydar Kozat** ·
|
|
190
|
+
[GitHub](https://github.com/haydarkozat) ·
|
|
191
|
+
[LinkedIn](https://linkedin.com/in/haydar-kozat)
|
|
192
|
+
|
|
193
|
+
Lizenz: [MIT](LICENSE)
|