Lazy Automation

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)

  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)

  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)

  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)
      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)

  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
  os.system("mcrcon -H -P 25575 -p REDACTED 'say Stopping server in 60 seconds to update to '" + version) #Use mcRcon to connect and issue remote commands
  os.system("mcrcon -H -P 25575 -p REDACTED 'say Stopping server in 5 seconds'")
  os.system("mcrcon -H -P 25575 -p REDACTED 'kick @a Updating'")
  os.system("mcrcon -H -P 25575 -p REDACTED 'save-all flush'")
  os.system("mcrcon -H -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

def backupWorld(): #Backs up the save
  print("Backing up the world file")
  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)

  print("Starting Minecraft")
  os.system("docker start minecraft")

1 Like

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 (

func main() {
	dir := flag.String("dir", "", "directory to scan")

	files, err := ioutil.ReadDir(*dir)
	if err != nil {

	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 {
			if bytes.Equal(file[:18], pngheader) == true {
				if err := os.Rename(orig, new); err != nil {
	fmt.Printf("renamed %d jpg files png\n", count)
1 Like

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.


function flac() {
    echo $1
    sox -b 24 "$1" "${1%.*}".flac
    shopt -s nullglob
    if [ ! -d "flac" ]; then
        mkdir flac
    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:

[email protected] ~/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:

[email protected] ~/Desktop/WAV » time
./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  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