pfSense only allows you to automatically backup your router config if you have a gold subscription, so I made this script that logins into the admin panel and retrieves the latest config and downloads it to my desktop using wget.
A very long time ago I had issues on ubuntu automatically mounting my NFS connection from my server on bootup using fstab. so I made this script to either run on startup in my rc.local or manaully if I needed to mount it after the system has started.
#!/bin/bash
address="192.168.0.6"
mount_point="/mnt/nfs-storage"
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
case $1 in
mount)
if mountpoint -q $mount_point; then
echo "$mount_point is active"
else
timeout 5 mount $address:/mnt/storage $mount_point
fi
;;
umount)
if mountpoint -q $mount_point; then
umount -f -l $mount_point
else
echo "$mount_point is not mounted"
fi
;;
*)
echo "Usage $0 {mount|umount}"
esac
When using the script above I noticed that my compile times were significantly longer than expected as I’m a dummy and I forgot to add the ccache variables. I’ve edited the script to include them.
Very simple script I made that pops up an i3 message bar after a specific amount of time.
#!/bin/bash
TIME=$1
MESSAGE=$2
if [[ -z $1 ]]; then TIME=1s; fi
if [[ -z $2 ]]; then MESSAGE="Your timer is ready"; fi
sleep $TIME && exec i3-nagbar -t warning -m "$MESSAGE"
This allows me to set a timer when its too late to otherwise set it on my phone and no more will I burn pizza
I can also be extra lazy and further automate it by setting an i3 hotkey for instant Pizza notification:
bindsym $mod+F10 exec ~/bin/timer 15m "Your Pizza is ready"
I made a neat little python program that will check for errors. Is mostly due to the fact that im too lazy to just open up a terminal to find any errors myself.
Right now it only checks for errors on systemd and disk usage but when i think of more things i would like checked than ill add that in too
Added in support for checking entropy and whether or not there is anything in trash
When installing Fedora 28 I forgot to backup my LineageOS container so I’ve made a build to automate the creation of a new container and a custom init script that sets up the build environment as much as possible. This does a couple of things:
Installs all the required packages needed to compile LineageOS
Creates a user with a pre-defined .bashrc and .profile for the build environment
Downloads and install the Android repo and platform-tools
Create and syncs the repository ready to build LineageOS
I also included my previous docker builds for flarum and tiddilywiki as well because why not.
Small script I made that will rotate the display and touchscreen input device on my laptop so that they are the correct orientation. The accelerometer doesn’t auto rotate in i3 like it does in gnome so I’ve setup a mode that calls this script and using the arrow keys to rotate it in whatever orientation I want it
Simple python script I made to change the brightness of my displays using xrandr by increments of .25 depending on which workspace is active (I.E which monitor has focus).
One of its main issues of the script above is it relies on me never changing the workspace on my second monitor, while this doesn’t happen often in an event that it does it’ll change the brightness of my primary display even though the second has focus, so to fix this i made a couple of improvements.
First I decided to get the location of my mouse and using that extrapolate which display has focused using:
xdotool getmouselocation --shell | head -n -3 | sed 's/[^0-9]*//g'
At first I was going to try and calculate between the x and y which has focus until I realized that all that really matters is that the x of the mouse is below the width of my left most display since the resolutions are combined into Screen 0 so could just be lazy and do a check such as:
if x < 2560:
return "DP-1"
return "DP-2"
with 2560 being the width of my display and x the mouse coordinates on that axis.
I plan to improve it further by pulling the display id from xrandr based on the coordinates so nothing is hard-coded but that is for another time and this works well enough for my needs now and is vast improvement over my previous version with the added benefit that this now works without requiring the use of i3.
kinda simple one but mother wanted me to print 34 pdf files and when I asked Windows to print them from the file manager it told me i had to open each one and print them individually so transferred them over to Linux and made this small shell script to do it.
#!/bin/sh
for f in *.pdf; do
cat "$f" | lp -o scaling=//100// -oColorModel=KGray
done
Probably could’ve found a way todo it on Windows too but I’m far less familiar with batch scripting and this way still saves me a lot of time and effort.
@tsk Its very rough draft but this should do it. It grabs the user list based from the tier level/group you specify inside the api section and checks if the user is inside the group, if not it will send a POST API request to the forum and assign the group to that user. The script is in two parts, first handles the API requests (api.py).
import requests, json
Api_Key = "" # your discourse api key
groupName = "NSFW" # name of the group
tier_level = 3
url = "https://forum.0cd.xyz"
def _url(path):
return url + path
def get_group():
return requests.get(_url('/g/{}.json').format(groupName))
def get_group_members():
return requests.get(_url('/g/{}/members.json').format(groupName))
def get_tl_members():
return requests.get(_url('/g/trust_level_{}/members.json').format(tier_level))
def add_users(users, gid):
headers = {'Api-Key': Api_Key, 'content-type':'application/json'}
return requests.put(_url("/g/{}/members.json").format(gid), data=json.dumps({'usernames': ','.join(users)}), headers=headers)
and the second the main logic of the program
#!/usr/bin/env python3
import sys, requests, json, api
def main():
try:
resp = api.get_tl_members()
gid = api.get_group()
if resp.status_code != 200 or gid.status_code != 200:
raise ApiError('Cannot fetch data: tl: {} group: {}'.format(resp.status_code, gid.status_code))
member = []
for users in resp.json()['members']:
if users['username'] not in group():
member.append(users['username'])
api.add_users(member, gid.json()['group']['id'])
except(ApiError, requests.exceptions.ConnectionError) as e:
print(e)
sys.exit(0)
def group():
try:
resp = api.get_group_members()
if resp.status_code != 200:
raise ApiError('Cannot fetch data: {}'.format(resp.status_code))
member = []
for members in resp.json()['members']:
member.append(members['username'])
return member
except(ApiError, requests.exceptions.ConnectionError) as e:
print(e)
sys.exit(0)
class ApiError(Exception): pass
main()
It should work mostly fine except there’s an issue in that the proper way handle the request is broken in Discourse so I’m brute forcing it by iterating over the users and sending a separate API request for each user, this has a downside in that Discourse has a request limit so adding lots of users at once will kill the API requests., haven’t done extensive testing because the only way todo that is in production but tested code works outside of the main for loop and it works fine.
fixed most of the issues above. it now makes a single API request with a list of usernames so no chance of hitting the requests limit and its a lot more efficient. old way was to add the users from their page on the admin panel one by one but now it pushes a list using the proper Add user call to the groups API.
Had todo another one of these large prints again and got complained at because they needed to be in reverse order and double-sided, fixed it up and included a watch for printer queue. thankfully zsh makes it super easy to reverse the order of the file list using the On glob qualifier.
#!/bin/zsh
for f in *.pdf(On); do
cat "$f" | lp -o scaling=//100// -o ColorModel=KGray -o sides=two-sided-long-edge
done
watch -n1 lpstat
lel did this with my media server but it actually paid off now i just hit go(to save power and shit by actually shutting it off at night/when go to work and whatever)
Was bored and decided to rewrite my xrandr brightness control script in golang, it effectively does exactly the same thing as the python version but its now its all fancy with compiled code.
package main
import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
)
func main() {
if len(os.Args) <= 1 {
fmt.Printf("Usage: %s [+|-]", os.Args[0])
return
}
brightness()
}
func brightness() {
cmd := fmt.Sprintf("xrandr --verbose | grep %s -A 5 | grep Brightness | cut -f 2 -d ' '", display())
brightness, err := exec.Command("sh", "-c", cmd).Output()
if err != nil {
log.Fatal(err)
}
f, err := strconv.ParseFloat(strings.TrimSpace(string(brightness)), 64)
if err != nil {
log.Fatal(err)
}
switch {
case os.Args[1] == "+" && f < 1:
f += .25
case os.Args[1] == "-" && f > 0:
f -= .25
}
exe := fmt.Sprintf("xrandr --output %s --brightness %s", display(), strconv.FormatFloat(f, 'f', 2, 64))
exec.Command("sh", "-c", exe).Output()
}
func display() string {
cmd, err := exec.Command("xdotool", "getmouselocation", "--shell").CombinedOutput()
if err != nil {
log.Fatal(err)
}
lines := strings.SplitAfter(string(cmd), "\n")
i, err := strconv.Atoi(strings.TrimSpace(lines[0][len(lines)-3:]))
if err != nil {
log.Fatal(err)
}
switch {
case i < 1920:
return "HDMI-1"
case i < 4480:
return "DP-1"
default:
return "DP-2"
}
}
So my mother got some Philips Hue lights for Christmas and messing around with them there 100% local and have an API that can interface directly with the hub. A couple of minutes and dirty shell functions in my .zshrc later I can turn off/on and the brightness of the light in my room from the terminal.
Run a small private local minecraft server and got tired of trying to keep it up to date with all of the snapshots. Made a script that will perform backups, update the server, and gracefully stop the server.
import docker
import os
import shutil
import time
client = docker.from_env()
def getURL(): #Gets URL from User
userInput = input("Please provide the URL for the server jar you wish to update too: ")
if userInput[0:4] != "http": #Validate that the URL is valid
print("Invalid URL. Make sure it includes the http protocall")
userInput = getURL()
if userInput[-4:] != ".jar": #Verify that we are downloading the correct file
print("Invalid URL. Missing proper file extension")
userInput = getURL()
return userInput
def fetchJar(URL): #Downloads the server jar file from a URL
if URL[0:4] != "http" or URL[-4:] != ".jar": #One last check to see if the URL was malformed between input and download
print("Function fetchJar was passed a malformed URL")
print("URL:" + URL)
exit(1)
file = URL.split("/")[-1] #Get the file name of the file we are downloading
if os.path.exists("/tmp/" + file) == True:
os.remove("/tmp/" + file)
if os.path.exists("/tmp/" + file) == True: #Verify that the remove was successful
print("ABORT. os.remove failed. Please delete /tmp/" + file)
exit(127)
print("Fetching Server Jar")
os.system("wget --quiet -O /tmp/" + file + " " + URL)
return file #Returns file name to ensure consistency across the code
def getVersion(): #Get version from User
version = input("What version are you updating too: ")
return version
def renameServer(version, file): #Takes a file and renames it to the server exec standard. "minecraft.VERSION.jar"
newFile = "minecraft-server." + version + ".jar"
if os.path.exists("/tmp/" + newFile) == True:
os.remove("/tmp/" + newFile)
if os.path.exists("/tmp/" + newFile) == True: # Verify the remove was successful
print("ABORT. os.remove failed. Please delete /tmp/" + file)
exit(126)
os.rename("/tmp/" + file, "/tmp/" + newFile)
return newFile #Returns the new file name
def moveServer(file): #Moves server from /tmp to /opt/minecraft-server/
if os.path.exists("/opt/minecraft-server/" + file) == True: #Verify to see if we are replacing a version that already exists EX: Corrupted server jar
if input("Server version already exists. Overwrite? (y/n): ").lower() == "y":
os.remove("/opt/minecraft-server/" + file)
else:
print("Aborting upgrade")
exit(0) #0 exit code since its not a error
if os.path.exists("/opt/minecraft-server/" + file) == True:
print("ABORT. os.remove failed. Please delete /tmp/" + file)
exit(125)
shutil.move("/tmp/" + file, "/opt/minecraft-server/" + file) #Since /tmp usually resides on a ramfs. Need to use shutil.move as we are going cross device and built in functions dont support it well
def dockerStop(version): # Stop and Verify that its stopped
print("Stopping Docker container")
if client.containers.list(filters={"name":"minecraft"}) == []: #Check to see if its already stopped. If so, just return
return
os.system("mcrcon -H 192.168.0.6 -P 25575 -p REDACTED 'say Stopping server in 60 seconds to update to '" + version) #Use mcRcon to connect and issue remote commands
time.sleep(55)
os.system("mcrcon -H 192.168.0.6 -P 25575 -p REDACTED 'say Stopping server in 5 seconds'")
time.sleep(5)
os.system("mcrcon -H 192.168.0.6 -P 25575 -p REDACTED 'kick @a Updating'")
os.system("mcrcon -H 192.168.0.6 -P 25575 -p REDACTED 'save-all flush'")
os.system("mcrcon -H 192.168.0.6 -P 25575 -p REDACTED 'stop'")
print("Waiting for server to gracefully stop")
while client.containers.list(filters={"name":"minecraft"}) != []: #Waits in a loop until the docker container drops off the list
time.sleep(1)
def backupWorld(): #Backs up the save
print("Backing up the world file")
os.chdir("/opt/minecraft-server")
os.system("cp -a world Backups/world-$(date +%B-%d-%Y-%R)") #Runs the backup command and timestamps the backup
def updateServer(file): #Update executable
os.chdir("/opt/minecraft-server") #Make sure we are in the correct directory
os.remove("server.jar") #Remove the old link
os.system("ln -s " + file + " server.jar") #Links the new file to the server.jar exec
def main(): #Main function that ties everything together
URL = getURL()
version = getVersion()
file = fetchJar(URL)
file = renameServer(version, file)
moveServer(file)
dockerStop(version)
backupWorld()
updateServer(file)
print("Starting Minecraft")
os.system("docker start minecraft")
main()
I had a lot of .jpg files on my computer that were actually png files so I wrote this small go program that goes though all the files in the specified directory and checks the first 18 bytes against the png header and renames the file to .png if the bytes of the header matches.
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
)
func main() {
dir := flag.String("dir", "", "directory to scan")
flag.Parse()
files, err := ioutil.ReadDir(*dir)
if err != nil {
log.Fatal(err)
}
pngheader := []byte{
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0,
0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0}
var count int
for _, f := range files {
orig := *dir + "/" + f.Name()
new := *dir + "/" + strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) + ".png"
if path.Ext(f.Name()) == ".jpg" {
file, err := ioutil.ReadFile(orig)
if err != nil {
log.Fatal(err)
}
if bytes.Equal(file[:18], pngheader) == true {
if err := os.Rename(orig, new); err != nil {
log.Fatal(err)
}
count++
}
}
}
fmt.Printf("renamed %d jpg files png\n", count)
}
I had a lot of files I wanted to convert to flac so I decided to rework this script so that it is multi-threaded using GNU parallel, I have all the cores might as well put them to good use.
#!/bin/sh
function flac() {
echo $1
sox -b 24 "$1" "${1%.*}".flac
shopt -s nullglob
if [ ! -d "flac" ]; then
mkdir flac
fi
mv "${1%.*}".flac flac/
}
declare -x -f flac
find . -name "*.wav" | parallel flac {}
I wrote a function in bash todo the conversion and create the flac folder if it doesn’t exsist and then exported it so parallel could actually see it as a command and just passed in the file names as an argument that were piped by find.
Using the origonal script it took 17.245 seconds to convert my test data from WAV to FLAC:
michael@fedora ~/Desktop/WAV » time convert_flac
01- Tapestry.wav
02- Journey_s Lift Off.wav
03- Faith in Our Steps.wav
04- The Wind_s Bazaar.wav
05- Moonlit Cartographers (Part 1).wav
06- Moonlit Cartographers (Part 2).wav
07- Swiftly Through the Valley.wav
08- We Return to Our Home (Night).wav
09- Traders of the Winds.wav
10- Untraveled.wav
11- We Return to Our Home (Day).wav
12- A Prophecy Fulfilled.wav
13- Muluab_s Endless Canon.wav
14- Airborne Nocturne.wav
convert_flac 16.19s user 0.50s system 96% cpu 17.245 total
and using the parallel version I had it down to 2.402 seconds:
michael@fedora ~/Desktop/WAV » time convert_flacv2.sh
./13- Muluab_s Endless Canon.wav
./01- Tapestry.wav
./14- Airborne Nocturne.wav
./05- Moonlit Cartographers (Part 1).wav
./11- We Return to Our Home (Day).wav
./02- Journey_s Lift Off.wav
./08- We Return to Our Home (Night).wav
./03- Faith in Our Steps.wav
./06- Moonlit Cartographers (Part 2).wav
./10- Untraveled.wav
./12- A Prophecy Fulfilled.wav
./09- Traders of the Winds.wav
./04- The Wind_s Bazaar.wav
./07- Swiftly Through the Valley.wav
convert_flacv2.sh 22.65s user 0.68s system 971% cpu 2.402 total
Those 14.843 seconds saved were totally worth the 30 minutes it took me to write this XD