hopeless-masquerade: birbs
Assisted-by: Claude Opus 5 via Claude Code
change yowzqlrzzxovlvzsmkkorowwtmqpmwnq
commit c6698093edd259bfdb9f7ffb1868ef41b9356e31
author Alpha Chen <alpha@kejadlen.dev>
date
parent vknokyyk
diff --git a/dns.tf b/dns.tf
index 6f8ff12..83f52d8 100644
--- a/dns.tf
+++ b/dns.tf
@@ -15,10 +15,10 @@ resource "dnsimple_zone" "zones" {
 
 resource "dnsimple_zone_record" "chislan_family_subdomains" {
   for_each = {
-    auth      = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
-    birdnetpi = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
-    docs      = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
-    files     = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
+    auth  = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
+    birbs = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
+    docs  = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
+    files = { zone = dnsimple_zone.zones["chislan_family"], ip = local.lotus_land_story.ip_address }
   }
 
   zone_name = each.value.zone.name
diff --git a/hopeless-masquerade/README.md b/hopeless-masquerade/README.md
new file mode 100644
index 0000000..117b41e
--- /dev/null
+++ b/hopeless-masquerade/README.md
@@ -0,0 +1,11 @@
+# hopeless-masquerade
+
+A Raspberry Pi 4 Model B running [BirdNET-Pi][birdnet-pi].
+
+## setup
+
+```sh
+ansible-playbook -l hopeless-masquerade -e ansible_host=ANSIBLE_HOST --ask-become-pass hopeless-masquerade/main.yml
+```
+
+[birdnet-pi]: https://github.com/zach7036/BirdNET-Pi
diff --git a/hopeless-masquerade/main.yml b/hopeless-masquerade/main.yml
index 8f789f6..5482505 100644
--- a/hopeless-masquerade/main.yml
+++ b/hopeless-masquerade/main.yml
@@ -16,7 +16,7 @@
 
 # TODO Standardize these into a shared playbook
 - name: Secure the system
-  hosts: legacy-of-lunatic-kingdom
+  hosts: hopeless-masquerade
   become: true
   tasks:
 
@@ -47,74 +47,106 @@
 # TODO disallow network access from non-tailscale interfaces
 - import_playbook: ../playbooks/tailscale.yml # noqa: name[play]
 
-- import_playbook: vector.yml # noqa: name[play]
-
+# https://setup.vector.dev
 # https://grafana.com/blog/2024/07/12/obirdability-how-to-build-an-observability-system-for-bird-songs/
-- name: Ship birdnet logs
-  tags: birdnet_o11y
+- name: Vector
+  tags: vector
   hosts: hopeless-masquerade
   become: true
   tasks:
 
-    - name: Create Vector configuration
+    - name: Install required dependencies
+      ansible.builtin.apt:
+        pkg:
+          - apt-transport-https
+          - curl
+          - gnupg
+          - python3-debian # for ansible.builtin.deb822_repository
+        state: present
+        update_cache: true
+
+    - name: Create keyrings directory
+      ansible.builtin.file:
+        path: /usr/share/keyrings
+        state: directory
+        mode: '0755'
+
+    - name: Download Vector GPG key
+      ansible.builtin.get_url:
+        url: https://keys.datadoghq.com/DATADOG_APT_KEY_CURRENT.public
+        dest: /tmp/vector_key.public
+        mode: '0644'
+
+    - name: Import Vector GPG key
+      ansible.builtin.command: gpg --dearmor --output /usr/share/keyrings/datadog-archive-keyring.gpg /tmp/vector_key.public
+      args:
+        creates: /usr/share/keyrings/datadog-archive-keyring.gpg
+
+    # Left behind by ansible.builtin.apt_repository; apt reads both formats, so
+    # leaving it would configure the repository twice.
+    - name: Remove one-line Vector repository
+      ansible.builtin.file:
+        path: /etc/apt/sources.list.d/vector.list
+        state: absent
+
+    - name: Add Vector repository
+      ansible.builtin.deb822_repository:
+        name: vector
+        types: deb
+        uris: https://apt.vector.dev/
+        suites: stable
+        components: vector-0
+        signed_by: /usr/share/keyrings/datadog-archive-keyring.gpg
+        state: present
+
+    - name: Install Vector
+      ansible.builtin.apt:
+        name: vector
+        state: present
+        update_cache: true
+
+    - name: Configure Vector
       ansible.builtin.copy:
-        content: |
-          sources:
-            journald:
-              type: journald
-
-          transforms:
-            birdnet:
-              type: remap
-              inputs:
-                - journald
-              source: |
-                if ._SYSTEMD_UNIT != "birdnet_analysis.service" {
-                  abort
-                }
-
-                parsed, err = parse_regex(.message, r'.+\((\'|")(?P<species_latin>.+?)_(?P<species>.+?)(\'|"), (?P<confidence>.+?)\)')
-                if err != null {
-                  abort
-                }
-
-                . |= parsed
-
-                if includes(["Engine", "Human"], .species) {
-                  abort
-                }
-
-                # actually probably unnecessary?
-                .confidence_interval = round(to_float!(.confidence), precision: 1)
-
-          sinks:
-            console:
-              type: console
-              inputs:
-                - birdnet
-              encoding:
-                codec: json
-            vlogs:
-              inputs:
-                - birdnet
-              type: http
-              uri: http://lotus-land-story:9428/insert/jsonline?_stream_fields=host&_msg_field=message&_time_field=timestamp
-              compression: gzip
-              encoding:
-                codec: json
-              framing:
-                method: newline_delimited
-              healthcheck:
-                enabled: false
+        src: templates/vector.yml
         dest: /etc/vector/vector.yaml
         mode: '0644'
         backup: true
       notify: Restart Vector
 
+    - name: Enable and start Vector service
+      ansible.builtin.systemd:
+        name: vector
+        enabled: true
+        state: started
+        daemon_reload: true
+
+    - name: Clean up temporary key file
+      ansible.builtin.file:
+        path: /tmp/vector_key.public
+        state: absent
+
   handlers:
     - name: Restart Vector
       ansible.builtin.systemd:
         name: vector
         state: restarted
 
+# - name: birbnet
+#   tags: birbnet
+#   hosts: hopeless-masquerade
+#   tasks:
+#
+#     - name: Install pipx
+#       become: true
+#       ansible.builtin.apt:
+#         pkg:
+#           - pipx
+#         state: present
+#         update_cache: true
+#
+#     - name: Install uv
+#       ansible.builtin.command: pipx install uv
+#       args:
+#         creates: /home/alpha/.local/bin/uv
+
 # vim: ft=yaml.ansible
diff --git a/hopeless-masquerade/templates/vector.yml b/hopeless-masquerade/templates/vector.yml
new file mode 100644
index 0000000..2b71fef
--- /dev/null
+++ b/hopeless-masquerade/templates/vector.yml
@@ -0,0 +1,80 @@
+sources:
+  journald:
+    type: journald
+  bird_db_txt:
+    type: file
+    include:
+      - "/home/alpha/BirdNET-Pi/BirdDB.txt"
+
+transforms:
+  birdnet:
+    type: remap
+    inputs:
+      - journald
+    source: |
+      if ._SYSTEMD_UNIT != "birdnet_analysis.service" {
+        abort
+      }
+
+      parsed, err = parse_regex(.message, r'.+\((\'|")(?P<species_latin>.+?)_(?P<species>.+?)(\'|"), (?P<confidence>.+?)\)')
+      if err != null {
+        abort
+      }
+
+      . |= parsed
+
+      if includes(["Engine", "Human"], .species) {
+        abort
+      }
+
+      # actually probably unnecessary?
+      .confidence_interval = round(to_float!(.confidence), precision: 1)
+  bird_db:
+    type: remap
+    inputs:
+      - bird_db_txt
+    source: |
+      keys = split("Date;Time;Sci_Name;Com_Name;Confidence;Lat;Lon;Cutoff;Week;Sens;Overlap", ";")
+      parsed = object_from_array(
+        split!(.message, ";"),
+        keys: keys,
+      )
+
+      # handle headers
+      if parsed.Date == "Date" {
+        abort
+      }
+
+      date = string!(parsed.Date)
+      time = string!(parsed.Time)
+      ts = "{{date}}T{{time}}"
+      .timestamp = parse_timestamp!(
+          ts,
+          format: "%Y-%m-%dT%H:%M:%S",
+          timezone: "America/Los_Angeles",
+      )
+
+      .com_name = parsed.Com_Name
+      .sci_name = parsed.Sci_Name
+      .confidence = parsed.Confidence
+
+sinks:
+  console:
+    type: console
+    inputs:
+      - bird_db
+      - bird_db_txt
+    encoding:
+      codec: json
+  vlogs:
+    inputs:
+      - birdnet
+    type: http
+    uri: http://lotus-land-story:9428/insert/jsonline?_stream_fields=host&_msg_field=message&_time_field=timestamp
+    compression: gzip
+    encoding:
+      codec: json
+    framing:
+      method: newline_delimited
+    healthcheck:
+      enabled: false
diff --git a/hopeless-masquerade/vector.yml b/hopeless-masquerade/vector.yml
deleted file mode 100644
index 0305637..0000000
--- a/hopeless-masquerade/vector.yml
+++ /dev/null
@@ -1,59 +0,0 @@
-# https://setup.vector.dev
-
-- name: Install Vector on Debian
-  tags: vector
-  hosts: hopeless-masquerade
-  become: true
-  tasks:
-
-    - name: Install required dependencies
-      ansible.builtin.apt:
-        pkg:
-          - apt-transport-https
-          - curl
-          - gnupg
-        state: present
-        update_cache: true
-
-    - name: Create keyrings directory
-      ansible.builtin.file:
-        path: /usr/share/keyrings
-        state: directory
-        mode: '0755'
-
-    - name: Download Vector GPG key
-      ansible.builtin.get_url:
-        url: https://keys.datadoghq.com/DATADOG_APT_KEY_CURRENT.public
-        dest: /tmp/vector_key.public
-        mode: '0644'
-
-    - name: Import Vector GPG key
-      ansible.builtin.command: gpg --dearmor --output /usr/share/keyrings/datadog-archive-keyring.gpg /tmp/vector_key.public
-      args:
-        creates: /usr/share/keyrings/datadog-archive-keyring.gpg
-
-    - name: Add Vector repository
-      ansible.builtin.apt_repository:
-        repo: deb [signed-by=/usr/share/keyrings/datadog-archive-keyring.gpg] https://apt.vector.dev/ stable vector-0
-        filename: vector
-        state: present
-        update_cache: true
-
-    - name: Install Vector
-      ansible.builtin.apt:
-        name: vector
-        state: present
-        update_cache: true
-
-    - name: Enable and start Vector service
-      ansible.builtin.systemd:
-        name: vector
-        enabled: true
-        state: started
-        daemon_reload: true
-
-    - name: Clean up temporary key file
-      ansible.builtin.file:
-        path: /tmp/vector_key.public
-        state: absent
-
diff --git a/lotus-land-story/templates/Caddyfile b/lotus-land-story/templates/Caddyfile
index 2f0d6a9..112bd90 100644
--- a/lotus-land-story/templates/Caddyfile
+++ b/lotus-land-story/templates/Caddyfile
@@ -197,7 +197,7 @@ gists.kejadlen.dev {
 	}
 }
 
-birdnetpi.chislan.family {
+birbs.chislan.family {
 	log
 
 	reverse_proxy hopeless-masquerade:80
diff --git a/lotus-land-story/templates/scrape.yml b/lotus-land-story/templates/scrape.yml
index 2ea8d4f..289d0a2 100644
--- a/lotus-land-story/templates/scrape.yml
+++ b/lotus-land-story/templates/scrape.yml
@@ -44,7 +44,7 @@ scrape_configs:
         # personal
         - https://auth.chislan.family
         - https://auth.kejadlen.dev
-        - https://birdnetpi.chislan.family
+        - https://birbs.chislan.family
         - https://git.kejadlen.dev
         - https://hey.kejadlen.dev
     relabel_configs: