src.nth.io/

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--unifi-controller/README.md12
-rw-r--r--unifi-controller/defaults/main.yaml3
-rw-r--r--unifi-controller/tasks/main.yaml39
-rw-r--r--unifi-protect-viewport/README.md283
-rw-r--r--unifi-protect-viewport/defaults/main.yaml19
-rw-r--r--unifi-protect-viewport/files/unifi-protect-viewport-debug208
-rw-r--r--unifi-protect-viewport/files/unifi-protect-viewport.py518
-rw-r--r--unifi-protect-viewport/handlers/main.yaml5
-rw-r--r--unifi-protect-viewport/tasks/main.yaml135
-rw-r--r--unifi-protect-viewport/templates/unifi-protect-viewport.env.j210
-rw-r--r--unifi-protect-viewport/templates/unifi-protect-viewport.service.j218
-rw-r--r--writefreely/aws-s3-backup/files/writefreely-s3-backup-lifecycle.json12
-rw-r--r--writefreely/aws-s3-backup/files/writefreely-s3-backup.sh13
-rw-r--r--writefreely/aws-s3-backup/handlers/main.yaml5
-rw-r--r--writefreely/aws-s3-backup/meta/main.yaml4
-rw-r--r--writefreely/aws-s3-backup/tasks/main.yaml26
-rw-r--r--writefreely/aws-s3-backup/templates/[email protected]9
-rw-r--r--writefreely/server/defaults/main.yaml12
-rw-r--r--writefreely/server/handlers/main.yaml16
-rw-r--r--writefreely/server/tasks/main.yaml102
-rw-r--r--writefreely/server/templates/config.ini.j271
-rw-r--r--writefreely/server/templates/nginx.conf.j254
-rw-r--r--writefreely/server/templates/[email protected]12
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:
- 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:
- 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