Przeglądaj źródła

Merge branch 'next' of github.com:OliveTin/OliveTin into next

jamesread 3 tygodni temu
rodzic
commit
1eb302fd8e

+ 2 - 0
.goreleaser.yml

@@ -72,6 +72,8 @@ archives:
   - formats: tar.gz
   - formats: tar.gz
     files:
     files:
       - config.yaml
       - config.yaml
+      - config.macos.yaml
+      - app.olivetin.olivetin.plist
       - LICENSE
       - LICENSE
       - README.md
       - README.md
       - src: Dockerfile.singlearch
       - src: Dockerfile.singlearch

+ 203 - 0
INSTALL-MACOS.md

@@ -0,0 +1,203 @@
+# Installing OliveTin on macOS
+
+OliveTin runs natively on macOS on both **Apple Silicon (M1/M2/M3/M4)** and
+**Intel** Macs. It is a single self-contained binary written in Go — there is no
+installer and no background dependencies to install.
+
+---
+
+## 1. Choose the right download
+
+macOS builds are published on the
+[GitHub releases page](https://github.com/OliveTin/OliveTin/releases). Pick the
+archive that matches your Mac's processor:
+
+| Your Mac | Archive |
+|---|---|
+| Apple Silicon (M-series) | `OliveTin-darwin-arm64.tar.gz` |
+| Intel | `OliveTin-darwin-amd64.tar.gz` |
+
+Not sure which you have? Run this in Terminal:
+
+```sh
+uname -m
+```
+
+`arm64` → Apple Silicon, `x86_64` → Intel.
+
+> If you download the wrong architecture, macOS will refuse to run it with a
+> "Bad CPU type in executable" error.
+
+---
+
+## 2. Extract and place the binary
+
+```sh
+# Move to your Downloads folder (adjust if needed)
+cd ~/Downloads
+
+# Extract — replace arm64 with amd64 on Intel
+tar -xzf OliveTin-darwin-arm64.tar.gz
+cd OliveTin-darwin-arm64
+```
+
+For a quick try-out you can run it straight from this folder. To install it
+system-wide, copy the binary somewhere on your `PATH`:
+
+```sh
+sudo cp OliveTin /usr/local/bin/OliveTin
+```
+
+---
+
+## 3. Clear the Gatekeeper quarantine
+
+Because the binary is downloaded from the internet and is **not notarized by
+Apple**, macOS Gatekeeper will block the first run with a message like
+*"OliveTin can't be opened because Apple cannot check it for malicious
+software."*
+
+Remove the quarantine attribute so it will run:
+
+```sh
+xattr -dr com.apple.quarantine ./OliveTin
+```
+
+Alternatively, the first time only, you can right-click the binary in Finder →
+**Open**, or approve it under **System Settings → Privacy & Security**.
+
+---
+
+## 4. Create a configuration file
+
+OliveTin looks for a file named `config.yaml` in its **config directory**, which
+defaults to the current directory (`.`). You can point elsewhere with
+`-configdir /path/to/dir`.
+
+A minimal `config.yaml` to confirm everything works:
+
+```yaml
+listenAddressSingleHTTPFrontend: 0.0.0.0:1337
+logLevel: "INFO"
+
+actions:
+  - title: Hello macOS
+    icon: terminal
+    shell: echo "Hello from $(scutil --get ComputerName)!"
+    popupOnStart: execution-dialog-stdout-only
+```
+
+For a fuller, macOS-tuned starting point — with working examples for
+notifications (`osascript`), `caffeinate`, `pmset`, disk usage, the unified
+system log, and Docker — see the **`macos.config.yaml`** that ships alongside
+this guide. Copy it in place with:
+
+```sh
+cp macos.config.yaml config.yaml
+```
+
+---
+
+## 5. Run OliveTin
+
+From the folder that contains both `OliveTin` and `config.yaml`:
+
+```sh
+./OliveTin
+```
+
+Or, if you installed it to `/usr/local/bin` and keep your config elsewhere:
+
+```sh
+OliveTin -configdir /usr/local/etc/OliveTin
+```
+
+Then open the web interface at:
+
+```
+http://localhost:1337
+```
+
+(or `http://<your-mac-hostname>:1337` from another device on your network).
+
+Press **Ctrl-C** in the Terminal to stop it.
+
+---
+
+## 6. Run OliveTin as a background service (launchd)
+
+On Linux, OliveTin is managed by **systemd**. The macOS equivalent is
+**launchd**. A ready-to-use service definition ships next to this guide as
+[`app.olivetin.olivetin.plist`](app.olivetin.olivetin.plist).
+
+You have two choices:
+
+* **LaunchAgent** (recommended for a desktop Mac) — runs as *your* user, starts
+  when you log in, no root required.
+* **LaunchDaemon** — runs as root, starts at boot before any user logs in. Use
+  this for a headless / always-on Mac.
+
+### As a LaunchAgent (per-user)
+
+```sh
+# Edit the two paths inside the plist to match your install first!
+cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
+
+# Start now and on every login
+launchctl load ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+```
+
+To stop and disable it:
+
+```sh
+launchctl unload ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+```
+
+### As a LaunchDaemon (system-wide, at boot)
+
+```sh
+sudo cp app.olivetin.olivetin.plist /Library/LaunchDaemons/
+sudo chown root:wheel /Library/LaunchDaemons/app.olivetin.olivetin.plist
+sudo launchctl load /Library/LaunchDaemons/app.olivetin.olivetin.plist
+```
+
+The plist sets `KeepAlive` (restart on crash, equivalent to systemd's
+`Restart=always`) and `RunAtLoad` (start immediately). It also writes logs to
+`/usr/local/var/log/olivetin.log`.
+
+---
+
+## Troubleshooting
+
+**"Bad CPU type in executable"** — you downloaded the wrong architecture. Get
+the `arm64` build for Apple Silicon, `amd64` for Intel (see step 1).
+
+**Gatekeeper still blocks it** — re-run the `xattr -dr com.apple.quarantine`
+command in step 3, or approve the app under **System Settings → Privacy &
+Security**.
+
+**It runs but the page won't load** — check that nothing else is using port
+1337 (`lsof -i :1337`), and that you're browsing to `http://` (not `https://`).
+
+**Reading the logs**
+
+* Running in Terminal: the log is printed directly to the window.
+* Running under launchd: tail the log file configured in the plist:
+
+  ```sh
+  tail -f /usr/local/var/log/olivetin.log
+  ```
+
+* You can raise detail by setting `logLevel: "DEBUG"` in `config.yaml`.
+
+**Still stuck?** Ask in the
+[OliveTin Discord](https://discord.gg/jhYWWpNJ3v) or open an issue on
+[GitHub](https://github.com/OliveTin/OliveTin/issues).
+
+---
+
+## Next steps
+
+* [Create your first action](https://docs.olivetin.app/action_execution/create_your_first.html)
+* [Configuration reference](https://docs.olivetin.app/)
+* [Security & authentication](https://docs.olivetin.app/security/local.html)

+ 45 - 0
app.olivetin.olivetin.plist

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<!--
+  launchd service definition for OliveTin on macOS.
+  This is the macOS equivalent of the Linux systemd unit (var/systemd/OliveTin.service).
+
+  As a per-user LaunchAgent (recommended, no root needed):
+      cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
+      launchctl load   ~/Library/LaunchAgents/app.olivetin.olivetin.plist   # start now + at login
+      launchctl unload ~/Library/LaunchAgents/app.olivetin.olivetin.plist   # stop + disable
+
+  As a system-wide LaunchDaemon (starts at boot, before login; needs root):
+      sudo cp app.olivetin.olivetin.plist /Library/LaunchDaemons/
+      sudo chown root:wheel /Library/LaunchDaemons/app.olivetin.olivetin.plist
+      sudo launchctl load /Library/LaunchDaemons/app.olivetin.olivetin.plist
+
+  Edit the two paths below to match where you installed the binary and config.
+-->
+<plist version="1.0">
+<dict>
+    <key>Label</key>
+    <string>app.olivetin.olivetin</string>
+
+    <!-- Path to the OliveTin binary, then flags. -configdir is the directory
+         that contains your config.yaml. -->
+    <key>ProgramArguments</key>
+    <array>
+        <string>/usr/local/bin/OliveTin</string>
+        <string>-configdir</string>
+        <string>/Users/YOUR_USER/etc/OliveTin</string>
+    </array>
+
+    <!-- systemd's Restart=always -->
+    <key>KeepAlive</key>
+    <true/>
+
+    <!-- Start at load (login for a LaunchAgent, boot for a LaunchDaemon) -->
+    <key>RunAtLoad</key>
+    <true/>
+
+<!-- For LaunchAgent (per-user): use /Users/YOUR_USER/Library/Logs/olivetin.log
+     For LaunchDaemon (system-wide): use /usr/local/var/log/olivetin.log -->
+    <key>StandardOutPath</key>
+    <string>/Users/YOUR_USER/Library/Logs/olivetin.log</string></dict>
+</plist>

+ 262 - 0
config.macos.yaml

@@ -0,0 +1,262 @@
+# =============================================================================
+# OliveTin example configuration — macOS (Apple Silicon & Intel)
+# =============================================================================
+#
+# This is a macOS-flavoured version of the stock `example.config.yaml`. Every
+# action that differs from the Linux example carries a `# Linux equivalent:`
+# comment so you can see exactly what was changed and why.
+#
+# All commands here are tested against macOS with /bin/zsh (the default login
+# shell since macOS Catalina) and also work under /bin/bash. OliveTin runs the
+# `shell:` string with `sh -c` by default, so these are written to be portable
+# POSIX/zsh/bash one-liners.
+#
+# To use this file, copy it next to the OliveTin binary as `config.yaml`:
+#   cp macos.config.yaml config.yaml
+#   ./OliveTin
+#
+# Docs: https://docs.olivetin.app/
+# =============================================================================
+
+# The built-in micro proxy hosts the WebUI and REST API on a single port.
+# Listen on all addresses, port 1337. Open http://localhost:1337 after start.
+listenAddressSingleHTTPFrontend: 0.0.0.0:1337
+
+# Choose from INFO (default), WARN and DEBUG.
+# Docs: https://docs.olivetin.app/advanced_configuration/logs.html
+logLevel: "INFO"
+
+# Actions are commands that OliveTin executes, normally shown as buttons.
+# Docs: https://docs.olivetin.app/action_execution/create_your_first.html
+actions:
+  # The simplest possible action: run a command, flash the button for status.
+  # `ping` works identically on macOS and Linux.
+  - title: Ping the Internet
+    shell: ping -c 3 1.1.1.1
+    icon: ping
+    popupOnStart: execution-dialog-stdout-only
+    execOnStartup: true
+
+  # Show just the command output in a popup.
+  #
+  # Linux equivalent: shell: df -h /media
+  # macOS has no /media mountpoint. The root volume is `/`; external/USB disks
+  # mount under /Volumes. `df -h /` shows the boot disk; drop the path to list
+  # every mounted volume.
+  - title: Check disk space
+    icon: disk
+    shell: df -h /
+    popupOnStart: execution-dialog-stdout-only
+
+  # Show a fuller dialog with details about the command that ran.
+  #
+  # Linux equivalent: shell: dmesg | tail
+  # macOS `dmesg` requires root and is rarely useful. The unified logging
+  # system is the macOS way to read kernel/system messages. This shows the
+  # last 2 minutes of high-level system log entries.
+  - title: Recent system log
+    shell: log show --last 2m --style compact | tail -n 40
+    icon: logs
+    popupOnStart: execution-dialog
+
+  # A mini button that links to the logs, with rate limiting and an hourly cron.
+  # `date` is identical across platforms.
+  - title: date
+    shell: date
+    id: date
+    timeout: 6
+    icon: clock
+    popupOnStart: execution-button
+    maxRate:
+      - limit: 3
+        duration: 1m
+    execOnCron:
+      - "@hourly"
+
+  # ---------------------------------------------------------------------------
+  # macOS-native actions (no Linux equivalent — these showcase the platform)
+  # ---------------------------------------------------------------------------
+
+  # Send a real macOS Notification Center banner via AppleScript.
+  - title: Send a notification
+    icon: '&#128276;'   # bell
+    shell: osascript -e 'display notification "Triggered from OliveTin" with title "OliveTin" sound name "Glass"'
+    popupOnStart: execution-button
+
+  # Stop the Mac from sleeping for 1 hour (handy during long jobs/downloads).
+  # `caffeinate` is a built-in macOS utility. `-t` is seconds.
+  - title: Keep awake for 1 hour
+    icon: '&#9749;'     # hot beverage
+    shell: caffeinate -d -i -t 3600 &
+    popupOnStart: execution-button
+
+  # Put the displays to sleep immediately (the Mac stays running).
+  - title: Sleep the displays
+    icon: '&#128164;'   # zzz
+    shell: pmset displaysleepnow
+    popupOnStart: execution-button
+
+  # Battery / power summary using the built-in `pmset`.
+  - title: Power & battery status
+    icon: '&#128267;'   # battery
+    shell: pmset -g batt
+    popupOnStart: execution-dialog-stdout-only
+
+  # ---------------------------------------------------------------------------
+
+  # Prompt the user for input with `arguments`. `ping` again works as-is.
+  # Docs: https://docs.olivetin.app/action_examples/ping.html
+  - title: Ping host
+    id: ping_host
+    shell: ping {{ host }} -c {{ count }}
+    icon: ping
+    timeout: 100
+    popupOnStart: execution-dialog-stdout-only
+    arguments:
+      - name: host
+        title: Host
+        type: ascii_identifier
+        default: example.com
+        description: The host that you want to ping
+
+      - name: count
+        title: Count
+        type: int
+        default: 3
+        description: How many times do you want to ping?
+
+  # OliveTin can control Docker containers — `docker` is just a CLI app.
+  # On macOS this requires Docker Desktop (or colima/podman) to be installed
+  # and running. The command itself is identical to Linux.
+  # Docs: https://docs.olivetin.app/solutions/container-control-panel/index.html
+  - title: Restart Docker Container
+    icon: restart
+    shell: docker restart {{ container }}
+    arguments:
+      - name: container
+        title: Container name
+        choices:
+          - value: plex
+          - value: traefik
+          - value: grafana
+
+  # The special `confirmation` argument guards against accidental clicks.
+  # Docs: https://docs.olivetin.app/args/input_confirmation.html
+  #
+  # Linux equivalent: shell: rm -rf /opt/oldBackups/
+  # Using a path under the user's home is more natural on macOS.
+  - title: Delete old backups
+    icon: ashtonished
+    shell: rm -rf "$HOME/Backups/old/"
+    arguments:
+      - name: confirm
+        type: confirmation
+        title: Are you sure?!
+
+  # Run your own scripts, not just OS commands. `maxConcurrent` prevents
+  # parallel runs; `timeout` kills a command that runs too long.
+  #
+  # Linux equivalent: shell: /opt/backupScript.sh
+  # macOS convention is to keep personal scripts under your home directory.
+  - title: Run backup script
+    shell: "$HOME/bin/backupScript.sh"
+    shellAfterCompleted: "osascript -e 'display notification \"Backup finished with code {{ exitCode }}\" with title \"OliveTin\"'"
+    maxConcurrent: 1
+    timeout: 10
+    icon: backup
+    popupOnStart: execution-dialog
+
+  # Download themes using a script bundled with OliveTin. You still need to set
+  # `themeName` in this config to actually use the theme.
+  # Docs: https://docs.olivetin.app/reference/reference_themes_for_users.html
+  - title: Get OliveTin Theme
+    exec:
+      - "olivetin-get-theme"
+      - "{{ themeGitRepo }}"
+      - "{{ themeFolderName }}"
+    icon: theme
+    arguments:
+      - name: themeGitRepo
+        title: Theme's Git Repository
+        description: Find new themes at https://olivetin.app/themes
+        type: url
+
+      - name: themeFolderName
+        title: Theme's Folder Name
+        type: ascii_identifier
+
+  # Run actions on other servers over SSH. macOS ships with an OpenSSH client,
+  # so this works out of the box. The helper below is optional.
+  # Docs: https://docs.olivetin.app/action_examples/ssh-easy.html
+  - title: "Setup easy SSH"
+    icon: ssh
+    shell: olivetin-setup-easy-ssh
+    popupOnStart: execution-dialog
+
+# Entities let you generate actions dynamically from "things" (servers,
+# containers, VMs) loaded from files on disk.
+# Docs: https://docs.olivetin.app/entities/intro.html
+#
+# entities:
+#   - file: entities/servers.yaml
+#     name: server
+
+# Dashboards organise actions into folders and fieldsets.
+# Docs: https://docs.olivetin.app/dashboards/intro.html
+#
+# dashboards:
+#   - title: My Mac
+#     contents:
+#       - title: Power & battery status
+#       - title: Sleep the displays
+
+# =============================================================================
+# Security - Authentication
+# =============================================================================
+
+# If "true", users must log in before doing anything.
+authRequireGuestsToLogin: false
+
+# The simplest auth: define users/passwords in this config. OliveTin also
+# supports header-based auth, OAuth2 and JWT (documented separately).
+# Docs: https://docs.olivetin.app/security/local.html
+#
+# Generating an argon2id hash on macOS:
+#   brew install argon2
+#   echo -n 'yourPassword' | argon2 "$(openssl rand -base64 16)" -id -e
+# (Linux equivalent typically uses the distro's `argon2` package directly.)
+authLocalUsers:
+  enabled: true
+#  users:
+#    - username: alice
+#      usergroup: admins
+#      password: "$argon2id$v=19$m=65536,t=4,p=2$puyxA0s555TSFx7hnFLCXA$PyhLGpZtvpMMvc2DgMWkM8OJMKO55euwV5gm//1iwx4"
+
+# =============================================================================
+# Security - Access Control
+# =============================================================================
+
+# Policies affect the whole app (eg: ability to view the log list).
+# Docs: https://docs.olivetin.app/security/acl.html
+defaultPolicy:
+  showDiagnostics: true
+  showLogList: true
+
+# Permissions affect individual actions.
+defaultPermissions:
+  view: true
+  exec: true
+  logs: true
+
+# ACLs match policy/permissions to users.
+accessControlLists:
+  - name: admin_acl
+    matchUsergroups: ["admins"]
+    policy:
+      showDiagnostics: true
+    permissions:
+      view: true
+      exec: true
+      logs: true
+
+# OliveTin has many more options not shown here. See docs.olivetin.app.

+ 1 - 0
docs/modules/ROOT/nav.adoc

@@ -18,6 +18,7 @@
 *** xref:install/windows.adoc[Windows]
 *** xref:install/windows.adoc[Windows]
 **** xref:install/windows_service.adoc[Windows Service]
 **** xref:install/windows_service.adoc[Windows Service]
 *** xref:install/macos.adoc[MacOS]
 *** xref:install/macos.adoc[MacOS]
+**** xref:install/macos_service.adoc[MacOS Service]
 *** xref:install/choose_package.adoc[All download options]
 *** xref:install/choose_package.adoc[All download options]
 * Upgrade Guide
 * Upgrade Guide
 ** xref:upgrade/2k3k.adoc[Understanding 2k vs 3k]
 ** xref:upgrade/2k3k.adoc[Understanding 2k vs 3k]

+ 47 - 4
docs/modules/ROOT/pages/install/macos.adoc

@@ -1,8 +1,51 @@
-= MacOS
+= macOS
 
 
-Sorry that these instructions are so primitive, the main developer of OliveTin's MacOS machine died :-( If you can help with instructions and screenshots that would be great.
+OliveTin runs natively on macOS, on both Apple Silicon (M-series) and Intel Macs. It is a single, self-contained binary - there is no installer and no dependencies to set up.
 
 
-. There is a .tar.gz that you can download, extract. Link: link:https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin-darwin-amd64.tar.gz[`OliveTin-darwin-amd64.tar.gz`]
-. Start a terminal and CD into the OliveTin directory.
+If you want OliveTin to run in the background and start automatically, follow the xref:install/macos_service.adoc[install OliveTin as a launchd service] instructions instead.
+
+== Download
+
+macOS builds are published on the link:https://github.com/OliveTin/OliveTin/releases/latest[releases page]. Choose the archive that matches your Mac's processor:
+
+[cols="1,1"]
+|===
+| Your Mac | Archive
+
+| Apple Silicon (M1/M2/M3/M4)
+| link:https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin-darwin-arm64.tar.gz[`OliveTin-darwin-arm64.tar.gz`]
+
+| Intel
+| link:https://github.com/OliveTin/OliveTin/releases/latest/download/OliveTin-darwin-amd64.tar.gz[`OliveTin-darwin-amd64.tar.gz`]
+|===
+
+Not sure which you have? Run `uname -m` in Terminal - `arm64` means Apple Silicon, `x86_64` means Intel.
+
+[NOTE]
+If you run the wrong architecture, macOS reports `Bad CPU type in executable`. Download the other archive if you see this.
+
+== Extract
+
+Start a terminal, then extract the archive and change into the directory (replace `arm64` with `amd64` on Intel):
+
+[source,shell]
+----
+tar -xzf OliveTin-darwin-arm64.tar.gz
+cd OliveTin-darwin-arm64
+----
+
+== Remove the Gatekeeper quarantine
+
+The binary is downloaded from the internet and is not notarized by Apple, so on first run Gatekeeper blocks it with a message like _"OliveTin can't be opened because Apple cannot check it for malicious software."_
+
+Clear the quarantine attribute so it will run:
+
+[source,shell]
+----
+xattr -dr com.apple.quarantine ./OliveTin
+----
+
+[TIP]
+Alternatively, the first time only, right-click the binary in Finder and choose *Open*, or approve it under *System Settings -> Privacy & Security*.
 
 
 include::partial$install/post_generic.adoc[]
 include::partial$install/post_generic.adoc[]

+ 109 - 0
docs/modules/ROOT/pages/install/macos_service.adoc

@@ -0,0 +1,109 @@
+= macOS launchd service install
+
+This option installs OliveTin as a launchd service, so it runs in the background and starts automatically. This is the macOS equivalent of running OliveTin as a Linux systemd service or a xref:install/windows_service.adoc[Windows service]. If you just want to run OliveTin as a regular application, follow the xref:install/macos.adoc[macOS install] instructions instead.
+
+Before continuing, complete the xref:install/macos.adoc[macOS install] steps (download, extract, and clear the Gatekeeper quarantine) and confirm OliveTin starts correctly by running `./OliveTin`.
+
+== Install the files
+
+Copy the binary somewhere on your `PATH`, and put your configuration in a dedicated directory:
+
+[source,shell]
+----
+sudo cp OliveTin /usr/local/bin/OliveTin
+
+sudo mkdir -p /usr/local/etc/OliveTin
+sudo cp config.yaml /usr/local/etc/OliveTin/config.yaml
+----
+
+[NOTE]
+OliveTin looks for `config.yaml` in the directory given by the `-configdir` flag, which defaults to the current directory. The service definition below passes `-configdir /usr/local/etc/OliveTin` explicitly.
+
+== Choose LaunchAgent or LaunchDaemon
+
+launchd offers two ways to run a background service:
+
+* *LaunchAgent* - runs as your user and starts when you log in. No root required. Best for a desktop Mac.
+* *LaunchDaemon* - runs as root and starts at boot, before any user logs in. Best for a headless, always-on Mac.
+
+== Create the service definition
+
+Create a file named `app.olivetin.olivetin.plist` with the following contents. Adjust the two paths if you installed OliveTin elsewhere.
+
+[source,xml]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>Label</key>
+    <string>app.olivetin.olivetin</string>
+
+    <key>ProgramArguments</key>
+    <array>
+        <string>/usr/local/bin/OliveTin</string>
+        <string>-configdir</string>
+        <string>/usr/local/etc/OliveTin</string>
+    </array>
+
+    <key>KeepAlive</key>
+    <true/>
+
+    <key>RunAtLoad</key>
+    <true/>
+
+    <key>StandardOutPath</key>
+    <string>/usr/local/var/log/olivetin.log</string>
+    <key>StandardErrorPath</key>
+    <string>/usr/local/var/log/olivetin.log</string>
+</dict>
+</plist>
+----
+
+`KeepAlive` restarts OliveTin if it exits (like systemd's `Restart=always`), and `RunAtLoad` starts it as soon as the service is loaded.
+
+== Register and start the service
+
+=== As a LaunchAgent (per-user)
+
+[source,shell]
+----
+mkdir -p /usr/local/var/log
+cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
+launchctl load ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+----
+
+To stop and disable it:
+
+[source,shell]
+----
+launchctl unload ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+----
+
+=== As a LaunchDaemon (system-wide, at boot)
+
+[source,shell]
+----
+sudo mkdir -p /usr/local/var/log
+sudo cp app.olivetin.olivetin.plist /Library/LaunchDaemons/
+sudo chown root:wheel /Library/LaunchDaemons/app.olivetin.olivetin.plist
+sudo launchctl load /Library/LaunchDaemons/app.olivetin.olivetin.plist
+----
+
+To stop and disable it:
+
+[source,shell]
+----
+sudo launchctl unload /Library/LaunchDaemons/app.olivetin.olivetin.plist
+----
+
+== Verify
+
+Open http://localhost:1337 in a browser. If the page does not load, check the service log:
+
+[source,shell]
+----
+tail -f /usr/local/var/log/olivetin.log
+----
+
+include::partial$install/post_generic.adoc[]