chore: coderabbit suggestions
This commit is contained in:
parent
35320aba24
commit
3471e2fb12
203
INSTALL-MACOS.md
203
INSTALL-MACOS.md
|
|
@ -1,203 +0,0 @@
|
||||||
# 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)
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
# =============================================================================
|
|
||||||
# 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: '🔔' # 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.
|
|
||||||
|
|
@ -4,41 +4,21 @@ This option installs OliveTin as a launchd service, so it runs in the background
|
||||||
|
|
||||||
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`.
|
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
|
== Choose LaunchAgent or LaunchDaemon
|
||||||
|
|
||||||
Run these from the extracted archive directory:
|
launchd offers two ways to run a background service:
|
||||||
|
|
||||||
[source,shell]
|
* *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 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
|
Follow one complete flow below. Both use the same plist structure; only the install locations and `launchctl` domain differ.
|
||||||
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:
|
== Service definition
|
||||||
|
|
||||||
[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.
|
Create a file named `app.olivetin.olivetin.plist` with the contents below.
|
||||||
|
|
||||||
[IMPORTANT]
|
[IMPORTANT]
|
||||||
launchd does *not* expand `~`, so the paths must be absolute. Replace `YOUR_USERNAME` with the output of `whoami` in every path.
|
launchd does *not* expand `~`, so every path in the plist must be absolute. Replace `YOUR_USERNAME` with the output of `whoami` when using the LaunchAgent paths.
|
||||||
|
|
||||||
[source,xml]
|
[source,xml]
|
||||||
----
|
----
|
||||||
|
|
@ -73,9 +53,65 @@ launchd does *not* expand `~`, so the paths must be absolute. Replace `YOUR_USER
|
||||||
</plist>
|
</plist>
|
||||||
----
|
----
|
||||||
|
|
||||||
`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.
|
For a LaunchDaemon, use the same keys but substitute the paths shown in the table:
|
||||||
|
|
||||||
=== Register and start the service
|
[cols="1,1,1"]
|
||||||
|
|===
|
||||||
|
| Plist entry | LaunchAgent | LaunchDaemon
|
||||||
|
|
||||||
|
| Binary (`ProgramArguments[0]`)
|
||||||
|
| `/Users/YOUR_USERNAME/Library/Application Support/OliveTin/OliveTin`
|
||||||
|
| `/usr/local/bin/OliveTin`
|
||||||
|
|
||||||
|
| `-configdir` and `WorkingDirectory`
|
||||||
|
| `/Users/YOUR_USERNAME/Library/Application Support/OliveTin`
|
||||||
|
| `/usr/local/etc/OliveTin`
|
||||||
|
|
||||||
|
| `StandardOutPath` / `StandardErrorPath`
|
||||||
|
| `/Users/YOUR_USERNAME/Library/Logs/OliveTin/olivetin.log`
|
||||||
|
| `/usr/local/var/log/olivetin.log`
|
||||||
|
|===
|
||||||
|
|
||||||
|
`WorkingDirectory` makes the relative `webui` and `var` folders resolve inside the config directory, `KeepAlive` restarts OliveTin if it exits (like systemd's `Restart=always`), and `RunAtLoad` starts it as soon as the service is loaded.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
OliveTin looks for `config.yaml` in the directory given by the `-configdir` flag. The plist passes `-configdir` explicitly so the service does not depend on the process working directory alone.
|
||||||
|
|
||||||
|
[NOTE]
|
||||||
|
`launchctl bootstrap`/`bootout` replace the deprecated `launchctl load`/`unload`.
|
||||||
|
|
||||||
|
== LaunchAgent (per-user)
|
||||||
|
|
||||||
|
=== Install the files
|
||||||
|
|
||||||
|
Run these from the extracted archive directory:
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
[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
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Register and start
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
|
|
@ -83,108 +119,95 @@ cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
|
||||||
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
|
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
|
||||||
----
|
----
|
||||||
|
|
||||||
[NOTE]
|
=== Verify
|
||||||
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
|
Open http://localhost:1337 in a browser. If the page does not load, check the service log:
|
||||||
|
|
||||||
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.
|
|
||||||
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]
|
|
||||||
----
|
|
||||||
<?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>WorkingDirectory</key>
|
|
||||||
<string>/usr/local/etc/OliveTin</string>
|
|
||||||
|
|
||||||
<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
|
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
mkdir -p /usr/local/var/log
|
tail -f ~/Library/Logs/OliveTin/olivetin.log
|
||||||
cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
|
|
||||||
launchctl load ~/Library/LaunchAgents/app.olivetin.olivetin.plist
|
|
||||||
----
|
----
|
||||||
|
|
||||||
To stop and disable it:
|
=== Stop and disable
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
launchctl unload ~/Library/LaunchAgents/app.olivetin.olivetin.plist
|
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
|
||||||
----
|
----
|
||||||
|
|
||||||
=== As a LaunchDaemon (system-wide, at boot)
|
=== Restart after a change
|
||||||
|
|
||||||
|
After editing `config.yaml` or replacing the binary, restart the service so the change takes effect:
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
sudo launchctl kickstart -k system/app.olivetin.olivetin
|
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):
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
|
||||||
|
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/app.olivetin.olivetin.plist
|
||||||
|
----
|
||||||
|
|
||||||
|
== LaunchDaemon (system-wide)
|
||||||
|
|
||||||
|
=== Install the files
|
||||||
|
|
||||||
|
Run these from the extracted archive directory:
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
sudo mkdir -p /usr/local/bin /usr/local/etc/OliveTin/var /usr/local/var/log
|
||||||
|
|
||||||
|
sudo cp OliveTin /usr/local/bin/OliveTin
|
||||||
|
sudo cp config.yaml /usr/local/etc/OliveTin/
|
||||||
|
sudo cp -R webui /usr/local/etc/OliveTin/
|
||||||
|
----
|
||||||
|
|
||||||
|
This gives you the following layout:
|
||||||
|
|
||||||
|
[source]
|
||||||
|
----
|
||||||
|
/usr/local/bin/OliveTin
|
||||||
|
/usr/local/etc/OliveTin/
|
||||||
|
├── config.yaml
|
||||||
|
├── webui/
|
||||||
|
└── var/
|
||||||
|
|
||||||
|
/usr/local/var/log/olivetin.log # service stdout/stderr
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Register and start
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
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
|
sudo launchctl bootstrap system /Library/LaunchDaemons/app.olivetin.olivetin.plist
|
||||||
----
|
----
|
||||||
|
|
||||||
[NOTE]
|
=== Verify
|
||||||
`bootstrap`/`bootout` replace the deprecated `launchctl load`/`unload`. The domain target for a LaunchDaemon is `system`.
|
|
||||||
|
|
||||||
To stop and disable it:
|
Open http://localhost:1337 in a browser. If the page does not load, check the service log:
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
|
tail -f /usr/local/var/log/olivetin.log
|
||||||
sudo launchctl bootstrap system /Library/LaunchDaemons/app.olivetin.olivetin.plist
|
|
||||||
----
|
----
|
||||||
|
|
||||||
== Verify
|
=== Stop and disable
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
|
sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
|
||||||
----
|
----
|
||||||
|
|
||||||
=== Restart after a change
|
=== Restart after a change
|
||||||
|
|
||||||
After editing `config.yaml` or replacing the binary, restart the service so the change takes effect. To restart in place:
|
After editing `config.yaml` or replacing the binary, restart the service so the change takes effect:
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
|
|
@ -199,13 +222,4 @@ sudo launchctl bootout system /Library/LaunchDaemons/app.olivetin.olivetin.plist
|
||||||
sudo launchctl bootstrap 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:
|
|
||||||
|
|
||||||
[source,shell]
|
|
||||||
----
|
|
||||||
tail -f /usr/local/var/log/olivetin.log
|
|
||||||
----
|
|
||||||
|
|
||||||
include::partial$install/post_generic.adoc[]
|
include::partial$install/post_generic.adoc[]
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -6,7 +6,7 @@
|
||||||
"source": "index.html",
|
"source": "index.html",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"stylelint": "^17.12.0",
|
"stylelint": "^17.13.0",
|
||||||
"stylelint-config-standard": "^40.0.0"
|
"stylelint-config-standard": "^40.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -22,21 +22,21 @@
|
||||||
],
|
],
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@connectrpc/connect": "^2.1.1",
|
"@connectrpc/connect": "^2.1.2",
|
||||||
"@connectrpc/connect-web": "^2.1.1",
|
"@connectrpc/connect-web": "^2.1.2",
|
||||||
"@hugeicons/core-free-icons": "^4.1.4",
|
"@hugeicons/core-free-icons": "^4.2.0",
|
||||||
"@hugeicons/vue": "^1.0.5",
|
"@hugeicons/vue": "^1.0.6",
|
||||||
"@vitejs/plugin-vue": "^6.0.7",
|
"@vitejs/plugin-vue": "^6.0.7",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
"@xterm/addon-web-links": "^0.12.0",
|
"@xterm/addon-web-links": "^0.12.0",
|
||||||
"@xterm/xterm": "^6.0.0",
|
"@xterm/xterm": "^6.0.0",
|
||||||
"iconify-icon": "^3.0.2",
|
"iconify-icon": "^3.0.2",
|
||||||
"picocrank": "^1.15.0",
|
"picocrank": "^1.16.0",
|
||||||
"standard": "^17.1.2",
|
"standard": "^17.1.2",
|
||||||
"unplugin-vue-components": "^32.1.0",
|
"unplugin-vue-components": "^32.1.0",
|
||||||
"vite": "^8.0.14",
|
"vite": "^8.0.16",
|
||||||
"vue": "^3.5.34",
|
"vue": "^3.5.38",
|
||||||
"vue-i18n": "^11.4.4",
|
"vue-i18n": "^11.4.5",
|
||||||
"vue-router": "^5.0.7"
|
"vue-router": "^5.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ const rateLimitExpires = ref(0)
|
||||||
const isRateLimited = ref(false)
|
const isRateLimited = ref(false)
|
||||||
const rateLimitMessage = ref('')
|
const rateLimitMessage = ref('')
|
||||||
let rateLimitInterval = null
|
let rateLimitInterval = null
|
||||||
|
let isComponentMounted = true
|
||||||
|
|
||||||
// Animation classes
|
// Animation classes
|
||||||
const buttonClasses = ref([])
|
const buttonClasses = ref([])
|
||||||
|
|
@ -217,9 +218,12 @@ async function pollExecutionUntilDone (trackingId) {
|
||||||
const pollTimeoutMs = 10 * 60 * 1000
|
const pollTimeoutMs = 10 * 60 * 1000
|
||||||
const deadline = Date.now() + pollTimeoutMs
|
const deadline = Date.now() + pollTimeoutMs
|
||||||
|
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline && isComponentMounted) {
|
||||||
try {
|
try {
|
||||||
const result = await window.client.executionStatus({ executionTrackingId: trackingId })
|
const result = await window.client.executionStatus({ executionTrackingId: trackingId })
|
||||||
|
if (!isComponentMounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (result.logEntry) {
|
if (result.logEntry) {
|
||||||
applyExecutionLogEntry(result.logEntry)
|
applyExecutionLogEntry(result.logEntry)
|
||||||
if (result.logEntry.executionFinished) {
|
if (result.logEntry.executionFinished) {
|
||||||
|
|
@ -230,6 +234,10 @@ async function pollExecutionUntilDone (trackingId) {
|
||||||
console.error('Failed to poll execution status:', err)
|
console.error('Failed to poll execution status:', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isComponentMounted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, pollIntervalMs))
|
await new Promise(resolve => setTimeout(resolve, pollIntervalMs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +358,7 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
isComponentMounted = false
|
||||||
if (rateLimitInterval) {
|
if (rateLimitInterval) {
|
||||||
clearInterval(rateLimitInterval)
|
clearInterval(rateLimitInterval)
|
||||||
rateLimitInterval = null
|
rateLimitInterval = null
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ const loadingTime = ref(0)
|
||||||
const initError = ref(null)
|
const initError = ref(null)
|
||||||
let loadingTimer = null
|
let loadingTimer = null
|
||||||
let checkInitInterval = null
|
let checkInitInterval = null
|
||||||
|
let dashboardRequestId = 0
|
||||||
|
|
||||||
const isDirectory = computed(() => {
|
const isDirectory = computed(() => {
|
||||||
if (!dashboard.value || !window.initResponse) {
|
if (!dashboard.value || !window.initResponse) {
|
||||||
|
|
@ -106,6 +107,7 @@ function goBack() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDashboard() {
|
async function getDashboard() {
|
||||||
|
const requestId = ++dashboardRequestId
|
||||||
let title = props.title
|
let title = props.title
|
||||||
|
|
||||||
// If no specific title was provided or it's the placeholder 'default',
|
// If no specific title was provided or it's the placeholder 'default',
|
||||||
|
|
@ -126,6 +128,10 @@ async function getDashboard() {
|
||||||
|
|
||||||
const ret = await window.client.getDashboard(request)
|
const ret = await window.client.getDashboard(request)
|
||||||
|
|
||||||
|
if (requestId !== dashboardRequestId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!ret || !ret.dashboard) {
|
if (!ret || !ret.dashboard) {
|
||||||
throw new Error('No dashboard found')
|
throw new Error('No dashboard found')
|
||||||
}
|
}
|
||||||
|
|
@ -146,6 +152,10 @@ async function getDashboard() {
|
||||||
// Set attribute to indicate dashboard is loaded successfully
|
// Set attribute to indicate dashboard is loaded successfully
|
||||||
document.body.setAttribute('loaded-dashboard', title || 'default')
|
document.body.setAttribute('loaded-dashboard', title || 'default')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (requestId !== dashboardRequestId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// On error, provide a safe fallback state
|
// On error, provide a safe fallback state
|
||||||
console.error('Failed to load dashboard', e)
|
console.error('Failed to load dashboard', e)
|
||||||
dashboard.value = { title: title || 'Default', contents: [] }
|
dashboard.value = { title: title || 'Default', contents: [] }
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, onUnmounted, nextTick } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { requestReconnectNow } from '../../../js/websocket.js'
|
import { requestReconnectNow } from '../../../js/websocket.js'
|
||||||
|
|
||||||
|
|
@ -76,6 +76,7 @@ const formErrors = ref({})
|
||||||
const actionArguments = ref([])
|
const actionArguments = ref([])
|
||||||
const popupOnStart = ref('')
|
const popupOnStart = ref('')
|
||||||
const formReady = ref(false)
|
const formReady = ref(false)
|
||||||
|
let isComponentMounted = true
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties
|
||||||
|
|
||||||
|
|
@ -145,8 +146,10 @@ async function setup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
formReady.value = true
|
if (isComponentMounted) {
|
||||||
document.body.setAttribute('loaded-argument-form', props.bindingId)
|
formReady.value = true
|
||||||
|
document.body.setAttribute('loaded-argument-form', props.bindingId)
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load argument form:', err)
|
console.error('Failed to load argument form:', err)
|
||||||
}
|
}
|
||||||
|
|
@ -486,6 +489,10 @@ onMounted(() => {
|
||||||
setup()
|
setup()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
isComponentMounted = false
|
||||||
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.body.removeAttribute('loaded-argument-form')
|
document.body.removeAttribute('loaded-argument-form')
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -105,11 +105,29 @@ class OliveTinTestRunnerStartLocalProcess extends OliveTinTestRunner {
|
||||||
if (this.ot.exitCode != null) {
|
if (this.ot.exitCode != null) {
|
||||||
console.log(' OliveTin local process tried stop(), but it already exited with code', this.ot.exitCode)
|
console.log(' OliveTin local process tried stop(), but it already exited with code', this.ot.exitCode)
|
||||||
} else {
|
} else {
|
||||||
|
const stopTimeoutMs = 5000
|
||||||
const closed = new Promise((resolve) => {
|
const closed = new Promise((resolve) => {
|
||||||
this.ot.once('close', resolve)
|
this.ot.once('close', resolve)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.ot.kill('SIGTERM')
|
this.ot.kill('SIGTERM')
|
||||||
await closed
|
|
||||||
|
const didStopGracefully = await Promise.race([
|
||||||
|
closed.then(() => true),
|
||||||
|
new Promise((resolve) => setTimeout(() => resolve(false), stopTimeoutMs))
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!didStopGracefully) {
|
||||||
|
console.log(' OliveTin local process did not exit after SIGTERM, sending SIGKILL')
|
||||||
|
if (this.ot.exitCode == null) {
|
||||||
|
this.ot.kill('SIGKILL')
|
||||||
|
}
|
||||||
|
await Promise.race([
|
||||||
|
closed,
|
||||||
|
new Promise((resolve) => setTimeout(resolve, stopTimeoutMs))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
console.log(' OliveTin local process killed')
|
console.log(' OliveTin local process killed')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import {
|
||||||
getRootAndWait,
|
getRootAndWait,
|
||||||
getActionButton,
|
getActionButton,
|
||||||
takeScreenshotOnFailure,
|
takeScreenshotOnFailure,
|
||||||
waitForArgumentFormPage,
|
waitForArgumentFormReady,
|
||||||
waitForLogsPage,
|
waitForLogsPage,
|
||||||
} from '../../lib/elements.js'
|
} from '../../lib/elements.js'
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ describe('config: datetime', function () {
|
||||||
|
|
||||||
await btn.click()
|
await btn.click()
|
||||||
|
|
||||||
await waitForArgumentFormPage()
|
await waitForArgumentFormReady()
|
||||||
|
|
||||||
// Find the datetime input field
|
// Find the datetime input field
|
||||||
const datetimeInput = await webdriver.findElement(By.id('datetime'))
|
const datetimeInput = await webdriver.findElement(By.id('datetime'))
|
||||||
|
|
@ -54,7 +54,7 @@ describe('config: datetime', function () {
|
||||||
|
|
||||||
await btn.click()
|
await btn.click()
|
||||||
|
|
||||||
await waitForArgumentFormPage()
|
await waitForArgumentFormReady()
|
||||||
|
|
||||||
// Find the datetime input field
|
// Find the datetime input field
|
||||||
const datetimeInput = await webdriver.findElement(By.id('datetime'))
|
const datetimeInput = await webdriver.findElement(By.id('datetime'))
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,10 @@ func (m *otWindowsService) Execute(args []string, r <-chan svc.ChangeRequest, st
|
||||||
func setupLogging() {
|
func setupLogging() {
|
||||||
logsDir := path.Join(GetConfigFilePath(), "logs")
|
logsDir := path.Join(GetConfigFilePath(), "logs")
|
||||||
|
|
||||||
os.MkdirAll(logsDir, 0755)
|
if err := os.MkdirAll(logsDir, 0755); err != nil {
|
||||||
|
log.Errorf("Failed to create logs directory %v: %v", logsDir, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
timestamp := time.Now().Format("2006-01-02_15-04-05")
|
timestamp := time.Now().Format("2006-01-02_15-04-05")
|
||||||
|
|
||||||
|
|
@ -64,7 +67,7 @@ func setupLogging() {
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infof("Failed to open log file: %v", err)
|
log.Errorf("Failed to open log file: %v", err)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Switching to log file: %v", f.Name())
|
log.Infof("Switching to log file: %v", f.Name())
|
||||||
log.SetOutput(f)
|
log.SetOutput(f)
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
As a per-user LaunchAgent (recommended, no root needed):
|
As a per-user LaunchAgent (recommended, no root needed):
|
||||||
cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
|
cp app.olivetin.olivetin.plist ~/Library/LaunchAgents/
|
||||||
launchctl bootstrap ~/Library/LaunchAgents/app.olivetin.olivetin.plist # start now + at login
|
launchctl load ~/Library/LaunchAgents/app.olivetin.olivetin.plist # start now + at login
|
||||||
launchctl bootout ~/Library/LaunchAgents/app.olivetin.olivetin.plist # stop + disable
|
launchctl unload ~/Library/LaunchAgents/app.olivetin.olivetin.plist # stop + disable
|
||||||
|
|
||||||
As a system-wide LaunchDaemon (starts at boot, before login; needs root):
|
As a system-wide LaunchDaemon (starts at boot, before login; needs root):
|
||||||
sudo cp app.olivetin.olivetin.plist /Library/LaunchDaemons/
|
sudo cp app.olivetin.olivetin.plist /Library/LaunchDaemons/
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
<array>
|
<array>
|
||||||
<string>/usr/local/bin/OliveTin</string>
|
<string>/usr/local/bin/OliveTin</string>
|
||||||
<string>-configdir</string>
|
<string>-configdir</string>
|
||||||
<string>/usr/local/etc/OliveTin</string>
|
<string>/Users/YOUR_USER/etc/OliveTin</string>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<!-- systemd's Restart=always -->
|
<!-- systemd's Restart=always -->
|
||||||
|
|
@ -38,10 +38,11 @@
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|
||||||
<!-- Where stdout/stderr go; tail these for troubleshooting -->
|
<!-- 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>
|
<key>StandardOutPath</key>
|
||||||
<string>/usr/local/var/log/olivetin.log</string>
|
<string>/Users/YOUR_USER/Library/Logs/olivetin.log</string>
|
||||||
<key>StandardErrorPath</key>
|
<key>StandardErrorPath</key>
|
||||||
<string>/usr/local/var/log/olivetin.log</string>
|
<string>/Users/YOUR_USER/Library/Logs/olivetin.log</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue