Pin each MusicMouse host to a deploy branch, and freeze musicmouse

musicmouse_version pointed both hosts at rearchitect-backend, a branch that only
ever existed on a laptop - so the previous commit's rework could never actually
have run. Point each host at a deploy/<host> branch on the remote instead. Those
branches are the deployment record: what is installed on that box right now,
moved with a force-push when you mean to deploy.

musicmouse (Kinderzimmer) is the reason this matters. It is in daily use and
still runs the pre-rearchitecture backend out of /opt/musicmouse_venv and
espmusicmouse/host_driver/main.py. That commit has no python-backend/
pyproject.toml, no python-backend/musicmouse.service and no web/ at all, so this
role cannot install it - and a run would have force-checked-out incompatible
code over a working device first and failed afterwards. Set
mediapi_install_kidsmusic: false there and leave it alone;
deploy/musicmouse records the commit that is installed, for rollback. Add a
stat + fail right after the checkout so the same mistake stops with an
explanation instead of failing three tasks deeper.

Also in the role:

- Install the typing game's curriculum next to config.yml. A config with a
  tippen: section refuses to start without it - curriculum_file is validated as
  must-exist - so musicdolphin's new config needs it present.

- Stop clobbering config.yml. The app writes to it at runtime: parent mode
  patches the volume keys and the remote-control page rewrites the remote:
  block. A plain copy: undid that on every run. Install-once by default, with
  -e pi_musicmouse_force_config=true to push a change deliberately.

- Fill out config-musicdolphin.yml against the current schema: library root and
  cache, an absolute static_dir (../web/dist would resolve to /media/web/dist
  from /media/musicmouse and silently serve nothing), tippen, and the volume
  range for a screen-driven instance. ha: is left commented out until the
  Wohnzimmer-oben entity ids are picked - the ones in the dev config are
  Arbeitszimmer. No mqtt: (it would publish a device of dead entities on a
  simulate-only host) and no lirc:/remote: (this host runs irserver, a serial
  daemon, not the lircd this speaks TCP to).

The curriculum is a verbatim copy of app content, so exclude it from
ansible-lint rather than reformat it away from its source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 19:04:57 +02:00
parent 9b1d01115b
commit 5763df90f8
9 changed files with 726 additions and 20 deletions

View File

