Added remaining pibox setup
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -3,3 +3,4 @@ inv.yaml | |||||||
| ansible.cfg | ansible.cfg | ||||||
| host_vars/ | host_vars/ | ||||||
| group_vars/ | group_vars/ | ||||||
|  | secrets/ | ||||||
|  | |||||||
| @ -4,6 +4,10 @@ | |||||||
|   vars: |   vars: | ||||||
|     password_excluded_hosts: |     password_excluded_hosts: | ||||||
|       - pikvm |       - pikvm | ||||||
|  |     non_raspbian_hosts: | ||||||
|  |       - pikvm | ||||||
|  |     pibox_hosts: | ||||||
|  |       - piparcel | ||||||
|   tasks: |   tasks: | ||||||
|     - name: Set root passwords |     - name: Set root passwords | ||||||
|       ansible.builtin.include_role: |       ansible.builtin.include_role: | ||||||
| @ -18,13 +22,16 @@ | |||||||
|     - name: Ensure vim config is present for pi |     - name: Ensure vim config is present for pi | ||||||
|       ansible.builtin.include_role: |       ansible.builtin.include_role: | ||||||
|         name: configure_vim |         name: configure_vim | ||||||
|  |       when: ansible_hostname not in non_raspbian_hosts | ||||||
|     - name: Include profile aliases |     - name: Include profile aliases | ||||||
|       ansible.builtin.include_role: |       ansible.builtin.include_role: | ||||||
|         name: profile_aliases |         name: profile_aliases | ||||||
|  |       when: ansible_hostname not in non_raspbian_hosts | ||||||
|     - name: Include basic package list |     - name: Include basic package list | ||||||
|       ansible.builtin.include_role: |       ansible.builtin.include_role: | ||||||
|         name: basic_setup |         name: basic_setup | ||||||
|  |       when: ansible_hostname not in non_raspbian_hosts | ||||||
|     - name: Configure pibox extras |     - name: Configure pibox extras | ||||||
|       ansible.builtin.include_role: |       ansible.builtin.include_role: | ||||||
|         name: pibox_basics |         name: pibox_basics | ||||||
|       when: ansible_hostname == 'piparcel' |       when: ansible_hostname in pibox_hosts | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/black-x240.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 689 B | 
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/x240-in-wget-we-trust.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 23 KiB | 
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/x240-iptables.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 43 KiB | 
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/x240-linux-is-coming.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 25 KiB | 
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/x240-open-world.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 73 KiB | 
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/x240-thanos-sudo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 36 KiB | 
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/x240-tux-text.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 81 KiB | 
							
								
								
									
										
											BIN
										
									
								
								roles/pibox_basics/files/img/x240-vim-shortcut.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 71 KiB | 
							
								
								
									
										35
									
								
								roles/pibox_basics/files/rgb
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,35 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | trap ctrl_c INT | ||||||
|  | 
 | ||||||
|  | delay=0.05 | ||||||
|  | 
 | ||||||
