diff --git a/.goreleaser.yml b/.goreleaser.yml
index 1d02b30..54e3545 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -79,7 +79,7 @@ archives:
- src: Dockerfile.singlearch
dst: Dockerfile
- webui
- - ./var/
+ - var
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}{{ .Arm }}"
wrap_in_directory: true
format_overrides:
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 39a0676..dc13185 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -17,8 +17,8 @@
*** xref:install/bsd.adoc[BSD]
*** xref:install/windows.adoc[Windows]
**** xref:install/windows_service.adoc[Windows Service]
-*** xref:install/macos.adoc[MacOS]
-**** xref:install/macos_service.adoc[MacOS Service]
+*** xref:install/macos.adoc[macOS Desktop]
+*** xref:install/macos_service.adoc[macOS Service]
*** xref:install/choose_package.adoc[All download options]
* Upgrade Guide
** xref:upgrade/2k3k.adoc[Understanding 2k vs 3k]
@@ -82,7 +82,7 @@
* xref:dashboards/intro.adoc[Dashboards]
** xref:dashboards/examples.adoc[Examples]
** xref:dashboards/actions.adoc[Actions (Linked)]
-** xref:dashboards/inline-actions.adoc[Actions (Inline)]
+** xref:dashboards/inline-actions.adoc[Actions (Inline)]
** xref:dashboards/css.adoc[Change component style]
** xref:dashboards/2-fieldsets.adoc[Fieldsets]
** xref:dashboards/3-folders.adoc[Folders]
diff --git a/docs/modules/ROOT/pages/install/macos.adoc b/docs/modules/ROOT/pages/install/macos.adoc
index e7ac27f..6c35f66 100644
--- a/docs/modules/ROOT/pages/install/macos.adoc
+++ b/docs/modules/ROOT/pages/install/macos.adoc
@@ -1,4 +1,4 @@
-= macOS
+= macOS Desktop
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.
diff --git a/docs/modules/ROOT/pages/install/macos_service.adoc b/docs/modules/ROOT/pages/install/macos_service.adoc
index 819d41a..0764d98 100644
--- a/docs/modules/ROOT/pages/install/macos_service.adoc
+++ b/docs/modules/ROOT/pages/install/macos_service.adoc
@@ -1,4 +1,4 @@
-= macOS launchd service install
+= macOS Service (launchd)
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.
@@ -6,14 +6,81 @@ Before continuing, complete the xref:install/macos.adoc[macOS install] steps (do
== Install the files
-Copy the binary somewhere on your `PATH`, and put your configuration in a dedicated directory:
+Run these from the extracted archive directory:
[source,shell]
----
-sudo cp OliveTin /usr/local/bin/OliveTin
+# Create the application folder and a place for logs
+mkdir -p ~/Library/Application\ Support/OliveTin/var
+mkdir -p ~/Library/Logs/OliveTin
-sudo mkdir -p /usr/local/etc/OliveTin
-sudo cp config.yaml /usr/local/etc/OliveTin/config.yaml
+# Copy in the binary, your config, and the bundled web UI
+cp OliveTin ~/Library/Application\ Support/OliveTin/
+cp config.yaml ~/Library/Application\ Support/OliveTin/
+cp -R webui ~/Library/Application\ Support/OliveTin/
+----
+
+This gives you the following layout, all owned by your user:
+
+[source]
+----
+~/Library/Application Support/OliveTin/
+├── OliveTin # the binary
+├── config.yaml # your configuration
+├── webui/ # the web interface assets (shipped in the archive)
+└── var/ # runtime data OliveTin writes (logs, etc.)
+
+~/Library/Logs/OliveTin/olivetin.log # service stdout/stderr
+----
+
+=== Create the service definition
+
+Create a file named `app.olivetin.olivetin.plist` with the contents below.
+
+[IMPORTANT]
+launchd does *not* expand `~`, so the paths must be absolute. Replace `YOUR_USERNAME` with the output of `whoami` in every path.
+
+[source,xml]
+----
+
+
+
+
+ Label
+ app.olivetin.olivetin
+
+ ProgramArguments
+
+ /Users/YOUR_USERNAME/Library/Application Support/OliveTin/OliveTin
+ -configdir
+ /Users/YOUR_USERNAME/Library/Application Support/OliveTin
+
+
+ WorkingDirectory
+ /Users/YOUR_USERNAME/Library/Application Support/OliveTin
+
+ KeepAlive
+
+
+ RunAtLoad
+
+
+ StandardOutPath
+ /Users/YOUR_USERNAME/Library/Logs/OliveTin/olivetin.log
+ StandardErrorPath
+ /Users/YOUR_USERNAME/Library/Logs/OliveTin/olivetin.log
+
+
+----
+
+`WorkingDirectory` makes the relative `webui` and `var` folders resolve inside the application folder, `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
+
+[source,shell]
+----
+cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
+launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
----
[NOTE]
@@ -29,6 +96,15 @@ launchd offers two ways to run a background service:
== 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.
+sudo cp -R webui /usr/local/etc/OliveTin/
+----
+
+[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, and sets `WorkingDirectory` so the `webui` and `var` folders resolve there.
+
+=== Create the service definition
+
+Create a file named `app.olivetin.olivetin.plist` with the following contents. Adjust the paths if you installed OliveTin elsewhere.
[source,xml]
----
@@ -46,6 +122,9 @@ Create a file named `app.olivetin.olivetin.plist` with the following contents. A
/usr/local/etc/OliveTin
+ WorkingDirectory
+ /usr/local/etc/OliveTin
+
KeepAlive
@@ -62,9 +141,7 @@ Create a file named `app.olivetin.olivetin.plist` with the following contents. A
`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)
+=== Register and start the service
[source,shell]
----
@@ -84,20 +161,45 @@ launchctl unload ~/Library/LaunchAgents/app.olivetin.olivetin.plist
[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
+sudo launchctl kickstart -k system/app.olivetin.olivetin
----
+sudo launchctl bootstrap system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+----
+
+[NOTE]
+`bootstrap`/`bootout` replace the deprecated `launchctl load`/`unload`. The domain target for a LaunchDaemon is `system`.
+
To stop and disable it:
[source,shell]
----
-sudo launchctl unload /Library/LaunchDaemons/app.olivetin.olivetin.plist
+sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+sudo launchctl bootstrap system /Library/LaunchDaemons/app.olivetin.olivetin.plist
----
== Verify
+sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+----
+
+=== Restart after a change
+
+After editing `config.yaml` or replacing the binary, restart the service so the change takes effect. To restart in place:
+
+[source,shell]
+----
+sudo launchctl kickstart -k system/app.olivetin.olivetin
+----
+
+If you changed the *plist* itself, `kickstart` is not enough - boot the service out and back in so launchd re-reads it (`bootstrap` errors if the service is still loaded):
+
+[source,shell]
+----
+sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+sudo launchctl bootstrap system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+----
+
+=== Verify
Open http://localhost:1337 in a browser. If the page does not load, check the service log:
diff --git a/var/macos/app.olivetin.olivetin.plist b/var/macos/app.olivetin.olivetin.plist
new file mode 100644
index 0000000..edc4af7
--- /dev/null
+++ b/var/macos/app.olivetin.olivetin.plist
@@ -0,0 +1,47 @@
+
+
+
+
+
+ Label
+ app.olivetin.olivetin
+
+
+ ProgramArguments
+
+ /usr/local/bin/OliveTin
+ -configdir
+ /usr/local/etc/OliveTin
+
+
+
+ KeepAlive
+
+
+
+ RunAtLoad
+
+
+
+ StandardOutPath
+ /usr/local/var/log/olivetin.log
+ StandardErrorPath
+ /usr/local/var/log/olivetin.log
+
+
diff --git a/var/macos/config.yaml b/var/macos/config.yaml
new file mode 100644
index 0000000..7e92e35
--- /dev/null
+++ b/var/macos/config.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 ./var/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: '🔔' # 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: '☕' # 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: '💤' # zzz
+ shell: pmset displaysleepnow
+ popupOnStart: execution-button
+
+ # Battery / power summary using the built-in `pmset`.
+ - title: Power & battery status
+ icon: '🔋' # 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.
diff --git a/var/macos/install.md b/var/macos/install.md
new file mode 100644
index 0000000..6c8ad6e
--- /dev/null
+++ b/var/macos/install.md
@@ -0,0 +1,383 @@
+# Installing OliveTin on macOS
+
+> **Draft** — local Markdown draft kept in sync with the AsciiDoc docs at
+> and
+>
+> (`docs/modules/ROOT/pages/install/macos.adoc` and `macos_service.adoc`).
+
+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
+properly, see step 6 — you can install it **as your own user (no root)** or
+**system-wide**.
+
+---
+
+## 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 **`config.macos.yaml`** that ships alongside
+this guide. Copy it in place with:
+
+```sh
+cp config.macos.yaml config.yaml
+```
+
+---
+
+## 5. Run OliveTin
+
+From the folder that contains both `OliveTin` and `config.yaml`:
+
+```sh
+./OliveTin
+```
+
+Then open the web interface at:
+
+```text
+http://localhost:1337
+```
+
+(or `http://: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**. launchd offers two ways to run a background service, and which one
+you pick decides whether you need root:
+
+* **LaunchAgent (local user)** — runs as *your* user and starts when you log in.
+ **No `sudo` required**, and everything lives under your home folder. Best for a
+ desktop Mac. See [Local user installation](#local-user-installation-no-root).
+* **LaunchDaemon (system-wide)** — runs as `root` and starts at boot, before any
+ user logs in. Requires `sudo`. Best for a headless, always-on Mac. See
+ [System-wide installation](#system-wide-installation-requires-root).
+
+You only need to follow **one** of the two sections below.
+
+### Local user installation (no root)
+
+Everything — the binary, configuration, the `var` data folder, and the `webui`
+folder — is kept together under `~/Library/Application Support/OliveTin`, so you
+never need `sudo`.
+
+**Install the files** (run from the extracted archive directory):
+
+```sh
+# Create the application folder and a place for logs
+mkdir -p ~/Library/Application\ Support/OliveTin/var
+mkdir -p ~/Library/Logs/OliveTin
+
+# Copy in the binary, your config, and the bundled web UI
+cp OliveTin ~/Library/Application\ Support/OliveTin/
+cp config.yaml ~/Library/Application\ Support/OliveTin/
+cp -R webui ~/Library/Application\ Support/OliveTin/
+```
+
+This gives you the following layout, all owned by your user:
+
+```
+~/Library/Application Support/OliveTin/
+├── OliveTin # the binary
+├── config.yaml # your configuration
+├── webui/ # the web interface assets (shipped in the archive)
+└── var/ # runtime data OliveTin writes (logs, etc.)
+
+~/Library/Logs/OliveTin/olivetin.log # service stdout/stderr
+```
+
+**Create the service definition.** Create a file named
+`app.olivetin.olivetin.plist` with the contents below.
+
+> **Important:** launchd does *not* expand `~`, so the paths must be absolute.
+> Replace `YOUR_USERNAME` with the output of `whoami` in every path.
+
+```xml
+
+
+
+
+ Label
+ app.olivetin.olivetin
+
+ ProgramArguments
+
+ /Users/YOUR_USERNAME/Library/Application Support/OliveTin/OliveTin
+ -configdir
+ /Users/YOUR_USERNAME/Library/Application Support/OliveTin
+
+
+ WorkingDirectory
+ /Users/YOUR_USERNAME/Library/Application Support/OliveTin
+
+ KeepAlive
+
+
+ RunAtLoad
+
+
+ StandardOutPath
+ /Users/YOUR_USERNAME/Library/Logs/OliveTin/olivetin.log
+ StandardErrorPath
+ /Users/YOUR_USERNAME/Library/Logs/OliveTin/olivetin.log
+
+
+```
+
+`WorkingDirectory` makes the relative `webui` and `var` folders resolve inside
+the application folder, `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:**
+
+```sh
+cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
+launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+```
+
+> `bootstrap`/`bootout` replace the deprecated `launchctl load`/`unload`. They
+> take a *domain target*: `gui/$(id -u)` is your own per-user GUI domain
+> (`id -u` is your numeric user ID).
+
+To stop and disable it:
+
+```sh
+launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+```
+
+**Restart after a change.** After editing `config.yaml` or replacing the
+binary, restart the service so the change takes effect. To restart in place:
+
+```sh
+launchctl kickstart -k gui/$(id -u)/app.olivetin.olivetin
+```
+
+If you changed the *plist* itself, `kickstart` is not enough — boot the service
+out and back in so launchd re-reads it (`bootstrap` errors if the service is
+still loaded):
+
+```sh
+launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
+```
+
+**Verify** — open . If the page does not load, check the
+service log:
+
+```sh
+tail -f ~/Library/Logs/OliveTin/olivetin.log
+```
+
+### System-wide installation (requires root)
+
+Use this for a headless or shared Mac that should start OliveTin at boot, before
+anyone logs in. It installs the binary on the system `PATH` and runs as `root`
+via a LaunchDaemon, so the commands use `sudo`.
+
+**Install the files:**
+
+```sh
+sudo cp OliveTin /usr/local/bin/OliveTin
+
+sudo mkdir -p /usr/local/etc/OliveTin
+sudo cp config.yaml /usr/local/etc/OliveTin/config.yaml
+sudo cp -R webui /usr/local/etc/OliveTin/
+```
+
+> 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, and sets
+> `WorkingDirectory` so the `webui` and `var` folders resolve there.
+
+**Create the service definition.** Create a file named
+`app.olivetin.olivetin.plist` with the following contents. Adjust the paths if
+you installed OliveTin elsewhere.
+
+```xml
+
+
+
+
+ Label
+ app.olivetin.olivetin
+
+ ProgramArguments
+
+ /usr/local/bin/OliveTin
+ -configdir
+ /usr/local/etc/OliveTin
+
+
+ WorkingDirectory
+ /usr/local/etc/OliveTin
+
+ KeepAlive
+
+
+ RunAtLoad
+
+
+ StandardOutPath
+ /usr/local/var/log/olivetin.log
+ StandardErrorPath
+ /usr/local/var/log/olivetin.log
+
+
+```
+
+`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:**
+
+```sh
+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 bootstrap system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+```
+
+> `bootstrap`/`bootout` replace the deprecated `launchctl load`/`unload`. The
+> domain target for a LaunchDaemon is `system`.
+
+To stop and disable it:
+
+```sh
+sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+```
+
+**Restart after a change.** After editing `config.yaml` or replacing the
+binary, restart the service so the change takes effect. To restart in place:
+
+```sh
+sudo launchctl kickstart -k system/app.olivetin.olivetin
+```
+
+If you changed the *plist* itself, `kickstart` is not enough — boot the service
+out and back in so launchd re-reads it (`bootstrap` errors if the service is
+still loaded):
+
+```sh
+sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+sudo launchctl bootstrap system /Library/LaunchDaemons/app.olivetin.olivetin.plist
+```
+
+**Verify** — open . If the page does not load, check the
+service log:
+
+```sh
+tail -f /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 as a local user: `tail -f ~/Library/Logs/OliveTin/olivetin.log`
+* Running under launchd system-wide: `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)