initial commit, carputer ansible role
This commit is contained in:
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
||||
This playbook will prep a computer to be a road trip car computer
|
||||
|
||||
It will capture snapshots from an attached camera, and it will display the feed on the screen
|
||||
|
||||
It will also host a site for the remote end that searches a working directory for the newest file and displays it and it does so every second
|
||||
|
||||
It will also have a site to press a button to start and stop the timelapse
|
||||
|
||||
A lot of this doesn't exist yet or needs to be tested
|
||||
|
||||
I want to write most of it first and then push it out to test
|
||||
33
defaults/main.yaml
Normal file
33
defaults/main.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
|
||||
packages:
|
||||
- ethtool
|
||||
- python3
|
||||
- python3-venv
|
||||
- pip
|
||||
- python-is-python3
|
||||
- dbus-user-session
|
||||
- apache2
|
||||
- php
|
||||
- kde-plasma-desktop
|
||||
- clinfo
|
||||
- mesa-utils
|
||||
- vulkan-tools
|
||||
- wayland-utils
|
||||
- xscreensaver
|
||||
|
||||
# this will be the parent folder where the photos will be stored
|
||||
# it should be big, I'm using a 1TB SD card for the 2025 road trip
|
||||
## ===Disk Info=======================================
|
||||
## Filesystem Size Used Avail Use% Mounted on
|
||||
## /dev/mmcblk0p2 112G 3.0G 103G 3% /
|
||||
## /dev/sda1 939G 28K 892G 1% /opt/carputer
|
||||
working_folder: "/opt/carputer"
|
||||
|
||||
# ustreamer variables
|
||||
video_device: "/dev/video0"
|
||||
resolution: "1920x1080"
|
||||
ustreamer_port: "7123"
|
||||
|
||||
|
||||
...
|
||||
78
files/app.py
Normal file
78
files/app.py
Normal file
@ -0,0 +1,78 @@
|
||||
# app.py
|
||||
from flask import Flask, request, jsonify
|
||||
import subprocess
|
||||
import requests
|
||||
import psycopg2
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
import io
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# API endpoint for charge
|
||||
@app.route('/test', methods=['GET'])
|
||||
def test():
|
||||
return jsonify({"message": "Hello there"}), 200
|
||||
|
||||
# API endpoint for service status
|
||||
@app.route('/get-info', methods=['GET'])
|
||||
def get_info():
|
||||
result = service_status()
|
||||
print(result)
|
||||
return jsonify(result)
|
||||
|
||||
# # Stop Service
|
||||
# @app.route('/stop', methods=['GET'])
|
||||
# def stop_service():
|
||||
# result = stop_timelapse()
|
||||
# print(result)
|
||||
# return jsonify(result)
|
||||
#
|
||||
# # Start Service
|
||||
# @app.route('/start', methods=['GET'])
|
||||
# def start_service():
|
||||
# result = start_timelapse()
|
||||
# print(result)
|
||||
# return jsonify(result)
|
||||
|
||||
# get service status
|
||||
|
||||
def service_status():
|
||||
service_name = "timelapse.service"
|
||||
try:
|
||||
# Check the status of the service using systemctl
|
||||
result = subprocess.run(['systemctl', 'is-active', service_name], check=True, capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
active_status = "running" if result.stdout.strip() == b'active\n' else "not running"
|
||||
|
||||
# Get the last message from the system log for this service
|
||||
logs_result = subprocess.run(['journalctl', '-u', service_name, '-xe'], check=True, capture_output=True, text=True)
|
||||
last_message = logs_result.stdout
|
||||
|
||||
response = {
|
||||
"service_status": active_status,
|
||||
"service_message": last_message.strip() if last_message else "No message available"
|
||||
}
|
||||
|
||||
return json.dumps(response)
|
||||
else:
|
||||
raise Exception("Service is not running")
|
||||
except subprocess.CalledProcessError as e:
|
||||
response = {
|
||||
"service_status": "error",
|
||||
"service_message": str(e)
|
||||
}
|
||||
return json.dumps(response)
|
||||
except Exception as e:
|
||||
response = {
|
||||
"service_status": "error",
|
||||
"service_message": str(e)
|
||||
}
|
||||
return json.dumps(response)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
12
files/create_timelapse.sh
Normal file
12
files/create_timelapse.sh
Normal file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# create timelapse
|
||||
|
||||
# Check if the timestamp is provided
|
||||
#if [ -z "$1" ]; then
|
||||
# echo "Usage: ./test.sh --timestamp=\"2025-07-25 12:00:00\""
|
||||
# exit 1
|
||||
#fi
|
||||
|
||||
# Extract the timestamp from the argument
|
||||
#TIMESTAMP=$(echo $1 | awk -F'=' '{print $2}')4
|
||||
|
||||
75
files/notes.txt
Normal file
75
files/notes.txt
Normal file
@ -0,0 +1,75 @@
|
||||
Notes on system itself
|
||||
The service contol will have the following components:
|
||||
PHP site that will make the API calls
|
||||
I don't have time to learn something new
|
||||
It will be similar to the photo refresh site
|
||||
Flow will be get status API, draw box depending on result, if pressed execute appropriate API, return
|
||||
The API calls are to a another service running a Python script
|
||||
I think I will have the following text in Green (Red):
|
||||
- THE SERVICE IS (NOT) RUNNING
|
||||
The button will be Red (Green):
|
||||
- Stop (Start)
|
||||
I'll rip off gali for the style
|
||||
|
||||
Notes on the python service script:
|
||||
There will be the cheap API, with a start, stop, and status
|
||||
Start will initialize another service
|
||||
There will be a variable to store the time of the last start/stop API call and limit calls for 5 seconds after this time
|
||||
No such limit will exist on the status API
|
||||
|
||||
Some Timelapse and snapshot Notes
|
||||
I think I will have two services, and the snapshot recording service will be a subservice of the timelapse service
|
||||
The timelapse service will start the snapshot
|
||||
|
||||
=== Old Timelapse Code ===
|
||||
|
||||
## Script to create timelapse
|
||||
|
||||
# Set TODAY variable
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
# Create folder for today's photos
|
||||
mkdir /var/opt/mattstream/$TODAY
|
||||
|
||||
# How long between photos
|
||||
PERIOD=1
|
||||
|
||||
# How long to record for
|
||||
LENGTH=900
|
||||
|
||||
# Calculate number of photos to take
|
||||
ITERATIONS=$((LENGTH / PERIOD))
|
||||
|
||||
# Begin capture loop
|
||||
i=1
|
||||
while [ $i -le $ITERATIONS ]
|
||||
do
|
||||
|
||||
# This variable is for the filename
|
||||
NOW=$(date +%Y%m%d-%H%M%S)
|
||||
# This variable is for the timestamp
|
||||
TIME=$(date +%Y%m%d-%H%M)
|
||||
# Download current capture
|
||||
curl http://10.55.10.1:6288/snapshot --output /var/opt/mattstream/$TODAY/$NOW.jpg
|
||||
# This should add the current time to the image and save it
|
||||
convert /var/opt/mattstream/$TODAY/$NOW.jpg -gravity NorthWest -pointsize 22 \
|
||||
-fill yellow -annotate +30+30 $TIME /var/opt/mattstream/$TODAY/$NOW-ts.jpg
|
||||
# Delete original
|
||||
rm /var/opt/mattstream/$TODAY/$NOW.jpg
|
||||
# And now we wait
|
||||
sleep $PERIOD
|
||||
# Increment the counter and start over duh
|
||||
((i++))
|
||||
# For more obvious commentary, the loop is now over
|
||||
done
|
||||
|
||||
# Generate the timelapse now
|
||||
ffmpeg -r 24 -pattern_type glob -i "/var/opt/mattstream/$TODAY/*.jpg" \
|
||||
-vf "scale=1280x720" -vcodec libx264 /var/opt/mattstream/$TODAY/$TODAY.mp4
|
||||
|
||||
# If we want to start deleting original images, uncomment this
|
||||
#rm /var/opt/mattstream/$TODAY/*.jpg
|
||||
|
||||
# Slap a copy of this somewhere convenient for me to grab it
|
||||
cp /var/opt/mattstream/$TODAY/$TODAY.mp4 /media/pleiades/matt-lapse/$TODAY.mp4
|
||||
|
||||
13
files/timelapse.service
Normal file
13
files/timelapse.service
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Timelapse service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/carputer/timelapse/timelapse_service.sh
|
||||
|
||||
Restart=always
|
||||
User=timelapse
|
||||
Group=timelapse
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
76
tasks/cosmos_autologin.yaml
Normal file
76
tasks/cosmos_autologin.yaml
Normal file
@ -0,0 +1,76 @@
|
||||
---
|
||||
|
||||
###############################################
|
||||
# Install KDE and other additional packages
|
||||
|
||||
# Install Packages
|
||||
- name: prereqs - install packages including KDE, will be slow
|
||||
apt:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop: "{{ packages }}"
|
||||
|
||||
# Set sddm as def
|
||||
- name: prereqs - enable sddm in xdm
|
||||
debconf:
|
||||
name: sddm
|
||||
question: shared/default-x-display-manager
|
||||
value: sddm
|
||||
vtype: select
|
||||
|
||||
# enable sddm service
|
||||
- name: prereqs - enable sddm
|
||||
systemd:
|
||||
name: sddm
|
||||
enabled: yes
|
||||
state: started
|
||||
|
||||
# Configure Autologin
|
||||
- name: prereqs - configure autologin et. al.
|
||||
template:
|
||||
src: sddm.conf.j2
|
||||
dest: /etc/sddm.conf
|
||||
mode: 0644
|
||||
|
||||
###############################################
|
||||
# Install Chrome
|
||||
- name: prereqs - Chrome - Check if installed
|
||||
command: dpkg -l google-chrome-stable
|
||||
register: chrome_installed
|
||||
ignore_errors: true
|
||||
|
||||
- name: prereqs - Chrome - Set chrome_present variable
|
||||
set_fact:
|
||||
chrome_present: "{{ chrome_installed.rc == 0 }}"
|
||||
|
||||
- name: prereqs - Install Chrome
|
||||
include_tasks: /var/jenkins_home/ansible/roles/install_apps/tasks/chrome.yaml
|
||||
when: not chrome_present | bool
|
||||
|
||||
###############################################
|
||||
# Apply Profile
|
||||
# Create Cosmos user folder
|
||||
- name: prereqs - create cosmos user profile folder
|
||||
file:
|
||||
path: /home/cosmos
|
||||
owner: cosmos
|
||||
group: cosmos
|
||||
state: directory
|
||||
mode: '0700'
|
||||
|
||||
# Extract profile
|
||||
- name: prereqs - extract cosmos user profile
|
||||
unarchive:
|
||||
src: /var/jenkins_home/ansible-files/lldp_scan/cosmos.tar.gz
|
||||
dest: /home
|
||||
owner: cosmos
|
||||
group: cosmos
|
||||
mode: 0700
|
||||
|
||||
# Restart sddm to initialize user profile
|
||||
- name: prereqs - restart sddm
|
||||
systemd:
|
||||
name: sddm
|
||||
state: restarted
|
||||
|
||||
...
|
||||
61
tasks/kiosk.yaml
Normal file
61
tasks/kiosk.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
###############################################
|
||||
# Chrome Kiosk Config
|
||||
# This creates the service and configures the user profile
|
||||
###############################################
|
||||
|
||||
- name: lldp - apache - clear www
|
||||
command: rm -R /var/www/html
|
||||
# Copy Web Server Files
|
||||
|
||||
- name: lldp - apache - web - copy files
|
||||
copy:
|
||||
src: "{{ item }}"
|
||||
dest: /var/www/html/
|
||||
owner: www-data
|
||||
mode: 0755
|
||||
with_fileglob:
|
||||
- "/var/jenkins_home/ansible/roles/lldp_scan/files/www/*"
|
||||
|
||||
- name: Chrome Kiosk - Create User Service Folder
|
||||
file:
|
||||
path: /home/cosmos/.config/systemd/user
|
||||
state: directory
|
||||
owner: cosmos
|
||||
group: cosmos
|
||||
mode: '0700'
|
||||
|
||||
- name: Chrome Kiosk - user stop service
|
||||
command: systemctl --user -M cosmos@ stop chrome.service
|
||||
register: user_start_chrome
|
||||
ignore_errors: true
|
||||
|
||||
- name: Chrome Kiosk - Kill chrome if running otherwise
|
||||
command: pkill chrome
|
||||
ignore_errors: true
|
||||
|
||||
- name: Chrome Kiosk - Copy chrome service file
|
||||
template:
|
||||
src: chrome-app.service.j2
|
||||
dest: /home/cosmos/.config/systemd/user/chrome.service
|
||||
owner: cosmos
|
||||
group: cosmos
|
||||
mode: 0600
|
||||
|
||||
- name: Chrome Kiosk - user daemon reload
|
||||
command: systemctl --user -M cosmos@ daemon-reload
|
||||
register: user_daemon_reload
|
||||
ignore_errors: true
|
||||
|
||||
- name: Chrome Kiosk - user enable service
|
||||
command: systemctl --user -M cosmos@ enable chrome.service
|
||||
register: user_enable_chrome
|
||||
ignore_errors: true
|
||||
|
||||
- name: Chrome Kiosk - user start service
|
||||
command: systemctl --user -M cosmos@ start chrome.service
|
||||
register: user_start_chrome
|
||||
ignore_errors: true
|
||||
|
||||
|
||||
...
|
||||
23
tasks/main.yaml
Normal file
23
tasks/main.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
|
||||
###########################################################
|
||||
# Playbook for configuring GOLE as Road Trip Carputer
|
||||
###########################################################
|
||||
# Steps:
|
||||
### uStreamer
|
||||
### Timelapse Service
|
||||
### Sharing Page
|
||||
### Cosmos Autologin
|
||||
### Top Cam Feed Kiosk
|
||||
### Bottom Service Control Panel
|
||||
|
||||
# select display_name, city, state, postcode from addresses order by id desc limit 1;
|
||||
# curl -s http://10.18.1.1:8184/wheres_gali?api_key=srJraFFprHadzQTDZf5hrmXh
|
||||
|
||||
- name: Carputer Early Tasks
|
||||
include_tasks:
|
||||
- ustreamer.yaml
|
||||
- timelapse.yaml
|
||||
- php_site.yaml
|
||||
|
||||
...
|
||||
0
tasks/php_site.yaml
Normal file
0
tasks/php_site.yaml
Normal file
15
tasks/timelapse.yaml
Normal file
15
tasks/timelapse.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
- name: Create user
|
||||
user:
|
||||
name: "timelapse"
|
||||
state: present
|
||||
shell: /bin/bash
|
||||
groups: video
|
||||
|
||||
|
||||
|
||||
...
|
||||
34
tasks/ustreamer.yaml
Normal file
34
tasks/ustreamer.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
|
||||
- name: Install Packages
|
||||
apt:
|
||||
name:
|
||||
- ustreamer
|
||||
state: present
|
||||
register: apt_result
|
||||
ignore_errors: true
|
||||
|
||||
- name: Create user
|
||||
user:
|
||||
name: "ustreamer"
|
||||
state: present
|
||||
shell: /bin/bash
|
||||
groups: video
|
||||
|
||||
- name: create service file
|
||||
template:
|
||||
src: ustreamer.service.j2
|
||||
dest: /etc/systemd/system/ustreamer.service
|
||||
owner: root
|
||||
group: root
|
||||
mode: 0644
|
||||
|
||||
- name: Enable and start service
|
||||
become: true
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
state: started
|
||||
enabled: yes
|
||||
name: ustreamer
|
||||
|
||||
...
|
||||
15
templates/chrome-app.service.j2
Executable file
15
templates/chrome-app.service.j2
Executable file
@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Chrome
|
||||
After=lldp_api.service
|
||||
After=sddm.service
|
||||
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
RestartSec=3s
|
||||
#ExecStartPre=/bin/sleep 5
|
||||
ExecStart=/usr/bin/google-chrome \
|
||||
--app="{{ chrome_website }}"
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
33
templates/record_snapshots.sh
Normal file
33
templates/record_snapshots.sh
Normal file
@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# this saves a snapshot of the camera every second or so depending on how long all the other crap takes
|
||||
# let's hope the vars make it otherwise imma need to parse a bollocksing file
|
||||
|
||||
i=1
|
||||
while [ -f "$WORKING_DIR/run" ]
|
||||
do
|
||||
FILENAME=$(printf "img-%05d" "$i")
|
||||
LATLON=$(curl -s http://10.18.1.1:8184/where_is?api_key={{ tesla_api_key }} )
|
||||
LON=$(echo $LATLON | jq .lon)
|
||||
LAT=$(echo $LATLON | jq .lat)
|
||||
COORDINATES="$LAT, $LON"
|
||||
curl http://127.0.0.1:7123/snapshot --output $WORKING_DIR/$BEGIN/$FILENAME.jpg
|
||||
# This variable is for the timestamp
|
||||
TIME=$(date +%Y%m%d-%H%M)xqa
|
||||
# This should add the current time to the image and save it
|
||||
convert $WORKING_DIR/$BEGIN/$FILENAME.jpg -gravity NorthWest -pointsize 22 \
|
||||
-fill yellow -annotate +30+30 $TIME $WORKING_DIR/$BEGIN/$FILENAME-temp.jpg
|
||||
# Delete original
|
||||
rm $WORKING_DIR/$BEGIN/$FILENAME.jpg
|
||||
|
||||
# This should add the current latlon to the image and save it
|
||||
convert $WORKING_DIR/$BEGIN/$FILENAME-temp.jpg -gravity NorthEast -pointsize 22 \
|
||||
-fill yellow -annotate +30+30 $COORDINATES $WORKING_DIR/$BEGIN/$FILENAME.jpg
|
||||
# Delete temp
|
||||
rm $WORKING_DIR/$BEGIN/$FILENAME-temp.jpg
|
||||
|
||||
sleep 1
|
||||
|
||||
((i++))
|
||||
|
||||
done
|
||||
3
templates/sddm.conf.j2
Normal file
3
templates/sddm.conf.j2
Normal file
@ -0,0 +1,3 @@
|
||||
[Autologin]
|
||||
User=cosmos
|
||||
Session=plasma.desktop
|
||||
30
templates/timelapse_service.sh.j2
Normal file
30
templates/timelapse_service.sh.j2
Normal file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# initialize all the variables
|
||||
# basic things
|
||||
BEGIN=$(date +%Y%m%d-%H%M%S)
|
||||
WORKING_DIR="/opt/carputer/timelapse/storage/$BEGIN"
|
||||
# be greedy about API calls
|
||||
WHERES_GALI=$(curl -s http://10.18.1.1:8184/wheres_gali?api_key={{ tesla_api_key }})
|
||||
# parse the one API call
|
||||
CITY=$(echo $WHERES_GALI | jq .city)
|
||||
STATE=$(echo $WHERES_GALI | jq .state)
|
||||
ZIPCODE=$(echo $WHERES_GALI | jq .postcode)
|
||||
DISPLAY_NAME=$(echo $WHERES_GALI | jq .display_name)
|
||||
|
||||
# Generate Status Report
|
||||
mkdir -p $WORKING_DIR
|
||||
echo Timelapse Initiated at $BEGIN >> $WORKING_DIR/info.txt
|
||||
echo Shuttlecraft Galileo located at $CITY, $STATE $ZIPCODE >> $WORKING_DIR/info.txt
|
||||
echo $DISPLAY_NAME >> $WORKING_DIR/info.txt
|
||||
|
||||
# Set the run file and fork the loop
|
||||
touch $WORKING_DIR/run
|
||||
source ./record_snapshots.sh &
|
||||
|
||||
# set the trap and wait
|
||||
trap "source ./record_timelapse" SIGINT SIGTERM
|
||||
while true; do
|
||||
echo "Running..."
|
||||
sleep 1
|
||||
done
|
||||
14
templates/ustreamer.service.j2
Normal file
14
templates/ustreamer.service.j2
Normal file
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=uStreamer - Lightweight HTTP Live Streaming server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/ustreamer --format=jpeg --resolution={{ resolution }} \
|
||||
--port={{ ustreamer_port}} --host=0.0.0.0 --device={{ video_device }}
|
||||
|
||||
Restart=always
|
||||
User=ustreamer
|
||||
Group=ustreamer
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user