Added lots of stuff needing to be backed up.
authorAndrew <andrew@liquid.me.uk>
Thu, 17 Jun 2021 16:17:05 +0000 (17:17 +0100)
committerAndrew <andrew@liquid.me.uk>
Thu, 17 Jun 2021 16:17:05 +0000 (17:17 +0100)
19 files changed:
gpu_usage/README [new file with mode: 0644]
gpu_usage/etc_local.d_gpu_usage.start [new file with mode: 0755]
gpu_usage/etc_local.d_gpu_usage.stop [new file with mode: 0755]
gpu_usage/gpu_usage [new file with mode: 0755]
minecraft/etc_init.d_spigot [new file with mode: 0755]
minecraft/usr_sbin_spigot_service [new file with mode: 0755]
pi_temp_daemon/README.md [new file with mode: 0644]
pi_temp_daemon/etc_init.d_pitempd [new file with mode: 0755]
pi_temp_daemon/usr_sbin_pitempd [new file with mode: 0755]
python_3.9_compat/python3_9-py-checker [new file with mode: 0755]
python_3.9_compat/python3_9-py-logger [new file with mode: 0755]
python_3.9_compat/python_3_9_compat.sh [new file with mode: 0755]
random_scripts/TV-On [new file with mode: 0755]
random_scripts/etc_local.d_nfs_mounts.stopper [new file with mode: 0755]
random_scripts/etc_local.d_soundcard.start [new file with mode: 0755]
random_scripts/etc_local.d_tcp_syn_retries.stop [new file with mode: 0755]
random_scripts/shebang_test.py [new file with mode: 0755]
random_scripts/speech.sh [new file with mode: 0755]
random_scripts/ytplay [new file with mode: 0755]

diff --git a/gpu_usage/README b/gpu_usage/README
new file mode 100644 (file)
index 0000000..3bee6a4
--- /dev/null
@@ -0,0 +1 @@
+Was too lazy to write an initscript, so made /etc/local.d/ x.start and x.stop scripts instead.
diff --git a/gpu_usage/etc_local.d_gpu_usage.start b/gpu_usage/etc_local.d_gpu_usage.start
new file mode 100755 (executable)
index 0000000..016e477
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/bash
+/usr/sbin/gpu_usage start
diff --git a/gpu_usage/etc_local.d_gpu_usage.stop b/gpu_usage/etc_local.d_gpu_usage.stop
new file mode 100755 (executable)
index 0000000..7c5bb3a
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+/usr/sbin/gpu_usage stop
+
diff --git a/gpu_usage/gpu_usage b/gpu_usage/gpu_usage
new file mode 100755 (executable)
index 0000000..80f78ae
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/python
+#Formats gpu usage data from radeontop and dumps it in /tmp/gpu_usage.txt for use in conky et al.
+import sys
+import re
+import subprocess
+import signal
+import os
+import time
+
+def write_usage(text):
+       file_handle=open('/tmp/gpu_usage.txt','w')
+       file_handle.write('%s\n'%text)
+       file_handle.close()
+
+def exit(sig,frame):
+       sys.exit()
+
+def fupdate():
+       try:
+               file_handle=open('/tmp/gpu_usage.tmp','r')
+               lines=file_handle.readlines()
+               file_handle.close()
+       except OSError:
+               write_usage('Unknown%')
+               return
+       for line in lines:
+               if re.match('.*gpu.*',line):
+                       text=re.sub('.* gpu ','',line.strip())
+                       text=re.sub('%.*','%',text)
+                       write_usage(text)
+                       return
+
+def start(fork1):
+       if fork1:return
+       signal.signal(signal.SIGUSR1, exit)#from main script stop()
+       signal.signal(signal.SIGTERM, exit)#from start-stop-daemon
+       while True:
+               try:
+                       os.remove("/tmp/gpu_usage.tmp")
+               except: pass
+               subprocess.run(['/usr/sbin/radeontop','-i1','-l1','-d','/tmp/gpu_usage.tmp'],capture_output=True)
+               fupdate()
+               #time.sleep(1)
+
+def stop():
+       running=subprocess.run(['ps','aux'],capture_output=True,text=True)
+       processes=running.stdout.split('\n')
+       for process in processes:
+               if re.match('.*%s %s.*'%(sys.argv[0],'start'),process):
+                       while process!=process.replace('  ',' '):process=process.replace('  ',' ')
+                       pid=int(process.split(' ')[1])
+                       if pid != os.getpid():os.kill(pid,signal.SIGUSR1)
+
+try:
+       command=sys.argv[1]
+except IndexError:
+       print('Usage: %s start|stop'%sys.argv[0])
+       sys.exit(1)
+if command=='start':
+       stop()
+       start(os.fork())
+elif command=='stop':
+       stop()
+else:
+       print ('Usage: %s start|stop'%sys.argv[0])
+       sys.exit(1)
diff --git a/minecraft/etc_init.d_spigot b/minecraft/etc_init.d_spigot
new file mode 100755 (executable)
index 0000000..fc8b65e
--- /dev/null
@@ -0,0 +1,46 @@
+#!/sbin/openrc-run
+#openrc wrapper for /usr/sbin/spigot_service
+extra_commands="backup reload restart"
+
+depend() {
+       need net
+}
+
+start() {
+       ebegin "Starting spigot service daemon"
+       start-stop-daemon --start --exec /usr/sbin/spigot_service \
+        --pidfile /var/run/spigot_service.pid -- start
+       eend $?
+}
+
+stop() {
+       ebegin "Stopping spigot service daemon"
+        start-stop-daemon --stop --exec /usr/sbin/spigot_service \
+        --pidfile /var/run/spigot_service.pid --retry 90 -- stop
+       eend $?
+}
+
+status() {
+       ebegin "Checking spigot service daemon status"
+       sleep 0.1
+       /usr/sbin/spigot_service status
+       eend $?
+}
+
+backup() {
+       ebegin "Running backup helper"
+       /usr/sbin/spigot_service backup
+        eend $?
+}
+
+reload() {
+       ebegin "Reloading spigot service daemon config"
+        /usr/sbin/spigot_service reload
+        eend $?
+}
+
+restart() {
+        ebegin "Restarting spigot service daemon"
+        /usr/sbin/spigot_service restart
+        eend $?
+}
diff --git a/minecraft/usr_sbin_spigot_service b/minecraft/usr_sbin_spigot_service
new file mode 100755 (executable)
index 0000000..48f7e86
--- /dev/null
@@ -0,0 +1,200 @@
+#!/usr/bin/env python3.7
+#/usr/sbin/spigot_service
+#python and screen daemonic wrapper for spigot servers
+from subprocess import Popen, run
+import os, sys, time, signal, shlex, re
+
+class server_env:
+
+        name=           'spigot'
+        working_path=   '/root/bukkit/'
+        screen_history= 4096
+        world=          'Significant_Bytes'
+        backup_path=    '/data/minecraft.backups/'
+        pid_file_path=  '/var/run/spigot_service.pid'
+        exec=           'spigot.current'
+        options=        'nogui'
+        java_maxheap=   4096
+        java_minheap=   1024
+        thread_count=   6
+
+        java_command=   'java -Xmx%sM -Xms%sM -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalPacing \
+                        -XX:ParallelGCThreads=%s -XX:+AggressiveOpts -jar %s %s'\
+                        %(java_maxheap, java_minheap, thread_count, exec, options)
+
+class ps_aux:
+
+    def get_pids(invocation):
+        pids={}
+        p=run(['ps','aux'],capture_output=True,text=True)
+        processes=p.stdout.split('\n')
+        for process in processes:
+            if re.match('.*%s.*'%invocation, process):
+                pids.append(re.sub(" +"," ", process).split(' ')[1])
+        return pids
+
+    def is_running(pid):
+        pid=str(pid)
+        p=run(['ps','aux'],capture_output=True,text=True)
+        processes=p.stdout.split('\n')
+        for process in processes:
+            if re.match('.*%s.*'%pid, process):
+                if pid==re.sub(" +", " ",process).split(' ')[1]:
+                    return True
+        return False
+
+class screen:
+
+    def __init__(self, **kwargs):
+        self.name=      kwargs.get('name', '')
+        self.window=    kwargs.get('window', 0)
+        self.history=   kwargs.get('history', 1024)
+        self.cwd=       kwargs.get('set_wd', os.getcwd())
+        self.child=     None
+
+    def create(self, command=''):
+        if self.child:return
+        cmd_string='/usr/bin/screen -h %s -DmS %s %s'%(self.history, self.name, command)
+        args=shlex.split(cmd_string)
+        self.child=Popen(args,cwd=self.cwd)
+
+    def send(self, input=None):
+        if not input:return
+        cmd_string='/usr/bin/screen -p %s -S %s -X stuff "%s\n"'%(self.window, self.name, input)
+        comm_child=Popen(shlex.split(cmd_string))
+        comm_child.communicate()
+
+class daemon:
+
+    def start(self):
+        try:
+            with open(server_env.pid_file_path, 'r') as pid_file:
+                pid_file.close()
+                print('Error: PID file exists, exiting...')
+                sys.exit(1)
+        except OSError:
+            pass
+        child=os.fork()
+        if child:
+            with open(server_env.pid_file_path, 'x') as pid_file:
+                pid_file.write(str(child))
+            return
+        #we are now the child daemon subprocess
+        signal.signal(signal.SIGINT, self.stop)#from ctrl-c
+        signal.signal(signal.SIGUSR1, self.stop)#from main script stop()
+        signal.signal(signal.SIGTERM, self.stop)#from start-stop-daemon
+        self.main_process=screen(name=server_env.name,  history=server_env.screen_history, set_wd=server_env.working_path)
+        self.main_process.create(server_env.java_command)
+        self.main_process.child.communicate()#lets just hang about here
+        try:
+            os.remove(server_env.pid_file_path)
+        except FileNotFoundError:
+            pass
+        sys.exit(0)
+
+    def stop(self, sig, frame):
+        self.main_process.send('say Server shutting down in 10 seconds')
+        #self.main_process.send('save-off')
+        #self.main_process.send('save-all')
+        time.sleep(5)
+        self.main_process.send('say Server shutting down in 5 seconds')
+        time.sleep(1)
+        self.main_process.send('say 4')
+        time.sleep(1)
+        self.main_process.send('say 3')
+        time.sleep(1)
+        self.main_process.send('say 2')
+        time.sleep(1)
+        self.main_process.send('say 1')
+        time.sleep(1)
+        self.main_process.send('say Server shutting down...')
+        self.main_process.send('stop')
+        try:
+            os.remove(server_env.pid_file_path)
+        except FileNotFoundError:
+            pass
+        #sys.exit(0)
+#endclass
+
+def usage():
+    print('Usage: %s start|stop|status|backup|reload|restart'%sys.argv[0][sys.argv[0].rfind('/')+1:])
+    sys.exit()
+
+def start():
+    main=daemon()
+    main.start()
+
+def stop():
+    try:
+        with open(server_env.pid_file_path, 'r') as pid_file:
+            pid=int(pid_file.read())
+        os.kill(pid, 10)
+        while ps_aux.is_running(pid):
+            time.sleep(1)
+    except ProcessLookupError:
+        print('Error: process not running, removing stale PID file')
+        os.remove(server_env.pid_file_path)
+    except FileNotFoundError:
+        print('Error: PID file not found')
+
+def status():
+    try:
+        with open(server_env.pid_file_path, 'r') as pid_file:
+            pid=pid_file.read()
+        if ps_aux.is_running(pid):
+            print('World %s is running.'%server_env.world, end='',flush=True)
+            sys.exit(0)
+        else:
+            print('World %s is not running.'%server_env.world, end='',flush=True )
+            sys.exit(1)
+    except FileNotFoundError:
+        print('World %s is not running.'%server_env.world, end='',flush=True)
+        sys.exit(1)
+
+def backup():
+    print(" \033[92m*\033[0m Starting backup process ... ")
+    comm_process=screen(name=server_env.name)
+    comm_process.send('say Starting world backup...')
+    comm_process.send('save-off')
+    comm_process.send('save-all')
+    time.sleep(25)
+    backup_filename=('%s/%s-%s')%(server_env.backup_path,server_env.world,time.strftime("%Y-%m-%d_%H-%M.tar", time.gmtime()))
+    backup_command=shlex.split('tar -C "%s" -cf "%s" %s %s_nether %s_the_end'\
+        %(server_env.working_path, backup_filename, server_env.world, server_env.world, server_env.world))
+    backup_p=Popen(backup_command)
+    backup_p.communicate()
+    comm_process.send('save-on')
+    comm_process.send('say World backup complete.')
+    #print(" \033[92m*\033[0m Compressing backup ... ")
+    #compress_p=Popen(['xz', '-fT0', backup_filename])
+    #compress_p.communicate()
+    print(" \033[92m*\033[0m Backup complete.")
+
+def reload():
+    comm_process=screen(name=server_env.name)
+    comm_process.send('say Re-loading world configuration...')
+    comm_process.send('reload')
+    comm_process.send('say Re-load complete.')
+
+try:
+    arg=sys.argv[1]
+except IndexError:
+    usage()
+
+if arg=='help':
+    usage()
+elif arg=='start':
+    start()
+elif arg=='stop':
+    stop()
+elif arg=='status':
+    status()
+elif arg=='backup':
+    backup()
+elif arg=='reload':
+    reload()
+elif arg=='restart':
+    stop()
+    start()
+else:
+    usage()
diff --git a/pi_temp_daemon/README.md b/pi_temp_daemon/README.md
new file mode 100644 (file)
index 0000000..56bcd46
--- /dev/null
@@ -0,0 +1,3 @@
+Temperature to fan speed daemon for Pi4 with https://www.waveshare.com/wiki/Fan_HAT 
+
+#ToDo Write an ebuild ( yeah, right, cos that's gonna happen ;P )
diff --git a/pi_temp_daemon/etc_init.d_pitempd b/pi_temp_daemon/etc_init.d_pitempd
new file mode 100755 (executable)
index 0000000..59bad3a
--- /dev/null
@@ -0,0 +1,39 @@
+#!/sbin/openrc-run
+extra_commands="restart monitor"
+
+depend(){
+       need rpi-i2c
+}
+
+start() {
+       ebegin "Starting pitempd daemon"
+       start-stop-daemon --start --exec /usr/sbin/pitempd \
+        --pidfile /var/run/pitempd.pid -- start
+       eend $?
+}
+
+stop() {
+       ebegin "Stopping pitempd daemon"
+        start-stop-daemon --stop --exec /usr/sbin/pitempd \
+        --pidfile /var/run/pitempd.pid --signal 10 --retry 5 --stop
+       eend $?
+}
+
+status() {
+       ebegin "Checking pitempd daemon status"
+       sleep 0.1
+       /usr/sbin/pitempd status
+       eend $?
+}
+
+restart() {
+        ebegin "Restarting pitempd daemon"
+        /usr/sbin/pitempd restart
+        eend $?
+}
+
+monitor(){
+               ebegin "Monitoring pitempd daemon"
+        /usr/sbin/pitempd monitor
+        eend $?
+}
diff --git a/pi_temp_daemon/usr_sbin_pitempd b/pi_temp_daemon/usr_sbin_pitempd
new file mode 100755 (executable)
index 0000000..73a2736
--- /dev/null
@@ -0,0 +1,434 @@
+#!/usr/bin/env python
+
+# /*****************************************************************************
+# * | File        :       SSD1306.py
+# * | Author      :   Waveshare team
+# * | Function    :   SSD1306
+# * | Info        :
+# *----------------
+# * | This version:   V1.0
+# * | Date        :   2019-11-14
+# * | Info        :
+# ******************************************************************************/
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documnetation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to  whom the Software is
+# furished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# ^^ Included because the SSD1306 class was directly copied from the wiki code ^^ #
+
+from PIL import Image,ImageDraw,ImageFont
+from subprocess import run
+import os, sys, signal
+import re, math,  time
+import smbus, psutil
+
+class PCA9685(object):
+    # Registers/etc.
+    __SUBADR1            = 0x02
+    __SUBADR2            = 0x03
+    __SUBADR3            = 0x04
+    __MODE1              = 0x00
+    __PRESCALE           = 0xFE
+    __LED0_ON_L          = 0x06
+    __LED0_ON_H          = 0x07
+    __LED0_OFF_L         = 0x08
+    __LED0_OFF_H         = 0x09
+    __ALLLED_ON_L        = 0xFA
+    __ALLLED_ON_H        = 0xFB
+    __ALLLED_OFF_L       = 0xFC
+    __ALLLED_OFF_H       = 0xFD
+
+    def __init__(self, address=0x40, debug=False):
+        self.bus = smbus.SMBus(1)
+        self.address = address
+        self.debug = debug
+        if (self.debug):
+            print("Reseting PCA9685")
+        self.write(self.__MODE1, 0x00)
+
+    def write(self, reg, value):
+        "Writes an 8-bit value to the specified register/address"
+        self.bus.write_byte_data(self.address, reg, value)
+        if (self.debug):
+            print("I2C: Write 0x%02X to register 0x%02X" % (value, reg))
+
+    def read(self, reg):
+        "Read an unsigned byte from the I2C device"
+        result = self.bus.read_byte_data(self.address, reg)
+        if (self.debug):
+            print("I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (self.address, result & 0xFF, reg))
+        return result
+
+    def setPWMFreq(self, freq):
+        "Sets the PWM frequency"
+        prescaleval = 25000000.0    # 25MHz
+        prescaleval /= 4096.0       # 12-bit
+        prescaleval /= float(freq)
+        prescaleval -= 1.0
+        if (self.debug):
+            print("Setting PWM frequency to %d Hz" % freq)
+            print("Estimated pre-scale: %d" % prescaleval)
+        prescale = math.floor(prescaleval + 0.5)
+        if (self.debug):
+            print("Final pre-scale: %d" % prescale)
+
+        oldmode = self.read(self.__MODE1);
+        newmode = (oldmode & 0x7F) | 0x10        # sleep
+        self.write(self.__MODE1, newmode)        # go to sleep
+        self.write(self.__PRESCALE, int(math.floor(prescale)))
+        self.write(self.__MODE1, oldmode)
+        time.sleep(0.005)
+        self.write(self.__MODE1, oldmode | 0x80)
+
+    def setPWM(self, channel, on, off):
+        "Sets a single PWM channel"
+        self.write(self.__LED0_ON_L+4*channel, on & 0xFF)
+        self.write(self.__LED0_ON_H+4*channel, on >> 8)
+        self.write(self.__LED0_OFF_L+4*channel, off & 0xFF)
+        self.write(self.__LED0_OFF_H+4*channel, off >> 8)
+        if (self.debug):
+            print("channel: %d  LED_ON: %d LED_OFF: %d" % (channel,on,off))
+
+    def setServoPulse(self, channel, pulse):
+        pulse = pulse*4095/100
+        self.setPWM(channel, 0, int(pulse))
+#end pwm class
+
+class SSD1306(object):
+    def __init__(self, width=128, height=32, addr=0x3c):
+        self.width = width
+        self.height = height
+        self.Column = width
+        self.Page = int(height/8)
+        self.addr = addr
+        self.bus = smbus.SMBus(1)
+
+    def SendCommand(self, cmd):# write command
+        self.bus.write_byte_data(self.addr, 0x00, cmd)
+
+    def SendData(self, cmd):# write ram
+        self.bus.write_byte_data(self.addr, 0x40, cmd)
+
+    def Closebus(self):
+        self.bus.close()
+
+    def Init(self):
+        self.SendCommand(0xAE)
+
+        self.SendCommand(0x40) # set low column address
+        self.SendCommand(0xB0) # set high column address
+
+        self.SendCommand(0xC8) # not offset
+
+        self.SendCommand(0x81)
+        self.SendCommand(0xff)
+
+        self.SendCommand(0xa1)
+
+        self.SendCommand(0xa6)
+
+        self.SendCommand(0xa8)
+        self.SendCommand(0x1f)
+
+        self.SendCommand(0xd3)
+        self.SendCommand(0x00)
+
+        self.SendCommand(0xd5)
+        self.SendCommand(0xf0)
+
+        self.SendCommand(0xd9)
+        self.SendCommand(0x22)
+
+        self.SendCommand(0xda)
+        self.SendCommand(0x02)
+
+        self.SendCommand(0xdb)
+        self.SendCommand(0x49)
+
+        self.SendCommand(0x8d)
+        self.SendCommand(0x14)
+
+        self.SendCommand(0xaf)
+
+    def ClearBlack(self):
+        for i in range(0, self.Page):
+            self.SendCommand(0xb0 + i)
+            self.SendCommand(0x00)
+            self.SendCommand(0x10)
+            for j in range(0, self.Column):
+                self.SendData(0x00)
+
+    def ClearWhite(self):
+        for i in range(0, self.Page):
+            self.SendCommand(0xb0 + i)
+            self.SendCommand(0x00)
+            self.SendCommand(0x10)
+            for j in range(0, self.Column):
+                self.SendData(0xff)
+
+    def getbuffer(self, image):
+        buf = [0xff] * (self.Page * self.Column)
+        image_monocolor = image.convert('1')
+        imwidth, imheight = image_monocolor.size
+        pixels = image_monocolor.load()
+        if(imwidth == self.width and imheight == self.height):
+            # print ("Horizontal screen")
+            for y in range(imheight):
+                for x in range(imwidth):
+                    # Set the bits for the column of pixels at the current position.
+                    if pixels[x, y] == 0:
+                        buf[x + int(y / 8) * self.width] &= ~(1 << (y % 8))
+        elif(imwidth == self.height and imheight == self.width):
+            # print ("Vertical screen")
+            for y in range(imheight):
+                for x in range(imwidth):
+                    newx = y
+                    newy = self.height - x - 1
+                    if pixels[x, y] == 0:
+                        buf[(newx + int(newy / 8 )*self.width) ] &= ~(1 << (y % 8))
+        for x in range(self.Page * self.Column):
+            buf[x] = ~buf[x]
+        return buf
+
+    def ShowImage(self, pBuf):
+        for i in range(0, self.Page):
+            self.SendCommand(0xB0 + i) # set page address
+            self.SendCommand(0x00) # set low column address
+            self.SendCommand(0x10) # set high column address
+            # write data #
+            for j in range(0, self.Column):
+                self.SendData(pBuf[j+self.width*i])
+#end oled class
+
+
+#start of #mycode
+class ps:
+
+    def get_pids(invocation):
+        pids=[]
+        p=run(['ps','aux'],capture_output=True,text=True)
+        processes=p.stdout.split('\n')
+        for process in processes:
+            if re.match('.*%s.*'%invocation, process):
+                pids.append(int(re.sub(" +"," ", process).split(' ')[1]))
+        return pids
+
+    def is_running(pid):
+        pid=str(pid)
+        p=run(['ps','aux'],capture_output=True,text=True)
+        processes=p.stdout.split('\n')
+        for process in processes:
+            if re.match('.*%s.*'%pid, process):
+                if pid==re.sub(" +", " ",process).split(' ')[1]:
+                    return True
+        return False
+#end ps class
+
+class daemon:
+    pid_file_path='/var/run/pitempd.pid'
+    fan_speed_path='/var/run/pitempd.pwm'
+    cpu_temp_path='/sys/class/hwmon/hwmon0/temp1_input'
+    fan_disable_temp=45
+
+    default_fan_speed=0
+    fan_min_speed=40
+    fan_max_speed=100
+    update_time=2.5
+    debug=True
+
+    def __init__(self):
+        self.pwm=PCA9685(0x40, debug=False)
+        self.pwm.setPWMFreq(50)
+        self.pwm.setServoPulse(0,100)
+        self.oled = SSD1306()
+        self.oled.Init()
+        self.oled.ClearBlack()
+        self.image1 = Image.new('1', (self.oled.width, self.oled.height), "WHITE")
+        self.draw = ImageDraw.Draw(self.image1)
+        self.font = ImageFont.load_default()
+
+    def curve(self, x):
+        if x <= self.fan_disable_temp:
+            return self.default_fan_speed
+        #y = (((x−40)/5.5)^2.4)+40 (curve from Kmplot)
+        y=(((x-self.fan_disable_temp)/5.5)**2.4)+self.fan_min_speed
+        if y > self.fan_max_speed:
+            y=self.fan_max_speed
+        return y
+
+    def start(self):
+        try:
+            with open(self.pid_file_path, 'r') as pid_file:
+                pid_file.close()
+                print('Error: PID file exists, exiting...')
+                sys.exit(1)
+        except OSError:
+            pass
+        child=os.fork()
+        if child:
+            with open(self.pid_file_path, 'x') as pid_file:
+                pid_file.write(str(child))
+            return
+        signal.signal(signal.SIGINT, self.stop)#from ctrl-c
+        signal.signal(signal.SIGUSR1, self.stop)#from main script stop()
+        signal.signal(signal.SIGTERM, self.stop)#from start-stop-daemon
+        #Main loop
+        top_line=True
+        while True:
+            if top_line:
+                cpu_string='CPU Usage:'
+                cpu_stat='%s%%'%int(psutil.cpu_percent(interval=self.update_time))
+            else:
+                cpu_string='CPU Speed:'
+                cpu_stat='%sMHz'%int(psutil.cpu_freq().current)
+                time.sleep(self.update_time)
+            top_line=not top_line
+            temp=self.get_temp()
+            fan=self.curve(temp)
+            with open(self.fan_speed_path, 'w') as pwm_file:
+                pwm_file.write('%.1f%%\n'%fan)
+            self.set_fan(fan)
+            self.set_display(   cpu_string,cpu_stat, \
+                                'CPU Temp:', '%.1f°C'%temp, \
+                                'Fan Speed:', '%.1f%%'%fan)
+
+    def get_temp(self):
+        with open(self.cpu_temp_path, 'r') as temp_file:
+            temp=int(temp_file.read())/1000
+        return temp
+
+    def set_fan(self,speed):
+        self.pwm.setServoPulse(0,speed)
+
+    def set_display(self,upper_left, upper_right,mid_left, mid_right, lower_left='test', lower_right='test'):
+        self.draw.rectangle((0,0,128,32), fill = 1)
+        self.draw.text((0,0),    upper_left,     font=self.font, fill = 0)    
+        self.draw.text((85,0),   upper_right,    font=self.font, fill = 0)
+        self.draw.text((0,11),   mid_left,     font=self.font, fill = 0)
+        self.draw.text((85,11),  mid_right,    font=self.font, fill = 0)
+        self.draw.text((0,22),   lower_left,     font=self.font, fill = 0)
+        self.draw.text((85,22),  lower_right,    font=self.font, fill = 0)        
+        self.oled.ShowImage(self.oled.getbuffer(self.image1.rotate(180)))
+
+    def stop(self, sig, frame):
+        self.set_fan(self.default_fan_speed)
+        with open(self.fan_speed_path, 'w') as pwm_file:
+            pwm_file.write('%.1f%%\n'%self.default_fan_speed)
+        self.draw.rectangle((0,0,128,32), fill = 1)
+        self.draw.text((40,0),'Daemon',    font=self.font, fill = 0)
+        self.draw.text((38,11),'Offline',    font=self.font, fill = 0)
+        self.draw.text((0,22),   'Fan Speed:',     font=self.font, fill = 0)
+        self.draw.text((85,22),  '%.1f%%'%self.default_fan_speed,    font=self.font, fill = 0)
+        self.oled.ShowImage(self.oled.getbuffer(self.image1.rotate(180)))
+        self.oled.Closebus()
+        try:
+            os.remove(daemon.pid_file_path)
+        except FileNotFoundError:
+            pass
+        sys.exit(0)
+
+#end daemon class
+
+def usage():
+    print('Usage: %s start|stop|status|monitor|restart'%sys.argv[0][sys.argv[0].rfind('/')+1:])
+    sys.exit()
+
+def start():
+    main=daemon()
+    main.start()
+
+def stop():
+    try:
+        with open(daemon.pid_file_path, 'r') as pid_file:
+            pid=int(pid_file.read())
+        os.kill(pid, 10)
+        while ps.is_running(pid): time.sleep(1)
+    except ProcessLookupError:
+        print('Error: process not running, removing stale PID file')
+        os.remove(daemon.pid_file_path)
+    except FileNotFoundError:
+        print('Error: PID file not found, checking for process...', end='',flush=True)
+        pids=ps.get_pids('%s start'%sys.argv[0])
+        if pids:
+            for pid in pids:
+                print(' killing process: %s'%pid)
+                os.kill(pid, 10)
+                while ps.is_running(pid): time.sleep(1)
+        else:
+            print(' daemon not running.')
+    sys.exit(0)
+
+def status():
+    try:
+        with open(daemon.pid_file_path, 'r') as pid_file:
+            pid=pid_file.read()
+        if ps.is_running(pid):
+            sys.exit(0)
+        else:
+            sys.exit(1)
+    except FileNotFoundError:
+        sys.exit(1)
+
+def monstop(sig, f):
+    print('Exiting: %s'%sig)
+    sys.exit(0)
+
+def monitor():
+    signal.signal(signal.SIGINT, monstop)#from ctrl-c
+    signal.signal(signal.SIGUSR1, monstop)#from main script stop()
+    signal.signal(signal.SIGTERM, monstop)#from start-stop-daemon
+    cpu_usage='Waiting...'
+    while True:
+        with open(daemon.cpu_temp_path, 'r') as temp_file:
+            temp=int(temp_file.read())/100
+        with open(daemon.fan_speed_path, 'r') as pwm_file:
+            str=pwm_file.read()
+            pwm=float(str[:-2])
+        try:
+            with open(daemon.pid_file_path, 'r') as pid_file:
+                pid=pid_file.read()
+        except FileNotFoundError:
+                pid='Not running'
+        cpu_mhz='%sMHz'%int(psutil.cpu_freq().current)
+        print('Daemon PID:',pid)
+        print('CPU Speed: ',cpu_mhz)
+        print('CPU Usage: ',cpu_usage)
+        print('CPU Temp:  ','%s°C'%(int(temp)/10))
+        print('Fan Speed: ','%.1f%%'%pwm)
+        print()
+        cpu_usage='%s%%'%int(psutil.cpu_percent(interval=daemon.update_time))
+
+try:
+    arg=sys.argv[1]
+except IndexError:
+    usage()
+
+if arg=='help':
+    usage()
+elif arg=='start':
+    start()
+elif arg=='stop':
+    stop()
+elif arg=='status':
+    status()
+elif arg=='restart':
+    stop()
+    start()
+elif arg=='monitor':
+    monitor()
+else:
+    usage()
diff --git a/python_3.9_compat/python3_9-py-checker b/python_3.9_compat/python3_9-py-checker
new file mode 100755 (executable)
index 0000000..bd47a1c
--- /dev/null
@@ -0,0 +1,119 @@
+#!/bin/bash
+# check the files generated by the python_3_9_compat.sh script that crawls the portage tree
+# usage python3_9-py-checker <path/to/file>
+log_file="/run/user/$UID/pycheker.log"
+#stolen from something in ~/bin/
+prog=('\' '|' '/' '-' 'done!')
+prog_ind=0
+function progress(){
+       echo -ne "\033[1K\r$* ${prog[${prog_ind}]}"
+       (( ++ prog_ind ))
+       [ ${prog_ind} -gt 3 ] && prog_ind=0
+}
+function end_progress(){
+       echo -e "\033[1K\r$* ${prog[4]}"
+}
+#end-stolen
+function log(){
+       echo "DEBUG: $1 failed due to $2" >> "${log_file}"
+       return 0
+}
+echo -n > "${log_file}"
+safelist=""
+for ebuild in $*
+do
+       echo -n "Running: ebuild ${ebuild} prepare"
+       workdir=$(USE="python_single_target_python3_8 python_targets_python3_8" ebuild "${ebuild}" prepare 2>&1 |tee -a "${log_file}"|awk '/Preparing source in/{print $5};/Remove.*\.prepared/{print $3}') || break
+       workdir=${workdir#\'}
+       workdir=${workdir%.prepared\'}
+       accept=true
+       echo " done!"
+       if [ -d "${workdir}" ]
+       then
+               while read file
+               do
+                       progress "Checking py files for ${ebuild}"
+
+                       error="__import__() now raises ImportError instead of ValueError"
+                       awk '/try:/{intry=1;impused=0};/__import__/ && intry{impused=1};intry && impused{print};/except|finally:/{intry=0}' "${file}" | grep -q "ValueError" && accept=false && log "$ebuild" "$error" && break
+
+                       error="importlib.util.resolve_name() now raise ImportError"
+                       grep -qw "importlib" "$file" && awk '/try:/{intry=1;impused=0};/resolve_name/{impused=1};intry && impused{print};/except|finally:/{intry=0}' "${file}" | grep -q "ValueError" && accept=false && log "$ebuild" "$error" && break
+
+                       error="The venv activation scripts no longer special-case when __VENV_PROMPT__ is set to ""."
+                       grep -q "from.*venv.*import\|import.*venv" "$file" && grep -q "__VENV_PROMPT__" "$file"                                                                                         && accept=false && log "$ebuild" "$error" && break
+                       #it's set by default to non null, we only have to find it being reassigned.
+
+                       #error="The compresslevel parameter of bz2.BZ2File became keyword-only"
+                       grep -q "from.*bz2.*import\|import.*bz2" "$file" && grep -q "BZ2File" "$file"|grep -qv "compresslevel="                                                         && accept=false && log "$ebuild" "$error" && break 
+                       #catching references
+
+                       error="Simplified AST for subscription."
+                       grep -q "from.*ast.*import\|import.*ast" "$file" && grep -q "issubclass\|visit_Num" "$file"                                                                             && accept=false && log "$ebuild" "$error" && break #lots of manual checking needed
+
+                       error="The encoding parameter has been added to the classes ftplib.FTP and ftplib.FTP_TLS"
+                       grep -q "from.*ftplib.*import\|import.*ftplib" "$file" && grep -q "FTP\|FTP_TLS" "$file"                                                                                        && accept=false && log "$ebuild" "$error" && break
+
+                       error="asyncio.loop.shutdown_default_executor() has been added to AbstractEventLoop"
+                       grep -q "from.*asyncio.*import\|import.*asyncio" "$file" && grep -q "class.*(.*loop):" "$file"                                                                          && accept=false && log "$ebuild" "$error" && break
+                       #need to refine this to instances only, unless it's inherited we don't care
+                       
+                       error="The logging.getLogger() API now returns the root logger when passed the name 'root'" #careful, possible security issue
+                       grep -q "from.*logging.*import\|import.*logging" "$file" && grep -q "getLogger(.*'root'.*)" "$file"                                                             && accept=false && log "$ebuild" "$error" && break 
+                       #directly exclude getLogger('root') calls
+                       grep -q "from.*logging.*import\|import.*logging" "$file" && grep -qv "getLogger(.*'" "$file"| grep -q "getLogger"                                       && accept=false && log "$ebuild" "$error" && break 
+                       #don't exclude if getLogger is called directly with a static name, but exclude variable and referenced calls to getLogger
+
+                       error="Division handling of PurePath now returns NotImplemented instead of raising a TypeError"
+                       grep -q "from.*pathlib.*import\|import.*pathlib" "$file" && grep -q "PurePath" "$file" && grep -q "TypeError" "$file"                           && accept=false && log "$ebuild" "$error" && break 
+                       #may need to awk this...
+
+                       error="Starting with Python 3.9.5 the ipaddress module no longer accepts any leading zeros in IPv4 address strings."
+                       grep -q "from.*ipaddress.*import\|import.*ipaddress" "$file" && grep -q "IPv4Address\|IPv4Network\|IPv4Interface\|collapse_addresses\|ip_address\|ip_interface\|ip_network\|summarize_address_range" "$file" && accept=false && log "$ebuild" "$error" && break 
+                       #Much grief :(
+
+                       error="codecs.lookup() now normalizes the encoding name"
+                       grep -q "from.*codecs.*import\|import.*codecs" "$file" && grep -q "lookup" "$file" && accept=false && log "$ebuild" "$error" && break 
+                       #much pain to check for :( should exclude for expediency and let upstream -> upstream sort it
+
+                       done < <(find "${workdir}" -iname '*.py')
+               end_progress "Checking py files for ${ebuild}"
+               while read file
+               do
+                       progress "Checking C files for ${ebuild}"
+
+                       error="PyInterpreterState.eval_frame (PEP 523) now requires a new mandatory tstate parameter"
+                       grep -q "PyInterpreterState" "$file" && grep -q "eval_frame" "$file"                                                                                                    && accept=false && log "$ebuild" "$error" && break
+
+                       error="Extension modules: m_traverse, m_clear and m_free functions of PyModuleDef are no longer called"
+                       grep -q "PyModuleDef" "$file" && "$file"                                                                                                                                                                && accept=false && log "$ebuild" "$error" && break
+
+                       error="Removed _PyRuntime.getframe hook and removed _PyThreadState_GetFrame also PyThreadFrameGetter"
+                       grep -q "_PyRuntime" "$file" && grep -q "getframe" "$file"                                                                                                                              && accept=false && log "$ebuild" "$error" && break
+                       grep -q " _PyThreadState_GetFrame\|PyThreadFrameGetter" "$file"                                                                                                                 && accept=false && log "$ebuild" "$error" && break
+
+                       error="removed PyAsyncGen_ClearFreeLists() PyContext_ClearFreeList() PyDict_ClearFreeList() PyFloat_ClearFreeList()"
+                       grep -q "PyAsyncGen_ClearFreeLists\|PyContext_ClearFreeList\|PyDict_ClearFreeList\|PyFloat_ClearFreeList" "$file"               && accept=false && log "$ebuild" "$error" && break
+
+                       error="removed PyFrame_ClearFreeList() PyList_ClearFreeList() PyMethod_ClearFreeList() PyCFunction_ClearFreeList() "
+                       grep -q "PyFrame_ClearFreeList\|PyList_ClearFreeList\|PyMethod_ClearFreeList\|PyCFunction_ClearFreeList" "$file"                && accept=false && log "$ebuild" "$error" && break
+
+                       error="removed PySet_ClearFreeList() PyTuple_ClearFreeList() PyUnicode_ClearFreeList()"
+                       grep -q "PySet_ClearFreeList\|PyTuple_ClearFreeLis\|PyUnicode_ClearFreeList" "$file"                                                                    && accept=false && log "$ebuild" "$error" && break
+
+                       error="removed PyBytes_InsertThousandsGroupingLocale, _PyBytes_InsertThousandsGrouping, _Py_InitializeFromArgs"
+                       grep -q "PyBytes_InsertThousandsGroupingLocale\|_PyBytes_InsertThousandsGrouping\|_Py_InitializeFromArgs" "$file"               && accept=false && log "$ebuild" "$error" && break
+
+                       error="removed _Py_InitializeFromWideArgs, _PyFloat_Repr, _PyFloat_Digits, _PyFloat_DigitsInit, PyFrame_ExtendStack, "
+                       grep -q "_Py_InitializeFromWideArgs\|_PyFloat_Repr\|_PyFloat_Digits\|PyFrame_ExtendStack" "$file"                                               && accept=false && log "$ebuild" "$error" && break
+
+                       error="removed PyNullImporter_Type, PyCmpWrapper_Type, PySortWrapper_Type, PyNoArgsFunction _PyAIterWrapper_Type,"
+                       grep -q "PyNullImporter_Type\|PyCmpWrapper_Type\|PySortWrapper_Type\|PyNoArgsFunction\|_PyAIterWrapper_Type" "$file"    && accept=false && log "$ebuild" "$error" && break
+               done < <(find "${workdir}" -regex "*.\.[c,h,cpp]")
+               end_progress "Checking C files for ${ebuild}"
+               $accept && safelist="${safelist} ${ebuild}"
+       fi
+done
+
+echo -e "${safelist// /\\n}\nare safe to add 3_9 support without patching"
+
diff --git a/python_3.9_compat/python3_9-py-logger b/python_3.9_compat/python3_9-py-logger
new file mode 100755 (executable)
index 0000000..d3f73e0
--- /dev/null
@@ -0,0 +1,120 @@
+#!/bin/bash
+# Check the files generated by the python_3_9_compat.sh script that crawls the portage tree, and log the failure points
+# The other script exits the source code path on first failure and moves on, this one doesn't
+# usage python3_9-py-checker <path/to/file>
+log_file="/run/user/$UID/pylogger.log"
+#stolen from something in ~/bin/
+prog=('\' '|' '/' '-' 'done!')
+prog_ind=0
+function progress(){
+       echo -ne "\033[1K\r$* ${prog[${prog_ind}]}"
+       (( ++ prog_ind ))
+       [ ${prog_ind} -gt 3 ] && prog_ind=0
+}
+function end_progress(){
+       echo -e "\033[1K\r$* ${prog[4]}"
+}
+#end-stolen
+function log(){
+       echo "DEBUG: $1 failed due to $2" >> "${log_file}"
+       return 0
+}
+echo -n > "${log_file}"
+safelist=""
+for ebuild in $*
+do
+       echo -n "Running: ebuild ${ebuild} prepare"
+       workdir=$(USE="python_single_target_python3_8 python_targets_python3_8" ebuild "${ebuild}" prepare 2>&1 |awk '/Preparing source in/{print $5};/Remove.*\.prepared/{print $3}') || break
+       workdir=${workdir#\'}
+       workdir=${workdir%.prepared\'}
+       echo " done!"
+       if [ -d "${workdir}" ]
+       then
+               accept=true
+               while read file
+               do
+                       progress "Checking py files for ${ebuild}"
+
+                       error="__import__() now raises ImportError instead of ValueError"
+                       awk '/try:/{intry=1;impused=0};/__import__/ && intry{impused=1};intry && impused{print};/except|finally:/{intry=0}' "${file}" | grep -q "ValueError" && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="importlib.util.resolve_name() now raise ImportError"
+                       grep -qw "importlib" "$file" && awk '/try:/{intry=1;impused=0};/resolve_name/{impused=1};intry && impused{print};/except|finally:/{intry=0}' "${file}" | grep -q "ValueError" && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="The venv activation scripts no longer special-case when __VENV_PROMPT__ is set to ""."
+                       grep -q "from.*venv.*import\|import.*venv" "$file" && grep -q "__VENV_PROMPT__" "$file"                                                                                         && accept=false && log "$ebuild" "$error" #&& break
+                       #it's set by default to non null, we only have to find it being reassigned.
+
+                       #error="The compresslevel parameter of bz2.BZ2File became keyword-only"
+                       grep -q "from.*bz2.*import\|import.*bz2" "$file" && grep -q "BZ2File" "$file"|grep -qv "compresslevel="                                                         && accept=false && log "$ebuild" "$error" #&& break 
+                       #catching references
+
+                       error="Simplified AST for subscription."
+                       grep -q "from.*ast.*import\|import.*ast" "$file" && grep -q "issubclass\|visit_Num" "$file"                                                                             && accept=false && log "$ebuild" "$error" #&& break #lots of manual checking needed
+
+                       error="The encoding parameter has been added to the classes ftplib.FTP and ftplib.FTP_TLS"
+                       grep -q "from.*ftplib.*import\|import.*ftplib" "$file" && grep -q "FTP\|FTP_TLS" "$file"                                                                                        && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="asyncio.loop.shutdown_default_executor() has been added to AbstractEventLoop"
+                       grep -q "from.*asyncio.*import\|import.*asyncio" "$file" && grep -q "class.*(.*loop):" "$file"                                                                          && accept=false && log "$ebuild" "$error" #&& break
+                       #need to refine this to instances only, unless it's inherited we don't care
+                       
+                       error="The logging.getLogger() API now returns the root logger when passed the name 'root'" #careful, possible security issue
+                       grep -q "from.*logging.*import\|import.*logging" "$file" && grep -q "getLogger(.*'root'.*)" "$file"                                                             && accept=false && log "$ebuild" "$error" #&& break 
+                       #directly exclude getLogger('root') calls
+                       grep -q "from.*logging.*import\|import.*logging" "$file" && grep -qv "getLogger(.*'" "$file"| grep -q "getLogger"                                       && accept=false && log "$ebuild" "$error" #&& break 
+                       #don't exclude if getLogger is called directly with a static name, but exclude variable and referenced calls to getLogger
+
+                       error="Division handling of PurePath now returns NotImplemented instead of raising a TypeError"
+                       grep -q "from.*pathlib.*import\|import.*pathlib" "$file" && grep -q "PurePath" "$file" && grep -q "TypeError" "$file"                           && accept=false && log "$ebuild" "$error" #&& break 
+                       #may need to awk this...
+
+                       error="Starting with Python 3.9.5 the ipaddress module no longer accepts any leading zeros in IPv4 address strings."
+                       grep -q "from.*ipaddress.*import\|import.*ipaddress" "$file" && grep -q "IPv4Address\|IPv4Network\|IPv4Interface\|collapse_addresses\|ip_address\|ip_interface\|ip_network\|summarize_address_range" "$file" && accept=false && log "$ebuild" "$error" #&& break 
+                       #Much grief :(
+
+                       error="codecs.lookup() now normalizes the encoding name"
+                       grep -q "from.*codecs.*import\|import.*codecs" "$file" && grep -q "lookup" "$file" && accept=false && log "$ebuild" "$error" #&& break 
+                       #much pain to check for :( should exclude for expediency and let upstream -> upstream sort it
+
+                       done < <(find "${workdir}" -iname '*.py')
+               end_progress "Checking py files for ${ebuild}"
+               while read file
+               do
+                       progress "Checking C files for ${ebuild}"
+
+                       error="PyInterpreterState.eval_frame (PEP 523) now requires a new mandatory tstate parameter"
+                       grep -q "PyInterpreterState" "$file" && grep -q "eval_frame" "$file"                                                                                                    && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="Extension modules: m_traverse, m_clear and m_free functions of PyModuleDef are no longer called"
+                       grep -q "PyModuleDef" "$file" && "$file"                                                                                                                                                                && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="Removed _PyRuntime.getframe hook and removed _PyThreadState_GetFrame also PyThreadFrameGetter"
+                       grep -q "_PyRuntime" "$file" && grep -q "getframe" "$file"                                                                                                                              && accept=false && log "$ebuild" "$error" #&& break
+                       grep -q " _PyThreadState_GetFrame\|PyThreadFrameGetter" "$file"                                                                                                                 && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="removed PyAsyncGen_ClearFreeLists() PyContext_ClearFreeList() PyDict_ClearFreeList() PyFloat_ClearFreeList()"
+                       grep -q "PyAsyncGen_ClearFreeLists\|PyContext_ClearFreeList\|PyDict_ClearFreeList\|PyFloat_ClearFreeList" "$file"               && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="removed PyFrame_ClearFreeList() PyList_ClearFreeList() PyMethod_ClearFreeList() PyCFunction_ClearFreeList() "
+                       grep -q "PyFrame_ClearFreeList\|PyList_ClearFreeList\|PyMethod_ClearFreeList\|PyCFunction_ClearFreeList" "$file"                && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="removed PySet_ClearFreeList() PyTuple_ClearFreeList() PyUnicode_ClearFreeList()"
+                       grep -q "PySet_ClearFreeList\|PyTuple_ClearFreeLis\|PyUnicode_ClearFreeList" "$file"                                                                    && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="removed PyBytes_InsertThousandsGroupingLocale, _PyBytes_InsertThousandsGrouping, _Py_InitializeFromArgs"
+                       grep -q "PyBytes_InsertThousandsGroupingLocale\|_PyBytes_InsertThousandsGrouping\|_Py_InitializeFromArgs" "$file"               && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="removed _Py_InitializeFromWideArgs, _PyFloat_Repr, _PyFloat_Digits, _PyFloat_DigitsInit, PyFrame_ExtendStack, "
+                       grep -q "_Py_InitializeFromWideArgs\|_PyFloat_Repr\|_PyFloat_Digits\|PyFrame_ExtendStack" "$file"                                               && accept=false && log "$ebuild" "$error" #&& break
+
+                       error="removed PyNullImporter_Type, PyCmpWrapper_Type, PySortWrapper_Type, PyNoArgsFunction _PyAIterWrapper_Type,"
+                       grep -q "PyNullImporter_Type\|PyCmpWrapper_Type\|PySortWrapper_Type\|PyNoArgsFunction\|_PyAIterWrapper_Type" "$file"    && accept=false && log "$ebuild" "$error" #&& break
+               done < <(find "${workdir}" -regex "*.\.[c,h,cpp]")
+               end_progress "Checking C files for ${ebuild}"
+               $accept && safelist="${safelist} ${ebuild}"
+       fi
+done
+
+echo -e "${safelist// /\\n}\nare safe to add 3_9 support without patching"
+
diff --git a/python_3.9_compat/python_3_9_compat.sh b/python_3.9_compat/python_3_9_compat.sh
new file mode 100755 (executable)
index 0000000..0175124
--- /dev/null
@@ -0,0 +1,136 @@
+#!/bin/bash
+# 3.9_compat-2.sh build lists of ebuilds that need updating for python3_9 compatability
+# stuff that's maybe worth altering
+[ -d "${REPO_PATH}" ] ||\
+REPO_PATH="/var/db/repos/updating"
+[ -z "${ARCH}" ] &&\
+ARCH="amd64"
+base_path="/run/user/${UID}/py-3_9-compat-v2.${REPO_PATH##*/}"
+# stuff that's not worth altering
+stable_list="${base_path}.stable.list"
+testing_list="${base_path}.testing.list"
+wip_list="${base_path}.wip.list"
+considerlist="${base_path}.maybe-wip.list"
+commands=$*
+message=""
+function finish {
+       tput cnorm
+       echo -e "$message"
+}
+trap finish EXIT
+#stolen from something in ~/bin/
+prog=('\' '|' '/' '-' 'done!')
+prog_ind=0
+function progress(){
+       echo -ne "\033[1K\r$* ${prog[${prog_ind}]}"
+       (( ++ prog_ind ))
+       [ ${prog_ind} -gt 3 ] && prog_ind=0
+}
+function end_progress(){
+       echo -e "\033[1K\r$* ${prog[4]}"
+}
+#end-stolen
+function build(){ #create a list of all ebuilds as a starting base
+       rm -f "${base_path}"*list
+       progress "Building master ebuild lists"
+       find "${REPO_PATH}/" -mindepth 3 -maxdepth 3 -name '*.ebuild' -a ! -name '*9999*' ! -iname '*-[0-9]*alpha*'|xargs grep -lw "PYTHON_COMPAT" -- |xargs grep -lw "KEYWORD.*~${ARCH}" -- >"${testing_list}.tmp"
+       progress "Building master ebuild lists"
+       sort -Vr "${testing_list}.tmp" > "${testing_list}" && rm -f "${testing_list}.tmp"
+       progress "Building master ebuild lists"
+       find "${REPO_PATH}/" -mindepth 3 -maxdepth 3 -name '*.ebuild' -a ! -name '*9999*' ! -iname '*-[0-9]*alpha*'|xargs grep -lw "PYTHON_COMPAT" -- |xargs grep -lw "KEYWORD.* ${ARCH}\|KEYWORD.*\"${ARCH}" -- >"${stable_list}.tmp"
+       progress "Building master ebuild lists"
+       sort -Vr "${stable_list}.tmp" > "${stable_list}" && rm -f "${stable_list}.tmp"
+       end_progress "Building master ebuild lists"
+}
+function filter_list(){
+       while read ebuild 
+       do
+               progress "Filtering compatible packages in ${1}"
+               atompath="${ebuild%-[0-9]*}";atompath="${atompath//\//\\/}"
+               if grep "python3_" ${ebuild}|grep -q "3_9\|,9\|\.\.9\|\.\.10"
+               then
+                       [[ "${2}" == "do_wip" ]] && echo $ebuild >> $wip_list
+                       sed -i "/${atompath}/d" "${1}"
+               else
+                       sed -i "0,/${atompath}/{s/${atompath}/RESTORE_ME/};/${atompath}-[0-9].*/d;s/RESTORE_ME/${atompath}/" ${1}
+               fi
+       done < "${1}"
+}
+function filter_stable(){ #filter the compatible ebuilds from the stable_list
+       filter_list "${stable_list}" 
+       end_progress "Filtering compatible packages in ${stable_list}"
+       [ -f $stable_list ] && message="${message}\nList of $(wc -l < ${stable_list}) non-compatible packages in: $stable_list"
+}
+function filter_testing(){ #filter the compatible ebuilds from the testing_list, build a compatible ebuilds list to further filter stable_list later
+       filter_list "${testing_list}" "do_wip"
+       end_progress "Filtering compatible packages in ${testing_list}"
+       [ -f $testing_list ] && message="${message}\nList of $(wc -l < ${testing_list}) non-compatible packages in: $testing_list"
+}
+function filter_wip(){ #remove duplicates package atoms
+       while read ebuild
+       do
+               progress "Filtering duplicate wip packages"
+               atompath="${ebuild%-[0-9]*}"
+               atompath="${atompath//\//\\/}"
+               sed -i "0,/${atompath}/{s/${atompath}/RESTORE_ME/};/${atompath}-[0-9].*/d;s/RESTORE_ME/${atompath}/" ${wip_list}
+       done < "${wip_list}"
+       end_progress "Filtering duplicate wip packages"
+}
+function check_wip(){ #filter the stable_list and log changes
+       echo -n >"${considerlist}"
+       for ebuild in $(grep -e "-r[0-9][0-9]*.ebuild" ${wip_list})
+       do
+               progress "Checking for wip packages"
+               [[ "$ebuild" =~ (.*)-r([0-9]*[0-9]).ebuild ]]
+               revision="${BASH_REMATCH[2]}"
+               (( -- revision  ))
+               if [ $revision -gt 0 ]
+               then #look for a previous revision
+                       temp=${revision:1}
+                       revision=${revision:${#temp}}
+                       poss_ebuild=$(echo "$ebuild"|sed "s/\(.*-r[0-9]*\)[0-9].ebuild/\1[0-9]*[0-$revision].ebuild/")
+               else #look for an unrevised similar version
+                       poss_ebuild=$(echo "$ebuild"|sed "s/\(.*\)-r[0-9]*[0-9].ebuild/\1.ebuild/")
+               fi
+               poss_fix=$(grep "${poss_ebuild}" ${stable_list})&&\
+               echo "$(echo ${ebuild}|sed 's/.*\/\(.*\/.*\/.*\)/\1/') seems to be a fix for $(echo ${poss_fix}|sed 's/.*\/\(.*\/.*\/.*\)/\1/')" >>"${considerlist}" &&\
+               sed_fix=$(echo ${poss_fix%-[0-9]*}|sed 's/\//\\\//g')&&\
+               sed -i "/${sed_fix}-[0-9].*/d" ${stable_list}
+       done
+       end_progress "Checking for wip packages"
+       if [ -f $considerlist ]
+       then
+               message="\nList of $(wc -l <${considerlist}) already fixed stable packages in: $considerlist${message}"
+               echo -e "\n\n*** The above packages have been removed from ${stable_list} ***" >>$considerlist
+               echo "*** This file is for logging purposes only. ***" >>$considerlist
+       fi
+}
+function all(){ #do everything
+       build
+       filter_testing
+       filter_stable
+       filter_wip
+       check_wip
+}
+function main(){ #cheesy hack for now...
+       for command in $commands
+       do
+               $command
+       done
+}
+function usage(){ #need to expand
+       echo -e "Usage: ${0##*/} <command(s)>
+               Where <command(s)> is one or more of [build|filter_testing|filter_stable|filter_wip|check_wip]
+               Use commands in the shown order, as each is dependant on the previous.
+               The special command [all] can be used to run a full scan and show results.
+               REPO_PATH (default /var/db/repos/gentoo) and ARCH (default amd64) can be set on the command line, e.g.\n
+               REPO_PATH=\"/usr/aarch64-unknown-linux-gnu/var/db/repos/gentoo\" ARCH=\"arm64\" ${0##*/} all\n"
+}
+if [ $# -gt 0 ] #do shit
+then
+       tput civis
+       main
+else
+       usage
+fi
+
diff --git a/random_scripts/TV-On b/random_scripts/TV-On
new file mode 100755 (executable)
index 0000000..77131dd
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/bash
+#Force monitor into existance because X won't do as it's told.
+xrandr --setmonitor +HDMI-A-0 1920/531x1080/299+1920+0 HDMI-A-0
+xrandr --newmode "TVMode" 148.50  1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
+xrandr --addmode HDMI-A-0 TVMode
+xrandr --output HDMI-A-0 --mode TVMode --right-of DisplayPort-0
diff --git a/random_scripts/etc_local.d_nfs_mounts.stopper b/random_scripts/etc_local.d_nfs_mounts.stopper
new file mode 100755 (executable)
index 0000000..58faddc
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/bash
+#Reads /proc/mounts and performs a lazy, forced un-mount of any nfs file systems if the server does not respond to ping
+cat /proc/mounts >/tmp/NFSumount_mounts.tmp
+
+DEBUG=false
+PRETEND=false
+
+while read line;do
+       if ! echo $line |awk '{print $3}'| grep -wq "nfs4\?";then
+               continue
+       fi
+       mountpoint=$(echo ${line}|awk '{print $2}')
+       if $DEBUG;then echo -en "NFS mount at ${mountpoint} found, ";fi
+       server=$(echo ${line}|grep -wo "addr=[0-2]\?[0-9]\?[0-9]\.[0-2]\?[0-9]\?[0-9]\.[0-2]\?[0-9]\?[0-9]\.[0-2]\?[0-9]\?[0-9]"|sed 's/addr=//')
+       if $DEBUG;then echo -en "server is ${server}. ";fi
+       if ! ping -w5 -i0.2 -c10 ${server} >/dev/null;then
+               if $DEBUG;then echo -ne "$server Did not respond to ping. ";fi
+               echo "Unmounting \"${mountpoint}\""
+               if ! $PRETEND;then umount -fl ${mountpoint};fi
+       else
+               if $DEBUG; then echo $server responded to ping;fi 
+       fi
+               
+done < /tmp/NFSumount_mounts.tmp
+
+rm /tmp/NFSumount_mounts.tmp
diff --git a/random_scripts/etc_local.d_soundcard.start b/random_scripts/etc_local.d_soundcard.start
new file mode 100755 (executable)
index 0000000..d6031a4
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+#redirect soundcard outputs on 'admin' host
+#generated by hdajackretask from media-sound/alsa-tools
+echo "0x12 0x411111f0"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x14 0x01014010"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x15 0x01011012"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x16 0x01016011"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x17 0x40170000"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x18 0x01a19040"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x19 0x02a19050"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x1a 0x0181304f"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x1b 0x01014013"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x1d 0x40e7e629"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo "0x1e 0x01456130"         > /sys/class/sound/hwC1D0/user_pin_configs 
+echo 1                                         > /sys/class/sound/hwC1D0/reconfig 
diff --git a/random_scripts/etc_local.d_tcp_syn_retries.stop b/random_scripts/etc_local.d_tcp_syn_retries.stop
new file mode 100755 (executable)
index 0000000..767041a
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/bash
+#The polite way of cutting down on "Unounting network filesystems", deprecates nfs_mounts.stop
+echo 1 > /proc/sys/net/ipv4/tcp_syn_retries
+
diff --git a/random_scripts/shebang_test.py b/random_scripts/shebang_test.py
new file mode 100755 (executable)
index 0000000..9ba6d5e
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/python
+#see what the actual commandline ues to invoke python is...
+import os,sys
+with open('/proc/%s/cmdline'%os.getpid(),'r') as file:cll=file.read().split('\x00')
+print('Does "/usr/bin/python" match "%s"?'%' '.join(cll[0:cll.index(sys.argv[0])]))
+
diff --git a/random_scripts/speech.sh b/random_scripts/speech.sh
new file mode 100755 (executable)
index 0000000..367ad58
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/bash
+#use google translate to convert text to speech
+rm /tmp/speech.ogg
+say() { local IFS=+;ffmpeg -i "http://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=$*&tl=en" /tmp/speech.ogg; }
+say $*
+ogg123 /tmp/speech.ogg
diff --git a/random_scripts/ytplay b/random_scripts/ytplay
new file mode 100755 (executable)
index 0000000..99ab9ad
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+#for livestream event viewing multiple streams, dump urls into the commandline, look at the pritty videos :)
+urls=$*
+for w in ${urls};do
+url=$(echo "${w}" |sed "s|'||g")
+echo "Opening : ${url}"
+ffplay $(youtube-dl -g $url) >/dev/null 2>&1 &
+done
+
+
+
+while read -p "Enter URL: " w;do
+url=$(echo "${w}" |sed "s|'||g"|sed 's/&list.*//')
+echo "Opening : ${url}"
+ffplay $(youtube-dl -g ${url}) >/dev/null 2>&1 &
+done