jamesread 16 часов назад
Родитель
Сommit
688022b450

+ 6 - 0
.github/workflows/docs-antora.yml

@@ -30,6 +30,12 @@ jobs:
       - name: Install Antora toolchain
         run: npm i antora@3.1.14 asciidoctor-kroki@0.18.1 @asciidoctor/tabs@1.0.0-beta.6
 
+      - name: Check docs config key casing
+        run: python3 docs/modules/ROOT/check_config_keys.py
+
+      - name: Check docs chevron links
+        run: python3 docs/modules/ROOT/check_chevron_links.py
+
       - name: Generate docs site (smoke)
         run: npx antora local-antora-playbook-ci.yml --log-level info
 

+ 1 - 0
docs/modules/ROOT/.gitignore

@@ -0,0 +1 @@
+__pycache__

+ 410 - 0
docs/modules/ROOT/check_config_keys.py

@@ -0,0 +1,410 @@
+#!/usr/bin/env python3
+
+# Find config option names in docs that do not match YAML keys from config.go.
+#
+# OliveTin config uses camelCase koanf tags (see service/internal/config/config.go).
+# Docs sometimes use Go struct field names (PascalCase) or other wrong spellings.
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+import re
+import sys
+
+ROOT = Path(__file__).resolve().parent
+REPO_ROOT = ROOT.parents[2]
+CONFIG_GO = REPO_ROOT / "service/internal/config/config.go"
+DOC_DIRS = (ROOT / "pages", ROOT / "partials")
+
+STRUCT_START_RE = re.compile(r"^type (\w+) struct\b")
+FIELD_RE = re.compile(
+    r'^\s+(\w+)\s+([^`]+?)`koanf:"([^"]+)"`',
+)
+BACKTICK_RE = re.compile(r"`([^`]+)`")
+YAML_BLOCK_RE = re.compile(
+    r"\[source,yaml\][^\n]*\n----\n(.*?)\n----",
+    re.DOTALL,
+)
+YAML_KEY_RE = re.compile(r"^(\s*)([A-Za-z][\w]*)\s*:", re.MULTILINE)
+
+SKIP_BACKTICK = frozenset({
+    "Insecure*",
+})
+
+SKIP_YAML_PREFIXES = frozenset({
+    "actions",
+    "dashboards",
+    "entities",
+    "title",
+    "shell",
+    "icon",
+    "arguments",
+    "name",
+    "type",
+    "default",
+    "description",
+    "choices",
+    "value",
+    "permissions",
+    "view",
+    "exec",
+    "logs",
+    "kill",
+    "matchUsergroups",
+    "matchUsernames",
+    "policy",
+    "users",
+    "username",
+    "password",
+    "usergroup",
+    "enabled",
+    "acls",
+    "groups",
+    "maxConcurrent",
+    "timeout",
+    "onclick",
+    "execOnStartup",
+    "maxRate",
+    "limit",
+    "duration",
+    "id",
+    "hidden",
+    "category",
+    "contents",
+    "file",
+    "properties",
+    "inlineAction",
+    "resultsDirectory",
+    "outputDirectory",
+    "directory",
+    "showDiagnostics",
+    "showLogList",
+    "showVersionNumber",
+    "headerSearch",
+    "defaultGoMetrics",
+    "contentSecurityPolicy",
+    "xFrameOptions",
+    "headerContentSecurityPolicy",
+    "headerXContentTypeOptions",
+    "headerXFrameOptions",
+    "forceSecureCookies",
+    "clientId",
+    "clientSecret",
+    "authUrl",
+    "tokenUrl",
+    "whoamiUrl",
+    "scopes",
+    "addToUsergroup",
+    "userGroupField",
+    "usernameField",
+    "certBundlePath",
+    "callbackTimeout",
+    "insecureSkipVerify",
+    "secret",
+    "authType",
+    "authHeader",
+    "matchHeaders",
+    "matchPath",
+    "matchQuery",
+    "extract",
+    "template",
+    "justification",
+    "apiKey",
+    "addToEveryAction",
+    "execOnCron",
+    "execOnCalendarFile",
+    "shellAfterCompleted",
+    "execOnWebhook",
+    "triggers",
+    "exec",
+    "execOnFileCreatedInDir",
+    "execOnFileChangedInDir",
+    "entity",
+    "popupOnStart",
+    "saveLogs",
+    "suggestions",
+    "suggestionsBrowserKey",
+    "rejectNull",
+    "queueSize",
+    "cssClass",
+    "url",
+    "target",
+    "styleMods",
+    "include",
+    "bannerCss",
+    "bannerMessage",
+    "serviceHostMode",
+    "themeCacheDisabled",
+    "checkForUpdates",
+    "logHistoryPageSize",
+    "additionalNavigationLinks",
+    "actionGroups",
+    "authOAuth2Providers",
+    "authOAuth2RedirectUrl",
+    "authJwtHmacSecret",
+})
+
+
+@dataclass(frozen=True)
+class Issue:
+    path: str
+    line: int
+    found: str
+    expected: str
+    kind: str
+
+
+def parse_structs(content: str) -> dict[str, list[tuple[str, str, str]]]:
+    structs: dict[str, list[tuple[str, str, str]]] = {}
+    current: str | None = None
+
+    for line in content.splitlines():
+        struct_match = STRUCT_START_RE.match(line)
+        if struct_match:
+            current = struct_match.group(1)
+            structs[current] = []
+            continue
+
+        if current is None:
+            continue
+
+        if line.strip() == "}":
+            current = None
+            continue
+
+        field_match = FIELD_RE.match(line)
+        if not field_match:
+            continue
+
+        field_name, field_type, koanf_tag = field_match.groups()
+        if koanf_tag == "-":
+            continue
+
+        structs[current].append((field_name, field_type.strip(), koanf_tag.strip()))
+
+    return structs
+
+
+def is_nested_struct(field_type: str, structs: dict[str, list[tuple[str, str, str]]]) -> str | None:
+    inner = field_type.removeprefix("[]").removeprefix("*").strip()
+    if inner in structs and inner not in {
+        "Action",
+        "EntityFile",
+        "AccessControlList",
+        "DashboardComponent",
+        "NavigationLink",
+        "OAuth2Provider",
+        "LocalUser",
+        "ActionArgument",
+        "ActionArgumentChoice",
+        "RateSpec",
+        "WebhookConfig",
+        "EntityProperty",
+        "ActionGroup",
+    }:
+        return inner
+    return None
+
+
+def collect_config_keys(
+    structs: dict[str, list[tuple[str, str, str]]],
+) -> tuple[frozenset[str], dict[str, str]]:
+    valid: set[str] = set()
+    aliases: dict[str, str] = {}
+
+    def walk(type_name: str, prefix: str = "") -> None:
+        for field_name, _field_type, koanf_tag in structs.get(type_name, []):
+            path = f"{prefix}.{koanf_tag}" if prefix else koanf_tag
+            valid.add(path)
+
+            if field_name != koanf_tag:
+                aliases[field_name] = koanf_tag
+                if prefix:
+                    aliases[f"{prefix}.{field_name}"] = path
+
+            nested = is_nested_struct(_field_type, structs)
+            if nested:
+                walk(nested, path)
+
+    walk("Config")
+    return frozenset(valid), aliases
+
+
+def camelize_path(key: str) -> str:
+    parts = []
+    for part in key.split("."):
+        if part and part[0].isupper():
+            parts.append(part[0].lower() + part[1:])
+        else:
+            parts.append(part)
+    return ".".join(parts)
+
+
+def looks_like_config_key(key: str) -> bool:
+    if not key or key in SKIP_BACKTICK:
+        return False
+    if "*" in key or " " in key or "/" in key or ":" in key:
+        return False
+    return bool(re.fullmatch(r"[A-Za-z][\w.]*", key))
+
+
+def has_internal_uppercase(key: str) -> bool:
+    if "." in key:
+        return any(has_internal_uppercase(part) for part in key.split("."))
+    return any(char.isupper() for char in key[1:])
+
+
+def case_insensitive_match(key: str, valid: frozenset[str]) -> str | None:
+    matches = [candidate for candidate in valid if candidate.lower() == key.lower()]
+    if len(matches) == 1:
+        return matches[0]
+    return None
+
+
+def resolve_key(
+    key: str,
+    valid: frozenset[str],
+    aliases: dict[str, str],
+    *,
+    allow_case_insensitive: bool = False,
+) -> str | None:
+    if key in valid:
+        return None
+
+    if key in aliases and key != aliases[key]:
+        return aliases[key]
+
+    if allow_case_insensitive:
+        matched = case_insensitive_match(key, valid)
+        if matched is not None and matched != key:
+            return matched
+
+    if not has_internal_uppercase(key):
+        return None
+
+    camelized = camelize_path(key)
+    if camelized in valid and key != camelized:
+        return camelized
+
+    return None
+
+
+def scan_backticks(
+    rel_path: str,
+    content: str,
+    valid: frozenset[str],
+    aliases: dict[str, str],
+) -> list[Issue]:
+    issues: list[Issue] = []
+
+    for line_number, line in enumerate(content.splitlines(), start=1):
+        for match in BACKTICK_RE.finditer(line):
+            key = match.group(1).strip()
+            if not looks_like_config_key(key):
+                continue
+
+            expected = resolve_key(key, valid, aliases)
+            if expected is None:
+                continue
+
+            # Backticks often label UI sections that share a name with config keys.
+            if expected == "actions" and key == "Actions":
+                continue
+
+            issues.append(
+                Issue(
+                    path=rel_path,
+                    line=line_number,
+                    found=key,
+                    expected=expected,
+                    kind="backtick",
+                )
+            )
+
+    return issues
+
+
+def scan_yaml_blocks(
+    rel_path: str,
+    content: str,
+    valid: frozenset[str],
+    aliases: dict[str, str],
+) -> list[Issue]:
+    issues: list[Issue] = []
+
+    for block in YAML_BLOCK_RE.finditer(content):
+        block_text = block.group(1)
+        block_start = block.start(1)
+
+        for match in YAML_KEY_RE.finditer(block_text):
+            indent = len(match.group(1).replace("\t", "    "))
+            key = match.group(2)
+
+            if indent != 0:
+                continue
+            if key in SKIP_YAML_PREFIXES:
+                continue
+            if key in valid:
+                continue
+
+            expected = resolve_key(
+                key,
+                valid,
+                aliases,
+                allow_case_insensitive=True,
+            )
+            if expected is None:
+                continue
+
+            issues.append(
+                Issue(
+                    path=rel_path,
+                    line=content.count("\n", 0, block_start + match.start()) + 1,
+                    found=key,
+                    expected=expected,
+                    kind="yaml",
+                )
+            )
+
+    return issues
+
+
+def iter_doc_files() -> list[Path]:
+    files: list[Path] = []
+    for doc_dir in DOC_DIRS:
+        files.extend(sorted(doc_dir.rglob("*.adoc")))
+    return files
+
+
+def main() -> int:
+    if not CONFIG_GO.is_file():
+        print(f"config.go not found: {CONFIG_GO}", file=sys.stderr)
+        return 2
+
+    structs = parse_structs(CONFIG_GO.read_text())
+    valid, aliases = collect_config_keys(structs)
+
+    issues: list[Issue] = []
+    for doc_path in iter_doc_files():
+        content = doc_path.read_text()
+        rel_path = str(doc_path.relative_to(ROOT))
+        issues.extend(scan_backticks(rel_path, content, valid, aliases))
+        issues.extend(scan_yaml_blocks(rel_path, content, valid, aliases))
+
+    if not issues:
+        print("No config key casing issues found.")
+        return 0
+
+    print(f"Issues: {len(issues)}")
+    for issue in issues:
+        print(
+            f"{issue.path}:{issue.line}: [{issue.kind}] "
+            f"`{issue.found}` should be `{issue.expected}`"
+        )
+
+    return 1
+
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 3 - 3
docs/modules/ROOT/pages/advanced_configuration/stylemods.adoc