@@ -0,0 +1,527 @@
# The typing game's lesson plan, referenced from config.yml's general.tippen.curriculum_file.
#
# Every lesson: title, subtitle (read aloud). Then either:
# - keys: the letters-kind keys this round has on the table. At most two of them may
# be new; the rest are keys already taught, written out to say "these are in play
# too". The loader tracks which keys were already active and spotlights only what is
# new, so `keys: [f, j, a, d, k]` right after `[f, j, a, d]` is a K lesson with the
# other four as review ("isolated" - heavy weight on K). A round that introduces
# nothing new spotlights its whole list instead ("mixed" - lighter weight, blended
# with everything learned so far), so two lessons with the same `keys` back to back
# is exactly how you write "isolated, then mixed" - no separate flag needed.
# - drill: true - a pure review round, no new content, whatever is active so far.
# - kind + words - a fragments/words/sentences consolidation round (dive mode by
# default; `mode:` overrides it - see the backend's `ELIGIBLE_MODES` for which
# modes fit which kind).
#
# Letters rounds always play in bubbles mode - the only arcade game that fits a single
# key at a time.
#
# Letter order follows German letter frequency, adapted to a home-row-first, mirrored
# pace for a six-year-old.
#
# Optional reward key, on any lesson:
#
# unlocks: <path>
#
# A path into the music library, written relative to config.yml's
# general.library.root - "Musik/Klaviersammlung/Lied.mp3", not a path from "/" or "~".
# (An absolute path, or one starting with "~", still resolves, so an old file keeps
# working - but relative is the form to write, since it survives moving the library.)
# Passing this lesson (two stars, or five attempts
# regardless of score) unlocks that track and everything before it in the same album,
# inclusive - so pointing at an album's last track unlocks the whole album, and
# pointing at track 5 of an audiobook unlocks chapters 1 through 5. The web front-end
# shows a still-locked track as a question mark, with a note on which lesson unlocks it.
#
# For a podcast (one album per episode under Kinderpodcasts), the same key unlocks that
# episode and every earlier one of the same show, oldest first - so pointing at the
# third episode ever published unlocks the first three, regardless of upload order.
#
# A path that matches nothing in the library is logged as a warning at startup, not a
# fatal error - a moved or mistyped path only loses that one reward, never the app.
#
# Unlike config.yml, this file is never written back to by the app - only progress is.
worlds:
- number: 1
title: "Die Grundstellung"
emoji: "🏝️"
reward: clownfish
lessons:
- title: "F und J"
subtitle: "Die Zeigefinger - die Tasten mit den Punkten"
keys: [f, j]
- title: "Mit A"
subtitle: "Das A kommt dazu"
keys: [f, j, a]
- title: "Ja"
subtitle: "Jaja ;)"
kind: fragments
words: [ja, fa, aja, afa, faja]
- title: "Wörter"
subtitle: "Aja"
kind: fragments
mode: feed
words: [ja, fa, jafa, aja, ajaja]
unlocks: "Musik/Klaviersammlung/Coffin Dance - Astronomía 2K.mp3"
- title: "D"
subtitle: "Das D kommt dazu"
keys: [f, j, a, d]
- title: "Wörter"
subtitle: "ja da"
kind: fragments
words: [da ja]
- title: "Wörter"
subtitle: "ja da"
kind: fragments
mode: feed
words: [da ja ada adda fada aja jaja]
- title: "Wörter"
subtitle: "ja da"
kind: fragments
words: [da ja ada adda fada aja jaja ajaja afa fa dafa jafa jada jajaja]
- title: "K"
subtitle: "Der rechte Mittelfinger"
keys: [f, j, a, d, k]
- title: "Wörter"
subtitle: "kaka"
kind: fragments
words: [ka, ak, kak, akka, kaka, daka, kada, jaka, kaja, fakka, kakka]
- title: "Wörter"
subtitle: "kada jaka"
kind: fragments
mode: feed
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
ka, ak, kak, akka, kaka, daka, kada, jaka, kaja, fakka,
ka, ak, kak, akka, kaka, daka, kada, jaka, kaja, fakka]
- title: "S"
subtitle: "Der linke Ringfinger"
keys: [f, j, a, d, k, s]
- title: "Wörter"
subtitle: "sassa"
kind: fragments
words: [sa, as, sas, assa, sass, das, dasa, sada, kasa, saka, fasa, jassa]
- title: "Wörter"
subtitle: "das sassa"
kind: fragments
mode: feed
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
ka, kak, akka, kaka, daka, kada, jaka, kaja,
sa, as, sas, assa, sass, das, dasa, sada, kasa, saka, fasa, jassa,
sa, as, sas, assa, sass, das, dasa, sada, kasa, saka, fasa, jassa]
# Unlocks track 2 of Klaviersammlung, Coffin Dance already being track 1.
unlocks: "Musik/Klaviersammlung/Shakira - Dai Dai.mp3"
- title: "L"
subtitle: "Der rechte Ringfinger"
keys: [f, j, a, d, k, s, l]
- title: "Wörter"
subtitle: "lalla"
kind: fragments
words: [la, al, als, all, lala, alla, salla, lasa, lass, kalla, falla, jalla]
- title: "Wörter"
subtitle: "lass das"
kind: fragments
mode: feed
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
ka, kak, akka, kaka, daka, kada, jaka, kaja,
sa, as, sas, assa, sass, das, dasa, saka, jassa,
la, al, als, all, lala, alla, salla, lasa, lass, kalla, falla, jalla,
la, al, als, all, lala, alla, salla, lasa, lass, kalla, falla, jalla]
# Unlocks track 3 of Klaviersammlung.
unlocks: "Musik/Klaviersammlung/Shakira - Waka Waka (This Time for Africa) (feat. Freshlyground).mp3"
- title: "Ö"
subtitle: "Der rechte kleine Finger"
keys: [f, j, a, d, k, s, l, ö]
- title: "Wörter"
subtitle: "öl"
kind: fragments
words: [öl, ölö, öla, ölda, ösa, öll, sölö, köla, dölö, jölö, fölö, lösa]
- title: "Wörter"
subtitle: "öl kalk salsa"
kind: fragments
words: [ja, da, ada, adda, fada, aja, jaja, afa, dafa, jafa, jada,
ka, kak, akka, kaka, daka, kada, jaka, kaja,
sa, as, sas, assa, sass, das, dasa, saka, jassa,
la, al, als, all, lala, alla, salla, lasa, lass, kalla,
öl, ölö, öla, ölda, ösa, öll, sölö, köla, dölö, jölö, fölö, lösa,
öl, ölö, öla, ölda, ösa, öll, sölö, köla, dölö, jölö, fölö, lösa]
- title: "Übung: die Grundstellung"
subtitle: "Alle acht Finger"
drill: true
- title: "Erste kleine Wörter"
subtitle: "Echte Wörter mit acht Tasten"
kind: fragments
words: [da, ja, das, dass, als, all, fall, falls, lass, saal, kalk, salsa, jass, öl]
- title: "Die Leertaste"
subtitle: "Der Daumen kommt dazu"
keys: [" "]
- title: "Übung: Grundstellung mit Leertaste"
subtitle: "Jetzt mit dem Daumen"
drill: true
- title: "Wörter mit Leertaste"
subtitle: "Kleine Wörter, kleine Sätze"
kind: fragments
mode: feed
words: ["lass das", "das da", "da ja", "fall da", "kalk da", "saal da", "ja lass das", "als da", "öl da"]
# Unlocks track 4 of Klaviersammlung - the reward for finishing die Grundstellung.
unlocks: "Musik/Klaviersammlung/Tones and I - Dance Monkey.mp3"
# to here
- number: 2
title: "Nach oben"
emoji: "🌊"
reward: octopus
lessons:
- title: "Das E"
subtitle: "Mittelfinger links nach oben"
keys: [e]
- title: "E üben"
subtitle: "Die neue Taste festigen"
keys: [e]
- title: "Das I"
subtitle: "Mittelfinger rechts nach oben"
keys: [i]
- title: "I üben"
subtitle: "Die neue Taste festigen"
keys: [i]
- title: "Übung: E und I"
subtitle: "Beide Mittelfinger nach oben"
drill: true
- title: "Das R"
subtitle: "Zeigefinger links nach oben"
keys: [r]
- title: "R üben"
subtitle: "Die neue Taste festigen"
keys: [r]
- title: "Das U"
subtitle: "Zeigefinger rechts nach oben"
keys: [u]
- title: "U üben"
subtitle: "Die neue Taste festigen"
keys: [u]
- title: "Übung: R und U"
subtitle: "Beide Zeigefinger nach oben"
drill: true
- title: "Kleine Wörter: E I R U"
subtitle: "Erste echte Wörter mit der oberen Reihe"
kind: fragments
words: [die, sie, elf, eis, esel, see, keks, fiel, lied, rad, reis, eier, rufe, feuer, sauer, lauf]
- title: "Das T"
subtitle: "Zeigefinger links weit nach oben"
keys: [t]
- title: "T üben"
subtitle: "Die neue Taste festigen"
keys: [t]
- title: "Das Z"
subtitle: "Zeigefinger rechts weit nach oben"
keys: [z]
- title: "Z üben"
subtitle: "Die neue Taste festigen"
keys: [z]
- title: "Übung: T und Z"
subtitle: "Weit nach oben greifen"
drill: true
- title: "Das O"
subtitle: "Ringfinger rechts nach oben"
keys: [o]
- title: "O üben"
subtitle: "Die neue Taste festigen"
keys: [o]
- title: "Das W"
subtitle: "Ringfinger links nach oben"
keys: [w]
- title: "W üben"
subtitle: "Die neue Taste festigen"
keys: [w]
- title: "Übung: O und W"
subtitle: "Beide Ringfinger nach oben"
drill: true
- title: "Kleine Wörter: T Z O W"
subtitle: "Noch mehr echte Wörter"
kind: fragments
mode: feed
words: [tier, tafel, kette, leiter, zeit, salz, zelt, katze, rot, tor, los, foto, wo, wald, zwei, wolke]
- title: "Das P"
subtitle: "Kleiner Finger rechts nach oben"
keys: [p]
- title: "P üben"
subtitle: "Die neue Taste festigen"
keys: [p]
- title: "Das Q"
subtitle: "Kleiner Finger links nach oben"
keys: [q]
- title: "Q üben"
subtitle: "Die neue Taste festigen"
keys: [q]
- title: "Das Ü"
subtitle: "Kleiner Finger rechts, ganz außen"
keys: [ü]
- title: "Ü üben"
subtitle: "Die neue Taste festigen"
keys: [ü]
- title: "Übung: P Q Ü"
subtitle: "Die kleinen Finger nach oben"
drill: true
- title: "Übung: die obere Reihe"
subtitle: "Die ganze Reihe zusammen"
kind: words
mode: feed
words: [wolke, zeit, pause, prüfe, qualle, torte, reiter, würfel]
- title: "Übung: Welt 2 komplett"
subtitle: "Alles aus der oberen Reihe"
drill: true
# Unlocks the whole audiobook (its last chapter, inclusive of every chapter
# before it).
unlocks: "Hörbücher/Grüffelo/Der Grüffelo/08 - Der Grüffelo - Outro.mp3"
- number: 3
title: "Nach unten"
emoji: "🪸"
reward: seahorse
lessons:
- title: "Das N"
subtitle: "Zeigefinger rechts nach unten"
keys: [n]
- title: "N üben"
subtitle: "Die neue Taste festigen"
keys: [n]
- title: "Das M"
subtitle: "Zeigefinger rechts, neben dem N"
keys: [m]
- title: "M üben"
subtitle: "Die neue Taste festigen"
keys: [m]
- title: "Übung: N und M"
subtitle: "Die neuen Tasten festigen"
drill: true
- title: "Das G"
subtitle: "Zeigefinger links, in der Mitte"
keys: [g]
- title: "G üben"
subtitle: "Die neue Taste festigen"
keys: [g]
- title: "Das H"
subtitle: "Zeigefinger rechts, in der Mitte"
keys: [h]
- title: "H üben"
subtitle: "Die neue Taste festigen"
keys: [h]
- title: "Übung: G und H"
subtitle: "Die Mitte der Grundreihe"
drill: true
- title: "Kleine Wörter: N M G H"
subtitle: "Erste echte Wörter nach unten"
kind: fragments
words: [nase, nein, kind, wind, sonne, mama, mond, meer, maus, gut, gans, regen, hase, haus, hund, hupe]
- title: "Das C"
subtitle: "Mittelfinger links nach unten"
keys: [c]
- title: "C üben"
subtitle: "Die neue Taste festigen"
keys: [c]
- title: "Das V"
subtitle: "Zeigefinger links nach unten"
keys: [v]
- title: "V üben"
subtitle: "Die neue Taste festigen"
keys: [v]
- title: "Übung: C und V"
subtitle: "Nach unten greifen"
drill: true
- title: "Das B"
subtitle: "Zeigefinger links, neben dem V"
keys: [b]
- title: "B üben"
subtitle: "Die neue Taste festigen"
keys: [b]
- title: "Das Y"
subtitle: "Kleiner Finger links nach unten"
keys: [y]
- title: "Y üben"
subtitle: "Die neue Taste festigen"
keys: [y]
- title: "Übung: B und Y"
subtitle: "Ganz unten links"
drill: true
- title: "Kleine Wörter: C V B Y"
subtitle: "Noch mehr echte Wörter"
kind: fragments
words: [koch, milch, schule, chaos, vier, vase, voll, vater, baum, boot, bunt, brot, yoga, baby, typ, pony]
- title: "Das X"
subtitle: "Ringfinger links nach unten"
keys: [x]
- title: "X üben"
subtitle: "Die neue Taste festigen"
keys: [x]
- title: "Das Ä"
subtitle: "Kleiner Finger rechts, ganz außen"
keys: [ä]
- title: "Ä üben"
subtitle: "Die neue Taste festigen"
keys: [ä]
- title: "Übung: X und Ä"
subtitle: "Die letzten beiden Tasten"
drill: true
- title: "Übung: alle Buchstaben"
subtitle: "Das ganze Alphabet"
kind: words
mode: race
words: [delfin, wasser, xylofon, bäume, vogel, qualle, muschel, tauchen]
- title: "Übung: Welt 3 komplett"
subtitle: "Alles aus der unteren Reihe"
drill: true
# Unlocks the first five episodes of GEOlino Spezial, oldest first.
unlocks: "Kinderpodcasts/GEOlino Spezial/20200323 - Das Immunsystem.mp3"
- number: 4
title: "Große Buchstaben"
emoji: "👑"
reward: turtle
lessons:
- title: "Umschalttaste rechts, Teil 1"
subtitle: "Große Buchstaben der linken Hand"
keys: ["⇧"]
kind: words
words: [Delfin, Wal, Fisch, Baum]
- title: "Umschalttaste rechts, Teil 2"
subtitle: "Noch mehr große Buchstaben"
kind: words
words: [Garten, Ente, Vogel, Riff]
- title: "Umschalttaste links, Teil 1"
subtitle: "Große Buchstaben der rechten Hand"
kind: words
words: [Haus, Kind, Mond, Nase]
- title: "Umschalttaste links, Teil 2"
subtitle: "Noch mehr große Buchstaben"
kind: words
words: [Lampe, Onkel, Uhr, Puppe]
- title: "Übung: Namen, Teil 1"
subtitle: "Namen fangen groß an"
kind: words
words: [Anna, Lena, Paul, Mia, Emil, Jonas, Tom, Lisa]
- title: "Übung: Namen, Teil 2"
subtitle: "Noch mehr Namen"
kind: words
mode: feed
words: [Ben, Nora, Finn, Ida, Max, Ella, Oskar, Greta]
- title: "Übung: große und kleine"
subtitle: "Beides gemischt"
kind: words
words: ["Das Meer", "Ein Delfin", "Die Sonne", "Mein Boot", "Der Wal", "Eine Muschel", "Ein Fisch", "Das Riff", "Mein Ball", "Die Welle", "Ein Stern", "Der Hai"]
- title: "Übung: Welt 4 komplett"
subtitle: "Groß und klein zusammen"
kind: words
drill: true
words: [Delfin, Haus, Anna, Ben, "Das Meer", "Der Wal", Mond, Riff]
- number: 5
title: "Ganze Sätze"
emoji: "📖"
reward: pearlmussel
lessons:
- title: "Der Punkt"
subtitle: "Ringfinger rechts nach unten"
keys: ["."]
- title: "Punkt üben"
subtitle: "Die neue Taste festigen"
keys: ["."]
- title: "Erste Sätze mit Punkt"
subtitle: "Ein Satz, ein Punkt"
kind: sentences
words:
- "Das Meer ist tief."
- "Der Hund bellt."
- "Ich mag Kekse."
- "Die Sonne scheint."
- "Der Wal ist riesig."
- "Wir gehen baden."
- "Mama liest ein Buch."
- "Der Fisch schwimmt."
- "Heute ist es warm."
- "Ich habe einen Ball."
- "Die Welle ist hoch."
- "Papa kocht Suppe."
- title: "Das Komma"
subtitle: "Mittelfinger rechts nach unten"
keys: [","]
- title: "Komma üben"
subtitle: "Die neue Taste festigen"
keys: [","]
- title: "Sätze mit Komma"
subtitle: "Zwei Gedanken, ein Satz"
kind: sentences
mode: race
words:
- "Ich mag Wale, Delfine und Fische."
- "Erst lesen, dann tippen."
- "Es ist warm, also baden wir."
- "Rot, gelb und blau sind Farben."
- "Wenn es regnet, bleiben wir drinnen."
- "Der Delfin springt, taucht und spielt."
- "Morgen, sagt Papa, fahren wir los."
- "Eins, zwei, drei, vier."
- "Oma, Opa und ich gehen schwimmen."
- "Die Sonne scheint, das Meer glitzert."
- "Muscheln, Steine und Sand liegen am Strand."
- title: "Der Bindestrich"
subtitle: "Kleiner Finger rechts, ganz außen"
keys: ["-"]
- title: "Bindestrich üben"
subtitle: "Die neue Taste festigen"
keys: ["-"]
- title: "Sätze mit Bindestrich"
subtitle: "Zwei Wörter, ein Strich"
kind: sentences
words:
- "Wir spielen mit dem Wasser-Ball."
- "Das ist ein Delfin-Baby."
- "Meine Ur-Oma kommt heute."
- "Wir bauen eine Sand-Burg."
- "Der Fisch-Schwarm ist riesig."
- "Ich trage mein T-Shirt."
- "Das Schwimm-Bad ist offen."
- "Die Bade-Hose ist nass."
- "Wir essen ein Eis-Hörnchen."
- "Das Segel-Boot ist blau."
- "Mein Lieblings-Tier ist der Delfin."
- title: "Fragezeichen und Ausrufezeichen"
subtitle: "Mit der Umschalttaste"
keys: ["ß", "1"]
- title: "Fragezeichen und Ausrufezeichen üben"
subtitle: "Die neuen Tasten festigen"
keys: ["ß", "1"]
- title: "Fragen und Rufe"
subtitle: "Wie klingt ein Satz?"
kind: sentences
words:
- "Wo ist der Delfin?"
- "Das war toll!"
- "Wie geht es dir?"
- "Pass auf!"
- "Kommst du mit?"
- "Der Wal ist so groß!"
- "Hast du Hunger?"
- "Hurra, Ferien!"
- "Was schwimmt da?"
- "Schau mal, ein Hai!"
- "Wie tief ist das Meer?"
- "Wir haben es geschafft!"
- title: "Übung: ganze Sätze"
subtitle: "Alles zusammen"
kind: sentences
drill: true
mode: race
words:
- "Der Delfin schwimmt sehr schnell."
- "Wo ist mein Boot?"
- "Ich tippe jetzt mit zehn Fingern!"
- "Das Meer ist blau, tief und kalt."
- "Kannst du das auch?"
- "Wir bauen eine Sand-Burg am Strand."
- "Die Möwe fliegt über das Wasser."
- "Oma, Opa und ich gehen schwimmen."
- "Das ist ja super!"
- "Wie heißt der große Wal?"
- "Im Riff wohnen bunte Fische."
- "Der Krake hat acht Arme."