Added remaining pibox setup
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -3,3 +3,4 @@ inv.yaml | ||||
| ansible.cfg | ||||
| host_vars/ | ||||
| group_vars/ | ||||
| secrets/ | ||||
|  | ||||
| @ -4,6 +4,10 @@ | ||||
|   vars: | ||||
|     password_excluded_hosts: | ||||
|       - pikvm | ||||
|     non_raspbian_hosts: | ||||
|       - pikvm | ||||
|     pibox_hosts: | ||||
|       - piparcel | ||||
|   tasks: | ||||
|     - name: Set root passwords | ||||
|       ansible.builtin.include_role: | ||||
| @ -18,13 +22,16 @@ | ||||
|     - name: Ensure vim config is present for pi | ||||
|       ansible.builtin.include_role: | ||||
|         name: configure_vim | ||||
|       when: ansible_hostname not in non_raspbian_hosts | ||||
|     - name: Include profile aliases | ||||
|       ansible.builtin.include_role: | ||||
|         name: profile_aliases | ||||
|       when: ansible_hostname not in non_raspbian_hosts | ||||
|     - name: Include basic package list | ||||
|       ansible.builtin.include_role: | ||||
|         name: basic_setup | ||||
|       when: ansible_hostname not in non_raspbian_hosts | ||||
|     - name: Configure pibox extras | ||||
|       ansible.builtin.include_role: | ||||
|         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