|  | function ctrl_c() { | ||||||
|  |  echo "0" > /sys/class/gpio/gpio17/value | ||||||
|  |  echo "0" > /sys/class/gpio/gpio27/value | ||||||
|  |  echo "0" > /sys/class/gpio/gpio23/value | ||||||
|  |  exit 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # RED | ||||||
|  | echo "17" > /sys/class/gpio/export 2>/dev/null | ||||||
|  | echo "out" > /sys/class/gpio/gpio17/direction | ||||||
|  | # GREEN | ||||||
|  | echo "27" > /sys/class/gpio/export 2>/dev/null | ||||||
|  | echo "out" > /sys/class/gpio/gpio27/direction | ||||||
|  | # BLUE | ||||||
|  | echo "23" > /sys/class/gpio/export 2>/dev/null | ||||||
|  | echo "out" > /sys/class/gpio/gpio23/direction | ||||||
|  | 
 | ||||||
|  | while : | ||||||
|  | do | ||||||
|  |     echo "1" > /sys/class/gpio/gpio17/value | ||||||
|  |     sleep $delay | ||||||
|  |     echo "0" > /sys/class/gpio/gpio17/value | ||||||
|  |     echo "1" > /sys/class/gpio/gpio27/value | ||||||
|  |     sleep $delay | ||||||
|  |     echo "0" > /sys/class/gpio/gpio27/value | ||||||
|  |     echo "1" > /sys/class/gpio/gpio23/value | ||||||
|  |     sleep $delay | ||||||
|  |     echo "0" > /sys/class/gpio/gpio23/value | ||||||
|  | done | ||||||
							
								
								
									
										53
									
								
								roles/pibox_basics/files/screensaver.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,53 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | sock=/var/run/pibox/framebuffer.sock | ||||||
|  | imgdir=~/img | ||||||
|  | blackimg='black-x240.png' | ||||||
|  | imglist=() | ||||||
|  | sleeptime=10 # seconds | ||||||
|  | hidecurloutput=1 | ||||||
|  | debug=0 | ||||||
|  | 
 | ||||||
|  | # -S FILE - True if the FILE exists and is a socket. | ||||||
|  | # https://linuxize.com/post/bash-check-if-file-exists/ | ||||||
|  | if [ ! -S ${sock} ]; then echo Unable to find unix socket. Pls fix.; exit 1; fi | ||||||
|  | [ "$hidecurloutput" == "1" ] && curlout='-o /dev/null' || curlout='' | ||||||
|  | 
 | ||||||
|  | trap ctrl_c INT | ||||||
|  | 
 | ||||||
|  | function ctrl_c() { | ||||||
|  |  sudo curl -sS --unix-socket ${sock} -X POST --data-binary @${imgdir}/${blackimg} http://localhost/image $curlout | ||||||
|  |  tput cub 5 | ||||||
|  |  echo exiting | ||||||
|  |  exit 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function debuglog() { | ||||||
|  |  [ $debug -eq 0 ] && return | ||||||
|  |  echo $1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function loadimgs() { | ||||||
|  |  for file in `ls $imgdir`; do | ||||||
|  |   debuglog "Found image file $file" | ||||||
|  |   [ ${file} == ${blackimg} ] && continue | ||||||
|  |   imglist[${#imglist[@]}]=${file} | ||||||
|  |   debuglog "Loaded image file $file" | ||||||
|  |  done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function displayimg() { | ||||||
|  |  newimg="${1}" | ||||||
|  |  debuglog "Displaying image ${1}" | ||||||
|  |  sudo curl -sS --unix-socket ${sock} -X POST --data-binary @${imgdir}/${newimg} http://localhost/image $curlout | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | loadimgs | ||||||
|  | debuglog "Image list: ${imglist[@]}" | ||||||
|  | while true; do | ||||||
|  |  for thisimg in ${imglist[@]}; do | ||||||
|  |   displayimg "${thisimg}" | ||||||
|  |   sleep ${sleeptime} | ||||||
|  |  done | ||||||
|  | done | ||||||
|  | 
 | ||||||
							
								
								
									
										347
									
								
								roles/pibox_basics/files/sys-stats.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,347 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | 
 | ||||||
|  | ## -=Notebook=- | ||||||
|  | # | ||||||
|  | # - Exception hierarchy: https://docs.python.org/3/library/exceptions.html#exception-hierarchy | ||||||
|  | # Non-default Prerequisites: | ||||||
|  | # - Font: Cascadia Code | ||||||
|  | # - APT packages: python3-dev | ||||||
|  | # - PIP packages: psutil, pillow | ||||||
|  | 
 | ||||||
|  | import socket, io, psutil, datetime | ||||||
|  | from os.path import exists as file_exists | ||||||
|  | from PIL import Image, ImageDraw, ImageFont | ||||||
|  | 
 | ||||||
|  | # Socket constants | ||||||
|  | KUBESOCK="/var/run/pibox/framebuffer.sock" | ||||||
|  | HEADER1=b"POST /image HTTP/1.1\x0d\x0aHost: localhost\x0d\x0aUser-Agent: python-socket\x0d\x0aAccept: */*\x0d\x0a" | ||||||
|  | HEADER2=b"\x0d\x0aContent-Type: application/x-www-form-urlencoded\x0d\x0a\x0d\x0a" | ||||||
|  | 
 | ||||||
|  | # Drawing constants | ||||||
|  | SCREENWIDTH=240 | ||||||
|  | SCREENHEIGHT=240 | ||||||
|  | CASCADIAFONTPATH="/usr/share/fonts/truetype/CascadiaCode/static/CascadiaMono-Regular.ttf" | ||||||
|  | FONTSIZE={"CHONKY": 35,"THICC": 25, "BIG":15, "MED":12,"SMOL":10} | ||||||
|  | #DISPLAYWIDTHCHARS={"CHONKY": 11, "THICC": 16, "BIG":26, "MED":34, "SMOL":40} | ||||||
|  | FONT={"CHONKY":ImageFont.truetype(CASCADIAFONTPATH,FONTSIZE["CHONKY"]), | ||||||
|  |       "THICC":ImageFont.truetype(CASCADIAFONTPATH,FONTSIZE["THICC"]), | ||||||
|  |       "BIG":ImageFont.truetype(CASCADIAFONTPATH,FONTSIZE["BIG"]), | ||||||
|  |       "MED":ImageFont.truetype(CASCADIAFONTPATH,FONTSIZE["MED"]), | ||||||
|  |       "SMOL":ImageFont.truetype(CASCADIAFONTPATH,FONTSIZE["SMOL"])} | ||||||
|  | BLACK,GRAY,WHITE=(0,0,0,255),(127,127,127,255),(255,255,255,255) | ||||||
|  | RED,ORANGE,YELLOW=(200,0,0,255),(200,125,0,255),(200,200,0,255) | ||||||
|  | DARK_GREEN, DARK_YELLOW, DARK_RED=(0,80,0,255),(150,150,0,255),(100,0,0,255) | ||||||
|  | GREEN,BLUE,PURPLE=(0,230,0,255),(80,190,255,255),(180,100,255,255) | ||||||
|  | AQUA,PINK=(55, 255, 255, 255),(255,150,230,255) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Metrics constants | ||||||
|  | NET_ADAPTER="eth0" | ||||||
|  | CPULOADLABELS=["1min", "5min", "15min"] | ||||||
|  | # only added to as-anticipated | ||||||
|  | SUBNET_CIDR_MAP={"255.0.0.0": "8", "255.255.0.0": "16", "255.255.255.0": "24", "255.255.255.128":"25"} | ||||||
|  | 
 | ||||||
|  | mystats=Image.new("RGBA", (240, 240), BLACK) | ||||||
|  | drawer=ImageDraw.Draw(mystats) | ||||||
|  | 
 | ||||||
|  | def precheck(): | ||||||
|  |  if not file_exists(KUBESOCK): | ||||||
|  |   raise FileNotFoundError(f"Cannot find socket {KUBESOCK} - ensure socket server is running") | ||||||
|  |  if not file_exists(CASCADIAFONTPATH): | ||||||
|  |   raise FileNotFoundError(f"Cannot find font file {CASCADIAFONTPATH} - please ensure it is installed on your system") | ||||||
|  |  return | ||||||
|  | 
 | ||||||
|  | def sendimage(imgdata): | ||||||
|  |  if type(imgdata) is Image.Image: | ||||||
|  |   # https://stackoverflow.com/a/33117447 | ||||||
|  |   newbytes=io.BytesIO() | ||||||
|  |   imgdata.save(newbytes, format="PNG") | ||||||
|  |   imgbytes=newbytes.getvalue() | ||||||
|  |  elif type(imgdata) is io.BytesIO: | ||||||
|  |   imgbytes=imgdata.getvalue() | ||||||
|  |  imglength=f"{len(imgbytes)}" | ||||||
|  |  request_data=HEADER1+b"Content-Length: "+bytes(imglength,"UTF-8")+HEADER2+imgbytes | ||||||
|  |  with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client: | ||||||
|  |   client.connect(KUBESOCK) | ||||||
|  |   client.send(request_data) | ||||||
|  |   #print(request_data) | ||||||
|  |   print(client.recv(4096)) | ||||||
|  |   client.close() | ||||||
|  |  return | ||||||
|  | 
 | ||||||
|  | def writewords(text, line=5, spacing=3, indent=0, right=SCREENWIDTH, font="BIG", color=WHITE, alignment="LEFT", outline=None): | ||||||
|  |  alignment=alignment.upper() | ||||||
|  |  font=font.upper() | ||||||
|  |  pixelwidth=round(drawer.textlength(text, font=FONT[font]),0) | ||||||
|  |  #print(f"text \"{text}\" has width {pixelwidth} pixels.") | ||||||
|  |  strokewidth=0 if outline == None else 1 | ||||||
|  |  if alignment == "LEFT": | ||||||
|  |   drawer.text((indent, line), text, font=FONT[font], fill=color, stroke_fill=outline, stroke_width=strokewidth) | ||||||
|  |  elif alignment == "RIGHT": | ||||||
|  |   drawer.text((right-pixelwidth-indent, line), text, font=FONT[font], fill=color, stroke_fill=outline, stroke_width=strokewidth) | ||||||
|  |  elif alignment == "CENTER": | ||||||
|  |   drawer.text(((((right-indent)/2)-(pixelwidth/2))+indent, line), text, font=FONT[font], fill=color, stroke_fill=outline, stroke_width=strokewidth) | ||||||
|  |  else: | ||||||
|  |   raise ValueError(f"Unsupported alignment provided to writewords function: {alignment}") | ||||||
|  |  return line+FONTSIZE[font]+spacing | ||||||
|  | 
 | ||||||
|  | def drawbar(top=50, indent=20, right=220, height=30, color=WHITE, corners=(True, True, True, True), radius=10, percent=.5): | ||||||
|  |  # Minimum 11% for easy draw (radius*2+1 pixels) | ||||||
|  |  # Maximum 95% for easy draw | ||||||
|  |  radius = radius if radius*2+2 < height else height/2-2 | ||||||
|  |  outlinecorners=corners | ||||||
|  |  fillcorners=corners | ||||||
|  |  #print(f"Top left: ({indent}, {top}) / Bottom right: ({right}, {top+height})") | ||||||
|  |  #             0          1           2             3 | ||||||
|  |  # corners=(top_left, top_right, bottom_right, bottom_left) | ||||||
|  |  if percent < .9: fillcorners=(corners[0], False, False, corners[3]) | ||||||
|  |  if percent >= .15: | ||||||
|  |   drawer.rounded_rectangle(((indent, top), (((right-indent)*percent)+indent, top+height)), radius=radius, outline=None, fill=color, corners=fillcorners) | ||||||
|  |  else: | ||||||
|  |   # Due to how the rounded_rectangle function draws the shape we have to draw at least 11% (20% is just a nice number) | ||||||
|  |   # and then "subtract" the leftover percents from the bar to show only what we want | ||||||
|  |   # (also the top 5% isn't neatly rounded but that's a problem for a later time) | ||||||
|  |   drawer.rounded_rectangle(((indent, top), (((right-indent)*.2)+indent, top+height)), radius=radius, outline=None, fill=color, corners=fillcorners) | ||||||
|  |   drawer.rectangle(((((right-indent)*percent)+indent, top), (((right-indent)*.2)+indent, top+height)), fill=BLACK, outline=None) | ||||||
|  |  # Then we draw the outline afterwards so it goes over our blacked-out region (if present) | ||||||
|  |  drawer.rounded_rectangle(((indent, top), (right, top+height)), radius=radius, outline=color, fill=None, corners=outlinecorners) | ||||||
|  |  return top+height+1 | ||||||
|  | 
 | ||||||
|  | def substring_indicies(string, character): | ||||||
|  |  # https://stackoverflow.com/a/13009866 | ||||||
|  |  return [i for i, letter in enumerate(string) if letter == character] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def textstats(): | ||||||
|  |  line=FONTSIZE['BIG'] | ||||||
|  |  column=(5,120) | ||||||
|  |  # CPU PERCENTS | ||||||
|  |  cpu_use=psutil.cpu_percent(interval=1,percpu=True) | ||||||
|  |  core=0 | ||||||
|  |  while core < len(cpu_use): | ||||||
|  |   drawer.text((column[core%2],line), f"CPU {core}: {cpu_use[core]}%", font=FONT['BIG'], fill=WHITE) | ||||||
|  |   line+= 0 if core%2==0 else FONTSIZE['BIG']+5 | ||||||
|  |   core+=1 | ||||||
|  |  # CPU LOAD | ||||||
|  |  cpu_loadsss=psutil.getloadavg() | ||||||
|  |  load=0 | ||||||
|  |  line+=10 | ||||||
|  |  while load < len(cpu_loadsss): | ||||||
|  |   drawer.text((column[0], line), f"{CPULOADLABELS[load]} load: {round(cpu_loadsss[load],4)}", font=FONT['BIG'], fill=WHITE) | ||||||
|  |   line+=FONTSIZE['BIG']+5 | ||||||
|  |   load+=1 | ||||||
|  |  line+=10 | ||||||
|  |  # MEMORY USAGE | ||||||
|  |  memories=psutil.virtual_memory() | ||||||
|  |  memtotal=round(memories.total/1024/1024/1024, 2) | ||||||
|  |  memavailable=round(memories.available/1024/1024/1024, 2) | ||||||
|  |  drawer.text((column[0], line), f"Total: {memtotal}G", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+5 | ||||||
|  |  drawer.text((column[0], line), f"Avail: {memavailable}G", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+15 | ||||||
|  |  temp=round(psutil.sensors_temperatures()['cpu_thermal'][0].current, 1) | ||||||
|  |  drawer.text((column[0], line),f"Temp: {temp}°C", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+15 | ||||||
|  |  # Hello, world! | ||||||
|  |  drawer.text((column[0],line), "Hello, world!", font=FONT['BIG'], fill=WHITE) | ||||||
|  | 
 | ||||||
|  | def texttest(): | ||||||
|  |  line=5 | ||||||
|  |  spacing=3 | ||||||
|  |  smolspacing=3 | ||||||
|  |  indent=0 | ||||||
|  |  linebreak=15 | ||||||
|  |  drawer.text((indent, line), "{:^26}".format("--Letter spacing test--"), font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+linebreak | ||||||
|  |  drawer.text((indent, line), f"font: {FONT['BIG'].getname()[0]} {FONTSIZE['BIG']}px", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+spacing | ||||||
|  |  drawer.text((indent, line), "123456789012345678901234567890", font=FONT['MED'], fill=RED) | ||||||
|  |  line+=FONTSIZE['BIG']+spacing | ||||||
|  |  drawer.text((indent, line), "abcdefghijklmnopqrstuvwxyz", font=FONT['BIG'], fill=ORANGE) | ||||||
|  |  line+=FONTSIZE['BIG']+spacing | ||||||
|  |  drawer.text((indent, line), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", font=FONT['BIG'], fill=YELLOW) | ||||||
|  |  line+=FONTSIZE['BIG']+spacing | ||||||
|  |  drawer.text((indent, line), f"line spacing: {spacing}", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+linebreak | ||||||
|  | 
 | ||||||
|  |  drawer.text((indent, line), f"smol font: {FONT['SMOL'].getname()[0]} {FONTSIZE['SMOL']}px", font=FONT["SMOL"], fill=WHITE) | ||||||
|  |  line+=FONTSIZE["SMOL"]+smolspacing | ||||||
|  |  drawer.text((indent, line), "1234567890123456789012345678901234567890", font=FONT["SMOL"], fill=GREEN) | ||||||
|  |  line+=FONTSIZE["SMOL"]+smolspacing | ||||||
|  |  drawer.text((indent, line), "abcdefghijklmnopqrstuvwxyz", font=FONT["SMOL"], fill=BLUE) | ||||||
|  |  line+=FONTSIZE["SMOL"]+smolspacing | ||||||
|  |  drawer.text((indent, line), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", font=FONT["SMOL"], fill=PURPLE) | ||||||
|  |  line+=FONTSIZE["SMOL"]+smolspacing | ||||||
|  |  drawer.text((indent, line), f"smol line spacing: {smolspacing}", font=FONT["SMOL"], fill=WHITE) | ||||||
|  |  line+=FONTSIZE["SMOL"]+linebreak | ||||||
|  | 
 | ||||||
|  |  drawer.text((indent, line), f"left indent: {indent}", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+spacing | ||||||
|  | 
 | ||||||
|  | def testguistats(): | ||||||
|  |  line=5 | ||||||
|  |  footerline=240-FONTSIZE['BIG']-5 | ||||||
|  |  barheight=10 | ||||||
|  |  barwidth=220 | ||||||
|  |  column=(5,120) | ||||||
|  |  # CPU PERCENTS | ||||||
|  |  line = writewords("CPU Usage", indent=5, line=line, font="big") | ||||||
|  |  cpu_use=psutil.cpu_percent(interval=1,percpu=True) | ||||||
|  |  core=0 | ||||||
|  |  while core < len(cpu_use): | ||||||
|  |   rect_start=(column[0], line) | ||||||
|  |   rect_end=(5+round(barwidth/100*cpu_use[core],0), line+barheight) | ||||||
|  |   rect_outline=(5+barwidth,line+barheight) | ||||||
|  |   drawer.rectangle([rect_start, rect_outline],fill=None,outline=GREEN) | ||||||
|  |   drawer.rectangle([rect_start, rect_end],fill=GREEN,outline=None) | ||||||
|  |   line+=barheight | ||||||
|  |   core+=1 | ||||||
|  |  line+=FONTSIZE['BIG'] | ||||||
|  |  # CPU LOAD | ||||||
|  |  # cpu_loadsss=psutil.getloadavg() | ||||||
|  |  # load=0 | ||||||
|  |  # line+=10 | ||||||
|  |  # while load < len(cpu_loadsss): | ||||||
|  |  #  drawer.text((column[0], line), f"{CPULOADLABELS[load]} load: {round(cpu_loadsss[load],4)}", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  #  line+=FONTSIZE['BIG']+5 | ||||||
|  |  #  load+=1 | ||||||
|  |  # line+=10 | ||||||
|  |  # MEMORY USAGE | ||||||
|  |  line = writewords("Memory Usage", line=line, indent=5) | ||||||
|  |  memories=psutil.virtual_memory() | ||||||
|  |  memtotal=round(memories.total/1024/1024/1024, 2) | ||||||
|  |  memavailable=round(memories.available/1024/1024/1024, 2) | ||||||
|  |  rect_start=(column[0], line) | ||||||
|  |  rect_end=(5+round(barwidth/100*memories.percent,0), line+barheight) | ||||||
|  |  rect_outline=(5+barwidth,line+barheight) | ||||||
|  |  drawer.rectangle([rect_start, rect_outline],fill=None,outline=GREEN) | ||||||
|  |  drawer.rectangle([rect_start, rect_end],fill=GREEN,outline=None) | ||||||
|  |  line+=barheight+FONTSIZE['BIG'] | ||||||
|  |  # From https://github.com/kubesail/pibox-os/blob/main/pwm-fan/pi_fan_hwpwm.c | ||||||
|  |  # Fan off = 60 / on = 65 / high = 80 | ||||||
|  |  temp=round(psutil.sensors_temperatures()['cpu_thermal'][0].current, 1) | ||||||
|  |  tempcolor=GREEN if temp <= 66 else ORANGE if temp <= 80 else RED | ||||||
|  |  writewords(f"Temp: {temp}°C", line=3, color=tempcolor, alignment='RIGHT') | ||||||
|  | 
 | ||||||
|  |  # FOOTER | ||||||
|  |  hostname=socket.gethostname() | ||||||
|  |  # Trusts that IPv4 is in the address list before IPv6 | ||||||
|  |  eth=psutil.net_if_addrs()[NET_ADAPTER][0] | ||||||
|  |  ip=eth.address | ||||||
|  |  cidr=SUBNET_CIDR_MAP[eth.netmask] | ||||||
|  |  writewords(f"{ip}/{cidr}", line=footerline, indent=5) | ||||||
|  |  writewords(f"{hostname}", line=footerline, alignment="RIGHT") | ||||||
|  |  return | ||||||
|  |  line+=barheight | ||||||
|  |  drawer.text((column[0], line), f"Total: {memtotal}G", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+5 | ||||||
|  |  drawer.text((column[0], line), f"Avail: {memavailable}G", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  line+=FONTSIZE['BIG']+5 | ||||||
|  |  # Hello, world! | ||||||
|  |  drawer.text((column[0],line+24), "Hello, world!", font=FONT['BIG'], fill=WHITE) | ||||||
|  |  return | ||||||
|  | 
 | ||||||
|  | def guistats(): | ||||||
|  |  headerline=round(FONTSIZE['BIG']*1.7,0) | ||||||
|  |  line=headerline+20 | ||||||
|  |  padding=20 | ||||||
|  |  barindent=75 | ||||||
|  |  linepadding=10 | ||||||
|  |  diskheight=15 | ||||||
|  |  #nowstr=datetime.datetime.now().strftime("%Y-%m-%d %I:%M%p").lower() | ||||||
|  |  nowstr=datetime.datetime.now().strftime("%b%d %I:%M%p") | ||||||
|  |  timestr=datetime.datetime.now().strftime("%H:%M") | ||||||
|  |  datestr=datetime.datetime.now().strftime("%b %d ") | ||||||
|  | 
 | ||||||
|  |  ## Header | ||||||
|  |  # From https://github.com/kubesail/pibox-os/blob/main/pwm-fan/pi_fan_hwpwm.c | ||||||
|  |  # Fan off = 60 / on = 65 / high = 80 | ||||||
|  |  temp=round(psutil.sensors_temperatures()['cpu_thermal'][0].current, 1) | ||||||
|  |  tempcolor=GREEN if temp <= 66 else ORANGE if temp <= 80 else RED | ||||||
|  |  writewords(f"{temp}°C", line=4, color=tempcolor, alignment='LEFT', indent=10) | ||||||
|  |  writewords(f"{timestr}", line=4, indent=5, alignment='CENTER') | ||||||
|  |  writewords(f"{datestr}", line=4, indent=5, alignment='RIGHT') | ||||||
|  |  drawer.line(((linepadding, headerline), (SCREENWIDTH-linepadding, headerline)), fill=WHITE) | ||||||
|  | 
 | ||||||
|  |  ## Body | ||||||
|  |  # CPU USAGE | ||||||
|  |  writewords("CPU", line=line,font="THICC", spacing=5, indent=padding) | ||||||
|  |  cpupercent=psutil.cpu_percent(interval=1)/100 | ||||||
|  |  cpucolor=GREEN if cpupercent <= .70 else ORANGE if cpupercent <= .85 else RED | ||||||
|  |  corecount=psutil.cpu_count() | ||||||
|  |  fiveminload=psutil.getloadavg()[1] | ||||||
|  |  fiveminperc=round(fiveminload/corecount,2) | ||||||
|  |  fiveminperc=.5 | ||||||
|  |  fivemincolor=DARK_GREEN if fiveminperc <= .70 else DARK_YELLOW if fiveminperc <= .85 else DARK_RED | ||||||
|  |  drawbar(top=line, indent=barindent, right=SCREENWIDTH-padding, corners=(True, True, False, False), color=fivemincolor, percent=fiveminperc) | ||||||
|  |  line=drawbar(top=line, indent=barindent, right=SCREENWIDTH-padding, corners=(True, True, False, False), color=cpucolor, percent=cpupercent) | ||||||
|  |  writewords(f"{int(cpupercent*100)}%", indent=barindent, right=SCREENWIDTH-padding,line=line-FONTSIZE['BIG']-8,alignment='CENTER',outline=BLACK) | ||||||
|  | 
 | ||||||
|  |  # MEMORY USAGE | ||||||
|  |  memories=psutil.virtual_memory() | ||||||
|  |  mempercent=round(memories.used/memories.total,2) | ||||||
|  |  memcolor=AQUA if mempercent <= .70 else ORANGE if mempercent <= .85 else RED | ||||||
|  |  memtotal=round(memories.total/1024/1024/1024, 2) | ||||||
|  |  memused=round(memories.used/1024/1024/1024, 2) | ||||||
|  |  memavailable=round(memories.available/1024/1024/1024, 2) | ||||||
|  |  writewords("RAM", line=line,font="THICC", spacing=5, indent=padding) | ||||||
|  |  line=drawbar(top=line, indent=barindent, right=SCREENWIDTH-padding, corners=(False, False, True, True), color=memcolor, percent=mempercent) | ||||||
|  |  writewords(f"{memused}G/{memtotal}G", indent=barindent, right=SCREENWIDTH-padding, line=line-23,alignment='CENTER',outline=BLACK, font='big') | ||||||
|  | #  line=drawbar(top=line, right=240-padding, corners=(False, False, True, True), color=memcolor, percent=mempercent) | ||||||
|  | #  writewords(f"{memused}G/{memtotal}G", line=line-27,alignment='CENTER',outline=BLACK, font='big') | ||||||
|  | #  line=writewords("Memory", line=line,font="THICC", spacing=5, indent=padding) | ||||||
|  |  line+=25 | ||||||
|  | 
 | ||||||
|  |  # DISK USAGE | ||||||
|  |  rawdisklist=psutil.disk_partitions(all=False) | ||||||
|  |  disklist=[] | ||||||
|  |  diskcolors=[BLUE,PINK,WHITE] | ||||||
|  |  for disk in rawdisklist: | ||||||
|  |   #if 'boot' in disk.mountpoint: | ||||||
|  |   # continue | ||||||
|  |   disklist.append(disk) | ||||||
|  |   #print(f'found disk {disk.device}') | ||||||
|  |  if len(disklist) == 1: | ||||||
|  |   #diskname=disklist[0].device[substring_indicies(disklist[0].device, '/')[-1]+1:] | ||||||
|  |   diskname=disklist[0].mountpoint | ||||||
|  |   writewords(f"{diskname}", line, indent=padding, font='BIG') | ||||||
|  |   drawbar(top=line,indent=SCREENWIDTH/2,height=diskheight, right=SCREENWIDTH-padding, color=diskcolors[0],percent=psutil.disk_usage(disklist[0].mountpoint).percent/100) | ||||||
|  |  else: | ||||||
|  |   diskindex=0 | ||||||
|  |   for disk in disklist: | ||||||
|  |    # e.g. /dev/mmcblk0p2 or /dev/mapper/pibox--group-k3s | ||||||
|  |    #diskname=disk.device[substring_indicies(disk.device, '/')[-1]+1:] | ||||||
|  |    # e.g. / or /mnt/pibox | ||||||
|  |    diskname=disk.mountpoint | ||||||
|  |    diskusage=psutil.disk_usage(disk.mountpoint).percent/100 | ||||||
|  |    writewords(f"{diskname}", line=line, indent=padding, font='MED') | ||||||
|  |    if disk.device == disklist[0].device: | ||||||
|  |     corners=(True, True, False, False) | ||||||
|  |    elif disk.device == disklist[-1].device: | ||||||
|  |     corners=(False, False, True, True) | ||||||
|  |    else: | ||||||
|  |     corners=(False, False, False, False) | ||||||
|  |    line=drawbar(top=line,indent=SCREENWIDTH/2,height=diskheight, right=SCREENWIDTH-padding, color=diskcolors[diskindex%len(diskcolors)],percent=diskusage, corners=corners) | ||||||
|  |    diskindex+=1 | ||||||
|  |  hostname=socket.gethostname() | ||||||
|  |  # Trusts that IPv4 is in the address list before IPv6 | ||||||
|  |  eth=psutil.net_if_addrs()[NET_ADAPTER][0] | ||||||
|  |  ip=eth.address | ||||||
|  |  cidr=SUBNET_CIDR_MAP[eth.netmask] | ||||||
|  |  footerfont="BIG" if drawer.textlength(f"{ip}/{cidr}  {hostname}", FONT['BIG']) <= SCREENWIDTH else "MED" | ||||||
|  |  footerline=240-FONTSIZE[footerfont]-5 | ||||||
|  |  writewords(f"{ip}/{cidr}", line=footerline, indent=5, font=footerfont) | ||||||
|  |  writewords(f"{hostname}", line=footerline, alignment="RIGHT", font=footerfont, indent=5) | ||||||
|  |  footerlineline=SCREENHEIGHT-round(FONTSIZE[footerfont]*1.8,0) | ||||||
|  |  drawer.line(((linepadding, footerlineline), (SCREENWIDTH-linepadding, footerlineline)), fill=WHITE) | ||||||
|  |  return | ||||||
|  | 
 | ||||||
|  | precheck() | ||||||
|  | #textstats() | ||||||
|  | #testguistats() | ||||||
|  | #texttest() | ||||||
|  | guistats() | ||||||
|  | #sendimage(mystats) | ||||||
|  | mystats.save("stats.png") | ||||||
|  | 
 | ||||||
							
								
								
									
										123
									
								
								roles/pibox_basics/tasks/main.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,123 @@ | |||||||
|  | --- | ||||||
|  | - name: Ensure img directory exists | ||||||
|  |   ansible.builtin.file: | ||||||
|  |     path: "{{ item }}" | ||||||
|  |     state: directory | ||||||
|  |   with_items: | ||||||
|  |     - "~/bin" | ||||||
|  |     - "~/img" | ||||||
|  | - name: Copy image directory | ||||||
|  |   ansible.builtin.copy: | ||||||
|  |     src: "img" | ||||||
|  |     dest: "~/img" | ||||||
|  | - name: Copy screensaver script | ||||||
|  |   ansible.builtin.copy: | ||||||
|  |     src: screensaver.sh | ||||||
|  |     dest: "~/bin/screensaver.sh" | ||||||
|  |     mode: '0755' | ||||||
|  | 
 | ||||||
|  | # BCM & Fan PWM drivers | ||||||
|  | - name: Clone pibox-os repository | ||||||
|  |   ansible.builtin.git: | ||||||
|  |     repo: https://github.com/kubesail/pibox-os.git | ||||||
|  |     dest: /usr/src/pibox-os | ||||||
|  |     clone: true | ||||||
|  |     force: true | ||||||
|  |     update: true | ||||||
|  |   register: piboxos_repo_updated | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Extract bcm2835-1.68.tar.gz | ||||||
|  |   ansible.builtin.unarchive: | ||||||
|  |     creates: /usr/src/pibox-os/pwm-fan/bcm2835-1.68/ | ||||||
|  |     dest: /usr/src/pibox-os/pwm-fan/ | ||||||
|  |     remote_src: true | ||||||
|  |     src: /usr/src/pibox-os/pwm-fan/bcm2835-1.68.tar.gz | ||||||
|  |   when: piboxos_repo_updated.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Configure build env for bcm2835-1.68 fan pwm driver | ||||||
|  |   ansible.builtin.command: | ||||||
|  |     chdir: /usr/src/pibox-os/pwm-fan/bcm2835-1.68/ | ||||||
|  |     cmd: bash ./configure | ||||||
|  |   when: piboxos_repo_updated.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Build bcm2835-1.68 driver | ||||||
|  |   ansible.builtin.command: | ||||||
|  |     chdir: /usr/src/pibox-os/pwm-fan/bcm2835-1.68/ | ||||||
|  |     cmd: make | ||||||
|  |   when: piboxos_repo_updated.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Install bcm2835-1.68 driver | ||||||
|  |   ansible.builtin.command: | ||||||
|  |     chdir: /usr/src/pibox-os/pwm-fan/bcm2835-1.68/ | ||||||
|  |     cmd: make install | ||||||
|  |   when: piboxos_repo_updated.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Build pwm fan driver | ||||||
|  |   ansible.builtin.command: | ||||||
|  |     chdir: /usr/src/pibox-os/pwm-fan/ | ||||||
|  |     cmd: make | ||||||
|  |   when: piboxos_repo_updated.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Install pwm fan driver | ||||||
|  |   ansible.builtin.command: | ||||||
|  |     chdir: /usr/src/pibox-os/pwm-fan/ | ||||||
|  |     cmd: make install | ||||||
|  |   when: piboxos_repo_updated.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | 
 | ||||||
|  | # Pibox framebuffer binary & service | ||||||
|  | - name: Create kubesail directory in /opt | ||||||
|  |   ansible.builtin.file: | ||||||
|  |     path: /opt/kubesail | ||||||
|  |     state: directory | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Download pibox-framebuffer binary | ||||||
|  |   ansible.builtin.get_url: | ||||||
|  |     dest: /opt/kubesail/pibox-framebuffer | ||||||
|  |     mode: '755' | ||||||
|  |     url: https://github.com/kubesail/pibox-framebuffer/releases/download/v22/pibox-framebuffer | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Download and install pibox-framebuffer service | ||||||
|  |   ansible.builtin.get_url: | ||||||
|  |     dest: /etc/systemd/system/pibox-framebuffer.service | ||||||
|  |     url: https://raw.githubusercontent.com/kubesail/pibox-framebuffer/main/pibox-framebuffer.service | ||||||
|  |   register: pibox_framebuffer_downloaded | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Reload systemctl daemon if necessary | ||||||
|  |   ansible.builtin.command: | ||||||
|  |     cmd: systemctl daemon-reload | ||||||
|  |   when: pibox_framebuffer_downloaded.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Enable SPI via /boot/config.txt edit | ||||||
|  |   ansible.builtin.lineinfile: | ||||||
|  |     path: /boot/firmware/config.txt | ||||||
|  |     line: "dtparam=spi=on" | ||||||
|  |     insertafter: "#dtparam=spi=on" | ||||||
|  |     state: present | ||||||
|  |   register: boot_config_updated | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Start pibox-framebuffer service if no boot config made | ||||||
|  |   ansible.builtin.service: | ||||||
|  |     name: pibox-framebuffer | ||||||
|  |     enabled: true | ||||||
|  |     state: started | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
|  | - name: Reboot if SPI updated | ||||||
|  |   ansible.builtin.reboot: | ||||||
|  |     post_reboot_delay: 10 | ||||||
|  |   when: boot_config_updated.changed | ||||||
|  |   become: true | ||||||
|  |   become_method: sudo | ||||||
 WhatTheMike
						WhatTheMike