Compare commits
9 Commits
5fc4a93b64
...
ca5368edd9
| Author | SHA1 | Date | |
|---|---|---|---|
| ca5368edd9 | |||
| 708c6a46a4 | |||
| 1c6ab71ada | |||
| 8bca9df2bc | |||
| ce02ae2359 | |||
| c19025c1c9 | |||
| e33f60dcea | |||
| bf6f69daab | |||
| d1e7cee64a |
@@ -2,19 +2,7 @@
|
||||
|
||||
# Konstantin Hintermayer
|
||||
|
||||
## Einleitung des individuellen Teils
|
||||
|
||||
In diesem Abschnitt wird meine persönliche Aufgabenstellung im Rahmen des Projektes (`Alumnihub`) beschrieben.
|
||||
|
||||
### Auftrag / persönliche Aufgabenstellungen
|
||||
|
||||
Meine Zuständigkeiten und Verantwortlichkeiten:
|
||||
|
||||
- Product Owner
|
||||
- Infrastruktur
|
||||
- Entwicklung
|
||||
- Auswertungen
|
||||
- Schwarzes Brett
|
||||
## Einleitung
|
||||
|
||||
### Motivation
|
||||
|
||||
@@ -22,6 +10,39 @@ Gegenstand der Diplomarbeit ist die Entwicklung modularer Webanwendungen mit Bla
|
||||
|
||||
Mein Untersuchungsanliegen: Inwieweit optimiert die Integration von Blazor und dem Oqtane-Framework die Konsistenz und Wartbarkeit modularer Web-Architekturen im Vergleich zu den für mich gewohnten Technologie-Stacks (React/Node.js)?
|
||||
|
||||
### Auftrag und persönliche Aufgabenstellungen
|
||||
|
||||
Mein Aufgabenbereich in diesem Projekt war vielseitig und umfasste sowohl leitende als auch tiefgreifende technische Aspekte. Die Rollen lassen sich in drei Kerngebiete unterteilen:
|
||||
|
||||
#### Projektleitung und Organisation (Product Owner)
|
||||
|
||||
\
|
||||
|
||||
Als Product Owner war ich für die Definition der Produktvision und die Priorisierung des Backlogs verantwortlich. Dies beinhaltete:
|
||||
- **Anforderungsmanagement**: Erhebung und Strukturierung der Anforderungen in Zusammenarbeit mit den Betreuern und dem Team.
|
||||
- **Sprint-Planung**: Organisation der 14-tägigen Sprints in YouTrack, um einen kontinuierlichen Entwicklungsfluss sicherzustellen.
|
||||
- **Qualitätssicherung**: Definition der *Definition of Done* (DoD) und Durchführung von Code-Reviews zur Einhaltung von Architekturstandards.
|
||||
|
||||
#### Infrastruktur und Systemarchitektur
|
||||
|
||||
\
|
||||
|
||||
Ein wesentlicher Teil meiner Arbeit lag in der Bereitstellung der technischen Basis für das gesamte Team:
|
||||
- **Deployment-Strategie**: Konzeption und Umsetzung der Server-Infrastruktur auf Basis von Debian Linux und NginX als Reverse-Proxy.
|
||||
- **Datenbankdesign**: Entwurf des relationalen Datenmodells in PostgreSQL, inklusive der Absicherung durch SSL/TLS.
|
||||
- **CI/CD-Pipeline**: Automatisierung der Build- und Deployment-Prozesse mittels Gitea Actions für eine effiziente Integration der Teambeiträge.
|
||||
|
||||
#### Modulentwicklung
|
||||
|
||||
\
|
||||
|
||||
Zusätzlich zur Infrastruktur habe ich drei zentrale Module für den Alumnihub entworfen und implementiert:
|
||||
|
||||
1. **Anmeldetool (EventRegistration)**: Ein Modul zum verwalten von Anmeldungen für Veranstaltungen. Es ermöglicht die einfache Erstellung von Einladungen zu Veranstaltungen und die Verwaltung von Anmeldungen.
|
||||
2. **Reporting System**: Ein generisches System zur Meldung von Inhalten, das nach dem *Open-Closed-Prinzip* entworfen wurde, um eine einfache Erweiterbarkeit für alle anderen Module zu bieten.
|
||||
3. **Schwarzes Brett (BlackBoard)**: Ein Community-Modul für den Austausch von Informationen, inklusive eines automatisierten E-Mail-Digest-Systems zur Nutzerbenachrichtigung.
|
||||
|
||||
|
||||
## Anforderungen an das entwickelte Modul bzw. die Funktionalität
|
||||
|
||||
### Modulanforderungen / funktionale Anforderungen
|
||||
@@ -88,21 +109,55 @@ Ein Modul in Oqtane besteht aus vier Projekten:
|
||||
|
||||
### Zusammenspiel der Infrastruktur
|
||||
|
||||
In diesem Kapitel erkläre ich wie die ausgewählten Komponenten zusammenspielen. Wir verwenden NginX als Reverse Proxy, welcher bei uns die SSL Terminierung macht. Das Zertifikat, welches verwendet wird, wird von Let's Encrypt bereit gestellt und mittels HTTP-Challenges und dem Certbot auf dem Server aktualisiert und verwaltet. Oqtane selber läuft als Systemd-Service im Kestrel Backend. Kestrel ist ein kleiner Webserver, welcher in das ASP.NET Core Framework eingebaut worden ist. Oqtane (bzw. der ASP.NET Core Server "Kestrel") hört auf der Loopback IP und Port 5000. Damit ist Oqtane nur durch NginX erreichbar. PostgreSQL ist die Datenbank in dem System: Sie hört wieder auf der Loopback IP und Port 5432. In der folgenden Grafik ist das System Schematisch dargestellt.
|
||||
In diesem Kapitel erkläre ich wie die ausgewählten Komponenten zusammenspielen.
|
||||
|
||||
#### NginX as Reverse Proxy
|
||||
|
||||
NginX fungiert in unserer Infrastruktur als `Reverse Proxy`. Ein Reverse Proxy nimmt Anfragen aus dem Internet entgegen und leitet sie an interne Server (wie Kestrel) weiter. Dies bietet mehrere Vorteile:
|
||||
|
||||
- **Sicherheit**: Die interne Applikation ist nicht direkt dem Internet ausgesetzt.
|
||||
- **SSL-Terminierung**: NginX übernimmt die rechenintensive Verschlüsselung (HTTPS), während die Applikation dahinter über einfaches HTTP kommuniziert.
|
||||
- **Statische Inhalte**: NginX kann statische Dateien (Bilder, CSS) effizienter ausliefern als ein Applikationsserver.
|
||||
|
||||
Wir verwenden NginX für die SSL Terminierung. Das Zertifikat wird von Let's Encrypt bereit gestellt und mittels HTTP-Challenges und dem Certbot auf dem Server aktualisiert.
|
||||
|
||||
Hier ist ein Auszug der NginX-Konfiguration (`nginx.conf`) für den Alumnihub:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name alumni.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/alumni.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/alumni.example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection keep-alive;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Oqtane selber läuft als Systemd-Service im Kestrel Backend. Kestrel ist ein kleiner Webserver, welcher in das ASP.NET Core Framework eingebaut worden ist. Oqtane (bzw. der ASP.NET Core Server "Kestrel") hört auf der Loopback IP und Port 5000. Damit ist Oqtane nur durch NginX erreichbar. PostgreSQL ist die Datenbank in dem System: Sie hört wieder auf der Loopback IP und Port 5432. In der folgenden Grafik ist das System Schematisch dargestellt.
|
||||
|
||||
```mermaid
|
||||
architecture-beta
|
||||
group server(server)[Server]
|
||||
sequenceDiagram
|
||||
participant browser as Client
|
||||
participant nginx as NginX
|
||||
participant oqtane as Oqtane
|
||||
participant db as PostgreSQL
|
||||
|
||||
service db(database)[PostgreSQL] in server
|
||||
service oqtane(server)[Oqtane] in server
|
||||
service nginx(server)[NginX] in server
|
||||
|
||||
service internet(cloud)[Internet]
|
||||
|
||||
internet:R <--> L:nginx
|
||||
nginx:R <--> L:oqtane
|
||||
oqtane:R <--> L:db
|
||||
browser->>+nginx: HTTPS => 0.0.0.0:443
|
||||
nginx->>nginx: SSL Terminierung
|
||||
nginx->>+oqtane: HTTP => 127.0.0.1:5000
|
||||
oqtane->>+db: SQL => 127.0.0.1:5432
|
||||
db-->>-oqtane: SQL
|
||||
oqtane-->>-nginx: HTTP
|
||||
nginx-->>-browser: HTTPS
|
||||
```
|
||||
|
||||
Zusätzlich gab es einen Administrationszugang zu den Servern, welcher über SSH möglich war. Dieser wurde für die Installation und Konfiguration der einzelnen Komponenten verwendet. Der SSH Service ist in jeder Umgebung anders erreichbar gewesen.
|
||||
@@ -125,14 +180,19 @@ Blazor ist ein kostenloses und quelloffenes Web-Framework, welches es möglich m
|
||||
|
||||
Blazor hat mehrere Hosting-Modelle:
|
||||
|
||||
- Blazor Web App: Hier wird die Web App als Teil einer ASP.NET Core Anwendung bereitgestellt. Dabei gibt es mehrere Render Modi:
|
||||
- Static Server: Die Komponente wird serverseitig gerendert und besitzt keine Interaktivität. Es ist der standardmäßige `RenderMode`
|
||||
- Interactive Server: Die Komponente wird serverseitig gerendert, diesmal ist sie jedoch interaktiv. Das bedeutet, man kann mithilfe von C# den Zustand der Seite dynamisch bearbeiten. Diese Zustandsänderungen werden serverseitig bearbeitet. Hierbei kommunizieren Server und Client mithilfe von SignalR über WebSockets miteinander.
|
||||
- Interactive WebAssembly: Die Komponente wird clientseitig, also im Browser, gerendert. Damit der Blazor C# Code auch im Browser ausgeführt werden kann, wird dieser in WebAssembly kompiliert.
|
||||
- Interactive Auto: Im `Interactive Auto` Modul wird bei dem initialen Besuch der Webseite der `Interactive Server` Modus gewählt und im Hintergrund wird der WebAssembly-Build heruntergeladen, damit bei weiteren Besuchen der Seite der `Interactive WebAssembly` Modus gewählt werden kann.
|
||||
- Hybrid: Dieser Modus ist der Einzige, welcher nicht innerhalb einer Blazor Web App läuft, sondern in einem WebView in einer Nativen App. Dabei wird innerhalb des Mutterprozesses, ganz ohne WebServer, der C# und Razor-Code ausgeführt. Das Ganze funktioniert in WPF und WinForms Apps und in nativen mobilen Apps für Android und iOS mithilfe von .NET MAUI.
|
||||
| Hosting-Modell | Ausführungsort | Interaktivität | Kommunikation |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Static Server** | Server | Keine | HTTP (Initialer Load) |
|
||||
| **Interactive Server** | Server | Hoch (Echtzeit) | SignalR / WebSockets |
|
||||
| **Interactive WebAssembly** | Client (Browser) | Hoch (Lokal) | REST API / HTTP |
|
||||
| **Interactive Auto ** | Server & Client (ab zweitem Besuch) | Hoch (Echtzeit / Lokal) | SignalR / Websockets / RestAPI / HTTP |
|
||||
| **Hybrid** | Native App (WebView) | Hoch (Lokal) | n/a (Mutterprozess) |
|
||||
|
||||
Razor-Komponenten (in dieser Arbeit, sowie umgangssprachlich, auch oft nur Komponenten genannt) sind der Grundbaustein für Blazor Web Apps. Sie bestehen aus HTML, welches mit der Verwendung von inline C# beeinflusst werden kann. Das Blazor stellt sicher, dass das gerenderte Markup aktualisiert wird, wenn sich der Status der Komponente ändert. Dieser Code kann entweder vollständig in einer .razor Datei liegen, oder in einer seperaten Code-Behind-Datei. Inline C# Code wird mithilfe von `@`- Zeichen markiert. Hier ist ein Beispiel für einen einfachen Counter:
|
||||
Table: Vergleich der Blazor Hosting-Modelle
|
||||
|
||||
Im Rahmen dieser Diplomarbeit haben wir und für den RenderModus Interactive Server entschieden. Dieser Modus ist für Oqtane die Standardeinstellung und bietet eine gute Balance zwischen Leistung und Benutzerfreundlichkeit. Das ist für unseren Anwendungsfall auch die Empfehlung von Oqtane selbst. [@oqtane_docs_rendermodes]
|
||||
|
||||
Razor-Komponenten (in dieser Arbeit, sowie umgangssprachlich, auch oft nur Komponenten genannt) sind der Grundbaustein für Blazor Web Apps. Sie bestehen aus HTML, welches mit der Verwendung von inline C# beeinflusst werden kann. Blazor stellt sicher, dass das gerenderte Markup aktualisiert wird, wenn sich der Status der Komponente ändert. Dieser Code kann entweder vollständig in einer .razor Datei liegen, oder in einer seperaten Code-Behind-Datei. Inline C# Code wird mithilfe von `@`- Zeichen markiert. Hier ist ein Beispiel für einen einfachen Counter:
|
||||
|
||||
```razor
|
||||
<h1>Counter</h1>
|
||||
@@ -164,7 +224,30 @@ Razor hat auch eine Reihe an Keywords, wie zum Beispiel (nur Auszugsweise, bzw.
|
||||
|
||||
#### Kommunikation zwischen Front- und Backend
|
||||
|
||||
Wie Front- und Backend miteinander interagieren hängt hauptsächlich vom Render-Modus ab. Oqtane kann auf verschiedene Arten betrieben werden. Hierbei ist es dem Modulentwickler ziemlich egal, welche Art der Kommunikation (WebSockets, HTTP Long-Polling, REST) verwendet wird. SignalR ist eine Library aus dem ASP.NET Framework, welche es möglich macht, Server zu Client Kommunikation zu betreiben. Oqtane verwendet SignalR im `Interactive Server (SignalR)` Render-Modus.
|
||||
Die Interaktion zwischen Client und Server folgt in Oqtane einem klaren Architekturmuster, das je nach Render-Modus unterschiedliche Technologien nutzt.
|
||||
|
||||
- **SignalR (Interactive Server)**: Für Echtzeit-Interaktionen nutzt Oqtane SignalR. Dabei wird eine persistente WebSocket-Verbindung (oder Fallbacks wie Long-Polling) aufgebaut. Zustandsänderungen im UI lösen C#-Events auf dem Server aus, und das resultierende "Diff" des DOMs wird zurück an den Client gestreamt.
|
||||
- **REST API (WebAssembly/Shared)**: Bei Modulen, die Daten asynchron laden (im WebAssembly-Modus), kommuniziert der Client über einen `HttpClient` mit dem Controller.
|
||||
|
||||
Der folgende Ablauf zeigt die Kommunikation bei einer typischen Datenabfrage in einem Oqtane-Modul:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
%
|
||||
participant C as Blazor Client (Razor)
|
||||
participant S as Oqtane Server (Controller)
|
||||
participant R as Repository (EntityFramework)
|
||||
participant D as PostgreSQL
|
||||
|
||||
C->>+S: HTTP GET /api/[Module]/[Id]
|
||||
S->>+R: GetEntity(Id)
|
||||
R->>+D: SQL Query
|
||||
D-->>-R: Data Result
|
||||
R-->>-S: Entity Model
|
||||
S-->>-C: JSON Response
|
||||
```
|
||||
|
||||
Durch diese Abstraktion bleibt die Geschäftslogik im Backend (Repository/Controller) gekapselt, während das Frontend lediglich die Daten präsentiert und Benutzereingaben entgegennimmt. Dies unterstützt die Testbarkeit, da Repositories unabhängig vom UI getestet werden können.
|
||||
|
||||
### Dependency Injection
|
||||
|
||||
@@ -296,14 +379,51 @@ Anwendungen von Gitea Actions bei dieser Diplomarbeit:
|
||||
|
||||
### Debian Paket
|
||||
|
||||
TODO
|
||||
Um die Anwendung und ihre Abhängigkeiten konsistent auf dem Zielserver (Linux/Hetzner) zu installieren, wurde ein eigenes Debian-Paket (`.deb`) erstellt. Das Debian-Paketformat bietet den Vorteil, dass es Metadaten über Versionen, Abhängigkeiten und Installationsskripte (Maintainer Scripts) enthält.
|
||||
|
||||
#### Struktur eines Debian-Pakets
|
||||
|
||||
Ein `.deb`-Paket ist im Grunde ein `ar`-Archiv, das drei wesentliche Bestandteile enthält:
|
||||
|
||||
- **debian-binary**: Eine Textdatei mit der Versionsnummer des Paketformats.
|
||||
- **control.tar.gz**: Enthält die Metadaten (`control`-Datei) und Skripte, die vor oder nach der Installation ausgeführt werden (z. B. `postinst` zum Starten des Systemd-Services).
|
||||
- **data.tar.gz**: Enthält die eigentlichen Anwendungsdateien (die kompilierten Oqtane-DLLs und statischen Assets), die in das Zielverzeichnis (z. B. `/opt/alumnihub`) entpackt werden.
|
||||
|
||||
#### Automatisierung im Build-Prozess
|
||||
|
||||
Der Bau des Pakets erfolgt vollautomatisch in der Gitea-CI-Pipeline. Dabei werden die folgenden Schritte durchlaufen:
|
||||
|
||||
1. **Dotnet Publish**: Kompilieren der Anwendung für Linux-x64.
|
||||
2. **Paketierung**: Erstellen der Verzeichnisstruktur gemäß dem FHS (Filesystem Hierarchy Standard).
|
||||
3. **dpkg-deb**: Aufruf des Standard-Werkzeugs `dpkg-deb --build`, um das fertige Paket zu schnüren.
|
||||
4. **Publish**: Das Paket wird in die Gitea Package Registry hochgeladen und steht dort für das Deployment via `apt` zur Verfügung.
|
||||
|
||||
Durch diesen Prozess wird sichergestellt, dass jede Version der Software eindeutig identifizierbar und einfach rückrollbar (Rollback) ist.
|
||||
|
||||
## Projektmanagement & Tools
|
||||
|
||||
### Scrum
|
||||
|
||||
Zum verwalten des Projektes haben wir uns an den Grundprinzipien von Scrum orientiert, um flexibel auf Änderungen reagieren zu können. Die Arbeit wurde in 14-tägige Sprints unterteilt.
|
||||
|
||||
- **Sprint Planning**: Zu Beginn jedes Sprints wurden die Aufgaben aus dem Product Backlog in das Sprint Backlog übernommen.
|
||||
- **Dailies/Weekly**: Wir hielten wöchentliche Treffen ab, um den Fortschritt zu synchronisieren und Blocker zu identifizieren.
|
||||
- **Sprint Review**: Am Ende eines Sprints wurden die funktionalen Module präsentiert und der Stand der Diplomarbeit mit dem Betreuer besprochen.
|
||||
- **Sprint Retrospective**: Am Ende eines Sprints wurde der Sprint reflektiert und es wurden Maßnahmen zur Verbesserung des Prozesses abgeleitet.
|
||||
- **Definition of Done**: Zu Beginn der Diplomarbeit haben wir uns auf eine Definition of Done geeinigt, welche im Laufe der Diplomarbeit immer wieder angepasst wurde.
|
||||
|
||||
Mehr zum Projektmanagementprozess steht im allgemeinen Teil [Projektmanagement mit Scrum](#projektmanagement-mit-scrum).
|
||||
|
||||
### YouTrack
|
||||
|
||||
Zur Verwaltung des Backlogs und der Sprints wurde YouTrack von JetBrains eingesetzt. Im Vergleich zu einfachen To-Do-Listen bietet YouTrack mächtige Features für Softwareteams:
|
||||
|
||||
- **Agile Boards**: Visualisierung des Aufgabenstatus (To Do, In Progress, Review, Done).
|
||||
- **Time Tracking**: Erfassung der aufgewendeten Zeit für die einzelnen Arbeitspakete.
|
||||
- **Gantt-Diagramme**: Zur groben zeitlichen Orientierung und Überprüfung von Meilensteinen.
|
||||
|
||||
Die Integration von YouTrack half uns dabei, die Verantwortlichkeiten klar zuzuweisen und den Überblick über den "Burndown" der Aufgaben zu behalten.
|
||||
|
||||
### Git
|
||||
|
||||
Git fungierte im Rahmen dieser Diplomarbeit als zentrales Werkzeug zur Versionskontrolle und Koordination sämtlicher Entwicklungsschritte. Die wichtigste Komponente stellt dabei der Commit dar, welcher als eindeutiger Snapshot des Projektzustands dient und über eine spezifische Commit-SHA (Secure Hash Algorithm) referenziert werden kann. Um eine lückenlose Nachvollziehbarkeit zu gewährleisten, wurde für jeden abgeschlossenen Arbeitsschritt ein eigener Commit erstellt.
|
||||
@@ -407,6 +527,8 @@ sequenceDiagram
|
||||
|
||||
Im oben dargestellten Ablaufdiagram werden das ReportingComponent und der ReportingHandler vom Reporting System über Dependency Injection bereitgestellt, nicht vom Modul selbst. Dadurch erreichen wir eine bessere Trennung der Zuständigkeiten und halten das S in SOLID ein: `Single responsibility`. Die Module sind jetzt nicht mehr für das Reporting selber verantwortlich, sondern müssen nur das Reportings System einbinden.
|
||||
|
||||
Darüber hinaus erfüllt dieses Design das **Open/Closed Principle**: Das Reporting-System ist offen für Erweiterungen (neue Module können einfach andocken), aber geschlossen für Modifikationen (der Kern des Reporting-Systems muss nicht für jedes neue Modul geändert werden).
|
||||
|
||||
Damit DI funktioniert muss für den DI Consumer (`also das Modul, welches das Reporting System einbinden möchte`) das Interface zur Kompilierzeit zur Verfügung stehen. Um das zu erreichen habe ich eine neue Klassenbibliothek erstellt: Sie heißt Interfaces wird per Gitea Actions automatisch in ein Nuget Paket gebaut und in der `Gitea Actions Nuget Registry` veröffentlicht. Dieses Nuget Paket wird dann in jedem notwendigen Modul als Dependency hinzugefügt und damit kann man Modulübergreifend auf die Services und das IReporting Component zugreifen.
|
||||
|
||||
Die Implementierung des IReportingComponents stellt nur eine Property (`ReportType`, welche den TypeName der Razor Komponente zurückliefert, damit Dynamic Component sie laden kann) und eine Methode (`ConstructParameterList`, welche das Parameter Dictionary erstellt. Nur zwecks Typensicherheit eingefügt) bereit. Mit dem Dynamic Component von Razor ist es möglich, per C# Code unterschiedliche Komponenten zu rendern und damit auch die per DI injizierte Klasse.
|
||||
@@ -528,7 +650,7 @@ Auf der Serverseite folgt das Modul dem etablierten Muster mit einem `BlackBoard
|
||||
|
||||
### Produktion != Staging
|
||||
|
||||
Ein Learning, welches doch relativ schnell aufkam ist im Bereich der IT eigentlich kein unbekanntes. Wir hatten dieses Learning relativ bald, im Frühling 2025, als die ersten Probleme mit dem Deployment von Oqtane aufkamen. Oqtane war zwar in unserer Entwicklungsumgebung sehr einfach zum einrichten gewesen, das Deployment in der Cloud vom Hetzner war jedoch geplagt von Problemen. Im Zeitraum von Mai bis Okober hatten wir keine laufende Produktivumgebung. Dadurch sind wir mit dieser Diplomarbeit auch in [Zeitverzug](#arbeitszeiteinschätzung-zeitverzug) gekommen. Hätte ich mich vor dem Start der Diplomarbeit mit dem Deployment von Oqtane auseinander gesetzt, dann wäre das in [Probleme mit Oqtane](#fehlende-oder-nur-schlechte-dokumentation-von-oqtane) beschriebene Problem früher aufgekommen und der Zeitverzug wäre nicht so groß, oder noch ganz vermeidbar gewesen.
|
||||
Ein Learning, welches doch relativ schnell aufkam ist im Bereich der IT eigentlich kein unbekanntes. Wir hatten dieses Learning relativ bald, im Frühling 2025, als die ersten Probleme mit dem Deployment von Oqtane aufkamen. Oqtane war zwar in unserer Entwicklungsumgebung sehr einfach zum einrichten gewesen, das Deployment in der Cloud vom Hetzner war jedoch geplagt von Problemen. Im Zeitraum von Mai bis Okober hatten wir keine laufende Produktivumgebung. Dadurch sind wir mit dieser Diplomarbeit auch in [Zeitverzug](#arbeitszeiteinschätzung-zeitverzug) gekommen. Hätte ich mich vor dem Start der Diplomarbeit mit dem Deployment von Oqtane auseinander gesetzt, dann wäre das in [Fehlende Dokumentation](#fehlende-dokumentation) beschriebene Problem früher aufgekommen und der Zeitverzug wäre nicht so groß, oder noch ganz vermeidbar gewesen.
|
||||
|
||||
### Teamleitung (Motivation / Downsizing)
|
||||
|
||||
@@ -540,6 +662,8 @@ Ein wesentliches Learning aus dem Projektverlauf war die Diskrepanz zwischen der
|
||||
|
||||
Es gibt mehrere Gründe dafür:
|
||||
|
||||
#### Fehlende Dokumentation {#fehlende-dokumentation}
|
||||
|
||||
- `Fehlende oder nur schlechte Dokumentation von Oqtane`: Einige Probleme im Deployment wurden in langer und mühseliger Arbeit auseinander gebrochen und in weitere immer kleinere Probleme unterteilt. Dadurch, dass wir alle keine Erfahrung mit der Entwicklung und dem Deployment von ASP.NET Code Anwendungen hatten und die Dokumentation doch schlecht war, blieb uns manchmal nichts anderes übrig als mit WireShark den Netzwerktraffic mit zu schneiden und nebenbei im Git Repository die geloggten Zeilen Code zu finden und so das Framework von innen heraus kennen zu lernen. Dadurch hatte ich dann nach einer Einarbeitungszeit von 4 Monaten ziemlich jede Stelle im Sourcecode von Oqtane gesehen und finde mich um das schneller zurecht.
|
||||
- `Team-Konsolidierung`: Durch das notwendige Downsizing des Teams mussten Aufgaben neu verteilt werden, was die individuelle Arbeitslast erhöhte und die Konzentration auf die Kernentwicklung zeitweise verzögert hat.
|
||||
- `Abhängigkeit von der Infrastruktur`: Dadurch, dass wir bis in den Oktober / November hinein nicht wussten, ob wir weiter bei Oqtane bleiben können, haben die anderen Teammitglieder nicht mit der sinnvollen Entwicklung ihrer Module starten können. Zitat: "Und was wenn wir am Ende doch noch das CMS umstellen müssen?" => Auch wenn der Auftrag war, mit der Modulentwicklung zu starten war die Motivation meiner Teammitglieder nicht so hoch. Selbst wenn sie nicht direkt von der Infrastruktur mit der Ausführung ihrer Aufgaben abhängig waren, motiviert waren sie wegen der Umstände auch nicht.
|
||||
@@ -567,6 +691,6 @@ Lösungsansatz: Meetings und Besprechungen asynchron zueinander setzen.
|
||||
|
||||
- Asynchrone Daily-Updates: Statusberichte erfolgen schriftlich (z. B. in Gitea Issues oder YouTrack), nicht mehr in stundenlangen Call-Marathons. Das nimmt den zeitlichen Druck vom einzelnen Entwickler. Oder zumindest in kurzen Commitnachrichten, welche am Ende des Tages automatisch an alle Teammitglieder zum Überblick gesendet werden (eventuell mit \@username tagging, um eine Person nochmal genau anzusprechen)
|
||||
- Review-First-Policy: Ein Feature gilt erst dann als „fertig“, wenn es einen asynchronen Code-Review-Prozess durchlaufen hat. Das Meeting dient nur noch der Klärung von Blockern, nicht der Präsentation von Code. Das war eigentlich schon von Anfang an in unserer `Definition of Done` festgelegt worden.
|
||||
- Entkoppelung von Meeting und Deadline: Meetings sollten der Synchronisation dienen, während die Abgabe von Arbeitspaketen kontinuierlich (Continuous Integration) erfolgen muss, um Lastspitzen (in der [Gitea Actions](#Continuous Integration) Pipeline) am Tag der Besprechung zu vermeiden.
|
||||
- Entkoppelung von Meeting und Deadline: Meetings sollten der Synchronisation dienen, während die Abgabe von Arbeitspaketen kontinuierlich (Continuous Integration) erfolgen muss, um Lastspitzen (in der [Gitea Actions](#continuous-integration) Pipeline) am Tag der Besprechung zu vermeiden.
|
||||
|
||||
\pagebreak
|
||||
|
||||
937
_extensions/nginx/nginx.xml
Normal file
937
_extensions/nginx/nginx.xml
Normal file
@@ -0,0 +1,937 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE language
|
||||
[
|
||||
<!-- list of units taken from https://nginx.org/en/docs/syntax.html -->
|
||||
<!ENTITY units "([dGghKkMmswy]|ms)">
|
||||
]
|
||||
>
|
||||
<language name="nginx" section="Configuration"
|
||||
version="3" kateversion="5.79"
|
||||
indenter="cstyle"
|
||||
extensions="nginx.conf;*.nginx"
|
||||
mimetype=""
|
||||
author="Jyrki Gadinger (nilsding@nilsding.org)" license="MIT">
|
||||
<highlighting>
|
||||
<!-- see https://nginx.org/en/docs/dirindex.html for a full list of directives -->
|
||||
<list name="directives">
|
||||
<item>absolute_redirect</item>
|
||||
<item>accept_mutex</item>
|
||||
<item>accept_mutex_delay</item>
|
||||
<item>access_log</item>
|
||||
<item>add_after_body</item>
|
||||
<item>add_before_body</item>
|
||||
<item>add_header</item>
|
||||
<item>add_trailer</item>
|
||||
<item>addition_types</item>
|
||||
<item>aio</item>
|
||||
<item>aio_write</item>
|
||||
<item>alias</item>
|
||||
<item>allow</item>
|
||||
<item>ancient_browser</item>
|
||||
<item>ancient_browser_value</item>
|
||||
<item>api</item>
|
||||
<item>auth_basic</item>
|
||||
<item>auth_basic_user_file</item>
|
||||
<item>auth_delay</item>
|
||||
<item>auth_http</item>
|
||||
<item>auth_http_header</item>
|
||||
<item>auth_http_pass_client_cert</item>
|
||||
<item>auth_http_timeout</item>
|
||||
<item>auth_jwt</item>
|
||||
<item>auth_jwt_claim_set</item>
|
||||
<item>auth_jwt_header_set</item>
|
||||
<item>auth_jwt_key_cache</item>
|
||||
<item>auth_jwt_key_file</item>
|
||||
<item>auth_jwt_key_request</item>
|
||||
<item>auth_jwt_leeway</item>
|
||||
<item>auth_jwt_require</item>
|
||||
<item>auth_jwt_type</item>
|
||||
<item>auth_request</item>
|
||||
<item>auth_request_set</item>
|
||||
<item>autoindex</item>
|
||||
<item>autoindex_exact_size</item>
|
||||
<item>autoindex_format</item>
|
||||
<item>autoindex_localtime</item>
|
||||
<item>break</item>
|
||||
<item>charset</item>
|
||||
<item>charset_map</item>
|
||||
<item>charset_types</item>
|
||||
<item>chunked_transfer_encoding</item>
|
||||
<item>client_body_buffer_size</item>
|
||||
<item>client_body_in_file_only</item>
|
||||
<item>client_body_in_single_buffer</item>
|
||||
<item>client_body_temp_path</item>
|
||||
<item>client_body_timeout</item>
|
||||
<item>client_header_buffer_size</item>
|
||||
<item>client_header_timeout</item>
|
||||
<item>client_max_body_size</item>
|
||||
<item>connect_timeout</item>
|
||||
<item>connection_pool_size</item>
|
||||
<item>create_full_put_path</item>
|
||||
<item>daemon</item>
|
||||
<item>dav_access</item>
|
||||
<item>dav_methods</item>
|
||||
<item>debug_connection</item>
|
||||
<item>debug_points</item>
|
||||
<item>default_type</item>
|
||||
<item>deny</item>
|
||||
<item>directio</item>
|
||||
<item>directio_alignment</item>
|
||||
<item>disable_symlinks</item>
|
||||
<item>empty_gif</item>
|
||||
<item>env</item>
|
||||
<item>error_log</item>
|
||||
<item>error_page</item>
|
||||
<item>etag</item>
|
||||
<item>events</item>
|
||||
<item>expires</item>
|
||||
<item>f4f</item>
|
||||
<item>f4f_buffer_size</item>
|
||||
<item>fastcgi_bind</item>
|
||||
<item>fastcgi_buffer_size</item>
|
||||
<item>fastcgi_buffering</item>
|
||||
<item>fastcgi_buffers</item>
|
||||
<item>fastcgi_busy_buffers_size</item>
|
||||
<item>fastcgi_cache</item>
|
||||
<item>fastcgi_cache_background_update</item>
|
||||
<item>fastcgi_cache_bypass</item>
|
||||
<item>fastcgi_cache_key</item>
|
||||
<item>fastcgi_cache_lock</item>
|
||||
<item>fastcgi_cache_lock_age</item>
|
||||
<item>fastcgi_cache_lock_timeout</item>
|
||||
<item>fastcgi_cache_max_range_offset</item>
|
||||
<item>fastcgi_cache_methods</item>
|
||||
<item>fastcgi_cache_min_uses</item>
|
||||
<item>fastcgi_cache_path</item>
|
||||
<item>fastcgi_cache_purge</item>
|
||||
<item>fastcgi_cache_revalidate</item>
|
||||
<item>fastcgi_cache_use_stale</item>
|
||||
<item>fastcgi_cache_valid</item>
|
||||
<item>fastcgi_catch_stderr</item>
|
||||
<item>fastcgi_connect_timeout</item>
|
||||
<item>fastcgi_force_ranges</item>
|
||||
<item>fastcgi_hide_header</item>
|
||||
<item>fastcgi_ignore_client_abort</item>
|
||||
<item>fastcgi_ignore_headers</item>
|
||||
<item>fastcgi_index</item>
|
||||
<item>fastcgi_intercept_errors</item>
|
||||
<item>fastcgi_keep_conn</item>
|
||||
<item>fastcgi_index</item>
|
||||
<item>fastcgi_intercept_errors</item>
|
||||
<item>fastcgi_keep_conn</item>
|
||||
<item>fastcgi_limit_rate</item>
|
||||
<item>fastcgi_max_temp_file_size</item>
|
||||
<item>fastcgi_next_upstream</item>
|
||||
<item>fastcgi_next_upstream_timeout</item>
|
||||
<item>fastcgi_next_upstream_tries</item>
|
||||
<item>fastcgi_no_cache</item>
|
||||
<item>fastcgi_param</item>
|
||||
<item>fastcgi_pass</item>
|
||||
<item>fastcgi_pass_header</item>
|
||||
<item>fastcgi_pass_request_body</item>
|
||||
<item>fastcgi_pass_request_headers</item>
|
||||
<item>fastcgi_read_timeout</item>
|
||||
<item>fastcgi_request_buffering</item>
|
||||
<item>fastcgi_send_lowat</item>
|
||||
<item>fastcgi_send_timeout</item>
|
||||
<item>fastcgi_socket_keepalive</item>
|
||||
<item>fastcgi_split_path_info</item>
|
||||
<item>fastcgi_store</item>
|
||||
<item>fastcgi_store_access</item>
|
||||
<item>fastcgi_temp_file_write_size</item>
|
||||
<item>fastcgi_temp_path</item>
|
||||
<item>flv</item>
|
||||
<item>geo</item>
|
||||
<item>geoip_city</item>
|
||||
<item>geoip_country</item>
|
||||
<item>geoip_org</item>
|
||||
<item>geoip_proxy</item>
|
||||
<item>geoip_proxy_recursive</item>
|
||||
<item>google_perftools_profiles</item>
|
||||
<item>grpc_bind</item>
|
||||
<item>grpc_buffer_size</item>
|
||||
<item>grpc_connect_timeout</item>
|
||||
<item>grpc_hide_header</item>
|
||||
<item>grpc_ignore_headers</item>
|
||||
<item>grpc_intercept_errors</item>
|
||||
<item>grpc_next_upstream</item>
|
||||
<item>grpc_next_upstream_timeout</item>
|
||||
<item>grpc_next_upstream_tries</item>
|
||||
<item>grpc_pass</item>
|
||||
<item>grpc_pass_header</item>
|
||||
<item>grpc_read_timeout</item>
|
||||
<item>grpc_send_timeout</item>
|
||||
<item>grpc_set_header</item>
|
||||
<item>grpc_socket_keepalive</item>
|
||||
<item>grpc_ssl_certificate</item>
|
||||
<item>grpc_ssl_certificate_key</item>
|
||||
<item>grpc_ssl_ciphers</item>
|
||||
<item>grpc_ssl_conf_command</item>
|
||||
<item>grpc_ssl_crl</item>
|
||||
<item>grpc_ssl_name</item>
|
||||
<item>grpc_ssl_password_file</item>
|
||||
<item>grpc_ssl_protocols</item>
|
||||
<item>grpc_ssl_server_name</item>
|
||||
<item>grpc_ssl_session_reuse</item>
|
||||
<item>grpc_ssl_trusted_certificate</item>
|
||||
<item>grpc_ssl_verify</item>
|
||||
<item>grpc_ssl_verify_depth</item>
|
||||
<item>gunzip</item>
|
||||
<item>gunzip_buffers</item>
|
||||
<item>gzip</item>
|
||||
<item>gzip_buffers</item>
|
||||
<item>gzip_comp_level</item>
|
||||
<item>gzip_disable</item>
|
||||
<item>gzip_http_version</item>
|
||||
<item>gzip_min_length</item>
|
||||
<item>gzip_proxied</item>
|
||||
<item>gzip_static</item>
|
||||
<item>gzip_types</item>
|
||||
<item>gzip_vary</item>
|
||||
<item>hash</item>
|
||||
<item>health_check</item>
|
||||
<item>health_check_timeout</item>
|
||||
<item>hls</item>
|
||||
<item>hls_buffers</item>
|
||||
<item>hls_forward_args</item>
|
||||
<item>hls_fragment</item>
|
||||
<item>hls_mp4_buffer_size</item>
|
||||
<item>hls_mp4_max_buffer_size</item>
|
||||
<item>http</item>
|
||||
<item>http2</item>
|
||||
<item>http2_body_preread_size</item>
|
||||
<item>http2_chunk_size</item>
|
||||
<item>http2_idle_timeout</item>
|
||||
<item>http2_max_concurrent_pushes</item>
|
||||
<item>http2_max_concurrent_streams</item>
|
||||
<item>http2_max_field_size</item>
|
||||
<item>http2_max_header_size</item>
|
||||
<item>http2_max_requests</item>
|
||||
<item>http2_push</item>
|
||||
<item>http2_push_preload</item>
|
||||
<item>http2_recv_buffer_size</item>
|
||||
<item>http2_recv_timeout</item>
|
||||
<item>http3</item>
|
||||
<item>http3_hq</item>
|
||||
<item>http3_max_concurrent_streams</item>
|
||||
<item>http3_stream_buffer_size</item>
|
||||
<item>if</item>
|
||||
<item>if_modified_since</item>
|
||||
<item>ignore_invalid_headers</item>
|
||||
<item>image_filter</item>
|
||||
<item>image_filter_buffer</item>
|
||||
<item>image_filter_interlace</item>
|
||||
<item>image_filter_jpeg_quality</item>
|
||||
<item>image_filter_sharpen</item>
|
||||
<item>image_filter_transparency</item>
|
||||
<item>image_filter_webp_quality</item>
|
||||
<item>imap_auth</item>
|
||||
<item>imap_capabilities</item>
|
||||
<item>imap_client_buffer</item>
|
||||
<item>include</item>
|
||||
<item>index</item>
|
||||
<item>internal</item>
|
||||
<item>internal_redirect</item>
|
||||
<item>ip_hash</item>
|
||||
<item>js_access</item>
|
||||
<item>js_body_filter</item>
|
||||
<item>js_content</item>
|
||||
<item>js_fetch_buffer_size</item>
|
||||
<item>js_fetch_ciphers</item>
|
||||
<item>js_fetch_max_response_buffer_size</item>
|
||||
<item>js_fetch_protocols</item>
|
||||
<item>js_fetch_timeout</item>
|
||||
<item>js_fetch_trusted_certificate</item>
|
||||
<item>js_fetch_verify</item>
|
||||
<item>js_fetch_verify_depth</item>
|
||||
<item>js_filter</item>
|
||||
<item>js_header_filter</item>
|
||||
<item>js_import</item>
|
||||
<item>js_include</item>
|
||||
<item>js_path</item>
|
||||
<item>js_periodic</item>
|
||||
<item>js_preload_object</item>
|
||||
<item>js_preread</item>
|
||||
<item>js_set</item>
|
||||
<item>js_shared_dict_zone</item>
|
||||
<item>js_var</item>
|
||||
<item>keepalive</item>
|
||||
<item>keepalive_disable</item>
|
||||
<item>keepalive_requests</item>
|
||||
<item>keepalive_time</item>
|
||||
<item>keepalive_timeout</item>
|
||||
<item>keyval</item>
|
||||
<item>keyval_zone</item>
|
||||
<item>large_client_header_buffers</item>
|
||||
<item>least_conn</item>
|
||||
<item>least_time</item>
|
||||
<item>limit_conn</item>
|
||||
<item>limit_conn_dry_run</item>
|
||||
<item>limit_conn_log_level</item>
|
||||
<item>limit_conn_status</item>
|
||||
<item>limit_conn_zone</item>
|
||||
<item>limit_except</item>
|
||||
<item>limit_rate</item>
|
||||
<item>limit_rate_after</item>
|
||||
<item>limit_req</item>
|
||||
<item>limit_req_dry_run</item>
|
||||
<item>limit_req_log_level</item>
|
||||
<item>limit_req_status</item>
|
||||
<item>limit_req_zone</item>
|
||||
<item>limit_zone</item>
|
||||
<item>lingering_close</item>
|
||||
<item>lingering_time</item>
|
||||
<item>lingering_timeout</item>
|
||||
<item>listen</item>
|
||||
<item>load_module</item>
|
||||
<item>location</item>
|
||||
<item>lock_file</item>
|
||||
<item>log_format</item>
|
||||
<item>log_not_found</item>
|
||||
<item>log_subrequest</item>
|
||||
<item>mail</item>
|
||||
<item>map</item>
|
||||
<item>map_hash_bucket_size</item>
|
||||
<item>map_hash_max_size</item>
|
||||
<item>master_process</item>
|
||||
<item>match</item>
|
||||
<item>max_errors</item>
|
||||
<item>max_ranges</item>
|
||||
<item>memcached_bind</item>
|
||||
<item>memcached_buffer_size</item>
|
||||
<item>memcached_connect_timeout</item>
|
||||
<item>memcached_gzip_flag</item>
|
||||
<item>memcached_next_upstream</item>
|
||||
<item>memcached_next_upstream_timeout</item>
|
||||
<item>memcached_next_upstream_tries</item>
|
||||
<item>memcached_pass</item>
|
||||
<item>memcached_read_timeout</item>
|
||||
<item>memcached_send_timeout</item>
|
||||
<item>memcached_socket_keepalive</item>
|
||||
<item>merge_slashes</item>
|
||||
<item>mgmt</item>
|
||||
<item>min_delete_depth</item>
|
||||
<item>mirror</item>
|
||||
<item>mirror_request_body</item>
|
||||
<item>modern_browser</item>
|
||||
<item>modern_browser_value</item>
|
||||
<item>mp4</item>
|
||||
<item>mp4_buffer_size</item>
|
||||
<item>mp4_limit_rate</item>
|
||||
<item>mp4_limit_rate_after</item>
|
||||
<item>mp4_max_buffer_size</item>
|
||||
<item>mp4_start_key_frame</item>
|
||||
<item>mqtt</item>
|
||||
<item>mqtt_buffers</item>
|
||||
<item>mqtt_preread</item>
|
||||
<item>mqtt_rewrite_buffer_size</item>
|
||||
<item>mqtt_set_connect</item>
|
||||
<item>msie_padding</item>
|
||||
<item>msie_refresh</item>
|
||||
<item>multi_accept</item>
|
||||
<item>ntlm</item>
|
||||
<item>open_file_cache</item>
|
||||
<item>open_file_cache_errors</item>
|
||||
<item>open_file_cache_min_uses</item>
|
||||
<item>open_file_cache_valid</item>
|
||||
<item>open_log_file_cache</item>
|
||||
<item>otel_exporter</item>
|
||||
<item>otel_service_name</item>
|
||||
<item>otel_span_attr</item>
|
||||
<item>otel_span_name</item>
|
||||
<item>otel_trace</item>
|
||||
<item>otel_trace_context</item>
|
||||
<item>output_buffers</item>
|
||||
<item>override_charset</item>
|
||||
<item>pass</item>
|
||||
<item>pcre_jit</item>
|
||||
<item>perl</item>
|
||||
<item>perl_modules</item>
|
||||
<item>perl_require</item>
|
||||
<item>perl_set</item>
|
||||
<item>pid</item>
|
||||
<item>pop3_auth</item>
|
||||
<item>pop3_capabilities</item>
|
||||
<item>port_in_redirect</item>
|
||||
<item>postpone_output</item>
|
||||
<item>preread_buffer_size</item>
|
||||
<item>preread_timeout</item>
|
||||
<item>protocol</item>
|
||||
<item>proxy_bind</item>
|
||||
<item>proxy_buffer</item>
|
||||
<item>proxy_buffer_size</item>
|
||||
<item>proxy_buffering</item>
|
||||
<item>proxy_buffers</item>
|
||||
<item>proxy_busy_buffers_size</item>
|
||||
<item>proxy_cache</item>
|
||||
<item>proxy_cache_background_update</item>
|
||||
<item>proxy_cache_bypass</item>
|
||||
<item>proxy_cache_convert_head</item>
|
||||
<item>proxy_cache_key</item>
|
||||
<item>proxy_cache_lock</item>
|
||||
<item>proxy_cache_lock_age</item>
|
||||
<item>proxy_cache_lock_timeout</item>
|
||||
<item>proxy_cache_max_range_offset</item>
|
||||
<item>proxy_cache_methods</item>
|
||||
<item>proxy_cache_min_uses</item>
|
||||
<item>proxy_cache_path</item>
|
||||
<item>proxy_cache_purge</item>
|
||||
<item>proxy_cache_revalidate</item>
|
||||
<item>proxy_cache_use_stale</item>
|
||||
<item>proxy_cache_valid</item>
|
||||
<item>proxy_connect_timeout</item>
|
||||
<item>proxy_cookie_domain</item>
|
||||
<item>proxy_cookie_flags</item>
|
||||
<item>proxy_cookie_path</item>
|
||||
<item>proxy_download_rate</item>
|
||||
<item>proxy_force_ranges</item>
|
||||
<item>proxy_half_close</item>
|
||||
<item>proxy_headers_hash_bucket_size</item>
|
||||
<item>proxy_headers_hash_max_size</item>
|
||||
<item>proxy_hide_header</item>
|
||||
<item>proxy_http_version</item>
|
||||
<item>proxy_ignore_client_abort</item>
|
||||
<item>proxy_ignore_headers</item>
|
||||
<item>proxy_intercept_errors</item>
|
||||
<item>proxy_limit_rate</item>
|
||||
<item>proxy_max_temp_file_size</item>
|
||||
<item>proxy_method</item>
|
||||
<item>proxy_next_upstream</item>
|
||||
<item>proxy_next_upstream_timeout</item>
|
||||
<item>proxy_next_upstream_tries</item>
|
||||
<item>proxy_no_cache</item>
|
||||
<item>proxy_pass</item>
|
||||
<item>proxy_pass_error_message</item>
|
||||
<item>proxy_pass_header</item>
|
||||
<item>proxy_pass_request_body</item>
|
||||
<item>proxy_pass_request_headers</item>
|
||||
<item>proxy_protocol</item>
|
||||
<item>proxy_protocol_timeout</item>
|
||||
<item>proxy_read_timeout</item>
|
||||
<item>proxy_redirect</item>
|
||||
<item>proxy_request_buffering</item>
|
||||
<item>proxy_requests</item>
|
||||
<item>proxy_responses</item>
|
||||
<item>proxy_send_lowat</item>
|
||||
<item>proxy_send_timeout</item>
|
||||
<item>proxy_session_drop</item>
|
||||
<item>proxy_set_body</item>
|
||||
<item>proxy_set_header</item>
|
||||
<item>proxy_smtp_auth</item>
|
||||
<item>proxy_socket_keepalive</item>
|
||||
<item>proxy_ssl</item>
|
||||
<item>proxy_ssl_certificate</item>
|
||||
<item>proxy_ssl_certificate_key</item>
|
||||
<item>proxy_ssl_ciphers</item>
|
||||
<item>proxy_ssl_conf_command</item>
|
||||
<item>proxy_ssl_crl</item>
|
||||
<item>proxy_ssl_name</item>
|
||||
<item>proxy_ssl_password_file</item>
|
||||
<item>proxy_ssl_protocols</item>
|
||||
<item>proxy_ssl_server_name</item>
|
||||
<item>proxy_ssl_session_reuse</item>
|
||||
<item>proxy_ssl_trusted_certificate</item>
|
||||
<item>proxy_ssl_verify</item>
|
||||
<item>proxy_ssl_verify_depth</item>
|
||||
<item>proxy_store</item>
|
||||
<item>proxy_store_access</item>
|
||||
<item>proxy_temp_file_write_size</item>
|
||||
<item>proxy_temp_path</item>
|
||||
<item>proxy_timeout</item>
|
||||
<item>proxy_upload_rate</item>
|
||||
<item>queue</item>
|
||||
<item>quic_active_connection_id_limit</item>
|
||||
<item>quic_bpf</item>
|
||||
<item>quic_gso</item>
|
||||
<item>quic_host_key</item>
|
||||
<item>quic_retry</item>
|
||||
<item>random</item>
|
||||
<item>random_index</item>
|
||||
<item>read_ahead</item>
|
||||
<item>read_timeout</item>
|
||||
<item>real_ip_header</item>
|
||||
<item>real_ip_recursive</item>
|
||||
<item>recursive_error_pages</item>
|
||||
<item>referer_hash_bucket_size</item>
|
||||
<item>referer_hash_max_size</item>
|
||||
<item>request_pool_size</item>
|
||||
<item>reset_timedout_connection</item>
|
||||
<item>resolver</item>
|
||||
<item>resolver_timeout</item>
|
||||
<item>return</item>
|
||||
<item>rewrite</item>
|
||||
<item>rewrite_log</item>
|
||||
<item>root</item>
|
||||
<item>satisfy</item>
|
||||
<item>scgi_bind</item>
|
||||
<item>scgi_buffer_size</item>
|
||||
<item>scgi_buffering</item>
|
||||
<item>scgi_buffers</item>
|
||||
<item>scgi_busy_buffers_size</item>
|
||||
<item>scgi_cache</item>
|
||||
<item>scgi_cache_background_update</item>
|
||||
<item>scgi_cache_bypass</item>
|
||||
<item>scgi_cache_key</item>
|
||||
<item>scgi_cache_lock</item>
|
||||
<item>scgi_cache_lock_age</item>
|
||||
<item>scgi_cache_lock_timeout</item>
|
||||
<item>scgi_cache_max_range_offset</item>
|
||||
<item>scgi_cache_methods</item>
|
||||
<item>scgi_cache_min_uses</item>
|
||||
<item>scgi_cache_path</item>
|
||||
<item>scgi_cache_purge</item>
|
||||
<item>scgi_cache_revalidate</item>
|
||||
<item>scgi_cache_use_stale</item>
|
||||
<item>scgi_cache_valid</item>
|
||||
<item>scgi_connect_timeout</item>
|
||||
<item>scgi_force_ranges</item>
|
||||
<item>scgi_hide_header</item>
|
||||
<item>scgi_ignore_client_abort</item>
|
||||
<item>scgi_ignore_headers</item>
|
||||
<item>scgi_intercept_errors</item>
|
||||
<item>scgi_limit_rate</item>
|
||||
<item>scgi_max_temp_file_size</item>
|
||||
<item>scgi_next_upstream</item>
|
||||
<item>scgi_next_upstream_timeout</item>
|
||||
<item>scgi_next_upstream_tries</item>
|
||||
<item>scgi_no_cache</item>
|
||||
<item>scgi_param</item>
|
||||
<item>scgi_pass</item>
|
||||
<item>scgi_pass_header</item>
|
||||
<item>scgi_pass_request_body</item>
|
||||
<item>scgi_pass_request_headers</item>
|
||||
<item>scgi_read_timeout</item>
|
||||
<item>scgi_request_buffering</item>
|
||||
<item>scgi_send_timeout</item>
|
||||
<item>scgi_socket_keepalive</item>
|
||||
<item>scgi_store</item>
|
||||
<item>scgi_store_access</item>
|
||||
<item>scgi_temp_file_write_size</item>
|
||||
<item>scgi_temp_path</item>
|
||||
<item>secure_link</item>
|
||||
<item>secure_link_md5</item>
|
||||
<item>secure_link_secret</item>
|
||||
<item>send_lowat</item>
|
||||
<item>send_timeout</item>
|
||||
<item>sendfile</item>
|
||||
<item>sendfile_max_chunk</item>
|
||||
<item>server</item>
|
||||
<item>server_name</item>
|
||||
<item>server_name_in_redirect</item>
|
||||
<item>server_names_hash_bucket_size</item>
|
||||
<item>server_names_hash_max_size</item>
|
||||
<item>server_tokens</item>
|
||||
<item>session_log</item>
|
||||
<item>session_log_format</item>
|
||||
<item>session_log_zone</item>
|
||||
<item>set</item>
|
||||
<item>set_real_ip_from</item>
|
||||
<item>slice</item>
|
||||
<item>smtp_auth</item>
|
||||
<item>smtp_capabilities</item>
|
||||
<item>smtp_client_buffer</item>
|
||||
<item>smtp_greeting_delay</item>
|
||||
<item>source_charset</item>
|
||||
<item>split_clients</item>
|
||||
<item>ssi</item>
|
||||
<item>ssi_last_modified</item>
|
||||
<item>ssi_min_file_chunk</item>
|
||||
<item>ssi_silent_errors</item>
|
||||
<item>ssi_types</item>
|
||||
<item>ssi_value_length</item>
|
||||
<item>ssl</item>
|
||||
<item>ssl_alpn</item>
|
||||
<item>ssl_buffer_size</item>
|
||||
<item>ssl_certificate</item>
|
||||
<item>ssl_certificate_key</item>
|
||||
<item>ssl_ciphers</item>
|
||||
<item>ssl_client_certificate</item>
|
||||
<item>ssl_conf_command</item>
|
||||
<item>ssl_crl</item>
|
||||
<item>ssl_dhparam</item>
|
||||
<item>ssl_early_data</item>
|
||||
<item>ssl_ecdh_curve</item>
|
||||
<item>ssl_engine</item>
|
||||
<item>ssl_handshake_timeout</item>
|
||||
<item>ssl_name</item>
|
||||
<item>ssl_ocsp</item>
|
||||
<item>ssl_ocsp_cache</item>
|
||||
<item>ssl_ocsp_responder</item>
|
||||
<item>ssl_password_file</item>
|
||||
<item>ssl_prefer_server_ciphers</item>
|
||||
<item>ssl_preread</item>
|
||||
<item>ssl_protocols</item>
|
||||
<item>ssl_reject_handshake</item>
|
||||
<item>ssl_server_name</item>
|
||||
<item>ssl_session_cache</item>
|
||||
<item>ssl_session_ticket_key</item>
|
||||
<item>ssl_session_tickets</item>
|
||||
<item>ssl_session_timeout</item>
|
||||
<item>ssl_stapling</item>
|
||||
<item>ssl_stapling_file</item>
|
||||
<item>ssl_stapling_responder</item>
|
||||
<item>ssl_stapling_verify</item>
|
||||
<item>ssl_trusted_certificate</item>
|
||||
<item>ssl_verify</item>
|
||||
<item>ssl_verify_client</item>
|
||||
<item>ssl_verify_depth</item>
|
||||
<item>starttls</item>
|
||||
<item>state</item>
|
||||
<item>status</item>
|
||||
<item>status_format</item>
|
||||
<item>status_zone</item>
|
||||
<item>sticky</item>
|
||||
<item>sticky_cookie_insert</item>
|
||||
<item>stream</item>
|
||||
<item>stub_status</item>
|
||||
<item>sub_filter</item>
|
||||
<item>sub_filter_last_modified</item>
|
||||
<item>sub_filter_once</item>
|
||||
<item>sub_filter_types</item>
|
||||
<item>subrequest_output_buffer_size</item>
|
||||
<item>tcp_nodelay</item>
|
||||
<item>tcp_nopush</item>
|
||||
<item>thread_pool</item>
|
||||
<item>timeout</item>
|
||||
<item>timer_resolution</item>
|
||||
<item>try_files</item>
|
||||
<item>types</item>
|
||||
<item>types_hash_bucket_size</item>
|
||||
<item>types_hash_max_size</item>
|
||||
<item>underscores_in_headers</item>
|
||||
<item>uninitialized_variable_warn</item>
|
||||
<item>upstream</item>
|
||||
<item>upstream_conf</item>
|
||||
<item>usage_report</item>
|
||||
<item>use</item>
|
||||
<item>user</item>
|
||||
<item>userid</item>
|
||||
<item>userid_domain</item>
|
||||
<item>userid_expires</item>
|
||||
<item>userid_flags</item>
|
||||
<item>userid_mark</item>
|
||||
<item>userid_name</item>
|
||||
<item>userid_p3p</item>
|
||||
<item>userid_path</item>
|
||||
<item>userid_service</item>
|
||||
<item>uuid_file</item>
|
||||
<item>uwsgi_bind</item>
|
||||
<item>uwsgi_buffer_size</item>
|
||||
<item>uwsgi_buffering</item>
|
||||
<item>uwsgi_buffers</item>
|
||||
<item>uwsgi_busy_buffers_size</item>
|
||||
<item>uwsgi_cache</item>
|
||||
<item>uwsgi_cache_background_update</item>
|
||||
<item>uwsgi_cache_bypass</item>
|
||||
<item>uwsgi_cache_key</item>
|
||||
<item>uwsgi_cache_lock</item>
|
||||
<item>uwsgi_cache_lock_age</item>
|
||||
<item>uwsgi_cache_lock_timeout</item>
|
||||
<item>uwsgi_cache_max_range_offset</item>
|
||||
<item>uwsgi_cache_methods</item>
|
||||
<item>uwsgi_cache_min_uses</item>
|
||||
<item>uwsgi_cache_path</item>
|
||||
<item>uwsgi_cache_purge</item>
|
||||
<item>uwsgi_cache_revalidate</item>
|
||||
<item>uwsgi_cache_use_stale</item>
|
||||
<item>uwsgi_cache_valid</item>
|
||||
<item>uwsgi_connect_timeout</item>
|
||||
<item>uwsgi_force_ranges</item>
|
||||
<item>uwsgi_hide_header</item>
|
||||
<item>uwsgi_ignore_client_abort</item>
|
||||
<item>uwsgi_ignore_headers</item>
|
||||
<item>uwsgi_intercept_errors</item>
|
||||
<item>uwsgi_limit_rate</item>
|
||||
<item>uwsgi_max_temp_file_size</item>
|
||||
<item>uwsgi_modifier1</item>
|
||||
<item>uwsgi_modifier2</item>
|
||||
<item>uwsgi_next_upstream</item>
|
||||
<item>uwsgi_next_upstream_timeout</item>
|
||||
<item>uwsgi_next_upstream_tries</item>
|
||||
<item>uwsgi_no_cache</item>
|
||||
<item>uwsgi_param</item>
|
||||
<item>uwsgi_pass</item>
|
||||
<item>uwsgi_pass_header</item>
|
||||
<item>uwsgi_pass_request_body</item>
|
||||
<item>uwsgi_pass_request_headers</item>
|
||||
<item>uwsgi_read_timeout</item>
|
||||
<item>uwsgi_request_buffering</item>
|
||||
<item>uwsgi_send_timeout</item>
|
||||
<item>uwsgi_socket_keepalive</item>
|
||||
<item>uwsgi_ssl_certificate</item>
|
||||
<item>uwsgi_ssl_certificate_key</item>
|
||||
<item>uwsgi_ssl_ciphers</item>
|
||||
<item>uwsgi_ssl_conf_command</item>
|
||||
<item>uwsgi_ssl_crl</item>
|
||||
<item>uwsgi_ssl_name</item>
|
||||
<item>uwsgi_ssl_password_file</item>
|
||||
<item>uwsgi_ssl_protocols</item>
|
||||
<item>uwsgi_ssl_server_name</item>
|
||||
<item>uwsgi_ssl_session_reuse</item>
|
||||
<item>uwsgi_ssl_trusted_certificate</item>
|
||||
<item>uwsgi_ssl_verify</item>
|
||||
<item>uwsgi_ssl_verify_depth</item>
|
||||
<item>uwsgi_store</item>
|
||||
<item>uwsgi_store_access</item>
|
||||
<item>uwsgi_temp_file_write_size</item>
|
||||
<item>uwsgi_temp_path</item>
|
||||
<item>valid_referers</item>
|
||||
<item>variables_hash_bucket_size</item>
|
||||
<item>variables_hash_max_size</item>
|
||||
<item>worker_aio_requests</item>
|
||||
<item>worker_connections</item>
|
||||
<item>worker_cpu_affinity</item>
|
||||
<item>worker_priority</item>
|
||||
<item>worker_processes</item>
|
||||
<item>worker_rlimit_core</item>
|
||||
<item>worker_rlimit_nofile</item>
|
||||
<item>worker_shutdown_timeout</item>
|
||||
<item>working_directory</item>
|
||||
<item>xclient</item>
|
||||
<item>xml_entities</item>
|
||||
<item>xslt_last_modified</item>
|
||||
<item>xslt_param</item>
|
||||
<item>xslt_string_param</item>
|
||||
<item>xslt_stylesheet</item>
|
||||
<item>xslt_types</item>
|
||||
<item>zone</item>
|
||||
<item>zone_sync</item>
|
||||
<item>zone_sync_buffers</item>
|
||||
<item>zone_sync_connect_retry_interval</item>
|
||||
<item>zone_sync_connect_timeout</item>
|
||||
<item>zone_sync_interval</item>
|
||||
<item>zone_sync_recv_buffer_size</item>
|
||||
<item>zone_sync_server</item>
|
||||
<item>zone_sync_ssl</item>
|
||||
<item>zone_sync_ssl_certificate</item>
|
||||
<item>zone_sync_ssl_certificate_key</item>
|
||||
<item>zone_sync_ssl_ciphers</item>
|
||||
<item>zone_sync_ssl_conf_command</item>
|
||||
<item>zone_sync_ssl_crl</item>
|
||||
<item>zone_sync_ssl_name</item>
|
||||
<item>zone_sync_ssl_password_file</item>
|
||||
<item>zone_sync_ssl_protocols</item>
|
||||
<item>zone_sync_ssl_server_name</item>
|
||||
<item>zone_sync_ssl_trusted_certificate</item>
|
||||
<item>zone_sync_ssl_verify</item>
|
||||
<item>zone_sync_ssl_verify_depth</item>
|
||||
<item>zone_sync_timeout</item>
|
||||
</list>
|
||||
|
||||
<!-- see https://nginx.org/en/docs/varindex.html for a full list of variables -->
|
||||
<list name="variables">
|
||||
<item>$ancient_browser</item>
|
||||
<item>$args</item>
|
||||
<item>$binary_remote_addr</item>
|
||||
<item>$body_bytes_sent</item>
|
||||
<item>$bytes_received</item>
|
||||
<item>$bytes_sent</item>
|
||||
<item>$connection</item>
|
||||
<item>$connection_requests</item>
|
||||
<item>$connection_time</item>
|
||||
<item>$connections_active</item>
|
||||
<item>$connections_reading</item>
|
||||
<item>$connections_waiting</item>
|
||||
<item>$connections_writing</item>
|
||||
<item>$content_length</item>
|
||||
<item>$content_type</item>
|
||||
<item>$date_gmt</item>
|
||||
<item>$date_local</item>
|
||||
<item>$document_root</item>
|
||||
<item>$document_uri</item>
|
||||
<item>$fastcgi_path_info</item>
|
||||
<item>$fastcgi_script_name</item>
|
||||
<item>$geoip_area_code</item>
|
||||
<item>$geoip_city</item>
|
||||
<item>$geoip_city_continent_code</item>
|
||||
<item>$geoip_city_country_code</item>
|
||||
<item>$geoip_city_country_code3</item>
|
||||
<item>$geoip_city_country_name</item>
|
||||
<item>$geoip_country_code</item>
|
||||
<item>$geoip_country_code3</item>
|
||||
<item>$geoip_country_name</item>
|
||||
<item>$geoip_dma_code</item>
|
||||
<item>$geoip_latitude</item>
|
||||
<item>$geoip_longitude</item>
|
||||
<item>$geoip_org</item>
|
||||
<item>$geoip_postal_code</item>
|
||||
<item>$geoip_region</item>
|
||||
<item>$geoip_region_name</item>
|
||||
<item>$gzip_ratio</item>
|
||||
<item>$host</item>
|
||||
<item>$hostname</item>
|
||||
<item>$http2</item>
|
||||
<item>$http3</item>
|
||||
<item>$https</item>
|
||||
<item>$invalid_referer</item>
|
||||
<item>$is_args</item>
|
||||
<item>$jwt_payload</item>
|
||||
<item>$limit_conn_status</item>
|
||||
<item>$limit_rate</item>
|
||||
<item>$limit_req_status</item>
|
||||
<item>$memcached_key</item>
|
||||
<item>$modern_browser</item>
|
||||
<item>$mqtt_preread_clientid</item>
|
||||
<item>$mqtt_preread_username</item>
|
||||
<item>$msec</item>
|
||||
<item>$msie</item>
|
||||
<item>$nginx_version</item>
|
||||
<item>$otel_parent_id</item>
|
||||
<item>$otel_parent_sampled</item>
|
||||
<item>$otel_span_id</item>
|
||||
<item>$otel_trace_id</item>
|
||||
<item>$pid</item>
|
||||
<item>$pipe</item>
|
||||
<item>$protocol</item>
|
||||
<item>$proxy_add_x_forwarded_for</item>
|
||||
<item>$proxy_host</item>
|
||||
<item>$proxy_port</item>
|
||||
<item>$proxy_protocol_addr</item>
|
||||
<item>$proxy_protocol_port</item>
|
||||
<item>$proxy_protocol_server_addr</item>
|
||||
<item>$proxy_protocol_server_port</item>
|
||||
<item>$proxy_protocol_tlv_aws_vpce_id</item>
|
||||
<item>$proxy_protocol_tlv_azure_pel_id</item>
|
||||
<item>$proxy_protocol_tlv_gcp_conn_id</item>
|
||||
<item>$query_string</item>
|
||||
<item>$realip_remote_addr</item>
|
||||
<item>$realip_remote_port</item>
|
||||
<item>$realpath_root</item>
|
||||
<item>$remote_addr</item>
|
||||
<item>$remote_port</item>
|
||||
<item>$remote_user</item>
|
||||
<item>$request</item>
|
||||
<item>$request_body</item>
|
||||
<item>$request_body_file</item>
|
||||
<item>$request_completion</item>
|
||||
<item>$request_filename</item>
|
||||
<item>$request_id</item>
|
||||
<item>$request_length</item>
|
||||
<item>$request_method</item>
|
||||
<item>$request_time</item>
|
||||
<item>$request_uri</item>
|
||||
<item>$scheme</item>
|
||||
<item>$secure_link</item>
|
||||
<item>$secure_link_expires</item>
|
||||
<item>$server_addr</item>
|
||||
<item>$server_name</item>
|
||||
<item>$server_port</item>
|
||||
<item>$server_protocol</item>
|
||||
<item>$session_log_binary_id</item>
|
||||
<item>$session_log_id</item>
|
||||
<item>$session_time</item>
|
||||
<item>$slice_range</item>
|
||||
<item>$ssl_alpn_protocol</item>
|
||||
<item>$ssl_cipher</item>
|
||||
<item>$ssl_ciphers</item>
|
||||
<item>$ssl_client_cert</item>
|
||||
<item>$ssl_client_escaped_cert</item>
|
||||
<item>$ssl_client_fingerprint</item>
|
||||
<item>$ssl_client_i_dn</item>
|
||||
<item>$ssl_client_i_dn_legacy</item>
|
||||
<item>$ssl_client_raw_cert</item>
|
||||
<item>$ssl_client_s_dn</item>
|
||||
<item>$ssl_client_s_dn_legacy</item>
|
||||
<item>$ssl_client_serial</item>
|
||||
<item>$ssl_client_v_end</item>
|
||||
<item>$ssl_client_v_remain</item>
|
||||
<item>$ssl_client_v_start</item>
|
||||
<item>$ssl_client_verify</item>
|
||||
<item>$ssl_curve</item>
|
||||
<item>$ssl_curves</item>
|
||||
<item>$ssl_early_data</item>
|
||||
<item>$ssl_preread_alpn_protocols</item>
|
||||
<item>$ssl_preread_protocol</item>
|
||||
<item>$ssl_preread_server_name</item>
|
||||
<item>$ssl_protocol</item>
|
||||
<item>$ssl_server_name</item>
|
||||
<item>$ssl_session_id</item>
|
||||
<item>$ssl_session_reused</item>
|
||||
<item>$status</item>
|
||||
<item>$tcpinfo_rcv_space</item>
|
||||
<item>$tcpinfo_rtt</item>
|
||||
<item>$tcpinfo_rttvar</item>
|
||||
<item>$tcpinfo_snd_cwnd</item>
|
||||
<item>$time_iso8601</item>
|
||||
<item>$time_local</item>
|
||||
<item>$uid_got</item>
|
||||
<item>$uid_reset</item>
|
||||
<item>$uid_set</item>
|
||||
<item>$upstream_addr</item>
|
||||
<item>$upstream_bytes_received</item>
|
||||
<item>$upstream_bytes_sent</item>
|
||||
<item>$upstream_cache_status</item>
|
||||
<item>$upstream_connect_time</item>
|
||||
<item>$upstream_first_byte_time</item>
|
||||
<item>$upstream_header_time</item>
|
||||
<item>$upstream_last_server_name</item>
|
||||
<item>$upstream_queue_time</item>
|
||||
<item>$upstream_response_length</item>
|
||||
<item>$upstream_response_time</item>
|
||||
<item>$upstream_session_time</item>
|
||||
<item>$upstream_status</item>
|
||||
<item>$uri</item>
|
||||
</list>
|
||||
|
||||
<list name="keywords">
|
||||
<item>on</item>
|
||||
<item>off</item>
|
||||
<item>ssl</item> <!-- listen ... ssl -->
|
||||
<item>all</item> <!-- deny all -->
|
||||
</list>
|
||||
|
||||
<contexts>
|
||||
<context attribute="Normal Text" lineEndContext="#stay" name="Normal">
|
||||
<DetectSpaces/>
|
||||
<keyword attribute="Directive" context="Params" String="directives"/>
|
||||
<DetectChar attribute="Symbol" context="#stay" char="}" endRegion="Block"/>
|
||||
<IncludeRules context="Variables"/>
|
||||
<DetectChar attribute="String" context="Strings" char="'" beginRegion="String"/>
|
||||
<DetectChar attribute="Comment" context="Comment" char="#"/>
|
||||
</context>
|
||||
|
||||
<context attribute="Normal Text" lineEndContext="#stay" name="Params">
|
||||
<DetectSpaces/>
|
||||
<DetectChar attribute="Symbol" context="#pop" char=";"/>
|
||||
<DetectChar attribute="Symbol" context="#pop" char="{" beginRegion="Block"/>
|
||||
<IncludeRules context="Variables"/>
|
||||
<DetectChar attribute="String" context="Strings" char="'" beginRegion="String"/>
|
||||
<RegExpr attribute="Number" context="#stay" String="\b[0-9]+&units;?\b"/>
|
||||
<keyword attribute="Keyword" context="#stay" String="keywords"/>
|
||||
<DetectChar attribute="Comment" context="Comment" char="#"/>
|
||||
</context>
|
||||
|
||||
<context attribute="Variable" lineEndContext="#pop" name="Variables">
|
||||
<keyword attribute="Variable" context="#stay" String="variables"/>
|
||||
<RegExpr attribute="Variable" String="\$[a-zA-Z_0-9]+\b" context="#stay"/>
|
||||
</context>
|
||||
|
||||
<context attribute="String" lineEndContext="#stay" name="Strings">
|
||||
<IncludeRules context="Variables"/>
|
||||
<DetectChar attribute="String" context="#pop" char="'" endRegion="String"/>
|
||||
</context>
|
||||
|
||||
<context name="Comment" attribute="Comment" lineEndContext="#pop">
|
||||
<DetectSpaces/>
|
||||
<IncludeRules context="##Comments"/>
|
||||
</context>
|
||||
</contexts>
|
||||
<itemDatas>
|
||||
<itemData name="Normal Text" defStyleNum="dsNormal" spellChecking="false"/>
|
||||
<itemData name="Directive" defStyleNum="dsDataType" spellChecking="false"/>
|
||||
<itemData name="Keyword" defStyleNum="dsKeyword" spellChecking="false"/>
|
||||
<itemData name="Comment" defStyleNum="dsComment"/>
|
||||
<itemData name="Variable" defStyleNum="dsVariable"/>
|
||||
<itemData name="Symbol" defStyleNum="dsOperator" spellChecking="false"/>
|
||||
<itemData name="String" defStyleNum="dsString"/>
|
||||
<itemData name="Number" defStyleNum="dsDecVal"/>
|
||||
</itemDatas>
|
||||
</highlighting>
|
||||
<general>
|
||||
<comments>
|
||||
<comment name="singleLine" start="#" position="afterwhitespace" />
|
||||
</comments>
|
||||
<keywords casesensitive="1" />
|
||||
</general>
|
||||
</language>
|
||||
<!-- kate: replace-tabs on; tab-width 2; indent-width 2; -->
|
||||
@@ -12,6 +12,7 @@ pandoc \
|
||||
--number-sections \
|
||||
--lua-filter _extensions/diagram/diagram.lua \
|
||||
--syntax-definition _extensions/razor/razor.xml \
|
||||
--syntax-definition _extensions/nginx/nginx.xml \
|
||||
--citeproc \
|
||||
--bibliography sources.bib \
|
||||
--csl _extensions/sources/tgm.csl \
|
||||
|
||||
@@ -249,3 +249,11 @@
|
||||
year = {2026},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
@online{oqtane_docs_rendermodes,
|
||||
title = {Oqtane Render Modes},
|
||||
url = {https://docs.oqtane.org/guides/concepts/render-modes/index.html},
|
||||
author = {{Oqtane Foundation}},
|
||||
year = {2024},
|
||||
urldate = {2026-03-19}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user