From e299712ca05c03030e882253de74c825bc4cf39e Mon Sep 17 00:00:00 2001 From: jamesread Date: Thu, 28 May 2026 17:23:03 +0100 Subject: [PATCH 01/37] chore: Trigger docs.olivetin.app build if antora succeeds --- .github/workflows/docs-antora.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/docs-antora.yml b/.github/workflows/docs-antora.yml index 96036b2..963f1c4 100644 --- a/.github/workflows/docs-antora.yml +++ b/.github/workflows/docs-antora.yml @@ -32,3 +32,13 @@ jobs: - name: Generate docs site (smoke) run: npx antora local-antora-playbook-ci.yml --log-level info + + trigger-docs-publish: + needs: antora + if: github.event_name == 'push' && github.ref == 'refs/heads/next' + runs-on: ubuntu-latest + steps: + - name: Trigger docs.olivetin.app publish + env: + GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }} + run: gh workflow run asciidoc.yml --repo OliveTin/docs.olivetin.app --ref main From 7655a988e76529077f6c3ccd7b1f5e57a48448ac Mon Sep 17 00:00:00 2001 From: hackdefendr Date: Fri, 5 Jun 2026 16:17:29 -0500 Subject: [PATCH 02/37] Creating MacOS specific documentation, configuration, and examples. --- INSTALL-MACOS.md | 207 ++++++++++++++ app.olivetin.olivetin.plist | 47 ++++ docs/modules/ROOT/nav.adoc | 1 + docs/modules/ROOT/pages/install/macos.adoc | 51 +++- .../ROOT/pages/install/macos_service.adoc | 109 ++++++++ macos.config.yaml | 262 ++++++++++++++++++ 6 files changed, 673 insertions(+), 4 deletions(-) create mode 100644 INSTALL-MACOS.md create mode 100644 app.olivetin.olivetin.plist create mode 100644 docs/modules/ROOT/pages/install/macos_service.adoc create mode 100644 macos.config.yaml diff --git a/INSTALL-MACOS.md b/INSTALL-MACOS.md new file mode 100644 index 0000000..96aa8ad --- /dev/null +++ b/INSTALL-MACOS.md @@ -0,0 +1,207 @@ +# Installing OliveTin on macOS + +> **Draft** — local Markdown draft intended to replace the current stub at +> . We'll convert this to AsciiDoc +> for the Antora docs (`docs/` in the OliveTin repo) before opening a PR. + +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://: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) diff --git a/app.olivetin.olivetin.plist b/app.olivetin.olivetin.plist new file mode 100644 index 0000000..da6b79d --- /dev/null +++ b/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/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 8438f60..39a0676 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -18,6 +18,7 @@ *** 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/choose_package.adoc[All download options] * Upgrade Guide ** xref:upgrade/2k3k.adoc[Understanding 2k vs 3k] diff --git a/docs/modules/ROOT/pages/install/macos.adoc b/docs/modules/ROOT/pages/install/macos.adoc index 1241181..e7ac27f 100644 --- a/docs/modules/ROOT/pages/install/macos.adoc +++ b/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[] diff --git a/docs/modules/ROOT/pages/install/macos_service.adoc b/docs/modules/ROOT/pages/install/macos_service.adoc new file mode 100644 index 0000000..819d41a --- /dev/null +++ b/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] +---- + + + + + 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 + + +---- + +`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[] diff --git a/macos.config.yaml b/macos.config.yaml new file mode 100644 index 0000000..87598f4 --- /dev/null +++ b/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 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. From 6daa5d5b8b78ace119dff3bb355f98c16f3ad2a3 Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Fri, 5 Jun 2026 18:31:11 -0500 Subject: [PATCH 03/37] Update app.olivetin.olivetin.plist --- app.olivetin.olivetin.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.olivetin.olivetin.plist b/app.olivetin.olivetin.plist index da6b79d..861d364 100644 --- a/app.olivetin.olivetin.plist +++ b/app.olivetin.olivetin.plist @@ -42,6 +42,6 @@ StandardOutPath /usr/local/var/log/olivetin.log StandardErrorPath - /usr/local/var/log/olivetin.log + /Users/YOUR_USER/Library/Logs/olivetin.log From 4139b9861f56fdd6e4518c8eeda277a5195a68d1 Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Fri, 5 Jun 2026 18:31:24 -0500 Subject: [PATCH 04/37] Update app.olivetin.olivetin.plist --- app.olivetin.olivetin.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.olivetin.olivetin.plist b/app.olivetin.olivetin.plist index 861d364..fa7abe8 100644 --- a/app.olivetin.olivetin.plist +++ b/app.olivetin.olivetin.plist @@ -40,7 +40,7 @@ StandardOutPath - /usr/local/var/log/olivetin.log + /Users/YOUR_USER/Library/Logs/olivetin.log StandardErrorPath /Users/YOUR_USER/Library/Logs/olivetin.log From 79fe6e7bb57353d1dd7dc1c863f0196ae8e08cc9 Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Fri, 5 Jun 2026 18:37:59 -0500 Subject: [PATCH 05/37] Update INSTALL-MACOS.md I'm following the code rabbit. --- INSTALL-MACOS.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/INSTALL-MACOS.md b/INSTALL-MACOS.md index 96aa8ad..b54a2a6 100644 --- a/INSTALL-MACOS.md +++ b/INSTALL-MACOS.md @@ -1,9 +1,5 @@ # Installing OliveTin on macOS -> **Draft** — local Markdown draft intended to replace the current stub at -> . We'll convert this to AsciiDoc -> for the Antora docs (`docs/` in the OliveTin repo) before opening a PR. - 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. From 513e6be4662fcb3dbc557e74c719daa64fe43204 Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Fri, 5 Jun 2026 20:29:19 -0500 Subject: [PATCH 06/37] Update app.olivetin.olivetin.plist --- app.olivetin.olivetin.plist | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app.olivetin.olivetin.plist b/app.olivetin.olivetin.plist index fa7abe8..d7dac3c 100644 --- a/app.olivetin.olivetin.plist +++ b/app.olivetin.olivetin.plist @@ -27,7 +27,7 @@ /usr/local/bin/OliveTin -configdir - /usr/local/etc/OliveTin + /Users/YOUR_USER/etc/OliveTin @@ -38,10 +38,8 @@ RunAtLoad - + StandardOutPath - /Users/YOUR_USER/Library/Logs/olivetin.log - StandardErrorPath - /Users/YOUR_USER/Library/Logs/olivetin.log - + /Users/YOUR_USER/Library/Logs/olivetin.log From 31513f302e0f7ef7ad5342a7bf4ff4664a2cb1fa Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Sat, 6 Jun 2026 09:38:31 -0500 Subject: [PATCH 07/37] Rename macos.config.yaml to config.macos.yaml --- macos.config.yaml => config.macos.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename macos.config.yaml => config.macos.yaml (100%) diff --git a/macos.config.yaml b/config.macos.yaml similarity index 100% rename from macos.config.yaml rename to config.macos.yaml From bec5f6ef405a9ac7bc415f9a27238dae91975a71 Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Sat, 6 Jun 2026 09:49:42 -0500 Subject: [PATCH 08/37] Update .goreleaser.yml --- .goreleaser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index 2e2ffad..1606416 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -72,6 +72,7 @@ archives: - formats: tar.gz files: - config.yaml + - config.macos.yaml - LICENSE - README.md - src: Dockerfile.singlearch From d253220c637438a2fec3117d0bd1c54feaf756c4 Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Sat, 6 Jun 2026 11:57:22 -0500 Subject: [PATCH 09/37] Update .goreleaser.yml --- .goreleaser.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.goreleaser.yml b/.goreleaser.yml index 1606416..1d02b30 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -73,6 +73,7 @@ archives: files: - config.yaml - config.macos.yaml + - app.olivetin.olivetin.plist - LICENSE - README.md - src: Dockerfile.singlearch From 0df9ff18064d08dcc77e4c198674552c1ab91b7f Mon Sep 17 00:00:00 2001 From: jamesread Date: Sun, 14 Jun 2026 21:57:42 +0100 Subject: [PATCH 10/37] docs: Document behavior for duplicate security issues --- SECURITY.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index f4268df..78480cb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,15 +13,15 @@ To understand more about 2k vs 3k, see the following docs; https://docs.olivetin ## OliveTin *is* a remote code execution (RCE) "tool" -The very purpose of OliveTin is to allow users to execute commands remotely on a machine. +The very purpose of OliveTin is to allow users to execute commands remotely on a machine. -This means that, by design, OliveTin has much higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occur have the potential to be much more severe than in other types of software. +This means that, by design, OliveTin has much higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occur have the potential to be much more severe than in other types of software. We hope that you understand that while the project goes to great aims to be safe, and mitigate, that security vulnerabilities are inevitable, as they are with all software of all sizes - like Kubernetes, the Kernel, etc - and OliveTin has substantially less resources than those projects. With that being said, OliveTin tries to follow examples of best practice, so judge the project not on if/when it has security issues, but how security issues are responded to as the measure of quality. -This is why we take security very seriously, and why we encourage responsible disclosure practices when reporting vulnerabilities. +This is why we take security very seriously, and why we encourage responsible disclosure practices when reporting vulnerabilities. ## Reporting a Vulnerability @@ -29,7 +29,7 @@ Please use responsible disclosure practices when reporting a vulnerability. **Yo * **Option A (preferred)**: GitHub Security Advisories, which allows you to report a vulnerability privately and securely. Use this direct link to report privately: `https://github.com/OliveTin/OliveTin/security/advisories/new`. This allows you to provide details without making them public. -* **Option B**: Please email `contact@jread.com` for responsible disclosure. +* **Option B**: Please email `contact@jread.com` for responsible disclosure. The following notes might be helpful when reporting a vulnerability: @@ -41,15 +41,20 @@ The following notes might be helpful when reporting a vulnerability: It is incredibly useful to not just patch security vulnerabilities, but also to understand how they were found. If you are able to share this information, it can help us and the community to better understand potential attack vectors and improve the overall security of the project. +## Duplicate reports + +If you are reporting via GitHub Security Advisories, search existing [repository advisories](https://github.com/OliveTin/OliveTin/security/advisories) for the same component and attack path before filing. Maintainers may close duplicate submissions and continue work on a single canonical advisory; duplicate reporters are still credited. + +Maintainers: see [.github/SECURITY_ADVISORY_DUPLICATES.md](.github/SECURITY_ADVISORY_DUPLICATES.md) for known duplicate pairs, triage steps, and OAuth2 issues that are easy to confuse with each other. + ## Process Once a vulnerability is reported, the process is; +* Check [.github/SECURITY_ADVISORY_DUPLICATES.md](.github/SECURITY_ADVISORY_DUPLICATES.md) and open advisories for duplicates before accepting. * Accept or reject the report, and communicate with the reporter about next steps. * If accepted, patch using a temporary branch, and code review will be requested from the original reporter if they are interested. * The severity of the vulnerability will be assessed using CVSS, and the patch will be prioritised accordingly. * Once the patch is ready, it will be queued for a release onto the `next` branch (3k) or `release/2k` branch (2k) * The reporter will be credited in the advisory and the release notes, but not the commit message. -* The commit message will contain a reference to the CVSS score (eg: MED) and the advisory ID. - - +* The commit message will contain a reference to the CVSS score (eg: MED) and the advisory ID. From 65ed5c1ff4ad02698dca669ae3da8e7389dba154 Mon Sep 17 00:00:00 2001 From: jamesread Date: Mon, 15 Jun 2026 00:10:07 +0100 Subject: [PATCH 11/37] feat: more intelligent reconnect method --- .gitignore | 3 +- frontend/js/websocket.js | 82 +++- .../gen/olivetin/api/v1/olivetin_pb.d.ts | 19 +- .../gen/olivetin/api/v1/olivetin_pb.js | 56 +-- frontend/resources/vue/ActionButton.vue | 28 ++ frontend/resources/vue/Dashboard.vue | 43 +- .../vue/components/ConnectionBanner.vue | 6 +- .../resources/vue/stores/connectionState.js | 4 +- .../resources/vue/views/ActionDetailsView.vue | 2 + frontend/resources/vue/views/ArgumentForm.vue | 2 + .../resources/vue/views/ExecutionView.vue | 2 + proto/olivetin/api/v1/olivetin.proto | 2 + service/gen/olivetin/api/v1/olivetin.pb.go | 407 ++++++++++-------- service/internal/api/api.go | 36 ++ 14 files changed, 455 insertions(+), 237 deletions(-) diff --git a/.gitignore b/.gitignore index 6986eee..aed5909 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ webui webui.dev sessions.yaml docs/build/ -build/ \ No newline at end of file +build/ +.cursor diff --git a/frontend/js/websocket.js b/frontend/js/websocket.js index bfb6345..0509358 100644 --- a/frontend/js/websocket.js +++ b/frontend/js/websocket.js @@ -2,7 +2,11 @@ import { buttonResults } from '../resources/vue/stores/buttonResults.js' import { rateLimits } from '../resources/vue/stores/rateLimits.js' import { connectionState } from '../resources/vue/stores/connectionState.js' -const RECONNECT_DELAY_MS = 10000 +const RECONNECT_DELAYS_MS = [0, 1000, 2000, 4000, 8000, 16000, 32000] +const BANNER_DELAY_MS = 2000 + +let reconnectAttempt = 0 +let reconnectTimer = null export function initWebsocket () { window.addEventListener('EventOutputChunk', onOutputChunk) @@ -14,6 +18,44 @@ export function initWebsocket () { window.websocketAvailable = false +export function requestReconnectNow () { + if (window.websocketAvailable) { + return + } + + if (reconnectTimer != null) { + clearTimeout(reconnectTimer) + reconnectTimer = null + } + + reconnectAttempt = 0 + scheduleReconnect(0) +} + +function scheduleReconnect (delayMs) { + if (reconnectTimer != null) { + clearTimeout(reconnectTimer) + reconnectTimer = null + } + + connectionState.scheduledReconnectDelayMs = delayMs + connectionState.nextReconnectAt = delayMs > 0 ? Date.now() + delayMs : null + updateBannerVisibility() + reconnectTimer = setTimeout(() => { + reconnectTimer = null + reconnectWebsocket() + }, delayMs) +} + +function updateBannerVisibility () { + if (connectionState.connected) { + connectionState.showDisconnectedBanner = false + return + } + + connectionState.showDisconnectedBanner = connectionState.scheduledReconnectDelayMs >= BANNER_DELAY_MS +} + async function reconnectWebsocket () { if (window.websocketAvailable) { return @@ -25,15 +67,19 @@ async function reconnectWebsocket () { connectionState.disconnectedAt = Date.now() } connectionState.nextReconnectAt = null + connectionState.scheduledReconnectDelayMs = 0 try { window.websocketAvailable = true const stream = window.client.eventStream() connectionState.connected = true connectionState.reconnecting = false + connectionState.disconnectedAt = null connectionState.nextReconnectAt = null + connectionState.scheduledReconnectDelayMs = 0 + connectionState.showDisconnectedBanner = false + reconnectAttempt = 0 for await (const e of stream) { - connectionState.disconnectedAt = null handleEvent(e) } } catch (err) { @@ -42,12 +88,13 @@ async function reconnectWebsocket () { window.websocketAvailable = false connectionState.connected = false + connectionState.reconnecting = false connectionState.disconnectedAt = connectionState.disconnectedAt ?? Date.now() - connectionState.nextReconnectAt = Date.now() + RECONNECT_DELAY_MS - console.log('Reconnecting websocket in ' + RECONNECT_DELAY_MS + 'ms...') - setTimeout(() => { - reconnectWebsocket() - }, RECONNECT_DELAY_MS) + + const delay = RECONNECT_DELAYS_MS[Math.min(reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)] + reconnectAttempt++ + console.log('Reconnecting websocket in ' + delay + 'ms...') + scheduleReconnect(delay) } async function refreshInitAfterConfigChange () { @@ -83,6 +130,8 @@ function handleEvent (msg) { console.error('EventConfigChanged handler failed:', err) }) break + case 'EventHeartbeat': + break case 'EventOutputChunk': case 'EventEntityChanged': window.dispatchEvent(j) @@ -108,18 +157,21 @@ function onOutputChunk (evt) { } } -function onExecutionChanged (evt) { - buttonResults[evt.payload.logEntry.executionTrackingId] = evt.payload.logEntry +export function applyExecutionLogEntry (logEntry) { + if (!logEntry?.executionTrackingId) { + return + } - const logEntry = evt.payload.logEntry + buttonResults[logEntry.executionTrackingId] = logEntry - // Update rate limit store from logEntry if rate limit expiry datetime is provided - if (logEntry && logEntry.datetimeRateLimitExpires && logEntry.bindingId) { - // Parse datetime string "2006-01-02 15:04:05" and convert to Unix timestamp + if (logEntry.datetimeRateLimitExpires && logEntry.bindingId) { const date = new Date(logEntry.datetimeRateLimitExpires.replace(' ', 'T') + 'Z') rateLimits[logEntry.bindingId] = date.getTime() / 1000 - } else if (logEntry && logEntry.bindingId) { - // Clear rate limit if not set + } else if (logEntry.bindingId) { rateLimits[logEntry.bindingId] = 0 } } + +function onExecutionChanged (evt) { + applyExecutionLogEntry(evt.payload.logEntry) +} diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts index 752456e..94d7386 100644 --- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts +++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts @@ -1135,6 +1135,12 @@ export declare type EventStreamResponse = Message<"olivetin.api.v1.EventStreamRe */ value: EventOutputChunk; case: "outputChunk"; + } | { + /** + * @generated from field: olivetin.api.v1.EventHeartbeat heartbeat = 7; + */ + value: EventHeartbeat; + case: "heartbeat"; } | { case: undefined; value?: undefined }; }; @@ -1189,6 +1195,18 @@ export declare type EventConfigChanged = Message<"olivetin.api.v1.EventConfigCha */ export declare const EventConfigChangedSchema: GenMessage; +/** + * @generated from message olivetin.api.v1.EventHeartbeat + */ +export declare type EventHeartbeat = Message<"olivetin.api.v1.EventHeartbeat"> & { +}; + +/** + * Describes the message olivetin.api.v1.EventHeartbeat. + * Use `create(EventHeartbeatSchema)` to create a new message. + */ +export declare const EventHeartbeatSchema: GenMessage; + /** * @generated from message olivetin.api.v1.EventExecutionFinished */ @@ -1919,4 +1937,3 @@ export declare const OliveTinApiService: GenService<{ output: typeof EntitySchema; }, }>; - diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js index 2ded9fe..314efdf 100644 --- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js +++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js @@ -8,7 +8,7 @@ import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2 * Describes the file olivetin/api/v1/olivetin.proto. */ export const file_olivetin_api_v1_olivetin = /*@__PURE__*/ - fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSK2AwoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkSFwoPZXhlY19vbl9zdGFydHVwGAogASgIEhQKDGV4ZWNfb25fY3JvbhgLIAMoCRIjChtleGVjX29uX2ZpbGVfY3JlYXRlZF9pbl9kaXIYDCADKAkSIwobZXhlY19vbl9maWxlX2NoYW5nZWRfaW5fZGlyGA0gAygJEh0KFWV4ZWNfb25fY2FsZW5kYXJfZmlsZRgOIAEoCRJAChBleGVjX29uX3dlYmhvb2tzGA8gAygLMiYub2xpdmV0aW4uYXBpLnYxLkFjdGlvbldlYmhvb2tFeGVjSGludCLDAgoVQWN0aW9uV2ViaG9va0V4ZWNIaW50EhAKCHRlbXBsYXRlGAEgASgJEhIKCm1hdGNoX3BhdGgYAiABKAkSTwoNbWF0Y2hfaGVhZGVycxgDIAMoCzI4Lm9saXZldGluLmFwaS52MS5BY3Rpb25XZWJob29rRXhlY0hpbnQuTWF0Y2hIZWFkZXJzRW50cnkSSwoLbWF0Y2hfcXVlcnkYBCADKAsyNi5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uV2ViaG9va0V4ZWNIaW50Lk1hdGNoUXVlcnlFbnRyeRozChFNYXRjaEhlYWRlcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGjEKD01hdGNoUXVlcnlFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIrsCCg5BY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXRpdGxlGAIgASgJEgwKBHR5cGUYAyABKAkSFQoNZGVmYXVsdF92YWx1ZRgEIAEoCRI2CgdjaG9pY2VzGAUgAygLMiUub2xpdmV0aW4uYXBpLnYxLkFjdGlvbkFyZ3VtZW50Q2hvaWNlEhMKC2Rlc2NyaXB0aW9uGAYgASgJEkUKC3N1Z2dlc3Rpb25zGAcgAygLMjAub2xpdmV0aW4uYXBpLnYxLkFjdGlvbkFyZ3VtZW50LlN1Z2dlc3Rpb25zRW50cnkSHwoXc3VnZ2VzdGlvbnNfYnJvd3Nlcl9rZXkYCCABKAkaMgoQU3VnZ2VzdGlvbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIjQKFEFjdGlvbkFyZ3VtZW50Q2hvaWNlEg0KBXZhbHVlGAEgASgJEg0KBXRpdGxlGAIgASgJIrIBCgZFbnRpdHkSDQoFdGl0bGUYASABKAkSEgoKdW5pcXVlX2tleRgCIAEoCRIMCgR0eXBlGAMgASgJEhMKC2RpcmVjdG9yaWVzGAQgAygJEjMKBmZpZWxkcxgFIAMoCzIjLm9saXZldGluLmFwaS52MS5FbnRpdHkuRmllbGRzRW50cnkaLQoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJUChRHZXREYXNoYm9hcmRSZXNwb25zZRINCgV0aXRsZRgBIAEoCRItCglkYXNoYm9hcmQYBCABKAsyGi5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkIl8KD0VmZmVjdGl2ZVBvbGljeRIYChBzaG93X2RpYWdub3N0aWNzGAEgASgIEhUKDXNob3dfbG9nX2xpc3QYAiABKAgSGwoTc2hvd192ZXJzaW9uX251bWJlchgDIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSJ9ChJTdGFydEFjdGlvblJlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCRI3Cglhcmd1bWVudHMYAiADKAsyJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25Bcmd1bWVudBIaChJ1bmlxdWVfdHJhY2tpbmdfaWQYAyABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJImcKGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IkoKGlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIsChdTdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiOQoYU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgCIAEoCSIzCh5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJIk8KH1N0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5Ik4KDkdldExvZ3NSZXF1ZXN0EhQKDHN0YXJ0X29mZnNldBgBIAEoAxITCgtkYXRlX2ZpbHRlchgCIAEoCRIRCglwYWdlX3NpemUYAyABKAMimgMKCExvZ0VudHJ5EhgKEGRhdGV0aW1lX3N0YXJ0ZWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEg4KBm91dHB1dBgDIAEoCRIRCgl0aW1lZF9vdXQYBSABKAgSEQoJZXhpdF9jb2RlGAYgASgFEgwKBHVzZXIYByABKAkSEgoKdXNlcl9jbGFzcxgIIAEoCRITCgthY3Rpb25faWNvbhgJIAEoCRIMCgR0YWdzGAogAygJEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgLIAEoCRIZChFkYXRldGltZV9maW5pc2hlZBgMIAEoCRIZChFleGVjdXRpb25fc3RhcnRlZBgOIAEoCBIaChJleGVjdXRpb25fZmluaXNoZWQYDyABKAgSDwoHYmxvY2tlZBgQIAEoCBIWCg5kYXRldGltZV9pbmRleBgRIAEoAxIQCghjYW5fa2lsbBgSIAEoCBIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYEyABKAkSEgoKYmluZGluZ19pZBgUIAEoCSKRAQoPR2V0TG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiPwoUR2V0QWN0aW9uTG9nc1JlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEhQKDHN0YXJ0X29mZnNldBgCIAEoAyKXAQoVR2V0QWN0aW9uTG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiZQobVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Eg0KBXZhbHVlGAEgASgJEgwKBHR5cGUYAiABKAkSEgoKYmluZGluZ19pZBgDIAEoCRIVCg1hcmd1bWVudF9uYW1lGAQgASgJIkIKHFZhbGlkYXRlQXJndW1lbnRUeXBlUmVzcG9uc2USDQoFdmFsaWQYASABKAgSEwoLZGVzY3JpcHRpb24YAiABKAkiNgoVV2F0Y2hFeGVjdXRpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCSImChRXYXRjaEV4ZWN1dGlvblVwZGF0ZRIOCgZ1cGRhdGUYASABKAkiSgoWRXhlY3V0aW9uU3RhdHVzUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSEQoJYWN0aW9uX2lkGAIgASgJIkcKF0V4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIPCg1XaG9BbUlSZXF1ZXN0ImwKDldob0FtSVJlc3BvbnNlEhoKEmF1dGhlbnRpY2F0ZWRfdXNlchgBIAEoCRIRCgl1c2VyZ3JvdXAYAiABKAkSEAoIcHJvdmlkZXIYAyABKAkSDAoEYWNscxgEIAMoCRILCgNzaWQYBSABKAkiEgoQU29zUmVwb3J0UmVxdWVzdCIiChFTb3NSZXBvcnRSZXNwb25zZRINCgVhbGVydBgBIAEoCSIRCg9EdW1wVmFyc1JlcXVlc3QilQEKEER1bXBWYXJzUmVzcG9uc2USDQoFYWxlcnQYASABKAkSQQoIY29udGVudHMYAiADKAsyLy5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZS5Db250ZW50c0VudHJ5Gi8KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI7CgxEZWJ1Z0JpbmRpbmcSFAoMYWN0aW9uX3RpdGxlGAEgASgJEhUKDWVudGl0eV9wcmVmaXgYAiABKAkiHgocRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdCLOAQodRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2USDQoFYWxlcnQYASABKAkSTgoIY29udGVudHMYAiADKAsyPC5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UuQ29udGVudHNFbnRyeRpOCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRIsCgV2YWx1ZRgCIAEoCzIdLm9saXZldGluLmFwaS52MS5EZWJ1Z0JpbmRpbmc6AjgBIhIKEEdldFJlYWR5elJlcXVlc3QiIwoRR2V0UmVhZHl6UmVzcG9uc2USDgoGc3RhdHVzGAEgASgJIhQKEkV2ZW50U3RyZWFtUmVxdWVzdCLjAgoTRXZlbnRTdHJlYW1SZXNwb25zZRI9Cg5lbnRpdHlfY2hhbmdlZBgCIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudEVudGl0eUNoYW5nZWRIABI9Cg5jb25maWdfY2hhbmdlZBgDIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudENvbmZpZ0NoYW5nZWRIABJFChJleGVjdXRpb25fZmluaXNoZWQYBCABKAsyJy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25GaW5pc2hlZEgAEkMKEWV4ZWN1dGlvbl9zdGFydGVkGAUgASgLMiYub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uU3RhcnRlZEgAEjkKDG91dHB1dF9jaHVuaxgGIAEoCzIhLm9saXZldGluLmFwaS52MS5FdmVudE91dHB1dENodW5rSABCBwoFZXZlbnQiQQoQRXZlbnRPdXRwdXRDaHVuaxIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGb3V0cHV0GAIgASgJIhQKEkV2ZW50RW50aXR5Q2hhbmdlZCIUChJFdmVudENvbmZpZ0NoYW5nZWQiRgoWRXZlbnRFeGVjdXRpb25GaW5pc2hlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiRQoVRXZlbnRFeGVjdXRpb25TdGFydGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIyChFLaWxsQWN0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkibQoSS2lsbEFjdGlvblJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZraWxsZWQYAiABKAgSGQoRYWxyZWFkeV9jb21wbGV0ZWQYAyABKAgSDQoFZm91bmQYBCABKAgiOwoVTG9jYWxVc2VyTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIikKFkxvY2FsVXNlckxvZ2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCInChNQYXNzd29yZEhhc2hSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJIiQKFFBhc3N3b3JkSGFzaFJlc3BvbnNlEgwKBGhhc2gYASABKAkiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSIXChVHZXREaWFnbm9zdGljc1JlcXVlc3QiRQoWR2V0RGlhZ25vc3RpY3NSZXNwb25zZRITCgtTc2hGb3VuZEtleRgBIAEoCRIWCg5Tc2hGb3VuZENvbmZpZxgCIAEoCSINCgtJbml0UmVxdWVzdCLrBQoMSW5pdFJlc3BvbnNlEhIKCnNob3dGb290ZXIYASABKAgSFgoOc2hvd05hdmlnYXRpb24YAiABKAgSFwoPc2hvd05ld1ZlcnNpb25zGAMgASgIEhgKEGF2YWlsYWJsZVZlcnNpb24YBCABKAkSFgoOY3VycmVudFZlcnNpb24YBSABKAkSEQoJcGFnZVRpdGxlGAYgASgJEh4KFnNlY3Rpb25OYXZpZ2F0aW9uU3R5bGUYByABKAkSGgoSZGVmYXVsdEljb25Gb3JCYWNrGAggASgJEhYKDmVuYWJsZUN1c3RvbUpzGAkgASgIEhQKDGF1dGhMb2dpblVybBgKIAEoCRIWCg5hdXRoTG9jYWxMb2dpbhgLIAEoCBIRCglzdHlsZU1vZHMYDCADKAkSOAoPb0F1dGgyUHJvdmlkZXJzGA0gAygLMh8ub2xpdmV0aW4uYXBpLnYxLk9BdXRoMlByb3ZpZGVyEjgKD2FkZGl0aW9uYWxMaW5rcxgOIAMoCzIfLm9saXZldGluLmFwaS52MS5BZGRpdGlvbmFsTGluaxIWCg5yb290RGFzaGJvYXJkcxgPIAMoCRIaChJhdXRoZW50aWNhdGVkX3VzZXIYECABKAkSIwobYXV0aGVudGljYXRlZF91c2VyX3Byb3ZpZGVyGBEgASgJEjoKEGVmZmVjdGl2ZV9wb2xpY3kYEiABKAsyIC5vbGl2ZXRpbi5hcGkudjEuRWZmZWN0aXZlUG9saWN5EhYKDmJhbm5lcl9tZXNzYWdlGBMgASgJEhIKCmJhbm5lcl9jc3MYFCABKAkSGAoQc2hvd19kaWFnbm9zdGljcxgVIAEoCBIVCg1zaG93X2xvZ19saXN0GBYgASgIEhYKDmxvZ2luX3JlcXVpcmVkGBcgASgIEhgKEGF2YWlsYWJsZV90aGVtZXMYGCADKAkSJAocc2hvd19uYXZpZ2F0ZV9vbl9zdGFydF9pY29ucxgZIAEoCCIsCg5BZGRpdGlvbmFsTGluaxINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkiOgoOT0F1dGgyUHJvdmlkZXISDQoFdGl0bGUYASABKAkSDAoEaWNvbhgDIAEoCRILCgNrZXkYBCABKAkiLQoXR2V0QWN0aW9uQmluZGluZ1JlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCSJDChhHZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2USJwoGYWN0aW9uGAEgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbiIUChJHZXRFbnRpdGllc1JlcXVlc3QiVAoTR2V0RW50aXRpZXNSZXNwb25zZRI9ChJlbnRpdHlfZGVmaW5pdGlvbnMYASADKAsyIS5vbGl2ZXRpbi5hcGkudjEuRW50aXR5RGVmaW5pdGlvbiJpChBFbnRpdHlEZWZpbml0aW9uEg0KBXRpdGxlGAEgASgJEioKCWluc3RhbmNlcxgCIAMoCzIXLm9saXZldGluLmFwaS52MS5FbnRpdHkSGgoSdXNlZF9vbl9kYXNoYm9hcmRzGAMgAygJIjQKEEdldEVudGl0eVJlcXVlc3QSEgoKdW5pcXVlX2tleRgBIAEoCRIMCgR0eXBlGAIgASgJIjUKFFJlc3RhcnRBY3Rpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCTLoEgoST2xpdmVUaW5BcGlTZXJ2aWNlEl0KDEdldERhc2hib2FyZBIkLm9saXZldGluLmFwaS52MS5HZXREYXNoYm9hcmRSZXF1ZXN0GiUub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlc3BvbnNlIgASWgoLU3RhcnRBY3Rpb24SIy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJvChJTdGFydEFjdGlvbkFuZFdhaXQSKi5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25BbmRXYWl0UmVxdWVzdBorLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXNwb25zZSIAEmkKEFN0YXJ0QWN0aW9uQnlHZXQSKC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlcXVlc3QaKS5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlIgASfgoXU3RhcnRBY3Rpb25CeUdldEFuZFdhaXQSLy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXF1ZXN0GjAub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2UiABJeCg1SZXN0YXJ0QWN0aW9uEiUub2xpdmV0aW4uYXBpLnYxLlJlc3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJXCgpLaWxsQWN0aW9uEiIub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXF1ZXN0GiMub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXNwb25zZSIAEmYKD0V4ZWN1dGlvblN0YXR1cxInLm9saXZldGluLmFwaS52MS5FeGVjdXRpb25TdGF0dXNSZXF1ZXN0Gigub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlIgASTgoHR2V0TG9ncxIfLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVxdWVzdBogLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVzcG9uc2UiABJgCg1HZXRBY3Rpb25Mb2dzEiUub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXF1ZXN0GiYub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXNwb25zZSIAEnUKFFZhbGlkYXRlQXJndW1lbnRUeXBlEiwub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVxdWVzdBotLm9saXZldGluLmFwaS52MS5WYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlIgASSwoGV2hvQW1JEh4ub2xpdmV0aW4uYXBpLnYxLldob0FtSVJlcXVlc3QaHy5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVzcG9uc2UiABJUCglTb3NSZXBvcnQSIS5vbGl2ZXRpbi5hcGkudjEuU29zUmVwb3J0UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXNwb25zZSIAElEKCER1bXBWYXJzEiAub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVxdWVzdBohLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlIgASeAoVRHVtcFB1YmxpY0lkQWN0aW9uTWFwEi0ub2xpdmV0aW4uYXBpLnYxLkR1bXBQdWJsaWNJZEFjdGlvbk1hcFJlcXVlc3QaLi5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UiABJUCglHZXRSZWFkeXoSIS5vbGl2ZXRpbi5hcGkudjEuR2V0UmVhZHl6UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXNwb25zZSIAEmMKDkxvY2FsVXNlckxvZ2luEiYub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5Mb2NhbFVzZXJMb2dpblJlc3BvbnNlIgASXQoMUGFzc3dvcmRIYXNoEiQub2xpdmV0aW4uYXBpLnYxLlBhc3N3b3JkSGFzaFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVzcG9uc2UiABJLCgZMb2dvdXQSHi5vbGl2ZXRpbi5hcGkudjEuTG9nb3V0UmVxdWVzdBofLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXNwb25zZSIAElwKC0V2ZW50U3RyZWFtEiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5FdmVudFN0cmVhbVJlc3BvbnNlIgAwARJjCg5HZXREaWFnbm9zdGljcxImLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1JlcXVlc3QaJy5vbGl2ZXRpbi5hcGkudjEuR2V0RGlhZ25vc3RpY3NSZXNwb25zZSIAEkUKBEluaXQSHC5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlcXVlc3QaHS5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlc3BvbnNlIgASaQoQR2V0QWN0aW9uQmluZGluZxIoLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBopLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2UiABJaCgtHZXRFbnRpdGllcxIjLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1JlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXRpZXNSZXNwb25zZSIAEkkKCUdldEVudGl0eRIhLm9saXZldGluLmFwaS52MS5HZXRFbnRpdHlSZXF1ZXN0Ghcub2xpdmV0aW4uYXBpLnYxLkVudGl0eSIAQjhaNmdpdGh1Yi5jb20vT2xpdmVUaW4vT2xpdmVUaW4vZ2VuL29saXZldGluL2FwaS92MTthcGl2MWIGcHJvdG8z"); + fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSK2AwoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkSFwoPZXhlY19vbl9zdGFydHVwGAogASgIEhQKDGV4ZWNfb25fY3JvbhgLIAMoCRIjChtleGVjX29uX2ZpbGVfY3JlYXRlZF9pbl9kaXIYDCADKAkSIwobZXhlY19vbl9maWxlX2NoYW5nZWRfaW5fZGlyGA0gAygJEh0KFWV4ZWNfb25fY2FsZW5kYXJfZmlsZRgOIAEoCRJAChBleGVjX29uX3dlYmhvb2tzGA8gAygLMiYub2xpdmV0aW4uYXBpLnYxLkFjdGlvbldlYmhvb2tFeGVjSGludCLDAgoVQWN0aW9uV2ViaG9va0V4ZWNIaW50EhAKCHRlbXBsYXRlGAEgASgJEhIKCm1hdGNoX3BhdGgYAiABKAkSTwoNbWF0Y2hfaGVhZGVycxgDIAMoCzI4Lm9saXZldGluLmFwaS52MS5BY3Rpb25XZWJob29rRXhlY0hpbnQuTWF0Y2hIZWFkZXJzRW50cnkSSwoLbWF0Y2hfcXVlcnkYBCADKAsyNi5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uV2ViaG9va0V4ZWNIaW50Lk1hdGNoUXVlcnlFbnRyeRozChFNYXRjaEhlYWRlcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGjEKD01hdGNoUXVlcnlFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIrsCCg5BY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXRpdGxlGAIgASgJEgwKBHR5cGUYAyABKAkSFQoNZGVmYXVsdF92YWx1ZRgEIAEoCRI2CgdjaG9pY2VzGAUgAygLMiUub2xpdmV0aW4uYXBpLnYxLkFjdGlvbkFyZ3VtZW50Q2hvaWNlEhMKC2Rlc2NyaXB0aW9uGAYgASgJEkUKC3N1Z2dlc3Rpb25zGAcgAygLMjAub2xpdmV0aW4uYXBpLnYxLkFjdGlvbkFyZ3VtZW50LlN1Z2dlc3Rpb25zRW50cnkSHwoXc3VnZ2VzdGlvbnNfYnJvd3Nlcl9rZXkYCCABKAkaMgoQU3VnZ2VzdGlvbnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIjQKFEFjdGlvbkFyZ3VtZW50Q2hvaWNlEg0KBXZhbHVlGAEgASgJEg0KBXRpdGxlGAIgASgJIrIBCgZFbnRpdHkSDQoFdGl0bGUYASABKAkSEgoKdW5pcXVlX2tleRgCIAEoCRIMCgR0eXBlGAMgASgJEhMKC2RpcmVjdG9yaWVzGAQgAygJEjMKBmZpZWxkcxgFIAMoCzIjLm9saXZldGluLmFwaS52MS5FbnRpdHkuRmllbGRzRW50cnkaLQoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJUChRHZXREYXNoYm9hcmRSZXNwb25zZRINCgV0aXRsZRgBIAEoCRItCglkYXNoYm9hcmQYBCABKAsyGi5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkIl8KD0VmZmVjdGl2ZVBvbGljeRIYChBzaG93X2RpYWdub3N0aWNzGAEgASgIEhUKDXNob3dfbG9nX2xpc3QYAiABKAgSGwoTc2hvd192ZXJzaW9uX251bWJlchgDIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSJ9ChJTdGFydEFjdGlvblJlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCRI3Cglhcmd1bWVudHMYAiADKAsyJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25Bcmd1bWVudBIaChJ1bmlxdWVfdHJhY2tpbmdfaWQYAyABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJImcKGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IkoKGlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIsChdTdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiOQoYU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgCIAEoCSIzCh5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJIk8KH1N0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5Ik4KDkdldExvZ3NSZXF1ZXN0EhQKDHN0YXJ0X29mZnNldBgBIAEoAxITCgtkYXRlX2ZpbHRlchgCIAEoCRIRCglwYWdlX3NpemUYAyABKAMimgMKCExvZ0VudHJ5EhgKEGRhdGV0aW1lX3N0YXJ0ZWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEg4KBm91dHB1dBgDIAEoCRIRCgl0aW1lZF9vdXQYBSABKAgSEQoJZXhpdF9jb2RlGAYgASgFEgwKBHVzZXIYByABKAkSEgoKdXNlcl9jbGFzcxgIIAEoCRITCgthY3Rpb25faWNvbhgJIAEoCRIMCgR0YWdzGAogAygJEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgLIAEoCRIZChFkYXRldGltZV9maW5pc2hlZBgMIAEoCRIZChFleGVjdXRpb25fc3RhcnRlZBgOIAEoCBIaChJleGVjdXRpb25fZmluaXNoZWQYDyABKAgSDwoHYmxvY2tlZBgQIAEoCBIWCg5kYXRldGltZV9pbmRleBgRIAEoAxIQCghjYW5fa2lsbBgSIAEoCBIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYEyABKAkSEgoKYmluZGluZ19pZBgUIAEoCSKRAQoPR2V0TG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiPwoUR2V0QWN0aW9uTG9nc1JlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEhQKDHN0YXJ0X29mZnNldBgCIAEoAyKXAQoVR2V0QWN0aW9uTG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiZQobVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Eg0KBXZhbHVlGAEgASgJEgwKBHR5cGUYAiABKAkSEgoKYmluZGluZ19pZBgDIAEoCRIVCg1hcmd1bWVudF9uYW1lGAQgASgJIkIKHFZhbGlkYXRlQXJndW1lbnRUeXBlUmVzcG9uc2USDQoFdmFsaWQYASABKAgSEwoLZGVzY3JpcHRpb24YAiABKAkiNgoVV2F0Y2hFeGVjdXRpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCSImChRXYXRjaEV4ZWN1dGlvblVwZGF0ZRIOCgZ1cGRhdGUYASABKAkiSgoWRXhlY3V0aW9uU3RhdHVzUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSEQoJYWN0aW9uX2lkGAIgASgJIkcKF0V4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIPCg1XaG9BbUlSZXF1ZXN0ImwKDldob0FtSVJlc3BvbnNlEhoKEmF1dGhlbnRpY2F0ZWRfdXNlchgBIAEoCRIRCgl1c2VyZ3JvdXAYAiABKAkSEAoIcHJvdmlkZXIYAyABKAkSDAoEYWNscxgEIAMoCRILCgNzaWQYBSABKAkiEgoQU29zUmVwb3J0UmVxdWVzdCIiChFTb3NSZXBvcnRSZXNwb25zZRINCgVhbGVydBgBIAEoCSIRCg9EdW1wVmFyc1JlcXVlc3QilQEKEER1bXBWYXJzUmVzcG9uc2USDQoFYWxlcnQYASABKAkSQQoIY29udGVudHMYAiADKAsyLy5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZS5Db250ZW50c0VudHJ5Gi8KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI7CgxEZWJ1Z0JpbmRpbmcSFAoMYWN0aW9uX3RpdGxlGAEgASgJEhUKDWVudGl0eV9wcmVmaXgYAiABKAkiHgocRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdCLOAQodRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2USDQoFYWxlcnQYASABKAkSTgoIY29udGVudHMYAiADKAsyPC5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UuQ29udGVudHNFbnRyeRpOCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRIsCgV2YWx1ZRgCIAEoCzIdLm9saXZldGluLmFwaS52MS5EZWJ1Z0JpbmRpbmc6AjgBIhIKEEdldFJlYWR5elJlcXVlc3QiIwoRR2V0UmVhZHl6UmVzcG9uc2USDgoGc3RhdHVzGAEgASgJIhQKEkV2ZW50U3RyZWFtUmVxdWVzdCKZAwoTRXZlbnRTdHJlYW1SZXNwb25zZRI9Cg5lbnRpdHlfY2hhbmdlZBgCIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudEVudGl0eUNoYW5nZWRIABI9Cg5jb25maWdfY2hhbmdlZBgDIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudENvbmZpZ0NoYW5nZWRIABJFChJleGVjdXRpb25fZmluaXNoZWQYBCABKAsyJy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25GaW5pc2hlZEgAEkMKEWV4ZWN1dGlvbl9zdGFydGVkGAUgASgLMiYub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uU3RhcnRlZEgAEjkKDG91dHB1dF9jaHVuaxgGIAEoCzIhLm9saXZldGluLmFwaS52MS5FdmVudE91dHB1dENodW5rSAASNAoJaGVhcnRiZWF0GAcgASgLMh8ub2xpdmV0aW4uYXBpLnYxLkV2ZW50SGVhcnRiZWF0SABCBwoFZXZlbnQiQQoQRXZlbnRPdXRwdXRDaHVuaxIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGb3V0cHV0GAIgASgJIhQKEkV2ZW50RW50aXR5Q2hhbmdlZCIUChJFdmVudENvbmZpZ0NoYW5nZWQiEAoORXZlbnRIZWFydGJlYXQiRgoWRXZlbnRFeGVjdXRpb25GaW5pc2hlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiRQoVRXZlbnRFeGVjdXRpb25TdGFydGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIyChFLaWxsQWN0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkibQoSS2lsbEFjdGlvblJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZraWxsZWQYAiABKAgSGQoRYWxyZWFkeV9jb21wbGV0ZWQYAyABKAgSDQoFZm91bmQYBCABKAgiOwoVTG9jYWxVc2VyTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIikKFkxvY2FsVXNlckxvZ2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCInChNQYXNzd29yZEhhc2hSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJIiQKFFBhc3N3b3JkSGFzaFJlc3BvbnNlEgwKBGhhc2gYASABKAkiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSIXChVHZXREaWFnbm9zdGljc1JlcXVlc3QiRQoWR2V0RGlhZ25vc3RpY3NSZXNwb25zZRITCgtTc2hGb3VuZEtleRgBIAEoCRIWCg5Tc2hGb3VuZENvbmZpZxgCIAEoCSINCgtJbml0UmVxdWVzdCLrBQoMSW5pdFJlc3BvbnNlEhIKCnNob3dGb290ZXIYASABKAgSFgoOc2hvd05hdmlnYXRpb24YAiABKAgSFwoPc2hvd05ld1ZlcnNpb25zGAMgASgIEhgKEGF2YWlsYWJsZVZlcnNpb24YBCABKAkSFgoOY3VycmVudFZlcnNpb24YBSABKAkSEQoJcGFnZVRpdGxlGAYgASgJEh4KFnNlY3Rpb25OYXZpZ2F0aW9uU3R5bGUYByABKAkSGgoSZGVmYXVsdEljb25Gb3JCYWNrGAggASgJEhYKDmVuYWJsZUN1c3RvbUpzGAkgASgIEhQKDGF1dGhMb2dpblVybBgKIAEoCRIWCg5hdXRoTG9jYWxMb2dpbhgLIAEoCBIRCglzdHlsZU1vZHMYDCADKAkSOAoPb0F1dGgyUHJvdmlkZXJzGA0gAygLMh8ub2xpdmV0aW4uYXBpLnYxLk9BdXRoMlByb3ZpZGVyEjgKD2FkZGl0aW9uYWxMaW5rcxgOIAMoCzIfLm9saXZldGluLmFwaS52MS5BZGRpdGlvbmFsTGluaxIWCg5yb290RGFzaGJvYXJkcxgPIAMoCRIaChJhdXRoZW50aWNhdGVkX3VzZXIYECABKAkSIwobYXV0aGVudGljYXRlZF91c2VyX3Byb3ZpZGVyGBEgASgJEjoKEGVmZmVjdGl2ZV9wb2xpY3kYEiABKAsyIC5vbGl2ZXRpbi5hcGkudjEuRWZmZWN0aXZlUG9saWN5EhYKDmJhbm5lcl9tZXNzYWdlGBMgASgJEhIKCmJhbm5lcl9jc3MYFCABKAkSGAoQc2hvd19kaWFnbm9zdGljcxgVIAEoCBIVCg1zaG93X2xvZ19saXN0GBYgASgIEhYKDmxvZ2luX3JlcXVpcmVkGBcgASgIEhgKEGF2YWlsYWJsZV90aGVtZXMYGCADKAkSJAocc2hvd19uYXZpZ2F0ZV9vbl9zdGFydF9pY29ucxgZIAEoCCIsCg5BZGRpdGlvbmFsTGluaxINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkiOgoOT0F1dGgyUHJvdmlkZXISDQoFdGl0bGUYASABKAkSDAoEaWNvbhgDIAEoCRILCgNrZXkYBCABKAkiLQoXR2V0QWN0aW9uQmluZGluZ1JlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCSJDChhHZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2USJwoGYWN0aW9uGAEgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbiIUChJHZXRFbnRpdGllc1JlcXVlc3QiVAoTR2V0RW50aXRpZXNSZXNwb25zZRI9ChJlbnRpdHlfZGVmaW5pdGlvbnMYASADKAsyIS5vbGl2ZXRpbi5hcGkudjEuRW50aXR5RGVmaW5pdGlvbiJpChBFbnRpdHlEZWZpbml0aW9uEg0KBXRpdGxlGAEgASgJEioKCWluc3RhbmNlcxgCIAMoCzIXLm9saXZldGluLmFwaS52MS5FbnRpdHkSGgoSdXNlZF9vbl9kYXNoYm9hcmRzGAMgAygJIjQKEEdldEVudGl0eVJlcXVlc3QSEgoKdW5pcXVlX2tleRgBIAEoCRIMCgR0eXBlGAIgASgJIjUKFFJlc3RhcnRBY3Rpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCTLoEgoST2xpdmVUaW5BcGlTZXJ2aWNlEl0KDEdldERhc2hib2FyZBIkLm9saXZldGluLmFwaS52MS5HZXREYXNoYm9hcmRSZXF1ZXN0GiUub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlc3BvbnNlIgASWgoLU3RhcnRBY3Rpb24SIy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJvChJTdGFydEFjdGlvbkFuZFdhaXQSKi5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25BbmRXYWl0UmVxdWVzdBorLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXNwb25zZSIAEmkKEFN0YXJ0QWN0aW9uQnlHZXQSKC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlcXVlc3QaKS5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlIgASfgoXU3RhcnRBY3Rpb25CeUdldEFuZFdhaXQSLy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXF1ZXN0GjAub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2UiABJeCg1SZXN0YXJ0QWN0aW9uEiUub2xpdmV0aW4uYXBpLnYxLlJlc3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJXCgpLaWxsQWN0aW9uEiIub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXF1ZXN0GiMub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXNwb25zZSIAEmYKD0V4ZWN1dGlvblN0YXR1cxInLm9saXZldGluLmFwaS52MS5FeGVjdXRpb25TdGF0dXNSZXF1ZXN0Gigub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlIgASTgoHR2V0TG9ncxIfLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVxdWVzdBogLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVzcG9uc2UiABJgCg1HZXRBY3Rpb25Mb2dzEiUub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXF1ZXN0GiYub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXNwb25zZSIAEnUKFFZhbGlkYXRlQXJndW1lbnRUeXBlEiwub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVxdWVzdBotLm9saXZldGluLmFwaS52MS5WYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlIgASSwoGV2hvQW1JEh4ub2xpdmV0aW4uYXBpLnYxLldob0FtSVJlcXVlc3QaHy5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVzcG9uc2UiABJUCglTb3NSZXBvcnQSIS5vbGl2ZXRpbi5hcGkudjEuU29zUmVwb3J0UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXNwb25zZSIAElEKCER1bXBWYXJzEiAub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVxdWVzdBohLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlIgASeAoVRHVtcFB1YmxpY0lkQWN0aW9uTWFwEi0ub2xpdmV0aW4uYXBpLnYxLkR1bXBQdWJsaWNJZEFjdGlvbk1hcFJlcXVlc3QaLi5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UiABJUCglHZXRSZWFkeXoSIS5vbGl2ZXRpbi5hcGkudjEuR2V0UmVhZHl6UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXNwb25zZSIAEmMKDkxvY2FsVXNlckxvZ2luEiYub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5Mb2NhbFVzZXJMb2dpblJlc3BvbnNlIgASXQoMUGFzc3dvcmRIYXNoEiQub2xpdmV0aW4uYXBpLnYxLlBhc3N3b3JkSGFzaFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVzcG9uc2UiABJLCgZMb2dvdXQSHi5vbGl2ZXRpbi5hcGkudjEuTG9nb3V0UmVxdWVzdBofLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXNwb25zZSIAElwKC0V2ZW50U3RyZWFtEiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5FdmVudFN0cmVhbVJlc3BvbnNlIgAwARJjCg5HZXREaWFnbm9zdGljcxImLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1JlcXVlc3QaJy5vbGl2ZXRpbi5hcGkudjEuR2V0RGlhZ25vc3RpY3NSZXNwb25zZSIAEkUKBEluaXQSHC5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlcXVlc3QaHS5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlc3BvbnNlIgASaQoQR2V0QWN0aW9uQmluZGluZxIoLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBopLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2UiABJaCgtHZXRFbnRpdGllcxIjLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1JlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXRpZXNSZXNwb25zZSIAEkkKCUdldEVudGl0eRIhLm9saXZldGluLmFwaS52MS5HZXRFbnRpdHlSZXF1ZXN0Ghcub2xpdmV0aW4uYXBpLnYxLkVudGl0eSIAQjhaNmdpdGh1Yi5jb20vT2xpdmVUaW4vT2xpdmVUaW4vZ2VuL29saXZldGluL2FwaS92MTthcGl2MWIGcHJvdG8z"); /** * Describes the message olivetin.api.v1.Action. @@ -332,170 +332,176 @@ export const EventEntityChangedSchema = /*@__PURE__*/ export const EventConfigChangedSchema = /*@__PURE__*/ messageDesc(file_olivetin_api_v1_olivetin, 45); +/** + * Describes the message olivetin.api.v1.EventHeartbeat. + * Use `create(EventHeartbeatSchema)` to create a new message. + */ +export const EventHeartbeatSchema = /*@__PURE__*/ + messageDesc(file_olivetin_api_v1_olivetin, 46); + /** * Describes the message olivetin.api.v1.EventExecutionFinished. * Use `create(EventExecutionFinishedSchema)` to create a new message. */ export const EventExecutionFinishedSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 46); + messageDesc(file_olivetin_api_v1_olivetin, 47); /** * Describes the message olivetin.api.v1.EventExecutionStarted. * Use `create(EventExecutionStartedSchema)` to create a new message. */ export const EventExecutionStartedSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 47); + messageDesc(file_olivetin_api_v1_olivetin, 48); /** * Describes the message olivetin.api.v1.KillActionRequest. * Use `create(KillActionRequestSchema)` to create a new message. */ export const KillActionRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 48); + messageDesc(file_olivetin_api_v1_olivetin, 49); /** * Describes the message olivetin.api.v1.KillActionResponse. * Use `create(KillActionResponseSchema)` to create a new message. */ export const KillActionResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 49); + messageDesc(file_olivetin_api_v1_olivetin, 50); /** * Describes the message olivetin.api.v1.LocalUserLoginRequest. * Use `create(LocalUserLoginRequestSchema)` to create a new message. */ export const LocalUserLoginRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 50); + messageDesc(file_olivetin_api_v1_olivetin, 51); /** * Describes the message olivetin.api.v1.LocalUserLoginResponse. * Use `create(LocalUserLoginResponseSchema)` to create a new message. */ export const LocalUserLoginResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 51); + messageDesc(file_olivetin_api_v1_olivetin, 52); /** * Describes the message olivetin.api.v1.PasswordHashRequest. * Use `create(PasswordHashRequestSchema)` to create a new message. */ export const PasswordHashRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 52); + messageDesc(file_olivetin_api_v1_olivetin, 53); /** * Describes the message olivetin.api.v1.PasswordHashResponse. * Use `create(PasswordHashResponseSchema)` to create a new message. */ export const PasswordHashResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 53); + messageDesc(file_olivetin_api_v1_olivetin, 54); /** * Describes the message olivetin.api.v1.LogoutRequest. * Use `create(LogoutRequestSchema)` to create a new message. */ export const LogoutRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 54); + messageDesc(file_olivetin_api_v1_olivetin, 55); /** * Describes the message olivetin.api.v1.LogoutResponse. * Use `create(LogoutResponseSchema)` to create a new message. */ export const LogoutResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 55); + messageDesc(file_olivetin_api_v1_olivetin, 56); /** * Describes the message olivetin.api.v1.GetDiagnosticsRequest. * Use `create(GetDiagnosticsRequestSchema)` to create a new message. */ export const GetDiagnosticsRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 56); + messageDesc(file_olivetin_api_v1_olivetin, 57); /** * Describes the message olivetin.api.v1.GetDiagnosticsResponse. * Use `create(GetDiagnosticsResponseSchema)` to create a new message. */ export const GetDiagnosticsResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 57); + messageDesc(file_olivetin_api_v1_olivetin, 58); /** * Describes the message olivetin.api.v1.InitRequest. * Use `create(InitRequestSchema)` to create a new message. */ export const InitRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 58); + messageDesc(file_olivetin_api_v1_olivetin, 59); /** * Describes the message olivetin.api.v1.InitResponse. * Use `create(InitResponseSchema)` to create a new message. */ export const InitResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 59); + messageDesc(file_olivetin_api_v1_olivetin, 60); /** * Describes the message olivetin.api.v1.AdditionalLink. * Use `create(AdditionalLinkSchema)` to create a new message. */ export const AdditionalLinkSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 60); + messageDesc(file_olivetin_api_v1_olivetin, 61); /** * Describes the message olivetin.api.v1.OAuth2Provider. * Use `create(OAuth2ProviderSchema)` to create a new message. */ export const OAuth2ProviderSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 61); + messageDesc(file_olivetin_api_v1_olivetin, 62); /** * Describes the message olivetin.api.v1.GetActionBindingRequest. * Use `create(GetActionBindingRequestSchema)` to create a new message. */ export const GetActionBindingRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 62); + messageDesc(file_olivetin_api_v1_olivetin, 63); /** * Describes the message olivetin.api.v1.GetActionBindingResponse. * Use `create(GetActionBindingResponseSchema)` to create a new message. */ export const GetActionBindingResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 63); + messageDesc(file_olivetin_api_v1_olivetin, 64); /** * Describes the message olivetin.api.v1.GetEntitiesRequest. * Use `create(GetEntitiesRequestSchema)` to create a new message. */ export const GetEntitiesRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 64); + messageDesc(file_olivetin_api_v1_olivetin, 65); /** * Describes the message olivetin.api.v1.GetEntitiesResponse. * Use `create(GetEntitiesResponseSchema)` to create a new message. */ export const GetEntitiesResponseSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 65); + messageDesc(file_olivetin_api_v1_olivetin, 66); /** * Describes the message olivetin.api.v1.EntityDefinition. * Use `create(EntityDefinitionSchema)` to create a new message. */ export const EntityDefinitionSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 66); + messageDesc(file_olivetin_api_v1_olivetin, 67); /** * Describes the message olivetin.api.v1.GetEntityRequest. * Use `create(GetEntityRequestSchema)` to create a new message. */ export const GetEntityRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 67); + messageDesc(file_olivetin_api_v1_olivetin, 68); /** * Describes the message olivetin.api.v1.RestartActionRequest. * Use `create(RestartActionRequestSchema)` to create a new message. */ export const RestartActionRequestSchema = /*@__PURE__*/ - messageDesc(file_olivetin_api_v1_olivetin, 68); + messageDesc(file_olivetin_api_v1_olivetin, 69); /** * @generated from service olivetin.api.v1.OliveTinApiService */ export const OliveTinApiService = /*@__PURE__*/ serviceDesc(file_olivetin_api_v1_olivetin, 0); - diff --git a/frontend/resources/vue/ActionButton.vue b/frontend/resources/vue/ActionButton.vue index e61e237..677aebb 100644 --- a/frontend/resources/vue/ActionButton.vue +++ b/frontend/resources/vue/ActionButton.vue @@ -29,6 +29,8 @@ \ No newline at end of file + diff --git a/frontend/resources/vue/components/DashboardComponentDisplay.vue b/frontend/resources/vue/components/DashboardComponentDisplay.vue index 95c3b5d..16cf3ea 100644 --- a/frontend/resources/vue/components/DashboardComponentDisplay.vue +++ b/frontend/resources/vue/components/DashboardComponentDisplay.vue @@ -13,7 +13,9 @@ const props = defineProps({ }) - \ No newline at end of file + diff --git a/frontend/resources/vue/components/DashboardComponentMostRecentExecution.vue b/frontend/resources/vue/components/DashboardComponentMostRecentExecution.vue index bcf03d7..53c6fb8 100644 --- a/frontend/resources/vue/components/DashboardComponentMostRecentExecution.vue +++ b/frontend/resources/vue/components/DashboardComponentMostRecentExecution.vue @@ -1,8 +1,8 @@