@@ -4,9 +4,9 @@ There are several style modifications that some people like to use, which can ea
 
 [source,yaml]
 ----
-stylemods:
+styleMods:
   - sm-side-icons
-----	
+----
 
 You can add as many style mods as you like, but note that some of them may conflict with each other. The style mods are applied in the order they are listed, so if you have a conflict, the last one in the list will take precedence.
 
@@ -19,7 +19,7 @@ You can add as many style mods as you like, but note that some of them may confl
 
 NOTE: The `sm-transparent-header` and `sm-transparent-footer` style mods often fix themes that were designed for OliveTin 2k.
 
-The list of style mods on this page is maintained as new style modifications are added to OliveTin. 
+The list of style mods on this page is maintained as new style modifications are added to OliveTin.
 
 == Feature history
 

+ 30 - 29
docs/modules/ROOT/pages/config.adoc

@@ -8,6 +8,8 @@ file in the following locations;
 2. `/config/` - Mostly used for containers
 3. `/etc/OliveTin/` - this is the recommended directory on Linux for your `config.yaml`.
 
+For a complete annotated example, see the https://github.com/OliveTin/OliveTin/blob/main/config.yaml[`config.yaml` in the OliveTin repository].
+
 The most simple `config.yaml` would be something like this;
 
 .The most simple `config.yaml` file.
@@ -68,21 +70,21 @@ All configuration options are covered in the solution sections
 |===
 | Option | Description | Default | Live Reloadable | Documentation
 
-| `AuthJwtCookieName` | The name of the cookie to use for JWT authentication. | `` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthJwtAud` | The audience to use for JWT authentication. | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthJwtDomain` | The domain to use for JWT authentication. | `` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthJwtCertsURL` | The URL to fetch the public keys from with JWKS | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthJwtClaimUsername` | The claim to use for the username. | `sub` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthJwtClaimUserGroup` | The claim to use for the usergroup. | `sub` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthJwtHeader` | The HTTP header to use for JWT authentication. | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthJwtPubKeyPath` | The path to the public key to use for JWT authentication. | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
-| `AuthHttpHeaderUsername` | The HTTP header to use for the username. | `` | Requires restart | xref:security/trusted_header.adoc[Trusted Headers]
-| `AuthHttpHeaderUserGroup` | The HTTP header to use for the usergroup. | `` | Requires restart | xref:security/trusted_header.adoc[Trusted Headers]
-| `AuthLocalUsers` | The list of local users. | `[]` | Requires restart | xref:security/local.adoc[Local Users]
-| `AuthLoginUrl` | The URL to redirect to for login. | `` | Requires restart | xref:security/local.adoc[Login URL]
-| `AuthRequireGuestsToLogin` | Basically disables all functionality for guests. It sets all default permissions to false. | `false` | Requires restart | xref:security/acl.adoc[Access Control Lists]
-| `DefaultPermissions` | The default permissions to use. | `[]` | Requires restart | xref:security/acl.adoc[Access Control Lists]
-| `AccessControlLists` | The list of access control lists. | `[]` | Requires restart | xref:security/acl.adoc[Access Control Lists]
+| `authJwtCookieName` | The name of the cookie to use for JWT authentication. | `` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
+| `authJwtAud` | The audience to use for JWT authentication. | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
+| `authJwtDomain` | The domain to use for JWT authentication. | `` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
+| `authJwtCertsUrl` | The URL to fetch the public keys from with JWKS | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
+| `authJwtClaimUsername` | The claim to use for the username. | `sub` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
+| `authJwtClaimUserGroup` | The claim to use for the usergroup. | `sub` | Requires restart | xref:security/jwt_hmac.adoc[JWT with HMAC], xref:security/jwt_keys.adoc[JWT with Keys]
+| `authJwtHeader` | The HTTP header to use for JWT authentication. | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
+| `authJwtPubKeyPath` | The path to the public key to use for JWT authentication. | `` | Requires restart | xref:security/jwt_keys.adoc[JWT with Keys]
+| `authHttpHeaderUsername` | The HTTP header to use for the username. | `` | Requires restart | xref:security/trusted_header.adoc[Trusted Headers]
+| `authHttpHeaderUserGroup` | The HTTP header to use for the usergroup. | `` | Requires restart | xref:security/trusted_header.adoc[Trusted Headers]
+| `authLocalUsers` | The list of local users. | `[]` | Requires restart | xref:security/local.adoc[Local Users]
+| `authLoginUrl` | The URL to redirect to for login. | `` | Requires restart | xref:security/local.adoc[Login URL]
+| `authRequireGuestsToLogin` | Basically disables all functionality for guests. It sets all default permissions to false. | `false` | Requires restart | xref:security/acl.adoc[Access Control Lists]
+| `defaultPermissions` | The default permissions to use. | `[]` | Requires restart | xref:security/acl.adoc[Access Control Lists]
+| `accessControlLists` | The list of access control lists. | `[]` | Requires restart | xref:security/acl.adoc[Access Control Lists]
 | `security.headerContentSecurityPolicy` | Whether to send a `Content-Security-Policy` header from the single HTTP frontend. | `true` | Live reloadable | xref:security/content_security_policy.adoc[Content Security Policy headers]
 | `security.contentSecurityPolicy` | CSP header value when `security.headerContentSecurityPolicy` is enabled. If empty, a built-in default is used. | (built-in default) | Live reloadable | xref:security/content_security_policy.adoc[Content Security Policy headers]
 |===
@@ -92,13 +94,12 @@ All configuration options are covered in the solution sections
 |===
 | Option | Description | Default | Live Reloadable | Documentation
 
-| `UseSingleHttpFrontend` | Whether or not to start the internal "microproxy" frontend. Disabling this is highly unusual and is only really useful for power users.  | true | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
-| `ListenAddressSingleHTTPFrontend` | The address to listen on for the internal "microproxy" frontend. | `0.0.0.0:1337` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
-| `ListenAddressWebUI` | The address to listen on for the web UI. | `localhost:1340` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
-| `ListenAddressRestActions` | The address for the API | `localhost:1338` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
-| `ListenAddressGrpcActions` | The address for the gRPC API | `localhost:1339` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
-| `ListenAddressPrometheus` | The address for the Prometheus metrics | `localhost:1341` | Requires Restart | xref:reference/network-ports.adoc[Network Ports], xref:advanced_configuration/prometheus.adoc[Prometheus]
-| `ExternalRestAddress` | The address the web browser should use to connect to the API. | `.` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
+| `useSingleHTTPFrontend` | Whether or not to start the internal "microproxy" frontend. Disabling this is highly unusual and is only really useful for power users.  | true | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
+| `listenAddressSingleHTTPFrontend` | The address to listen on for the internal "microproxy" frontend. | `0.0.0.0:1337` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
+| `listenAddressWebUI` | The address to listen on for the web UI. | `localhost:1340` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
+| `listenAddressRestActions` | The address for the API | `localhost:1338` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
+| `listenAddressPrometheus` | The address for the Prometheus metrics | `localhost:1341` | Requires Restart | xref:reference/network-ports.adoc[Network Ports], xref:advanced_configuration/prometheus.adoc[Prometheus]
+| `externalRestAddress` | The address the web browser should use to connect to the API. | `.` | Requires Restart | xref:reference/network-ports.adoc[Network Ports]
 |===
 
 == Debugging Configuration
@@ -106,8 +107,8 @@ All configuration options are covered in the solution sections
 |===
 | Option | Description | Default | Live Reloadable | Documentation
 
-| `LogLevel` | The log level to use. `INFO`, `DEBUG`, `WARN` | `INFO` | Requires Restart | -
-| `LogDebugOptions` | Enable various debug logs. | `-` | Requires Restart | xref:troubleshooting/advanced.adoc[Advanced Troubleshooting]
+| `logLevel` | The log level to use. `INFO`, `DEBUG`, `WARN` | `INFO` | Requires Restart | -
+| `logDebugOptions` | Enable various debug logs. | `-` | Requires Restart | xref:troubleshooting/advanced.adoc[Advanced Troubleshooting]
 | `Insecure*` | Various options to disable security features. | `false` | Restart recommended | xref:troubleshooting/advanced.adoc[Advanced Troubleshooting]
 |===
 
@@ -116,11 +117,11 @@ All configuration options are covered in the solution sections
 |===
 | Option | Description | Default | Live Reloadable | Documentation
 
-| `WebUIDir` | The directory to serve the web UI from. | Calculated at runtime. | Requires Restart | -
-| `CronSupportForSeconds` | Whether or not to support seconds in cron expressions. | `false` | Requires Restart | xref:action_execution/oncron.adoc[Cron]
-| `SaveLogs` | Whether or not to save logs to disk. | `[]` | Requires Restart | xref:logs/saving.adoc[Save Logs]
-| `ServiceLogs` | Windows process log directory (`serviceLogs.directory`). | `%ProgramData%\OliveTin\logs\` on Windows | Requires Restart | xref:install/windows_service.adoc#windows-service-logs[Windows service logs]
-| `Prometheus` | Prometheus configuration. | `-` | Requires Restart | xref:advanced_configuration/prometheus.adoc[Prometheus]
+| `webUIDir` | The directory to serve the web UI from. | Calculated at runtime. | Requires Restart | -
+| `cronSupportForSeconds` | Whether or not to support seconds in cron expressions. | `false` | Requires Restart | xref:action_execution/oncron.adoc[Cron]
+| `saveLogs` | Whether or not to save logs to disk. | `[]` | Requires Restart | xref:logs/saving.adoc[Save Logs]
+| `serviceLogs` | Windows process log directory (`serviceLogs.directory`). | `%ProgramData%\OliveTin\logs\` on Windows | Requires Restart | xref:install/windows_service.adoc#windows-service-logs[Windows service logs]
+| `prometheus` | Prometheus configuration. | `-` | Requires Restart | xref:advanced_configuration/prometheus.adoc[Prometheus]
 |===
 
 == What's Next?

+ 3 - 3
docs/modules/ROOT/pages/security/example_login_required.adoc

@@ -40,11 +40,11 @@ dashboards:
 
 Note, to use this configuration, you will need to replace `-- your password hash here --` with a password hash. You can generate a password hash by looking at the options in the xref:security/local.adoc[local-users] configuration section.
 
-== Important configuration option: `AuthRequireGuestsToLogin`
+== Important configuration option: `authRequireGuestsToLogin`
 
-The `AuthRequireGuestsToLogin` option is a helpful shortcut that sets all `defaultPermissions` to false, and makes it so that all guests are prompted to login before they can do anything with OliveTin.
+The `authRequireGuestsToLogin` option is a helpful shortcut that sets all `defaultPermissions` to false, and makes it so that all guests are prompted to login before they can do anything with OliveTin.
 
-Technically, you could achieve the same effect by setting `defaultPermissions` to `false` and setting up an ACL that allows access to the login page, but `AuthRequireGuestsToLogin` is a more convenient way to achieve the same effect.
+Technically, you could achieve the same effect by setting `defaultPermissions` to `false` and setting up an ACL that allows access to the login page, but `authRequireGuestsToLogin` is a more convenient way to achieve the same effect.
 
 == Per-action ACLs, vs `addToEveryAction`
 

+ 2 - 2
docs/modules/ROOT/pages/security/oauth2_authelia.adoc

@@ -14,7 +14,7 @@ identity_providers:
         algorithm: "RS256"
         use: "sig"
         key: |
-          -----BEGIN PRIVATE KEY-----
+          -----BEGIN PRIVATE KEY----- #notsecret
           xxxxxxxxxxxxxxxxxxxxxxxxxx
           -----END PRIVATE KEY-----
 
@@ -46,7 +46,7 @@ Digest: $pbkdf2-sha512$310000$yQogpMZvkHoAmOBGiIHVJQ$hxKuvar6Q6pOlkdzQBMWq1i5WjX
 [source,yaml]
 ----
 authRequireGuestsToLogin: true
-authOAuth2RedirectURL: https://olivetin.hostname.com/oauth/callback
+authOAuth2RedirectUrl: https://olivetin.hostname.com/oauth/callback
 authOAuth2Providers:
   authelia:
     name: authelia

+ 1 - 1
docs/modules/ROOT/pages/security/oauth2_authentik.adoc

@@ -92,7 +92,7 @@ return {
 ----
 
 [IMPORTANT]
-If you use this multiple group mapping, you will need to set the `AuthHttpHeaderUserGroupSep` field to `,`. This may sound like a strangely named field, but it is the correct one to use for this mapping. It was originally created for the HTTP Trusted Header authentication method, but it is also used for OAuth2 group mapping.
+If you use this multiple group mapping, you will need to set the `authHttpHeaderUserGroupSep` field to `,`. This may sound like a strangely named field, but it is the correct one to use for this mapping. It was originally created for the HTTP Trusted Header authentication method, but it is also used for OAuth2 group mapping.
 
 === Single group mapping: Specific Group Match
 

+ 4 - 4
docs/modules/ROOT/pages/security/trusted_header.adoc

@@ -17,18 +17,18 @@ To configure Trusted Header Authorization, set the following configuration optio
 .`config.yaml`
 ----
 authHttpHeaderUsername: "X-Username"
-authHttpHeaderUsergroup: "X-Usergroup"
+authHttpHeaderUserGroup: "X-Usergroup"
 ----
 
 The value of `X-Username` and `X-Usergroup` can be whatever you like, as long as they match the headers set by your reverse proxy.
 
-NOTE: You *must* set `AuthHttpHeaderUsername` to some value, even if you only intend to use `AuthHttpHeaderUsergroup`, otherwise usergroups will be ignored.
+NOTE: You *must* set `authHttpHeaderUsername` to some value, even if you only intend to use `authHttpHeaderUserGroup`, otherwise usergroups will be ignored.
 
 == Multiple usergroups
 
-OliveTin will automatically detect multiple usergroups in the `authHttpHeaderUsergroup` header if they are separated by a space. You can also set a configuration option to use a different separator string with `authHttpHeaderUsergroupSep`. For example, if you set `authHttpHeaderUsergroupSep` to `,`, then the header `X-Usergroup: group1,group2` will be interpreted as two usergroups: `group1` and `group2`. 
+OliveTin will automatically detect multiple usergroups in the `authHttpHeaderUserGroup` header if they are separated by a space. You can also set a configuration option to use a different separator string with `authHttpHeaderUserGroupSep`. For example, if you set `authHttpHeaderUserGroupSep` to `,`, then the header `X-Usergroup: group1,group2` will be interpreted as two usergroups: `group1` and `group2`.
 
 [source, yaml]
 ----
-authHttpHeaderUsergroupSep: ","
+authHttpHeaderUserGroupSep: ","
 ----