Commit a5852fea authored by Ole Lorbacher's avatar Ole Lorbacher 💬
Browse files

Merge branch 'yaliketranslations' into 'master'

Yaliketranslations?

See merge request !24
parents 344f013e ddd3c623
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
@@ -26,6 +26,50 @@ aside section > a, aside section > i {
    margin-bottom: 32px;
}

aside section.start .language-switcher-wrap {
    position: static;
    left: auto;
    top: auto;
    margin-bottom: 32px;
    z-index: auto;
}

aside section.start .language-switcher {
    background: transparent;
    border: 0;
    box-shadow: none;
    color: inherit;
    min-width: 0;
    width: 32px;
    height: 32px;
    padding: 0;
    box-sizing: border-box;
    position: relative;
    grid-template-columns: 1fr;
}

aside section.start .language-switcher:focus-within {
    border: 0;
    box-shadow: none;
}

aside section.start .language-switcher .bi-translate {
    justify-self: center;
}

aside section.start .language-switcher .bi-chevron-down {
    display: none;
}

aside section.start #languageSwitcher {
    opacity: 0;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    padding: 0;
}

aside i {
    font-size: 32px;
}
+71 −1
Original line number Diff line number Diff line
@@ -317,3 +317,73 @@ button.primary:not([disabled]):hover, a.btn.primary:not([disabled]):hover {
a.ribbon {
    text-decoration: none;
}


.language-switcher-wrap {
    left: 16px;
    position: fixed;
    top: 16px;
    z-index: 20;
}

.language-switcher {
    align-items: center;
    background-color: #212c35;
    border: 1px solid #60788c;
    border-radius: 10px;
    box-shadow: 2px 2px 2px black;
    column-gap: 8px;
    color: #f0f0f0;
    display: inline-grid;
    grid-template-columns: auto 1fr auto;
    min-width: 168px;
    padding: 4px 10px;
}

.language-switcher .bi-translate {
    color: #ffd533;
}

.language-switcher .bi-chevron-down {
    color: #60788c;
    font-size: 90%;
    pointer-events: none;
}

#languageSwitcher {
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 0;
    color: inherit;
    cursor: pointer;
    font: inherit;
    margin: 0;
    padding: 3px 0;
    width: 100%;
}

#languageSwitcher:focus {
    outline: none;
}

.language-switcher:focus-within {
    border-color: #ffd533;
    box-shadow: 0 0 0 2px #ffd53380;
}

#languageSwitcher option {
    background-color: #212c35;
    color: #f0f0f0;
}

@media (max-width: 600px) {
    .language-switcher-wrap {
        left: 12px;
        top: 12px;
    }

    .language-switcher {
        min-width: 150px;
    }
}
+18 −6
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>YALIKEJAZZ · Institut für Computerlinguistik</title>
    <title data-i18n="title">YALIKEJAZZ · Institut für Computerlinguistik</title>
    <link rel="icon" href="./img/bee.png" />
    <link rel="stylesheet" href="./css/yalikejazz.css" />
    <link rel="stylesheet" href="./css/index.css" />
@@ -12,6 +12,16 @@
</head>

<body>
    <div class="language-switcher-wrap">
        <label class="language-switcher" for="languageSwitcher" aria-label="Choose language">
            <i class="bi bi-translate" aria-hidden="true"></i>
            <select id="languageSwitcher">
                <option value="de">Deutsch</option>
                <option value="en">English</option>
            </select>
            <i class="bi bi-chevron-down" aria-hidden="true"></i>
        </label>
    </div>
    <header>
        <div class="logo">
            <img src="/img/bee.png" />
@@ -24,11 +34,13 @@
        </div>
    </header>
    <main>
        <h1>Was möchtest du tun?</h1>
        <a href="./test.html?id=practice" class="btn big">Linux üben</a>
        <a href="./test.html?id=pooltest" class="btn primary big" id="pooltest-start-btn">Den Pooltest ablegen</a>
        <h1 data-i18n="index:what_to_do">Was möchtest du tun?</h1>
        <a data-i18n="index:button_practice" href="./test.html?id=practice" class="btn big">Linux üben</a>
        <a data-i18n="index:button_pooltest" href="./test.html?id=pooltest" class="btn primary big"
            id="pooltest-start-btn">Den Pooltest ablegen</a>
    </main>
    <a href="https://gitlab.cl.uni-heidelberg.de/technik/teaching/yalikejazz" class="ribbon" target="_blank">Quelltext</a>
    <a data-i18n="index:source_code" href="https://gitlab.cl.uni-heidelberg.de/technik/teaching/yalikejazz"
        class="ribbon" target="_blank">Quelltext</a>
    <script type="module" src="./js/index.mjs"></script>
</body>

+7 −13
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
import { createCard, setCardHoverTitle } from "./cards.mjs"
import { Exercise } from "./exercises.api.mjs"
import { createElementWithClass } from "./util.mjs"
import { t } from "./i18n.mjs"

/**
 * Changes the appearance of the given exercise card so it looks like it is solved
@@ -11,7 +12,7 @@ import { createElementWithClass } from "./util.mjs"
 */
export function displayAsSolved(cardEl) {
    cardEl.classList.add("solved")
    setCardHoverTitle(cardEl, "Diese Aufgabe ist bereits gelöst, du kannst nicht mehr zu ihr springen.", false)
    setCardHoverTitle(cardEl, t("exercise_cards:hover_title_solved"), false)
}

/**
@@ -20,7 +21,7 @@ export function displayAsSolved(cardEl) {
 */
export function displayAsCurrent(cardEl) {
    cardEl.classList.remove("collapsed")
    setCardHoverTitle(cardEl, "Du kannst nicht zur aktuellen Aufgabe springen.", false)
    setCardHoverTitle(cardEl, t("exercise_cards:hover_title_current"), false)
}

/**
@@ -31,7 +32,7 @@ export function displayAsNonCurrent(cardEl) {
    cardEl.classList.add("collapsed")

    if (!cardEl.classList.contains("solved")) {
        setCardHoverTitle(cardEl, "Zu dieser Aufgabe springen", true)
        setCardHoverTitle(cardEl, t("exercise_cards:hover_title_non_current"), true)
    }
}

@@ -69,11 +70,11 @@ export function displayAsNonVerifying(cardEl) {
 */
function createExerciseCardActionButtons(manuallyConfirm) {
    const confirmButton = createElementWithClass("button", "primary")
    confirmButton.textContent = "Ich denke, ich bin fertig."
    confirmButton.textContent = t("exercise_cards:button_finish")
    confirmButton.onclick = () => manuallyConfirm()

    const resetButton = document.createElement("button")
    resetButton.textContent = "Aufgabe neu beginnen"
    resetButton.textContent = t("exercise_cards:button_reset")
    resetButton.onclick = () => location.reload()

    return [confirmButton, resetButton]
@@ -100,14 +101,7 @@ export function createExerciseCard(exercise, onTitleClick) {
        exercise.manuallyConfirm.bind(exercise)
    ))
    const loaderEl = createElementWithClass("div", "loader")
    loaderEl.innerHTML = `
    <div class="loader-bg"></div>
    <div class="loader-animation-wrapper">
        <span class="loader-animation"></span>
    </div>
    <span class="loader-desc standard">Deine Lösung wird überprüft...</span>
    <span class="loader-desc unusual">Das braucht ungewöhnlich lange. <a href="#">Aufgabe neu beginnen?</a></span>
    `
    loaderEl.innerHTML = t("exercise_cards:loading_element")
    loaderEl.querySelector(".loader-desc.unusual a").onclick = () => location.reload()
    cardEl.querySelector(".content").append(descriptionEl, actionsEl, loaderEl)

+57 −84
Original line number Diff line number Diff line
/** Exercises that students must pass to pass the pool test **/
import { test } from "./exercises.api.mjs"
import { t, initI18n} from './i18n.mjs';

