From 198067ddffd2bc8f90fc93410b1b00eb89e1e280 Mon Sep 17 00:00:00 2001
From: Sascha Schulz <sschulz@dh-software.de>
Date: Mo, 10 Mär 2025 16:14:20 +0100
Subject: [PATCH] Merge branch 'draft'

---
 index.html |  709 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 709 insertions(+), 0 deletions(-)

diff --git a/index.html b/index.html
index 31beec7..96a514b 100644
--- a/index.html
+++ b/index.html
@@ -4163,6 +4163,715 @@
 						<p>Aufgabe</p>
 						<p>Implementiere zur vorherigen Verschlüsselung eine Entschlüsselung.</p>
 					</section>
+					<section>
+						<p>Lösung</p>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								const word = Buffer.from("abcdef");
+								const key = Buffer.from("xyz");
+								const encrypted = Buffer.alloc(word.length, 0);
+								
+								// encrypt
+								for (let i = 0; i &lt; word.length; i++) {
+									encrypted[i] = word[i] ^ key[i % key.length];
+								}
+								
+								// decrypt
+								for (let i = 0; i &lt; word.length; i++) {
+									word[i] = encrypted[i] ^ key[i % key.length];
+								}
+								
+								console.log(word.toString()); // => "abcdef"
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Streams</h3>
+						<p>Konstanter Fluss von (meist) großen Datenmengen in kleinen Paketen</p>
+					</section>
+					<section>
+						<p>Gängige Typen von Streams</p>
+						<ul>
+							<li>Standard In / Out</li>
+							<li>HTTP-Requests / -Responses</li>
+							<li>TCP-Sockets</li>
+							<li>Lese- und Schreib-Streams auf Dateien</li>
+							<li>Große Datenbank-Ergebnisse mittels Cursor</li>
+						</ul>
+					</section>
+					<section>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								const readableStream = fs.createReadStream("./source.txt");
+								const writableStream = fs.createWriteStream("./target.txt");
+								
+								readableStream.on("data", (data /* Buffer */) =&gt; {
+									// process data here
+									writableStream.write(data)
+								});
+								
+								readableStream.on("end", (data) =&gt; {
+									// stream has finished, e.g. EOF (end of file)
+								});
+								
+								// alternatively pipe content
+								readableStream.pipe(writableStream);
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Lese eine beliebige Datei als Readable Stream, verschlüssele den Inhalt mit der XOR-Methode und schreibe die verschlüsselten Bytes in eine Zieldatei</p>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Ändere das Programm, sodass auf Konsolenebene ein Pipe (|) ermöglicht wird:</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								echo "Hello World!" | node index.js
+							</code>
+						</pre>
+						<p>Nutze dafür die implizit vorhandenen Streams von Standard In / Out:</p>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								process.stdin; // Readable Stream
+								process.stdout; // Writable Stream
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Kryptographie</h3>
+					</section>
+					<section>
+						Mithilfe des nativen <code>crypto</code>-Moduls ist es möglich, moderne sichere kryptografische Verfahren zu verwenden:
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								const crypto = require("crypto");
+							</code>
+						</pre>
+						Hierzu nutzt NodeJS selber die weit verbreitete OpenSSL-Bibliothek
+					</section>
+					<section>
+						Einen MD5-Hash erzeugen:
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								const crypto = require("crypto");
+
+								const hash = crypto.createHash("md5");
+								const md5 = hash.update("abcdefg")
+									.digest(); // Buffer (default)
+									// .digest("hex") // Hexadezimal
+									// .digest("utf8") // Bytes forciert als Chars
+								
+								console.log(md5);
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Erzeuge jeweils den md5- und den sha256-Hash als Hex-Wert der beiden Texte in der Datei <code>text-examples.txt</code> und vergleiche jeweils ihren md5- und sha256-Hash miteinander</p>
+					</section>
+					<section>
+						<h3>Zippen</h3>
+					</section>
+					<section>
+						Mit dem nativen Modul <code>zlib</code> ist es möglich, Kompression in seinen Anwendungen zu nutzen.
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								const zlib = require("zlib");
+
+								// Komprimieren
+								zlib.deflate("abc" /* buffer | string */, (err, compressed) => {});
+								
+								// Dekomprimieren
+								zlib.inflate(compressed /* buffer */, (err, decompressed) => {});
+								
+								// Als Stream
+								const zipper = zlib.createDeflate();
+								const unzipper = zlib.createInflate();
+								
+								source.pipe(zipper).pipe(target);
+								source.pipe(unzipper).pipe(target);
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Nutze die einfachen Varianten <code>zlib.deflate</code> und <code>zlib.inflate</code>, um einen beliebigen Text zu komprimieren und anschließend wieder zu dekomprimieren</p>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Nutze die Stream-Varianten <code>zlib.createDeflate</code> und <code>zlib.createInflate</code>, um jeweils ein getrenntes Programm für die Kompression und Dekompression mit <code>process.stdin</code> und <code>process.stdout</code> zu machen, sodass in der Konsole folgende Nutzung möglich ist:</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								echo "abc" | node compress.js | node decompress.js
+								cat file.txt | node compress.js > file.zipped
+								cat file.zipped | node decompress.js > file.txt
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>TCP-Verbindungen</h3>
+					</section>
+					<section>
+						<ul>
+							<li>Bidirektionale Verbindung</li>
+							<li>Duplexfähig</li>
+							<li>Besteht immer aus Server- und Client-Socket</li>
+						</ul>
+					</section>
+					<section>
+						Mit dem nativen Modul <code>net</code> ist es möglich, eine TCP-Verbindung aufzubauen
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								/* Server */
+								const net = require("net");
+
+								const server = net.createServer((socket) => {
+								
+									// new incoming connection
+									socket.on("data", (data) => {
+										// data is a buffer
+									});
+								
+									socket.write("Hello World!");
+								});
+								
+								server.listen(3000);
+							</code>
+						</pre>
+					</section>
+					<section>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								/* Client */
+								const net = require("net");
+
+								const socket = net.connect(3000, () => {
+									// 'connect' listener
+								});
+								
+								socket.on("data", (data) => {
+									// data is a buffer
+								});
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Implementiere die Clientlogik in der <code>tcp-client.js</code>, um dem Server ein <code>"Hello Server!"</code> zurück zu senden</p>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Sende eine festgelegte Datei vom Server zum Client. Der Server liest die Datei ein und sendet die Inhalte zum Client, welcher diese wiederum in eine Zieldatei schreibt. Eventuell darf auch noch eine Kompression mittels Zip / Deflate implementiert werden.</p>
+					</section>
+					<section>
+						<p>Kurze Wiederholung des HTTP-Protokolls</p>
+						<p>Request</p>
+						<pre>
+							<code class="http" data-trim data-line-numbers>
+								GET / HTTP/1.1
+								Host: localhost:3456
+							</code>
+						</pre>
+						<p>Response</p>
+						<pre>
+							<code class="http" data-trim data-line-numbers>
+								HTTP/1.1 200
+								Content-Type: text/plain
+								Content-Length: 12
+								
+								Hello World!
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Implementiere mithilfe des TCP-Sockets einen minimalen HTTP-Server, welcher <code>Hello &lt;name></code> als Text zurück gibt, wobei <code>&lt;name></code> aus dem Query-Parameter gelesen werden soll</p>
+					</section>
+					<section>
+						<h3>HTTP-Server</h3>
+					</section>
+					<section>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								const http = require("http");
+						
+								const server = http.createServer((req, res) => {
+									res.writeHeader(200, {
+										"Content-Type": "text/plain", /* Put Content-Type here */
+									});
+									
+									console.log(req.rawHeaders);
+									console.log(req.method);
+									console.log(req.url);
+									
+									res.write(/* response content */);
+									res.end(); /* important if no Content-Length is specified */
+								});
+								
+								server.listen(3456);
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>Experimentiert mit verschiedenen Möglichkeiten von <code>Content-Length</code> und dem Vorhandensein von <code>res.end()</code>, mit längerer oder kürzerer <code>Content-Length</code> als der tatsächliche Body oder teilt den Body in zwei <code>res.write()</code> und verzögert künstlich mit einem setTimeout() den zweiten Teil und schaut euch im Browser bei den Entwickler-Tools die Timings der Response an.</p>
+					</section>
+					<section>
+						<h3>Worker Threads</h3>
+					</section>
+					<section>
+						<p>Was sind Worker Threads?</p>
+						<p>Eine bidirektionale nachrichten-basierte Implementierung von Multithreading in NodeJS</p>
+					</section>
+					<section>
+						<p>Beispiel:</p>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								// Main Thread
+								const { Worker } = require('worker_threads');
+
+								const data = 'some data';
+								
+								const worker = new Worker("path/to/worker.js", { workerData: data });
+								
+								worker.on('message', message => console.log('Reply from Thread:', message));
+								
+								// Worker Thread
+								const { workerData, parentPort } = require('worker_threads');
+
+								const result = workerData.toUpperCase();
+								
+								parentPort.postMessage(result);
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Weitere nützliche Funktionen:</p>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								// Main Thread
+								worker.on('message', message => processMessage(message));
+								worker.on('error', error => console.error(error));
+								
+								worker.postMessage({a: 1}); // send any data to the worker
+								
+								worker.terminate(); // manually terminate worker thread
+								
+								// Worker Thread
+								parentPort.on('message', message => doSomething(message)); // receive messages from the main thread
+								
+								process.exit(0); // terminate self, optionally with exit code
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufgabe</p>
+						<p>
+							Nutze die in den <code>examples</code> vorhandene Funktion <code>isPrime</code> (010-nodejs/mt-main.js und 010-nodejs/mt-worker.js) und implementiere mit Hilfe der <code>Worker Threads</code> die Suche nach Primzahlen.
+							Die gewünschte maximale Zahl, bis zu der die Primzahlen gesucht werden sollen als auch die Anzahl der Threads, mit der gleichzeitig gesucht werden soll, sollen möglichst einfach parametrisierbar sein (einfache Variable im Code genügt).
+							
+							Probiert verschiedene Thread-Anzahlen aus und vergleicht einmal die benötigten Zeiten.
+						</p>
+					</section>
+					<section>
+						<p>Hinweise:</p>
+						<ul>
+							<li>Bei der Suche bis zu 1.000.000 und 8 Threads soll der erste Thread von 0 bis 125.000 suchen, der zweite von 125.000 bis 250.000 usw.</li>
+							<li>Nutzt Promises, um auf alle Ergebnisse warten zu können. (s. <code>Promise.all</code>)</li>
+							<li>per <code>workerData</code> können auch Objekte übergeben werden</li>
+							<li>Die gefundenen Primzahlen können ausgegeben werden, müssen aber nicht. In erster Linie geht es darum, die Arbeit auf mehrere Threads aufzuteilen.</li>
+						</ul>
+					</section>
+					<section>
+						<h3>Multi Page Applications</h3>
+					</section>
+					<section>
+						<p>Im Gegensatz zu einer SPA ("Single Page Application") teilt sich eine Multi Page Application in mehrere Unterseiten auf, die sich per URL z.B. im Browser adressieren lassen.</p>
+					</section>
+					<section>
+						<p>In einem neuen Ordner / Projekt:</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								npm init -y
+								
+								npm install express pug
+								
+								mkdir public
+							</code>
+						</pre>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								// index.js (s. examples 010-nodejs/mpa.js)
+								const express = require("express");
+								const app = express();
+								
+								app.use(express.static("public"));
+								
+								app.listen(3002);
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Aufgabe</h3>
+						<p>Fülle das Verzeichnis "public" mit jeweis einer HTML-Datei für eine "Home"- und eine "About Us"-Seite, die beide eine einfache Navigations-Leiste enthalten (simple <code>span</code> mit Link (href)), um zwischen diesen beiden Seiten navigieren zu können.
+							Unterscheiden sollen sich diese beiden Seiten durch ihren "Inhalt", indem dort einfach "Home" und "About Us" enthalten ist.</p>
+					</section>
+					<section>
+						<h3>Serverseitiges Rendern</h3>
+					</section>
+					<section>
+						<p>Template-Engine "pug"</p>
+						<pre>
+							<code class="css" data-trim data-line-numbers>
+								html
+									head
+									body
+										div This is a div with some text
+										#main.my-class This is also a div with short-hands for id and class
+										a(href="/") This is a link with href
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Conditionals</p>
+						<pre>
+							<code class="css" data-trim data-line-numbers>
+								html
+									head
+									body
+										if name
+											div Hello #{name}!
+										else
+											div Hello Anonymous"
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Extension / Inheritance</p>
+						<pre>
+							<code class="css" data-trim data-line-numbers>
+								// layout.pug
+								html
+									head
+									body
+										div Some static content
+										block content
+								
+								// page.pug
+								extends layout.pug
+								
+								block content
+									div This is my home content
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Pug in Express</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								mkdir views
+							</code>
+						</pre>
+						<pre>
+							<code class="js" data-trim data-line-numbers>
+								// ...
+								
+								app.set('view engine', 'pug');
+								
+								app.get('/', (req, res) => {
+									// file name without .pug and some data passed to the template
+									res.render('index', { title: 'Hey', message: 'Hello there!' })
+								});
+								
+								app.listen(3002);
+							</code>
+						</pre>
+						<pre>
+							<code class="css" data-trim data-line-numbers>
+								// views/index.pug
+								html
+									head
+										title #{title}
+									body #{message}
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Aufgabe</h3>
+						<p>Implementiere die Seiten aus dem vorherigen Beispiel mit Pug (nutze Vererbung, um die Navigationsleiste nicht doppelt implementieren zu müssen) und
+							baue einen Gruß im Seitentext mit dynamischen Namen ein, den man z.B. in der URL als Query-Parameter "?name=Joe" angibt und der im Server ausgewertet wird.</p>
+					</section>
+				</section>
+				<section>
+					<section>
+						<h2>Betrieb von Anwendungen</h2>
+					</section>
+					<section>
+						<p>Wichtige Aspekte beim produktiven Betrieb von Anwendungen</p>
+						<ul>
+							<li>Stabilität</li>
+							<li>Zugriffsschutz</li>
+							<li>Sicherheitsupdates</li>
+							<li>Ausfallsicherheit</li>
+						</ul>
+					</section>
+					<section>
+						<p>Typische Werkzeuge beim Betrieb</p>
+						<ul>
+							<li>Webserver</li>
+							<li>Proxies</li>
+							<li>Laufzeitumgebungen</li>
+							<li>Application-Server / Prozess-Manager</li>
+							<li>Remote-Zugänge</li>
+						</ul>
+					</section>
+					<section>
+						<h3>Beispielhafte Produktivumgebung mit lokaler VM</h3>
+					</section>
+					<section>
+						<p>Vorbereitungen</p>
+						<ul>
+							<li>Oracle VirtualBox</li>
+							<li>Ubuntu Server 24.04</li>
+						</ul>
+					</section>
+					<section>
+						<p>Eckdaten für die VM</p>
+						<ul>
+							<li>2048 MB Ram</li>
+							<li>Netzwerktyp "Brücke" / "Bridge"</li>
+							<li>Linux / Ubuntu (64-Bit)</li>
+							<li>20GB HDD</li>
+						</ul>
+					</section>
+					<section>
+						<img data-src="/assets/images/ubuntu-installation.png">
+					</section>
+					<section>
+						<p>Nach der Installation in der VM mit den bekannten Credentials anmelden und die IP in Erfahrung bringen:</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								ip a
+							</code>
+						</pre>
+					</section>
+					<section>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								2: enp0s3: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc pfifo_fast state UP [...]
+									link/ether 08:00:27:43:e5:2c brd ff:ff:ff:ff:ff:ff
+									inet 10.0.0.166/16 metric 100 brd 10.0.255.255 scope global dynamic enp0s3
+										valid_lft 863191sec preferred_lft 863191sec
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Anmeldung am Server</h3>
+						<p>Nach der Installation am Server anmelden</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								ssh user@domain [-p 12345]
+							</code>
+						</pre>
+						<p>Beispiele für eine Domain: localhost, www.example.com, 127.0.0.1, [0:0:0:0:0:0:0:1], [::1]</p>
+					</section>
+					<section>
+						<p>Es kann auch der bei der Installation des Servers angegebene Host-Name verwendet werden, z.B.</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# "vm-ubuntu-server"
+								ssh user@vm-ubuntu-server
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Pakete aktualisieren</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Paketlisten updaten
+								sudo apt update
+								
+								# Pakete updaten
+								sudo apt upgrade
+							</code>
+						</pre>
+						<p>sonstige Werkzeuge installieren</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								sudo apt install vim git curl build-essential
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Ordner-Navigation</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# In einen Ordner gehen
+								cd [Ordner]
+								
+								# Zum vorherigen Ordner gehen
+								cd -
+								
+								# Längere Ordnerpfade sind auch möglich
+								# realtiv
+								cd a/b/c/d
+								
+								# absolut
+								cd /var/www/html
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Ordner erstellen und löschen</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Einen Ordner erstellen
+								mkdir my-folder
+								
+								# Mehrere Hierarchien
+								mkdir -p a/b/c/d
+								
+								# Mehrere Ordner
+								mkdir a b c
+								
+								# Ordner rekrusiv löschen
+								rm -rf [Ordner]
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Ordner und Dateien kopieren und verschieben</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Ordner kopieren
+								cp -r quelle ziel
+								
+								# Datei kopieren
+								cp quelle ziel
+								
+								# Ordner und Dateien verschieben
+								mv quelle ziel
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Eigenen User anlegen</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# User anlegen
+								# -m => Home Ordner anlegen
+								# -G => zur angegeben Gruppe hinzufügen
+								# -s => Pfad zur Shell, in diesem Fall /bin/bash
+								useradd [user] -m -G sudo -s /bin/bash
+								
+								# Passwort setzen
+								passwd [user]
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Authentifizierung per SSH-Key-Pair</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Schlüsselpaar lokal erzeugen, falls nicht vorhanden
+								ssh-keygen
+								
+								# Inhalt aus Datei kopieren:
+								cat ~/.ssh/id_rsa.pub
+							</code>
+						</pre>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Auf dem Server in folgenden Datei einfügen (Public Key pro Zeile):
+								~/.ssh/authorized_keys
+							</code>
+						</pre>
+						<p>anschließend wird kein Passwort mehr beim Login benötigt</p>
+					</section>
+					<section>
+						<h3>Apache einrichten</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								sudo apt install apache2
+							</code>
+						</pre>
+						<p>Einrichtung überprüfen</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Oder im Browser aufrufen:
+								curl http://vm-ubuntu-server
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Aufbau der Apache-Konfiguration</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Hauptdatei, selten gebraucht
+								/etc/apache2/apache2.conf
+								
+								# Verfügbare vhosts
+								/etc/apache2/sites-available/*.conf
+								
+								# Aktivierte vhosts, Symlinks auf obige verfügbare
+								/etc/apache2/sites-enabled/*.conf
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>vhost (virtual host) einrichten</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								cd /etc/apache/sites-available
+								
+								# 000-default.conf als Vorlage nutzen
+								# vm-ubuntu-server durch eigenen Host ersetzen
+								sudo cp 000-default.conf www.vm-ubuntu-server
+								
+								# 'ServerName' einkommentieren und mit eigenem Host anpassen
+								# Weitere Optionen je nach Fall anpassen, z.B. 'DocumentRoot'
+								sudo vim www.vm-ubuntu-server
+								
+								# vhost / Site aktivieren
+								sudo a2ensite www.vm-ubuntu-server.conf
+							</code>
+						</pre>
+					</section>
+					<section>
+						<p>Domains lokal auf dem Entwicklungssystem bekannt machen:</p>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# Windows
+								# z.B. mit Notepadd++ editieren. Benötigt Administrator-Rechte
+								C:\Windows\System32\drivers\etc\hosts
+								
+								# Unix
+								# z.B. mit vim editieren. Benötigt Administrator-Rechte
+								/etc/hosts
+							</code>
+						</pre>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# unten hinzufügen
+								[IP des Servers] www.vm-ubuntu-server
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Dateien zum Server kopieren</h3>
+						<pre>
+							<code class="bash" data-trim data-line-numbers>
+								# scp [Quelle] [Ziel], z.B.
+								scp index.html user@domain:~/html
+							</code>
+						</pre>
+					</section>
+					<section>
+						<h3>Aufgabe</h3>
+						<p>Richte die Sub-Domain <code>www.vm-ubuntu-server</code> ein und liefere unter dieser Adresse eine beliebige HTML-Seite aus. Dies kann eine minimale selbstgeschriebene oder eine beliebige komplexere aus den vergangenen Veranstaltungen sein, z.B. aus den CSS-Themen (jedoch keine NodeJS-Projekte).</p>
+					</section>
 				</section>
 			</div>
 		</div>

--
Gitblit v1.9.3