Jon Brookes
2025-01-21
It is possible and often overkill to compile a program to do something a single line of Bash, Perl, Python, Ruby, even plain sh can do but what is not as easy is to handle json, present a web service such as a RESTful API endpoint in the same one liner or short script.
for now though, lets keep our example simple, just a few system commands for now and see how to compile and cross compile for an architecture different to our own.
having installed go, to prep a starter project
mkdir etc_backup && cd etc_backup
go mod init github.com/marshyon/etc_backup
a program to create a backup file of the /etc
directory of our system could look like
package main
import (
"fmt"
"os"
"os/exec"
"time"
)
func main() {
cmd := exec.Command("sh", "-c", "echo 'hi there from sh cli'")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
fmt.Println("Error:", err)
}
currentTime := time.Now()
timeString := currentTime.Format("2006-01-02-1504")
tarFileName := fmt.Sprintf("backup_etc_%s.tar.tgz", timeString)
fmt.Println("backup file will be :", tarFileName)
tarCmd := exec.Command("sh", "-c", fmt.Sprintf("tar -czf %s /etc", tarFileName))
tarCmd.Stdout = os.Stdout
tarCmd.Stderr = os.Stderr
err = tarCmd.Run()
if err != nil {
fmt.Println("Error creating tar file:", err)
}
}
To quickly run our go program in a shell of the above directory we can use go run main.go
and this will run the above code.
But what we want to use this elsewhere as a complied executable, so that can be done with
go build -o etc_backup_x86_64
I’ve called this x86_64 here because thats the architecure I’m running on Linux. So the binary file this produces looks like
file etc_backup_x86_64
etc_backup_x86_64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=dQlpa8rRiWq486-YXwRn/mGQCD_hKj83HXsTp-xr6/WE-E6m6h4ArHGpGNNnJI/dWE6TLhulkFWc2-MaeqC, with debug_info, not stripped
and it will run on any system with this chipset and architecture
I have an OpenWRT One device that arrived over the weekend and this has an ARM chipset, so like a mac, this would have different architecture and this executable would not run on that.
env GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -o etc_backup_arm64
this creates a new executable file that looks like this :
file etc_backup_arm64
etc_backup_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=ko7OIBeH7Dvd-zGKVNYi/k_0neJnA3MHW8ax43Rhx/l0UPdgHjDeAj3QL34I2i/k7WIdTXSAG7zVSDn05Rz, stripped
the -ldflas
switch strips debug symbols and reduces the size of the executatble on my system from 2.5M to 1.7M and I can copy this to and run it on my new ARM chipset OpenWRT system :
scp etc_backup_arm64 root@192.168.1.1:/root
etc_backup_arm64 100% 2549KB 18.1MB/s 00:00
on my OpenWRT One in a new shell this can be executed like any other binary for that platform :
root@OpenWrt:~# ./etc_backup_arm64
hi there from sh cli
backup file will be : backup_etc_2025-01-21-1740.tar.tgz
tar: removing leading '/' from member names
Now, this is only printing things out to the shell and running commands using systems exectuion but it has potential to do much more.
What is more, this executable is not easy to change and obscures what it does, unless we add command line options to show help of course but that would be another missive.
Importantly, we can checksum this script and see that it is unchanged but it is also, inconvenient for someone to poke around inside it, change it and cause mischief, should that be a concern that we may have.
The possible use cases are endless and this opens up another world of programming in Go to add to our skills in shell scripting and automation where a fully functional program can be copied to a server and work without needing dependencies as would often a script in Python, Ruby or others like them. This has produced for us a ‘statically compiled’ and ‘stand alone’ executable. So long as it matches the architecture of the target system, it will run and we dont need to write our code on the same architecture even.
This makes light(er) work of developing software for embedded devices or appliances like the OpenWRT One that would typiclly need us to set up an entire build tool chain to cross compile C. This can be a lot of bother.
Not any more.