export const practice = test("Linux-Übungsmodus", function() {
    this.welcome(`
        <h1>Willkommen zum Linux-Übungsmodus</h1>
        <p>Hier kannst du vollkommen frei mit dem Linux-Terminal auf der linken Seite herumexperimentieren. Das Terminal läuft isoliert in diesem Browsertab, <strong>du kannst also absolut nichts kaputt machen</strong> (weder auf deinem Rechner, noch bei uns) – im schlimmsten Fall lädst du die Seite neu und alles ist vergessen ;)</p>
        <p>Es gibt ein paar Aufgaben im Übungsmodus, diese sind allerdings komplett freiwillig. Falls du sie bearbeitest, solltest du sie allerdings in der angegebenen Reihenfolge lösen, da die Aufgaben aufeinander aufbauen.</p>
        <p>Zögere nicht, dich <strong>bei Fragen und Problemen jederzeit an uns zu wenden</strong> (<code>technik@cl.uni-heidelberg.de</code>) – wenn dir etwas sehr komisch vorkommt, kann es gut sein, dass das Problem bei YALIKEJAZZ liegt und nicht bei dir 😉.</p>
    `)
initI18n()

export const practice = test(t("exercises_practice_title"), function() {
    this.welcome(t("exercises:practice_welcome"))

    this.disableHandIn()

    this.exercise("Verzeichnisinhalt anzeigen", async function () {
        this.describe(`Lass dir anzeigen, ob und wenn ja welche Dateien sich in diesem Verzeichnis befinden.`)
    this.exercise(t("exercises:practice_title_show_dir"), async function () {
        this.describe(t("exercises:practice_desc_show_dir"))

        await this.prepareWith("cd /root; if [[ -d tux ]]; then rm -r tux; fi")

@@ -21,8 +19,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify().commandWas((cmd)=>cmd?.match(/ls.*/))
    })

    this.exercise("Auch versteckte Dateien anzeigen", async function () {
        this.describe(`Lass dir anzeigen, welche Dateien sich in diesem Verzeichnis befinden (inklusive versteckter Dateien).`)
    this.exercise(t("exercises:practice_title_hidden_files"), async function () {
        this.describe(t("exercises:practice_desc_hidden_files"))

        await this.prepareWith("cd /root; if [[ -d tux ]]; then rm -r tux; fi")

@@ -39,8 +37,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        })
    })

    this.exercise("Verzeichnis anlegen", async function () {
        this.describe(`Erstelle einen Ordner (= ein Verzeichnis) mit dem Namen "tux" (ohne Anführungszeichen).`)
    this.exercise(t("exercises:practice_title_create_dir"), async function () {
        this.describe(t("exercises:practice_desc_create_dir"))

        await this.prepareWith("cd /root; if [[ -d tux ]]; then rm -r tux; fi")

@@ -49,8 +47,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify("if [[ -d tux ]]; then echo yes; else echo no; fi").hasOutput("yes")
    })

    this.exercise("Aktuelles Verzeichnis anzeigen", async function () {
        this.describe("Lass dir anzeigen, in welchem Verzeichnis du gerade arbeitest.")
    this.exercise(t("exercises:practice_title_pwd"), async function () {
        this.describe(t("exercises:practice_desc_pwd"))

        await this.prepareWith("cd /root")

@@ -59,8 +57,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify().hasOutput("/root")
    })

    this.exercise("Verzeichnis wechseln", async function() {
        this.describe(`Wechsele in das von dir erstelle Verzeichnis "tux".`)
    this.exercise(t("exercises:practice_title_cd"), async function() {
        this.describe(t("exercises:practice_desc_cd"))

        await this.prepareWith("cd /root; if [[ ! -d tux ]]; then mkdir tux; fi")

@@ -69,9 +67,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify("pwd").hasOutput("/root/tux")
    })

    this.exercise("Datei anlegen", async function () {
        this.describe(`Erstelle eine Textdatei mit dem Namen "satz" und dem Inhalt "Colorless green ideas sleep furiously"
                      (jeweils ohne Anführungszeichen).`)
    this.exercise(t("exercises:practice_title_create_file"), async function () {
        this.describe(t("exercises:practice_desc_create_file"))

        await this.prepareWith("if [[ ! -d /root/tux ]]; then mkdir /root/tux; fi; cd /root/tux; if [[ -f satz ]]; then rm satz; fi")

@@ -80,8 +77,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify("cat satz").hasOutput("Colorless green ideas sleep furiously")
    })

    this.exercise("Inhalt ausgeben", async function () {
        this.describe(`Lasse den Inhalt der Datei "satz" in die Konsole ausgeben.`)
    this.exercise(t("exercises:practice_title_print_content"), async function () {
        this.describe(t("exercises:practice_desc_print_content"))

        await this.prepareWith("if [[ ! -d /root/tux ]]; then mkdir /root/tux; fi; cd /root/tux; echo Colorless green ideas sleep furiously > satz")

@@ -90,8 +87,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify().terminalContains("Colorless green ideas sleep furiously")
    })

    this.exercise("Datei umbenennen", async function () {
        this.describe(`Ändere den Namen der Datei "satz" in "saetze".`)
    this.exercise(t("exercises:practice_title_rename"), async function () {
        this.describe(t("exercises:practice_desc_rename"))

        await this.prepareWith("if [[ ! -d /root/tux ]]; then mkdir /root/tux; fi; cd /root/tux; if [[ -f saetze ]]; then rm saetze; fi; if [[ ! -f satz ]]; then echo Colorless green ideas sleep furiously > satz; fi")

@@ -100,8 +97,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify("if [[ -f saetze && ! -f satz ]]; then echo yes; else echo no; fi").hasOutput("yes")
    })

    this.exercise("Datei bearbeiten", async function () {
        this.describe(`Füge die Zeile "The cat is on the mat" ans Ende der Datei "saetze" an.`)
    this.exercise(t("exercises:practice_title_edit_file"), async function () {
        this.describe(t("exercises:practice_desc_edit_file"))

        await this.prepareWith("if [[ ! -d /root/tux ]]; then mkdir /root/tux; fi; cd /root/tux; echo Colorless green ideas sleep furiously > saetze")

@@ -110,8 +107,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify("cat saetze").hasOutput(["Colorless green ideas sleep furiously", "The cat is on the mat"])
    })

    this.exercise("Berechtigungen anzeigen", async function () {
        this.describe(`Lass dir die aktuellen Berechtigungen für die Datei "saetze" anzeigen.`)
    this.exercise(t("exercises:practice_title_show_perms"), async function () {
        this.describe(t("exercises:practice_desc_show_perms"))

        await this.prepareWith("if [[ ! -d /root/tux ]]; then mkdir /root/tux; fi; cd /root/tux; echo Colorless green ideas sleep furiously > saetze; echo The cat is on the mat >> saetze; chmod 644 saetze")

@@ -128,8 +125,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        })
    })

    this.exercise("Berechtigungen anpassen", async function () {
        this.describe(`Ändere die Berechtigungen der Datei "/resources/supergeheim" so, dass der Besitzer sie lesen und schreiben kann, sonst aber niemand.`)
    this.exercise(t("exercises:practice_title_edit_perms"), async function () {
        this.describe(t("exercises:practice_desc_edit_perms"))

        await this.prepareWith(`mkdir -p /resources &&
                                echo 'Dies ist ein Geheimnis.' > /resources/supergeheim &&
@@ -144,8 +141,8 @@ export const practice = test("Linux-Übungsmodus", function() {
        })
    })

    this.exercise("Datei löschen", async function () {
        this.describe(`Lösche die Datei "saetze".`)
    this.exercise(t("exercises:practice_title_rm_file"), async function () {
        this.describe(t("exercises:practice_desc_rm_file"))

        await this.prepareWith("if [[ ! -d /root/tux ]]; then mkdir /root/tux; fi; cd /root/tux; if [[ ! -f saetze ]]; then echo Colorless green ideas sleep furiously > saetze; echo The cat is on the mat >> saetze; fi; chmod 460 saetze")

@@ -154,9 +151,9 @@ export const practice = test("Linux-Übungsmodus", function() {
        this.verify("if [[ ! -e saetze ]]; then echo yes; else echo no; fi").hasOutput("yes")
    })

    this.exercise("Freies Spiel", async function () {
    this.exercise(t("exercises:practice_title_free_play"), async function () {
        // TODO Replace with something better
        this.describe(`Wir haben leider keine weiteren Übungsaufgaben mehr vorbereitet, aber fühle dich frei, noch weiter herumzuspielen (zum Beispiel mit Dateirechten).`)
        this.describe(t("exercises:practice_desc_free_play"))

        await this.clear()

@@ -172,27 +169,15 @@ export const pooltest = test((function(){
    const year = now.getFullYear()
    const is_winterterm = month >= 10 || month < 4
    return "Pooltest "
        + (is_winterterm ? "Winter" : "Sommer")
        + "semester "
        + (is_winterterm ? t("exercises:term_winter") : t("exercises:term_summer"))
        + t("exercises:term")
        + year.toString()
        + (is_winterterm ? ("/" + (year+1).toString()) : "")
})(), async function () {
    this.welcome(`
        <h1>Willkommen zum Pooltest!</h1>
        <p>In diesem Test sollst du nachweisen, dass du grundlegende Linux-Kenntnisse besitzt und diese auch anwenden kannst. Der Test besteht aus <strong id="exerciseCnt"></strong> Aufgaben, dauert etwa eine Stunde und wir empfehlen, ihn am Stück durchzuarbeiten.</p>
        <p>Jede Aufgabe läuft nach dem gleichen Prinzip ab: Links siehst du ein Linux-Terminal, in dem du Befehle eingeben kannst. Rechts steht eine Aufgabenstellung mit einem Ziel, das du erreichen sollst. Wie du das tust, ist dir überlassen (es gibt oft mehr als einen Weg zum Ziel). Sobald du denkst, dass du mit einer Aufgabe fertig bist, drücke auf den Knopf, um deine Lösung zu überprüfen. Falls sie richtig war, wird die Aufgabe als gelöst markiert, ansonsten kannst du es beliebig oft erneut versuchen.</p>
        </p>
        <p><strong>Du kannst nichts kaputt machen!</strong> Das Linux-Terminal läuft sicher und isoliert nur in deinem Browser und hat auf nichts Zugriff außerhalb von diesem Tab. Wenn du denkst, dass du in einer Sackgasse gelandet bist, kannst du über „Aufgabe neu beginnen“ das Terminal neustarten. Ist in einer Aufgabe von einer Datei die Rede, aber es wird kein Pfad zu dieser Datei genannt, so bleibe einfach in dem Verzeichnis, in dem du dich befindest.</p>
        
        <p>Du kannst Aufgaben auch überspringen und später wieder zu ihnen zurückkehren. Sobald du alle Aufgaben gelöst hast, kannst du durch Klick auf den stilisierten Papierflieger (<i class="bi bi-send"></i>) am linken Rand abgeben. Falls du eine Aufgabe partout nicht gelöst bekommst, kannst du auch unvollständig abgeben (sag dann am besten dazu, mit welchen Aufgaben du Probleme hattest). Wir schauen uns deinen Test dann an, und sofern er ansonsten einen guten Eindruck macht, bestehst du ihn trotzdem :)</p>

        <p>Zögere nicht, dich <strong>bei Fragen und Problemen jederzeit an uns zu wenden</strong> (<code>technik@cl.uni-heidelberg.de</code>) – ganz gleich, ob das Problem eine zu schwere oder leichte Aufgabe ist oder ein Programmierfehler in YALIKEJAZZ, wir schauen uns das an und finden gemeinsam eine Lösung.</p>
    `)
    this.welcome(t("exercises:test_welcome"))

    this.exercise("Datei anlegen", async function () {
        this.describe(`Erstelle eine Datei mit dem Namen "botschaft" und dem Inhalt
        "Time flies like an arrow" (jeweils ohne
        Anführungszeichen und ohne Punkt am Ende).`)
    this.exercise(t("exercises:test_title_create_file"), async function () {
        this.describe(t("exercises:test_desc_create_file"))

        // Wait until the user confirms that they believe they are done with the exercise ...
        await this.manualConfirmation()
@@ -201,9 +186,8 @@ export const pooltest = test((function(){
        this.verify("cat botschaft").hasOutput("Time flies like an arrow")
    })

    this.exercise("Textdateien lesen und schreiben", async function () {
        this.describe(`Lasse dir den Inhalt der Datei "reverse.txt" anzeigen und schreibe den Inhalt 
        in umgekehrter Reihenfolge in eine neue Datei mit dem Namen "correct.txt"`)
    this.exercise(t("exercises:test_title_text_rw"), async function () {
        this.describe(t("exercises:test_desc_text_rw"))
    
        await this.prepareWith("echo '?was I tac a ro rac a ti saW' > reverse.txt; "+
                               // Because "rev" doesn't work in this BusyBox installation, we create a set of scripts to emulate its behavior
@@ -218,9 +202,8 @@ export const pooltest = test((function(){
        this.verify("cat correct.txt").hasOutput("Was it a car or a cat I saw?")
    })

    this.exercise("Ordner erstellen", async function () {
        this.describe(`Erstelle unter dem Pfad "/mnt" einen Ordner mit dem Namen "uni-heidelberg", in dem sich ein weiterer Ordner
        mit dem Namen "coli" befindet.`)
    this.exercise(t("exercises:test_title_mkdir"), async function () {
        this.describe(t("exercises:test_desc_mkdir"))

        await this.manualConfirmation()

@@ -231,9 +214,8 @@ export const pooltest = test((function(){
        this.verify('/val/2h7je1.sh').hasOutput("yes")
    })

    this.exercise("Aktuelles Arbeitsverzeichnis anzeigen", async function () {
        this.describe(`Lasse dir den absoluten Pfad ausgeben, an dem du dich gerade befindest. Verwende dazu einen Befehl, 
        auch wenn du den Pfad auf dieser Linux-Maschine bereits in der Prompt ablesen könntest.`)
    this.exercise(t("exercises:test_title_pwd"), async function () {
        this.describe(t("exercises:test_desc_pwd"))

        await this.prepareWith("cd /proc/tty")

@@ -242,9 +224,8 @@ export const pooltest = test((function(){
        this.verify().hasOutput("/proc/tty")
    })

    this.exercise("Kopieren vieler Dateien in ein neues Verzeichnis", async function () {
        this.describe(`Kopiere alle Dateien aus dem Verzeichnis "/resources/so_much" in einen 
        neuen Unterordner deines Home-Verzeichnisses mit dem Namen "much".`)
    this.exercise(t("exercises:test_title_copy_files"), async function () {
        this.describe(t("exercises:test_desc_copy_files"))

        await this.prepareWith(`mkdir -p /resources/so_much &&
                                cd /resources/so_much &&
@@ -266,9 +247,8 @@ export const pooltest = test((function(){
        this.verify('/val/3w8ocm.sh').hasOutput("yes")
    })

    this.exercise("Löschen und Umbenennen von Ordnern und Dateien", async function () {
        this.describe(`Gehe in den Ordner "/resources/stuff". Benenne die Datei "a_file.png" zu "b_file.png" um, 
        und lösche den Unterordner "to/" mit all seinen Inhalten.`)
    this.exercise(t("exercises:test_title_rm_mv_dirs_n_files"), async function () {
        this.describe(t("exercises:test_desc_rm_mv_dirs_n_files"))
    
        await this.prepareWith(`mkdir -p /resources/stuff/to/delete &&
                                touch /resources/stuff/a_file.png &&
@@ -286,10 +266,8 @@ export const pooltest = test((function(){
        this.verify('/val/18ne2x.sh').hasOutput("yes")
    })

    this.exercise("Verschieben der korrekten Datei", async function () {
        this.describe(`Du bist innehalb des Verzeichnisses "Contacts" in einem Unterordner mit Dateien, die als Namen mögliche Kontaktadressen tragen.
        Verschiebe die Datei mit der korrekten Mail-Adresse der Gruppe Technik in das übergeordnete Verzeichnis. Lösche alle anderen Dateien aus
        diesem Ordner, die als Namen Mail-Adressen haben, mit denen du uns nicht erreichen kannst.`)
    this.exercise(t("exercises:test_test_mv_correct_file"), async function () {
        this.describe(t("exercises:test_desc_mv_correct_file"))

        await this.prepareWith(`mkdir -p /resources/Contacts/unsure && 
                                cd /resources/Contacts/unsure &&
@@ -309,9 +287,8 @@ export const pooltest = test((function(){
        this.verify('/val/2bx821.sh').hasOutput("yes")
    })

    this.exercise("Berechtigungen einer versteckten Datei herausfinden", async function () {
        this.describe(`Im Ordner "resources" im Wurzelverzeichnis liegt eine versteckte Datei mit dem Namen ".my-little-secret".
        Finde heraus, wer auf diese Datei Lesezugriff hat.`)
    this.exercise(t("exercises:test_title_get_hidden_perms"), async function () {
        this.describe(t("exercises:test_desc_get_hidden_perms"))

        await this.prepareWith(`if [[ ! -d /resources ]]; then mkdir /resources; fi &&
                                echo 'Ich mag Pizza Hawaii' > /resources/.my-little-secret &&
@@ -330,8 +307,8 @@ export const pooltest = test((function(){
        })
    })

    this.exercise("Berechtigungen ändern", async function () {
        this.describe(`Ändere die Dateirechte von "herausforderung" so, dass der Besitzer die Datei nur lesen kann, die besitzende Gruppe die Datei lesen und schreiben kann und alle anderen gar nichts dürfen.`)
    this.exercise(t("exercises:test_title_chmod"), async function () {
        this.describe(t("exercises:test_desc_chmod"))

        await this.prepareWith("if [[ ! -d /root/tux ]]; then mkdir /root/tux; fi; cd /root/tux; echo Wreck a nice beach > herausforderung; echo Recognize speech >> herausforderung; chmod 644 herausforderung")

@@ -342,10 +319,8 @@ export const pooltest = test((function(){
        })
    })

    this.exercise("PDF-Datei ausdrucken", async function () {
        this.describe(`Im Ordner "/home/students/robobert" befindet sich eine PDF-Datei mit dem Namen "dokument.pdf".
        Gib den Befehl an, mit dem du die Datei von einem Poolrechner aus ausdrucken würdest (keine Sorge: es wird nicht wirklich
            etwas ausgedruckt).`)
    this.exercise(t("exercises:test_title_print_pdf"), async function () {
        this.describe(t("exercises:test_desc_print_pdf"))

        await this.prepareWith(` if [[ ! -f /bin/lp ]]; then echo '[ "$1" != "-h" ] && echo "lp: Error - No default destination." && exit 1' > /bin/lp; echo '[ "$2" != "miller" ] && echo "lp: Error - No default destination." && exit 1' >> /bin/lp; echo '[ ! -f "$3" ] && echo "lp: Error - unable to access \\"$3\\" - No such file or directory" && exit 1' >> /bin/lp; echo 'realpath "$3" > /tmp/.file-to-print' >> /bin/lp; echo 'echo "request id is pool-printer-1337 (1 file(s))"' >> /bin/lp; chmod +x /bin/lp; fi &&
            if [[ ! -d /home/students/robobert ]]; then mkdir -p /home/students/robobert; fi &&
@@ -356,10 +331,8 @@ export const pooltest = test((function(){
        this.verify('cat /tmp/.file-to-print').hasOutput("/home/students/robobert/dokument.pdf")
    })

    this.exercise("SSH-Key erstellen", async function () {
        this.describe(`Um Zugang zu unseren Serven zu bekommen, benötigst du einen SSH Key.
            Erstelle einen neuen Key und kopiere den Inhalt des public keys in eine neue Datei "gt_mail.txt".
            Sobald du den Pooltest bestanden hast musst du auch auf deinem eigenen Gerät ein SSH Key erstellen und uns den public key zusammen mit dem Token aus dem Pooltest zuschicken.`)
    this.exercise(t("exercises:test_title_ssh_key"), async function () {
        this.describe(t("exercises:test_desc_ssh_key"))

        const pubKey = "ssh-ed25519 AAAABBBBCCCCDDDDP00lT35t55HK3yPlac3h0ld3rF0rYal1k3zazz pooltest key"
        const privKey = "IN DIESER DATEI WIRD DEIN PRIVATE KEY GESPEICHERT, TEILE DIESEN NIEMALS MIT ANDEREN! ---- THIS IS WHERE YOUR PRIVATE KEY IS STORED! NEVER SHARE IT WITH ANYONE"
Loading