diff options
23 files changed, 0 insertions, 1586 deletions
diff --git a/unifi-controller/README.md b/unifi-controller/README.md deleted file mode 100644 index 40d5be6..0000000 --- a/unifi-controller/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# UniFi Controller on ARM64 - -This role is for setting up a Ubiquiti Unifi Controller on ARM64 Ubuntu building on the [official docs](https://help.ui.com/hc/en-us/articles/220066768-UniFi-How-to-Install-and-Update-via-APT-on-Debian-or-Ubuntu) - -## Notes - -Unfortunately Unifi Controller relies on some pretty outdated packages, making them hard to find in recent Linux distributions. Specifically, the following packages need to be backported: - -- Mongo 3.6 -- libssl1.1 - -By specifying arm64 in the Xenial Mongo 3.6 apt repo, the diff --git a/unifi-controller/defaults/main.yaml b/unifi-controller/defaults/main.yaml deleted file mode 100644 index e066408..0000000 --- a/unifi-controller/defaults/main.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- - -unifi_controller_apt_packages: ["haveged", "openjdk-8-jre-headless", "mongodb-org-server", "unifi"] diff --git a/unifi-controller/tasks/main.yaml b/unifi-controller/tasks/main.yaml deleted file mode 100644 index 6880f20..0000000 --- a/unifi-controller/tasks/main.yaml +++ /dev/null @@ -1,39 +0,0 @@ ---- - -- name: add mongo apt key - become: yes - get_url: - url: "https://pgp.mongodb.com/server-3.6.asc" - dest: "/etc/apt/trusted.gpg.d/mongo-3.6.asc" - mode: "0644" - -- name: add mongo apt repo - become: yes - apt_repository: repo="deb [arch=arm64 signed-by=/etc/apt/trusted.gpg.d/mongo-3.6.asc] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.6 multiverse" - -- name: install libssl1.1 deb - become: yes - apt: deb="http://ports.ubuntu.com/pool/main/o/openssl1.0/libssl1.0.0_1.0.2n-1ubuntu5_{{arch}}.deb" - -# To accept codename change: -# 1. sudo apt update -# 2. sudo apt-get upgrade -y - -- name: add unifi apt key - become: yes - get_url: - url: "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x06e85760c0a52c50" - dest: "/etc/apt/trusted.gpg.d/unifi.asc" - mode: "0644" - -- name: add unifi apt repo - become: yes - apt_repository: repo="deb [arch=armhf signed-by=/etc/apt/trusted.gpg.d/unifi.asc] https://www.ui.com/downloads/unifi/debian unifi-7.4 ubiquiti" - -- name: update apt package cache - become: yes - apt: upgrade="dist" update_cache="yes" cache_valid_time="3600" - -- name: install unifi apt packages - become: yes - apt: name="{{unifi_controller_apt_packages}}" diff --git a/unifi-protect-viewport/README.md b/unifi-protect-viewport/README.md deleted file mode 100644 index c81cd75..0000000 --- a/unifi-protect-viewport/README.md +++ /dev/null @@ -1,283 +0,0 @@ -# unifi-protect-viewport - -Raspberry Pi 4 portrait touchscreen that shows a live UniFi Protect doorbell -stream when the doorbell is pressed or the screen is touched. Display backlight -is fully off at idle. - -## Hardware - -- Raspberry Pi 4 + PoE HAT -- HDMI touchscreen (800×480, mounted portrait) -- Wired Ethernet (PoE) -- UniFi Protect PoE doorbell - -## Behavior - -| Trigger | From | To | -|---------|------|----| -| Doorbell ring | IDLE | ACTIVE | -| Doorbell ring | ACTIVE | extend timer | -| Touch | IDLE | ACTIVE | -| Touch | ACTIVE | immediate OFF | -| 45s timeout | ACTIVE | IDLE | - -IDLE: backlight fully off, no visible pixels. -ACTIVE: backlight on, live RTSP stream playing fullscreen. - ---- - -## UniFi Protect Setup - -### Create a local API user - -The daemon authenticates directly with UniFi Protect using a **local account**. -UniFi SSO accounts (ubiquiti.com login) do not support API authentication and -will not work. - -1. In UniFi OS, go to **OS Settings → Admins & Users → Users** -2. Click **Add User** -3. Fill in: - - **Username**: choose a service account name (e.g. `unifi-protect-viewport`) - - **Password**: generate a strong password (store in Ansible Vault) - - **Account Type**: Local Access Only -4. Click **Add** - -### Assign the user a Protect role - -After creating the user: - -1. Go to **UniFi Protect → Settings → Manage → Admins** -2. Find the user you created and click the pencil icon -3. Set the Protect role to **View Only** - -View Only is sufficient. It grants: -- API login (`POST /api/auth/login`) -- Camera info and RTSP stream URLs (`GET /proxy/protect/api/cameras/{id}`) -- Live WebSocket event stream including ring events - -### Get the camera ID - -You need the camera's ID (a 24-character hex string) to filter ring events. -Run these two commands from any machine that can reach the Protect host: - -```bash -curl -sk -c /tmp/prot.cookies \ - -H 'Content-Type: application/json' \ - -d '{"username":"unifi-protect-viewport","password":"YOUR_PASSWORD"}' \ - https://YOUR_PROTECT_HOST/api/auth/login -``` - -```bash -curl -sk -b /tmp/prot.cookies https://YOUR_PROTECT_HOST/proxy/protect/api/cameras | python3 -c "import sys,json; [print(c['id'], c.get('type',''), c['name']) for c in json.load(sys.stdin)]" -``` - -Output looks like: -``` -aabbcc1122334455aabbcc00 UVC-G4-Doorbell Front Door -``` - -Copy the ID of the doorbell camera and set it as `unifi_protect_viewport_camera_id`. - -### Verify access - -Once the role is deployed: - -```bash -unifi-protect-viewport-debug test-protect -``` - ---- - -## DRM/KMS and the Framebuffer Console - -This role uses mpv with `--vo=drm` — direct DRM/KMS rendering, no X11 or -Wayland required. - -### DRM driver: use vc4-fkms-v3d (firmware KMS), not vc4-kms-v3d (full KMS) - -`vc4-fkms-v3d` uses the VideoCore GPU firmware for HDMI negotiation and mode -setting. `vc4-kms-v3d` (full KMS) attempts atomic mode setting directly from -the kernel, which on some displays causes a color-cycling test pattern that mpv -cannot override. Use `vc4-fkms-v3d`. The Ansible role sets this automatically. - -On Ubuntu Server the kernel framebuffer console (`fbcon`) holds the DRM device -while a getty is running on tty1. This will cause mpv to fail with a "device -busy" error on first boot. - -### Fix: disable fbcon on HDMI - -Add `video=HDMI-A-1:D` to `/boot/firmware/cmdline.txt`. This tells the kernel -not to bind the framebuffer console to the HDMI output, freeing the DRM device -for mpv. Since this is an appliance with no local console, this is the correct -permanent state. - -Edit the file on the Pi: - -```bash -sudo sed -i 's/$/ video=HDMI-A-1:D/' /boot/firmware/cmdline.txt -``` - -The connector name (`HDMI-A-1`) is configurable via `unifi_protect_viewport_drm_connector` if your hardware uses a different name. The Ansible role manages this automatically. - -Then reboot. The HDMI output will be dark at the console from that point on, -which is exactly what you want — the display is managed entirely by -unifi-protect-viewport. - -Verify mpv can open the DRM device after reboot: - -```bash -sudo -u unifi-protect-viewport mpv --vo=drm --length=3 /dev/zero -``` - -If it exits without a "device busy" error, DRM access is working. - ---- - -## Configuration - -### host_vars (plain) - -```yaml -unifi_protect_viewport_protect_host: "unifi.lan.example.com" # or IP address -unifi_protect_viewport_camera_id: "abcdef1234567890abcdef12" -unifi_protect_viewport_timeout: 45 -unifi_protect_viewport_touch_match: "" # substring match on evdev device name -unifi_protect_viewport_prebuffer_mode: "warm" # warm | cold -unifi_protect_viewport_display_backend: "vcgencmd" # vcgencmd | drm | panel -unifi_protect_viewport_orientation: 270 # degrees: 90 | 180 | 270 -unifi_protect_viewport_drm_device: "/dev/dri/card1" # RPi4: card1; RPi3: card0 -unifi_protect_viewport_drm_connector: "HDMI-A-1" -unifi_protect_viewport_drm_mode: "848x480" # nearest mode advertised by your display -``` - -### host_vars (vault) - -```yaml -vault_unifi_protect_viewport_protect_username: "unifi-protect-viewport" -vault_unifi_protect_viewport_protect_password: "secret" -``` - -Create and encrypt the vault file: - -```bash -ansible-vault create inventory/host_vars/HOSTNAME/vault.yaml -``` - ---- - -## Display Backends - -### vcgencmd (default) - -Uses `vcgencmd display_power 0/1` (Raspberry Pi firmware command). -Cuts the HDMI signal. Displays that support DPMS will cut their backlight. - -```yaml -unifi_protect_viewport_display_backend: "vcgencmd" -``` - -### drm - -Writes `0` / `max_brightness` to the first device under `/sys/class/backlight/`. -Use when the display exposes a sysfs backlight node. - -```yaml -unifi_protect_viewport_display_backend: "drm" -``` - -Check available devices on the Pi: `ls /sys/class/backlight/` - -### panel - -Alias for `drm`. Use when the backlight path is panel-specific and the `drm` -label would be confusing. - -### Testing - -```bash -unifi-protect-viewport-debug test-display # on -> 3s -> off -``` - ---- - -## Prebuffer Modes - -### warm (default, recommended) - -mpv starts at boot and continuously decodes the RTSP stream, rendering to the -framebuffer while the display stays off. Activation just turns on the backlight -— essentially zero latency. The live frame is already there. - -```yaml -unifi_protect_viewport_prebuffer_mode: "warm" -``` - -### cold - -mpv is launched only when the display activates, then stopped when idle. Higher -activation latency (stream connect + first keyframe decode). Use as fallback if -warm mode causes problems (power draw, memory pressure). - -```yaml -unifi_protect_viewport_prebuffer_mode: "cold" -``` - ---- - -## Touch Discovery - -The daemon discovers the touch device via evdev at startup and re-discovers it -automatically if it disappears and re-enumerates (e.g. USB replug). - -If `unifi_protect_viewport_touch_match` is set, the daemon matches on any device -whose name contains that substring (case-insensitive). Otherwise it finds the -first device advertising `ABS_MT_POSITION_X` (multitouch absolute position). - -To find the right match string for your display: - -```bash -unifi-protect-viewport-debug test-touch -``` - -Example output: -``` -/dev/input/event0: 'WaveShare WS170120' (multitouch=True, btn_touch=True) -``` - -```yaml -unifi_protect_viewport_touch_match: "waveshare" -``` - ---- - -## Debug Commands - -```bash -unifi-protect-viewport-debug show # turn display on -unifi-protect-viewport-debug hide # turn display off -unifi-protect-viewport-debug test-display # power cycle: on -> 3s -> off -unifi-protect-viewport-debug test-touch # list touch devices -unifi-protect-viewport-debug test-stream # fetch RTSP URL and play via mpv -unifi-protect-viewport-debug test-protect # verify Protect API + camera info -``` - ---- - -## Logs - -```bash -journalctl -u unifi-protect-viewport -f -``` - ---- - -## Dependencies - -Installed by the role: - -| Package | Purpose | -|---------|---------| -| `mpv` | DRM/KMS video playback | -| `python3-evdev` | Touch input via evdev | -| `python3-requests` | UniFi Protect HTTP API | -| `python3-websockets` | UniFi Protect WebSocket event stream | diff --git a/unifi-protect-viewport/defaults/main.yaml b/unifi-protect-viewport/defaults/main.yaml deleted file mode 100644 index f812cf2..0000000 --- a/unifi-protect-viewport/defaults/main.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- - -unifi_protect_viewport_user: "unifi-protect-viewport" -unifi_protect_viewport_protect_host: "" -unifi_protect_viewport_camera_id: "" -unifi_protect_viewport_timeout: 45 -unifi_protect_viewport_touch_match: "" -unifi_protect_viewport_orientation: 270 -unifi_protect_viewport_drm_connector: "HDMI-A-1" -unifi_protect_viewport_drm_device: "/dev/dri/card1" -unifi_protect_viewport_drm_mode: "" -unifi_protect_viewport_poe_fan: true -unifi_protect_viewport_poe_fan_temp0: 60000 -unifi_protect_viewport_poe_fan_temp1: 70000 -unifi_protect_viewport_poe_fan_temp2: 80000 -unifi_protect_viewport_poe_fan_temp3: 85000 - -# vault_unifi_protect_viewport_protect_username: — required, set in host_vars -# vault_unifi_protect_viewport_protect_password: — required, set in host_vars diff --git a/unifi-protect-viewport/files/unifi-protect-viewport-debug b/unifi-protect-viewport/files/unifi-protect-viewport-debug deleted file mode 100644 index 65f67b0..0000000 --- a/unifi-protect-viewport/files/unifi-protect-viewport-debug +++ /dev/null @@ -1,208 +0,0 @@ -#!/bin/bash -# unifi-protect-viewport-debug: CLI debug tool -# -# Commands: -# show Turn display on -# hide Turn display off -# test-display Test display power cycle (on -> 3s -> off) -# test-touch List touch devices and capabilities -# test-stream Fetch RTSP URL from Protect and play via mpv -# test-protect Test Protect API authentication and camera info - -set -e - -ENV_FILE="/etc/unifi-protect-viewport/unifi-protect-viewport.env" -CMD="${1:-help}" - -_load_env() { - if [ -f "$ENV_FILE" ]; then - set -a - # shellcheck disable=SC1090 - source "$ENV_FILE" - set +a - else - echo "Warning: $ENV_FILE not found" >&2 - fi -} - -_display_on() { - BACKLIGHT=$(ls /sys/class/backlight/ 2>/dev/null | head -1) - if [ -n "$BACKLIGHT" ]; then - MAX=$(cat "/sys/class/backlight/$BACKLIGHT/max_brightness") - echo "$MAX" > "/sys/class/backlight/$BACKLIGHT/brightness" - else - echo "No backlight device found" >&2 - fi -} - -_display_off() { - BACKLIGHT=$(ls /sys/class/backlight/ 2>/dev/null | head -1) - if [ -n "$BACKLIGHT" ]; then - echo "0" > "/sys/class/backlight/$BACKLIGHT/brightness" - else - echo "No backlight device found" >&2 - fi -} - -cmd_show() { - _load_env - echo "Turning display on..." - _display_on - echo "Display on" -} - -cmd_hide() { - _load_env - echo "Turning display off..." - _display_off - echo "Display off" -} - -cmd_test_display() { - _load_env - echo "Testing display power cycle..." - echo "ON..." - _display_on - sleep 3 - echo "OFF..." - _display_off - echo "Done" -} - -cmd_test_touch() { - echo "Touch devices:" - python3 - <<'PYEOF' -import evdev -for path in evdev.list_devices(): - try: - dev = evdev.InputDevice(path) - caps = dev.capabilities() - has_mt = ( - evdev.ecodes.EV_ABS in caps - and any( - code == evdev.ecodes.ABS_MT_POSITION_X - for code, _ in caps[evdev.ecodes.EV_ABS] - ) - ) - has_btn = ( - evdev.ecodes.EV_KEY in caps - and evdev.ecodes.BTN_TOUCH in [code for code, _ in caps.get(evdev.ecodes.EV_KEY, [])] - ) - print(f" {path}: {dev.name!r} (multitouch={has_mt}, btn_touch={has_btn})") - dev.close() - except Exception as e: - print(f" {path}: error: {e}") -PYEOF - echo "" - echo "To monitor live touch events interactively:" - echo " python3 -m evdev.evtest" -} - -cmd_test_stream() { - _load_env - if [ -z "$UNIFI_PROTECT_VIEWPORT_PROTECT_HOST" ]; then - echo "Error: UNIFI_PROTECT_VIEWPORT_PROTECT_HOST not set" >&2 - exit 1 - fi - - echo "Fetching RTSP URL from $UNIFI_PROTECT_VIEWPORT_PROTECT_HOST..." - RTSP_URL=$(python3 - <<PYEOF -import os, sys, requests, urllib3 -urllib3.disable_warnings() -host = os.environ['UNIFI_PROTECT_VIEWPORT_PROTECT_HOST'] -user = os.environ['UNIFI_PROTECT_VIEWPORT_PROTECT_USERNAME'] -passwd = os.environ['UNIFI_PROTECT_VIEWPORT_PROTECT_PASSWORD'] -camera_id = os.environ['UNIFI_PROTECT_VIEWPORT_CAMERA_ID'] -s = requests.Session() -s.verify = False -r = s.post(f'https://{host}/api/auth/login', - json={'username': user, 'password': passwd}, timeout=10) -r.raise_for_status() -r2 = s.get(f'https://{host}/proxy/protect/api/cameras/{camera_id}', timeout=10) -r2.raise_for_status() -for ch in r2.json().get('channels', []): - if ch.get('isRtspEnabled') and ch.get('rtspAlias'): - print(f"rtsp://{host}:7447/{ch['rtspAlias']}") - sys.exit(0) -print('no-rtsp-url', file=sys.stderr) -sys.exit(1) -PYEOF - ) - - if [ -z "$RTSP_URL" ]; then - echo "Failed to get RTSP URL" >&2 - exit 1 - fi - - echo "Playing: $RTSP_URL" - echo "(press q to quit)" - mpv \ - --vo=drm \ - "--video-rotate=${UNIFI_PROTECT_VIEWPORT_ORIENTATION:-270}" \ - --fullscreen \ - --no-border \ - --no-osc \ - --no-input-default-bindings \ - --really-quiet \ - "$RTSP_URL" -} - -cmd_test_protect() { - _load_env - if [ -z "$UNIFI_PROTECT_VIEWPORT_PROTECT_HOST" ]; then - echo "Error: UNIFI_PROTECT_VIEWPORT_PROTECT_HOST not set" >&2 - exit 1 - fi - - python3 - <<PYEOF -import os, requests, urllib3 -urllib3.disable_warnings() -host = os.environ['UNIFI_PROTECT_VIEWPORT_PROTECT_HOST'] -user = os.environ['UNIFI_PROTECT_VIEWPORT_PROTECT_USERNAME'] -passwd = os.environ['UNIFI_PROTECT_VIEWPORT_PROTECT_PASSWORD'] -camera_id = os.environ['UNIFI_PROTECT_VIEWPORT_CAMERA_ID'] -s = requests.Session() -s.verify = False - -print(f"Connecting to {host}...") -r = s.post(f'https://{host}/api/auth/login', - json={'username': user, 'password': passwd}, timeout=10) -r.raise_for_status() -print("Authentication: OK") - -r2 = s.get(f'https://{host}/proxy/protect/api/cameras/{camera_id}', timeout=10) -r2.raise_for_status() -data = r2.json() -print(f"Camera name: {data.get('name', 'unknown')}") -print(f"Camera type: {data.get('type', 'unknown')}") -channels = [ch for ch in data.get('channels', []) if ch.get('isRtspEnabled')] -print(f"RTSP channels: {len(channels)}") -for ch in channels: - name = ch.get('name', '') - alias = ch.get('rtspAlias', '') - print(f" {name}: rtsp://{host}:7447/{alias}") -print("Protect connection: OK") -PYEOF -} - -case "$CMD" in - show) cmd_show ;; - hide) cmd_hide ;; - test-display) cmd_test_display ;; - test-touch) cmd_test_touch ;; - test-stream) cmd_test_stream ;; - test-protect) cmd_test_protect ;; - help|*) - echo "Usage: unifi-protect-viewport-debug <command>" - echo "" - echo "Commands:" - echo " show Turn display on" - echo " hide Turn display off" - echo " test-display Test display power cycle (on -> 3s -> off)" - echo " test-touch List touch devices and capabilities" - echo " test-stream Fetch RTSP URL and play via mpv" - echo " test-protect Test Protect API connection and camera info" - echo "" - echo "Config: $ENV_FILE" - ;; -esac diff --git a/unifi-protect-viewport/files/unifi-protect-viewport.py b/unifi-protect-viewport/files/unifi-protect-viewport.py deleted file mode 100644 index f5283ae..0000000 --- a/unifi-protect-viewport/files/unifi-protect-viewport.py +++ /dev/null @@ -1,518 +0,0 @@ -#!/usr/bin/env python3 -""" -unifi-protect-viewport: UniFi Protect doorbell display daemon - -Connects to UniFi Protect, listens for doorbell ring events, and manages -display power + video playback on a Raspberry Pi portrait touchscreen. - -States: - IDLE — display backlight off, no playback - ACTIVE — display on, live RTSP stream playing via mpv - -Transitions: - ring event : IDLE -> ACTIVE (or extend timer if already ACTIVE) - touch (idle) : IDLE -> ACTIVE - touch (active): ACTIVE -> IDLE (immediate) - timeout : ACTIVE -> IDLE (after unifi_protect_viewport_timeout seconds) -""" - -import asyncio -import json -import logging -import os -import signal -import ssl -import struct -import subprocess -import sys -import time -import zlib -from enum import Enum -from pathlib import Path - -import evdev -import requests -import urllib3 -import websockets - -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - -_SSL_CTX = ssl.create_default_context() -_SSL_CTX.check_hostname = False -_SSL_CTX.verify_mode = ssl.CERT_NONE - -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s %(levelname)s %(message)s", - stream=sys.stdout, -) -log = logging.getLogger("unifi-protect-viewport") - - -class State(Enum): - IDLE = "idle" - ACTIVE = "active" - - -class Config: - def __init__(self): - self.protect_host = os.environ["UNIFI_PROTECT_VIEWPORT_PROTECT_HOST"] - self.protect_username = os.environ["UNIFI_PROTECT_VIEWPORT_PROTECT_USERNAME"] - self.protect_password = os.environ["UNIFI_PROTECT_VIEWPORT_PROTECT_PASSWORD"] - self.camera_id = os.environ["UNIFI_PROTECT_VIEWPORT_CAMERA_ID"] - self.timeout = int(os.environ.get("UNIFI_PROTECT_VIEWPORT_TIMEOUT", "45")) - self.touch_match = os.environ.get("UNIFI_PROTECT_VIEWPORT_TOUCH_MATCH", "") - self.orientation = int(os.environ.get("UNIFI_PROTECT_VIEWPORT_ORIENTATION", "270")) - self.drm_device = os.environ.get("UNIFI_PROTECT_VIEWPORT_DRM_DEVICE", "/dev/dri/card1") - self.drm_connector = os.environ.get("UNIFI_PROTECT_VIEWPORT_DRM_CONNECTOR", "HDMI-A-1") - self.drm_mode = os.environ.get("UNIFI_PROTECT_VIEWPORT_DRM_MODE", "") - self.rtsp_url = None - - def log_config(self): - log.info( - "Config: protect_host=%s camera_id=%s timeout=%ds " - "orientation=%d drm_device=%s drm_connector=%s drm_mode=%s", - self.protect_host, - self.camera_id, - self.timeout, - self.orientation, - self.drm_device, - self.drm_connector, - self.drm_mode, - ) - - -class DisplayController: - """Controls display backlight via /sys/class/backlight sysfs (DRM/KMS).""" - - def on(self): - log.info("Display: ON") - self._sysfs_set(True) - - def off(self): - log.info("Display: OFF") - self._sysfs_set(False) - - def _sysfs_set(self, enabled: bool): - paths = sorted(Path("/sys/class/backlight").glob("*")) - if not paths: - log.warning("No backlight device found in /sys/class/backlight") - return - path = paths[0] - try: - if enabled: - max_b = int((path / "max_brightness").read_text().strip()) - (path / "brightness").write_text(str(max_b)) - log.info("Backlight %s: brightness -> %d (max)", path.name, max_b) - else: - (path / "brightness").write_text("0") - log.info("Backlight %s: brightness -> 0", path.name) - except Exception as exc: - log.error("Backlight %s failed: %s", "on" if enabled else "off", exc) - - -class ProtectClient: - """Handles UniFi Protect authentication and camera info retrieval.""" - - def __init__(self, config: Config): - self.config = config - self.session = requests.Session() - self.session.verify = False - self.base_url = f"https://{config.protect_host}" - - def authenticate(self) -> bool: - log.info("Protect: authenticating at %s", self.config.protect_host) - try: - resp = self.session.post( - f"{self.base_url}/api/auth/login", - json={ - "username": self.config.protect_username, - "password": self.config.protect_password, - }, - timeout=10, - ) - resp.raise_for_status() - log.info("Protect: authentication successful") - return True - except Exception as exc: - log.error("Protect: authentication failed: %s", exc) - return False - - def get_last_update_id(self) -> str | None: - try: - resp = self.session.get( - f"{self.base_url}/proxy/protect/api/bootstrap", - timeout=10, - ) - resp.raise_for_status() - uid = resp.json().get("lastUpdateId") - log.info("Protect: lastUpdateId=%s", uid) - return uid - except Exception as exc: - log.warning("Protect: failed to fetch lastUpdateId: %s", exc) - return None - - def get_camera_rtsp_url(self) -> str | None: - log.info("Protect: fetching camera info for id=%s", self.config.camera_id) - try: - resp = self.session.get( - f"{self.base_url}/proxy/protect/api/cameras/{self.config.camera_id}", - timeout=10, - ) - resp.raise_for_status() - data = resp.json() - for channel in data.get("channels", []): - if channel.get("isRtspEnabled"): - alias = channel.get("rtspAlias") - if alias: - url = f"rtsp://{self.config.protect_host}:7447/{alias}" - log.info("Protect: RTSP URL: %s", url) - return url - log.error("Protect: no enabled RTSP channel for camera %s", self.config.camera_id) - return None - except Exception as exc: - log.error("Protect: failed to fetch camera info: %s", exc) - return None - - def cookie_header(self) -> dict: - cookies = self.session.cookies.get_dict() - return {"Cookie": "; ".join(f"{k}={v}" for k, v in cookies.items())} - - -def decode_protect_packets(data: bytes) -> list: - """ - Decode all binary UniFi Protect WebSocket packets from a single message. - - Each WebSocket message contains one or more concatenated packets. - Each packet header (8 bytes): - [0] packet_type 1=action, 2=data - [1] payload_format 1=JSON, 2=UTF8, 3=buffer - [2] deflated 0 or 1 - [3] unused - [4:8] payload_size big-endian uint32 - - Followed by payload_size bytes of payload. - """ - packets = [] - offset = 0 - while offset + 8 <= len(data): - packet_type = data[offset] - payload_format = data[offset + 1] - deflated = bool(data[offset + 2]) - payload_size = struct.unpack(">I", data[offset + 4:offset + 8])[0] - end = offset + 8 + payload_size - if end > len(data): - break - payload_bytes = data[offset + 8:end] - if deflated: - try: - payload_bytes = zlib.decompress(payload_bytes) - except Exception as exc: - log.debug("Packet decompression failed: %s", exc) - offset = end - continue - if payload_format in (1, 2): - try: - packets.append({"packet_type": packet_type, "payload": json.loads(payload_bytes)}) - except Exception: - pass - offset = end - return packets - - -class DoorbellViewport: - def __init__(self, config: Config): - self.config = config - self.state = State.IDLE - self.display = DisplayController() - self.protect = ProtectClient(config) - self.mpv_proc = None - self.timer_task = None - self._running = True - - async def run(self): - log.info("unifi-protect-viewport starting") - self.config.log_config() - - self.display.off() - await self._login_and_fetch_rtsp() - - if not self.config.rtsp_url: - log.warning("No RTSP URL at startup; will retry after reconnect") - - await asyncio.gather( - self.protect_listener(), - self.touch_listener(), - ) - - async def _login_and_fetch_rtsp(self): - ok = await asyncio.to_thread(self.protect.authenticate) - if ok and not self.config.rtsp_url: - url = await asyncio.to_thread(self.protect.get_camera_rtsp_url) - if url: - self.config.rtsp_url = url - - async def activate(self): - """Transition IDLE->ACTIVE, or extend timer if already ACTIVE.""" - was_idle = self.state == State.IDLE - self.state = State.ACTIVE - - if self.timer_task and not self.timer_task.done(): - self.timer_task.cancel() - self.timer_task = asyncio.create_task(self._timeout_task()) - - if was_idle: - log.info("State: IDLE -> ACTIVE") - await self.start_mpv() - self.display.on() - else: - log.info("State: ACTIVE -> timer extended") - - async def deactivate(self): - """Transition ACTIVE->IDLE: stop playback and turn off display.""" - if self.state == State.IDLE: - return - self.state = State.IDLE - log.info("State: ACTIVE -> IDLE") - - if self.timer_task and not self.timer_task.done(): - self.timer_task.cancel() - self.timer_task = None - - self.display.off() - await self.stop_mpv() - - async def on_touch(self): - log.info("Event: touch input (state=%s)", self.state.value) - if self.state == State.IDLE: - await self.activate() - else: - await self.deactivate() - - async def _timeout_task(self): - await asyncio.sleep(self.config.timeout) - log.info("Timeout: %ds elapsed", self.config.timeout) - await self.deactivate() - - async def start_mpv(self): - if self.mpv_proc and self.mpv_proc.returncode is None: - log.debug("mpv already running (pid=%d)", self.mpv_proc.pid) - return - if not self.config.rtsp_url: - log.error("Cannot start mpv: no RTSP URL") - return - - cmd = [ - "mpv", - "--vo=drm", - f"--drm-device={self.config.drm_device}", - f"--drm-connector={self.config.drm_connector}", - *( - [f"--drm-mode={self.config.drm_mode}"] - if self.config.drm_mode else [] - ), - f"--video-rotate={self.config.orientation}", - "--fullscreen", - "--no-border", - "--no-osc", - "--no-audio", - "--no-input-default-bindings", - "--no-config", - "--really-quiet", - "--loop=no", - "--cache=yes", - "--demuxer-max-bytes=1M", - "--hwdec=no", - self.config.rtsp_url, - ] - log.info("Starting mpv: %s", " ".join(cmd)) - try: - self.mpv_proc = await asyncio.create_subprocess_exec( - *cmd, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - log.info("mpv started (pid=%d)", self.mpv_proc.pid) - asyncio.create_task(self._watch_mpv()) - except Exception as exc: - log.error("Failed to start mpv: %s", exc) - - async def stop_mpv(self): - if not self.mpv_proc or self.mpv_proc.returncode is not None: - return - pid = self.mpv_proc.pid - log.info("Stopping mpv (pid=%d)", pid) - try: - self.mpv_proc.terminate() - await asyncio.wait_for(self.mpv_proc.wait(), timeout=3.0) - except asyncio.TimeoutError: - log.warning("mpv (pid=%d) did not terminate cleanly, killing", pid) - self.mpv_proc.kill() - await self.mpv_proc.wait() - self.mpv_proc = None - - async def _watch_mpv(self): - """Restart mpv on unexpected exit while display is active.""" - proc = self.mpv_proc - if not proc: - return - await proc.wait() - rc = proc.returncode - if not self._running: - return - if rc != 0: - log.warning("mpv exited unexpectedly (rc=%d)", rc) - if self.mpv_proc is proc: - self.mpv_proc = None - await asyncio.sleep(2) - if self.state == State.ACTIVE: - log.info("Restarting mpv after unexpected exit") - await self.start_mpv() - - async def protect_listener(self): - """Maintain UniFi Protect WebSocket connection with exponential backoff.""" - backoff = 1 - while self._running: - try: - await self._connect_protect_ws() - backoff = 1 - except Exception as exc: - log.error("Protect WebSocket error: %s", exc) - if not self._running: - break - log.info("Protect: reconnecting in %ds", backoff) - await asyncio.sleep(backoff) - backoff = min(backoff * 2, 60) - await self._login_and_fetch_rtsp() - - async def _connect_protect_ws(self): - log.info("Protect: connecting to WebSocket") - last_update_id = await asyncio.to_thread(self.protect.get_last_update_id) - ws_url = f"wss://{self.config.protect_host}/proxy/protect/ws/updates" - if last_update_id: - ws_url += f"?lastUpdateId={last_update_id}" - - async with websockets.connect( - ws_url, - extra_headers=self.protect.cookie_header(), - ssl=_SSL_CTX, - ping_interval=20, - ping_timeout=10, - ) as ws: - log.info("Protect: WebSocket connected") - pending_action = None - async for msg in ws: - if isinstance(msg, bytes): - packets = decode_protect_packets(msg) - for pkt in packets: - if pkt["packet_type"] == 1: - pending_action = pkt["payload"] - elif pkt["packet_type"] == 2 and pending_action is not None: - await self._handle_protect_event(pending_action, pkt["payload"]) - pending_action = None - - async def _handle_protect_event(self, action: dict, data: dict): - if action.get("action") != "add": - return - if action.get("modelKey") != "event": - return - event_type = data.get("type") - camera = data.get("camera") or data.get("cameraId") or "" - if event_type == "ring" and camera == self.config.camera_id: - log.info("Protect: ring event from camera %s", camera) - await self.activate() - - async def touch_listener(self): - """Monitor evdev touch input with automatic device re-discovery.""" - while self._running: - try: - await self._monitor_touch_device() - except Exception as exc: - log.error("Touch device error: %s", exc) - if not self._running: - break - log.info("Touch: retrying device discovery in 10s") - await asyncio.sleep(10) - - def _find_touch_device_sync(self): - """Find touch device by name match or multitouch capability.""" - match = self.config.touch_match.lower() - for path in evdev.list_devices(): - try: - dev = evdev.InputDevice(path) - if match and match in dev.name.lower(): - log.info("Touch: matched by name: %s (%s)", dev.name, path) - return dev - caps = dev.capabilities() - if evdev.ecodes.EV_ABS in caps: - axis_codes = [code for code, _ in caps[evdev.ecodes.EV_ABS]] - if evdev.ecodes.ABS_MT_POSITION_X in axis_codes: - log.info("Touch: matched by capability: %s (%s)", dev.name, path) - return dev - dev.close() - except Exception: - continue - return None - - async def _monitor_touch_device(self): - dev = await asyncio.to_thread(self._find_touch_device_sync) - if not dev: - log.warning("Touch: no device found") - await asyncio.sleep(10) - return - - log.info("Touch: monitoring %s (%s)", dev.name, dev.path) - last_touch_time = 0.0 - try: - async for event in dev.async_read_loop(): - if not self._running: - break - triggered = ( - (event.type == evdev.ecodes.EV_ABS - and event.code == evdev.ecodes.ABS_MT_TRACKING_ID - and event.value >= 0) - or (event.type == evdev.ecodes.EV_KEY - and event.code == evdev.ecodes.BTN_TOUCH - and event.value == 1) - ) - if triggered: - now = time.monotonic() - if now - last_touch_time > 0.5: # 500ms debounce - last_touch_time = now - await self.on_touch() - finally: - dev.close() - - -async def main(): - try: - config = Config() - except KeyError as exc: - log.error("Missing required environment variable: %s", exc) - sys.exit(1) - - viewport = DoorbellViewport(config) - loop = asyncio.get_running_loop() - - def shutdown(sig): - log.info("Signal %s received, shutting down", sig.name) - viewport._running = False - for task in asyncio.all_tasks(loop): - task.cancel() - - for sig in (signal.SIGTERM, signal.SIGINT): - loop.add_signal_handler(sig, lambda s=sig: shutdown(s)) - - try: - await viewport.run() - except asyncio.CancelledError: - pass - finally: - log.info("Shutting down") - await viewport.stop_mpv() - viewport.display.off() - log.info("unifi-protect-viewport stopped") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/unifi-protect-viewport/handlers/main.yaml b/unifi-protect-viewport/handlers/main.yaml deleted file mode 100644 index 833cc39..0000000 --- a/unifi-protect-viewport/handlers/main.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- - -- name: restart unifi-protect-viewport - become: yes - systemd: name="unifi-protect-viewport.service" state="restarted" daemon_reload="yes" diff --git a/unifi-protect-viewport/tasks/main.yaml b/unifi-protect-viewport/tasks/main.yaml deleted file mode 100644 index 9a51e0b..0000000 --- a/unifi-protect-viewport/tasks/main.yaml +++ /dev/null @@ -1,135 +0,0 @@ ---- - -- name: add unifi-protect-viewport user - become: yes - user: - name: "{{unifi_protect_viewport_user}}" - shell: "/bin/false" - system: "yes" - create_home: "no" - groups: "video,input,render,tty" - append: "yes" - -- name: allow unifi-protect-viewport user to control sysfs backlight - become: yes - copy: - dest: "/etc/udev/rules.d/99-unifi-protect-viewport-backlight.rules" - owner: "root" - group: "root" - mode: "0644" - content: | - SUBSYSTEM=="backlight", ACTION=="add", RUN+="/bin/chgrp video /sys/class/backlight/%k/brightness", RUN+="/bin/chmod g+w /sys/class/backlight/%k/brightness" - -- name: set backlight brightness writable by video group - become: yes - file: - path: "/sys/class/backlight/rpi_backlight/brightness" - group: "video" - mode: "0664" - -- name: apt install unifi-protect-viewport dependencies - become: yes - apt: name="{{item}}" - notify: restart unifi-protect-viewport - loop: - - "mpv" - - "python3-evdev" - - "python3-requests" - - "python3-websockets" - -- name: create unifi-protect-viewport config dir - become: yes - file: - path: "/etc/unifi-protect-viewport" - owner: "root" - group: "{{unifi_protect_viewport_user}}" - mode: "0750" - state: "directory" - -- name: install unifi-protect-viewport daemon - become: yes - copy: - src: "unifi-protect-viewport.py" - dest: "/usr/local/bin/unifi-protect-viewport" - owner: "root" - group: "root" - mode: "0755" - notify: restart unifi-protect-viewport - -- name: install unifi-protect-viewport debug tool - become: yes - copy: - src: "unifi-protect-viewport-debug" - dest: "/usr/local/bin/unifi-protect-viewport-debug" - owner: "root" - group: "root" - mode: "0755" - -- name: configure unifi-protect-viewport environment - become: yes - template: - src: "unifi-protect-viewport.env.j2" - dest: "/etc/unifi-protect-viewport/unifi-protect-viewport.env" - owner: "root" - group: "{{unifi_protect_viewport_user}}" - mode: "0640" - notify: restart unifi-protect-viewport - no_log: true - -- name: install unifi-protect-viewport systemd service - become: yes - template: - src: "unifi-protect-viewport.service.j2" - dest: "/lib/systemd/system/unifi-protect-viewport.service" - mode: "0644" - notify: restart unifi-protect-viewport - -- name: configure PoE HAT fan temperature thresholds - become: yes - blockinfile: - path: "/boot/firmware/config.txt" - marker: "# {mark} unifi-protect-viewport poe fan" - block: | - dtoverlay=rpi-poe - dtparam=poe_fan_temp0={{unifi_protect_viewport_poe_fan_temp0}},poe_fan_temp0_hyst=5000 - dtparam=poe_fan_temp1={{unifi_protect_viewport_poe_fan_temp1}},poe_fan_temp1_hyst=5000 - dtparam=poe_fan_temp2={{unifi_protect_viewport_poe_fan_temp2}},poe_fan_temp2_hyst=5000 - dtparam=poe_fan_temp3={{unifi_protect_viewport_poe_fan_temp3}},poe_fan_temp3_hyst=5000 - when: unifi_protect_viewport_poe_fan - register: fan_result - -- name: remind to reboot for PoE fan config to take effect - debug: - msg: "config.txt updated — reboot {{inventory_hostname}} for PoE HAT fan thresholds to take effect" - when: fan_result is not skipped and fan_result.changed - -- name: enable vc4 KMS DRM driver - become: yes - lineinfile: - path: "/boot/firmware/config.txt" - line: "dtoverlay=vc4-fkms-v3d" - regexp: "^dtoverlay=vc4-(f?)kms-v3d" - register: vc4_result - -- name: remind to reboot for vc4 DRM driver to take effect - debug: - msg: "config.txt updated — reboot {{inventory_hostname}} for dtoverlay=vc4-fkms-v3d to take effect" - when: vc4_result.changed - -- name: disable fbcon to free DRM device for mpv - become: yes - lineinfile: - path: "/boot/firmware/cmdline.txt" - backrefs: yes - regexp: "^((?!.*fbcon=map:99).*)$" - line: "\\1 fbcon=map:99" - register: cmdline_result - -- name: remind to reboot for cmdline.txt change to take effect - debug: - msg: "cmdline.txt updated — reboot {{inventory_hostname}} for fbcon=map:99 to take effect" - when: cmdline_result.changed - -- name: ensure unifi-protect-viewport is started - become: yes - systemd: name="unifi-protect-viewport.service" enabled="yes" state="started" daemon_reload="yes" diff --git a/unifi-protect-viewport/templates/unifi-protect-viewport.env.j2 b/unifi-protect-viewport/templates/unifi-protect-viewport.env.j2 deleted file mode 100644 index 6d5dc9c..0000000 --- a/unifi-protect-viewport/templates/unifi-protect-viewport.env.j2 +++ /dev/null @@ -1,10 +0,0 @@ -UNIFI_PROTECT_VIEWPORT_PROTECT_HOST={{unifi_protect_viewport_protect_host}} -UNIFI_PROTECT_VIEWPORT_PROTECT_USERNAME={{vault_unifi_protect_viewport_protect_username}} -UNIFI_PROTECT_VIEWPORT_PROTECT_PASSWORD={{vault_unifi_protect_viewport_protect_password}} -UNIFI_PROTECT_VIEWPORT_CAMERA_ID={{unifi_protect_viewport_camera_id}} -UNIFI_PROTECT_VIEWPORT_TIMEOUT={{unifi_protect_viewport_timeout}} -UNIFI_PROTECT_VIEWPORT_TOUCH_MATCH={{unifi_protect_viewport_touch_match}} -UNIFI_PROTECT_VIEWPORT_ORIENTATION={{unifi_protect_viewport_orientation}} -UNIFI_PROTECT_VIEWPORT_DRM_DEVICE={{unifi_protect_viewport_drm_device}} -UNIFI_PROTECT_VIEWPORT_DRM_CONNECTOR={{unifi_protect_viewport_drm_connector}} -UNIFI_PROTECT_VIEWPORT_DRM_MODE={{unifi_protect_viewport_drm_mode}} diff --git a/unifi-protect-viewport/templates/unifi-protect-viewport.service.j2 b/unifi-protect-viewport/templates/unifi-protect-viewport.service.j2 deleted file mode 100644 index 43dfce6..0000000 --- a/unifi-protect-viewport/templates/unifi-protect-viewport.service.j2 +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=UniFi Protect Viewport -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -User={{unifi_protect_viewport_user}} -Group={{unifi_protect_viewport_user}} -EnvironmentFile=/etc/unifi-protect-viewport/unifi-protect-viewport.env -ExecStart=/usr/local/bin/unifi-protect-viewport -Restart=always -RestartSec=5 -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=multi-user.target diff --git a/writefreely/aws-s3-backup/files/writefreely-s3-backup-lifecycle.json b/writefreely/aws-s3-backup/files/writefreely-s3-backup-lifecycle.json deleted file mode 100644 index 44036c0..0000000 --- a/writefreely/aws-s3-backup/files/writefreely-s3-backup-lifecycle.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "Rules": [ - { - "ID": "expiration", - "Filter": {}, - "Status": "Enabled", - "NoncurrentVersionExpiration": { - "NoncurrentDays": 30 - } - } - ] -} diff --git a/writefreely/aws-s3-backup/files/writefreely-s3-backup.sh b/writefreely/aws-s3-backup/files/writefreely-s3-backup.sh deleted file mode 100644 index 402d6fe..0000000 --- a/writefreely/aws-s3-backup/files/writefreely-s3-backup.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -BUCKET=$1 -BACKUP_DIR=$2 -BACKUP_TAR="/tmp/$BUCKET.tgz" - -tar -zc -f $BACKUP_TAR $BACKUP_DIR -aws s3 mb "s3://$BUCKET/" -aws s3api put-bucket-versioning --bucket "$BUCKET" --versioning-configuration Status=Enabled -aws s3api put-bucket-lifecycle-configuration --bucket "$BUCKET" --lifecycle-configuration "file:///usr/local/share/writefreely-s3-backup-lifecycle.json" -aws s3 cp $BACKUP_TAR "s3://$BUCKET/" - -rm $BACKUP_TAR diff --git a/writefreely/aws-s3-backup/handlers/main.yaml b/writefreely/aws-s3-backup/handlers/main.yaml deleted file mode 100644 index 0e39754..0000000 --- a/writefreely/aws-s3-backup/handlers/main.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- - -- name: reload s3 backup service - systemd: name="writefreely-s3-backup@{{writefreely_s3_backup_bucket}}.service" enabled="yes" daemon_reload="yes" - become: yes diff --git a/writefreely/aws-s3-backup/meta/main.yaml b/writefreely/aws-s3-backup/meta/main.yaml deleted file mode 100644 index f9c6f63..0000000 --- a/writefreely/aws-s3-backup/meta/main.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- - -dependencies: - - aws-s3-backup diff --git a/writefreely/aws-s3-backup/tasks/main.yaml b/writefreely/aws-s3-backup/tasks/main.yaml deleted file mode 100644 index e9b005e..0000000 --- a/writefreely/aws-s3-backup/tasks/main.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- - -- name: create writefreely s3 backup shell script - become: yes - copy: - src: "writefreely-s3-backup.sh" - dest: "/usr/local/bin/writefreely-s3-backup.sh" - mode: "0755" - -- name: create s3 backup lifesycle json file - become: yes - copy: - src: "writefreely-s3-backup-lifecycle.json" - dest: "/usr/local/share/writefreely-s3-backup-lifecycle.json" - mode: "0755" - -- name: configure writefreely s3 backup systemd service - become: yes - template: - src: "[email protected]" - dest: "/lib/systemd/system/writefreely-s3-backup@{{writefreely_s3_backup_bucket}}.service" - notify: reload s3 backup service - -- name: ensure writefreely s3 backup service is started - become: yes - systemd: name="writefreely-s3-backup@{{writefreely_s3_backup_bucket}}.service" enabled="yes" diff --git a/writefreely/aws-s3-backup/templates/[email protected] b/writefreely/aws-s3-backup/templates/[email protected] deleted file mode 100644 index 2f9d272..0000000 --- a/writefreely/aws-s3-backup/templates/[email protected] +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=Writefreely s3 backup for "%I" - -[Service] -Type=oneshot -ExecStart=/usr/local/bin/writefreely-s3-backup.sh %i "{{writefreely_s3_backup_dir}}" - -[Install] -WantedBy=aws-s3-backup.target diff --git a/writefreely/server/defaults/main.yaml b/writefreely/server/defaults/main.yaml deleted file mode 100644 index 4acf148..0000000 --- a/writefreely/server/defaults/main.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- - -writefreely_user: "writefreely" -writefreely_arch: "arm64" -writefreely_version: "0.16.0" -# https://github.com/writeas/writefreely/releases -writefreely_tar: "https://github.com/writeas/writefreely/releases/download/v{{writefreely_version}}/writefreely_{{writefreely_version}}_linux_{{writefreely_arch}}.tar.gz" - -writefreely_dir: "/var/writefreely" -writefreely_port: "8080" -writefreely_url: "https://{{writefreely_instance}}" -writefreely_site_name: "{{writefreely_instance}}" diff --git a/writefreely/server/handlers/main.yaml b/writefreely/server/handlers/main.yaml deleted file mode 100644 index 855b3e0..0000000 --- a/writefreely/server/handlers/main.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- - -- name: reload service - become: yes - systemd: name="writefreely@{{writefreely_instance}}.service" enabled="yes" daemon_reload="yes" - -- name: restart service - become: yes - systemd: name="writefreely@{{writefreely_instance}}.service" state="restarted" enabled="yes" daemon_reload="yes" - -- name: migrate db - become: yes - become_user: "{{writefreely_user}}" - command: "writefreely db migrate" - args: - chdir: "{{writefreely_dir}}/{{writefreely_instance}}/" diff --git a/writefreely/server/tasks/main.yaml b/writefreely/server/tasks/main.yaml deleted file mode 100644 index 7fe608a..0000000 --- a/writefreely/server/tasks/main.yaml +++ /dev/null @@ -1,102 +0,0 @@ ---- - -- name: add writefreely user - become: yes - user: name="{{writefreely_user}}" shell="/bin/false" system="yes" create_home="no" - -- name: unarchive writefreely - become: yes - unarchive: - remote_src: yes - src: "{{writefreely_tar}}" - dest: "/tmp/" - creates: "/tmp/writefreely/" - owner: "root" - group: "root" - -- name: install writefreely binary - become: yes - copy: - src: "/tmp/writefreely/writefreely" - dest: "/usr/local/bin/" - remote_src: yes - owner: "root" - group: "root" - mode: "0755" - notify: - - migrate db - - restart service - -- name: create instance dir - become: yes - file: - path: "{{item}}" - state: "directory" - owner: "{{writefreely_user}}" - group: "{{writefreely_user}}" - mode: "0755" - notify: restart service - loop: - - "{{writefreely_dir}}" - - "{{writefreely_dir}}/{{writefreely_instance}}" - -- name: install static content - become: yes - copy: - src: "/tmp/writefreely/" - remote_src: yes - dest: "{{writefreely_dir}}/{{writefreely_instance}}/" - owner: "{{writefreely_user}}" - group: "{{writefreely_user}}" - mode: "0755" - notify: restart service - -- name: configure - become: yes - template: - src: "config.ini.j2" - dest: "{{writefreely_dir}}/{{writefreely_instance}}/config.ini" - mode: "0644" - owner: "{{writefreely_user}}" - group: "{{writefreely_user}}" - notify: restart service - -- name: generate keys - become: yes - become_user: "{{writefreely_user}}" - command: "writefreely keys generate" - args: - chdir: "{{writefreely_dir}}/{{writefreely_instance}}/" - changed_when: false - -- name: check if db exists - stat: - path: "{{writefreely_dir}}/{{writefreely_instance}}/writefreely.db" - register: db_file - -- name: create db - become: yes - become_user: "{{writefreely_user}}" - command: "writefreely db init" - args: - chdir: "{{writefreely_dir}}/{{writefreely_instance}}/" - when: not db_file.stat.exists - -- name: create admin - become: yes - become_user: "{{writefreely_user}}" - command: "writefreely --create-admin admin:admin" - args: - chdir: "{{writefreely_dir}}/{{writefreely_instance}}/" - when: not db_file.stat.exists - -- name: install systemd service - become: yes - template: - src: "[email protected]" - dest: "/lib/systemd/system/[email protected]" - notify: reload service - -- name: ensure service is started - become: yes - systemd: name="writefreely@{{writefreely_instance}}.service" enabled="yes" state="started" diff --git a/writefreely/server/templates/config.ini.j2 b/writefreely/server/templates/config.ini.j2 deleted file mode 100644 index bb5fe7f..0000000 --- a/writefreely/server/templates/config.ini.j2 +++ /dev/null @@ -1,71 +0,0 @@ -[server] -hidden_host = -port = {{writefreely_port}} -bind = localhost -tls_cert_path = -tls_key_path = -autocert = false -templates_parent_dir = -static_parent_dir = -pages_parent_dir = -keys_parent_dir = -hash_seed = - -[database] -type = sqlite3 -filename = writefreely.db -username = -password = -database = writefreely -host = localhost -port = 3306 - -[app] -site_name = {{writefreely_site_name}} -site_description = -host = {{writefreely_url}} -theme = write -editor = -disable_js = false -webfonts = true -landing = /read -simple_nav = false -wf_modesty = false -chorus = false -forest = false -disable_drafts = false -single_user = false -open_registration = false -min_username_len = 3 -max_blogs = 1 -federation = true -public_stats = true -private = false -local_timeline = true -user_invites = admin -default_visibility = public -update_checks = false - -[oauth.slack] -client_id = -client_secret = -team_id = -callback_proxy = -callback_proxy_api = - -[oauth.writeas] -client_id = -client_secret = -auth_location = -token_location = -inspect_location = -callback_proxy = -callback_proxy_api = - -[oauth.gitlab] -client_id = -client_secret = -host = -display_name = -callback_proxy = -callback_proxy_api = diff --git a/writefreely/server/templates/nginx.conf.j2 b/writefreely/server/templates/nginx.conf.j2 deleted file mode 100644 index 4f1cf4d..0000000 --- a/writefreely/server/templates/nginx.conf.j2 +++ /dev/null @@ -1,54 +0,0 @@ -server { - listen 80; - server_name {{nginx_server_name}}; - return 301 https://$host$request_uri; -} - -server { - listen 443 ssl http2; - server_name {{nginx_server_name}}; - - ssl_certificate {{nginx_ssl_cert}}; - ssl_certificate_key {{nginx_ssl_privkey}}; - ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1; - - gzip on; - gzip_types - application/javascript - application/x-javascript - application/json - application/rss+xml - application/xml - image/svg+xml - image/x-icon - application/vnd.ms-fontobject - application/font-sfnt - text/css - text/plain; - gzip_min_length 256; - gzip_comp_level 5; - gzip_http_version 1.1; - gzip_vary on; - - location ~ ^/.well-known/(webfinger|nodeinfo|host-meta) { - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_pass http://127.0.0.1:{{nginx_proxy_port}}; - proxy_redirect off; - } - - location ~ ^/(css|img|js|fonts)/ { - root {{nginx_static_content}}; - # Optionally cache these files in the browser: - # expires 12M; - } - - location / { - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_pass http://127.0.0.1:{{nginx_proxy_port}}; - proxy_redirect off; - } -} diff --git a/writefreely/server/templates/[email protected] b/writefreely/server/templates/[email protected] deleted file mode 100644 index bf0d03b..0000000 --- a/writefreely/server/templates/[email protected] +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=WriteFreely %I Instance -After=syslog.target network.target - -[Service] -WorkingDirectory={{writefreely_dir}}/%i/ -ExecStart=/usr/local/bin/writefreely -Restart=always -User={{writefreely_user}} - -[Install] -WantedBy=multi-user.target |
