14 Commits

Author SHA1 Message Date
e718c9c29d moved to codeberg 2025-11-25 22:16:30 +01:00
a90d6e5c3c fix algo 2025-02-05 18:03:52 +01:00
ed4ad2340b add changelog builder, update release builder 2025-02-05 17:56:05 +01:00
f7b0cfa905 fix heading 2025-01-19 16:27:03 +01:00
b914fdfcdf simplified verbose output and enhanced readme. 2025-01-19 16:23:16 +01:00
f2116f39ef fix name typo 2025-01-18 11:36:56 +01:00
5907c5b2be add badges 2025-01-18 11:35:12 +01:00
8dbdebee46 fix ci builder 2025-01-18 11:33:02 +01:00
daabdc5c9b update dependencies 2025-01-18 11:29:45 +01:00
f4b1ba5863 fixes:
- fix encryption, used the wrong nonce size
- encrypted files were not deleted
- fixed recursion
- fixed linter warnings
2025-01-18 11:27:57 +01:00
eaca5ad181 fix typo 2025-01-18 11:03:18 +01:00
5ca1be594b mv to correct dir 2025-01-18 11:02:26 +01:00
aa03ba5281 add ci pipelines 2025-01-18 11:00:15 +01:00
74c801b914 typo 2023-11-25 14:53:07 +01:00
9 changed files with 177 additions and 663 deletions

35
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: build-and-test-gowipe
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
version: ['1.21','1.22']
os: [ubuntu-latest, macos-latest, windows-latest]
name: Build
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go ${{ matrix.version }}
uses: actions/setup-go@v5
with:
go-version: '${{ matrix.version }}'
id: go
- name: checkout
uses: actions/checkout@v4
- name: build
run: go build
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: 1.22
- uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
skip-cache: true

87
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,87 @@
name: build-release
on:
push:
tags:
- "v*.*.*"
jobs:
release:
name: Build Release Assets
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.22.11
- name: Build the executables
run: ./mkrel.sh gowipe ${{ github.ref_name}}
- name: List the executables
run: ls -l ./releases
- name: Upload the binaries
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
file: ./releases/*
file_glob: true
- name: Build Changelog
id: github_release
uses: mikepenz/release-changelog-builder-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
mode: "PR"
configurationJson: |
{
"template": "#{{CHANGELOG}}\n\n**Full Changelog**: #{{RELEASE_DIFF}}",
"pr_template": "- #{{TITLE}} (##{{NUMBER}}) by #{{AUTHOR}}\n#{{BODY}}",
"empty_template": "- no changes",
"categories": [
{
"title": "## New Features",
"labels": ["add", "feature"]
},
{
"title": "## Bug Fixes",
"labels": ["fix", "bug", "revert"]
},
{
"title": "## Documentation Enhancements",
"labels": ["doc"]
},
{
"title": "## Refactoring Efforts",
"labels": ["refactor"]
},
{
"title": "## Miscellaneus Changes",
"labels": []
}
],
"ignore_labels": [
"duplicate", "good first issue", "help wanted", "invalid", "question", "wontfix"
],
"label_extractor": [
{
"pattern": "(.) (.+)",
"target": "$1"
},
{
"pattern": "(.) (.+)",
"target": "$1",
"on_property": "title"
}
]
}
- name: Create Release
uses: softprops/action-gh-release@v2
with:
body: ${{steps.github_release.outputs.changelog}}

View File

@@ -1,89 +0,0 @@
# Copyright © 2023 Thomas von Dein
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# no need to modify anything below
tool = gowipe
VERSION = $(shell grep VERSION main.go | head -1 | cut -d '"' -f2)
archs = darwin freebsd linux windows
PREFIX = /usr/local
UID = root
GID = 0
HAVE_POD :=
all: $(tool) buildlocal
buildlocal:
CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o $(tool)
install: buildlocal
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
clean:
rm -rf $(tool) coverage.out
test:
go test -v ./...
singletest:
@echo "Call like this: ''make singletest TEST=TestPrepareColumns"
go test -run $(TEST)
cover-report:
go test ./... -cover -coverprofile=coverage.out
go tool cover -html=coverage.out
goupdate:
go get -t -u=patch ./...
buildall:
./mkrel.sh $(tool) $(VERSION)
release: buildall
gh release create v$(VERSION) --generate-notes releases/*
show-versions: buildlocal
@echo "### gowipe version:"
@./gowipe -v
@echo
@echo "### go module versions:"
@go list -m all
@echo
@echo "### go version used for building:"
@grep -m 1 go go.mod
dir:
rm -rf a
mkdir -p a/b/c
date > a/filea
date > a/b/fileb
date > a/b/c/filec
bench: all
dd if=/dev/zero of=t/fileZ bs=1024 count=200000
dd if=/dev/zero of=t/fileM bs=1024 count=200000
dd if=/dev/zero of=t/fileS bs=1024 count=200000
dd if=/dev/zero of=t/fileE bs=1024 count=200000
/usr/bin/time -f "%S" ./gowipe -Z t/fileZ
/usr/bin/time -f "%S" ./gowipe -M t/fileM
/usr/bin/time -f "%S" ./gowipe -S t/fileS
/usr/bin/time -f "%S" ./gowipe -E t/fileE

View File

@@ -1,5 +1,59 @@
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://github.com/tlinden/gowipe/blob/master/LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/tlinden/gowipe)](https://goreportcard.com/report/github.com/tlinden/gowipe)
[![GitHub release](https://img.shields.io/github/v/release/tlinden/gowipe?color=%2300a719)](https://github.com/TLINDEN/gowipe/releases/latest)
## gowipe - securely delete files and directories (not for SSD)
> [!CAUTION]
> This software is now being maintained on [Codeberg](https://codeberg.org/scip/gowipe/).
## Description
`gowipe` is a simple self contained tool to securely wipe files and
directories. By default it renames and overwrites files and
directories 30 times and uses the `secure` mode, which uses strong
random bytes for the overwriting process. Gowipe writes as much bytes
into a file as its original size.
You can tweak mode and round numbers. Other modes are `zero`, which
uses zeroes for overwriting (not recommended) or `encrypt` which
encrypts the data using ChaCha20Poly1305 and a strong random key. This is the most
secure but also to slowest mode.
Although you can use `gowipe` on SSD disks, it doesn't make much
sense. To wipe such a disk you have to resort to other means. But you
can savely use it on magnetic discs or usb drives.
Of course there are many other such tools available, this one is
insofar special as you can download a pre-compiled binary without any
library dependencies. This allows you to wipe files on systems, where
you cannot install a wiper via some package management (such as
appliance systems or vm's).
## Example
Overwrite the directory `vhs` recursively 50 times using strong
encryption and verbose output:
```shell
gowipe -c 50 -E -r -v vhs
Wiped vhs/help.png (355011 bytes)
Wiped vhs/rec.Dockerfile (348 bytes)
Wiped vhs/rec.gif (3533338 bytes)
Wiped vhs/rec.tape (852 bytes)
Wiped vhs (4096 bytes)
Dirs wiped: 1
Files wiped: 5
Bytes deleted: 3889549
Time elapsed: 426.286639ms
Overwritten: 50 times
Wipe mode: encrypt
Recurse dirs: true
```
## Usage
```
Usage: gowipe [-rcvz] <file|directory>...
@@ -22,7 +76,7 @@ encrypt Overwrite with ChaCha2Poly1305 encryption (most secure) (-E)
## Getting help
Although I'm happy to hear from tablizer users in private email,
Although I'm happy to hear from gowipe users in private email,
that's the best way for me to forget to do something.
In order to report a bug, unexpected behavior, feature requests

249
crypto.go
View File

@@ -1,249 +0,0 @@
/*
Copyright © 2022 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"crypto/cipher"
cryptorand "crypto/rand"
"errors"
"fmt"
"io"
"math/big"
mathrand "math/rand"
"os"
"time"
"unsafe"
"golang.org/x/crypto/argon2"
chapo "golang.org/x/crypto/chacha20poly1305"
)
const (
SaltSize = 32 // in bytes
NonceSize = 24 // in bytes. taken from aead.NonceSize()
KeySize = uint32(32) // KeySize is 32 bytes (256 bits).
KeyTime = uint32(5)
KeyMemory = uint32(1024 * 64) // KeyMemory in KiB. here, 64 MiB.
KeyThreads = uint8(4)
chunkSize = 1024 * 32 // chunkSize in bytes. here, 32 KiB.
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
// via https://gist.github.com/dopey/c69559607800d2f2f90b1b1ed4e550fb
func AssertAvailablePRNG() {
// Assert that a cryptographically secure PRNG is available.
// Panic otherwise.
buf := make([]byte, 1)
_, err := io.ReadFull(cryptorand.Reader, buf)
if err != nil {
panic(fmt.Sprintf("crypto/rand is unavailable: Read() failed with %#v", err))
}
}
// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateSecureRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := cryptorand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}
return b, nil
}
// GenerateRandomString returns a securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateSecureRandomString(n int) (string, error) {
ret := make([]byte, n)
for i := 0; i < n; i++ {
num, err := cryptorand.Int(cryptorand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
return "", err
}
ret[i] = letters[num.Int64()]
}
return string(ret), nil
}
// via:
// https://stackoverflow.com/a/31832326
func GenerateMathRandomString(n int) string {
b := make([]byte, n)
var src = mathrand.NewSource(time.Now().UnixNano())
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letters) {
b[i] = letters[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return *(*string)(unsafe.Pointer(&b))
}
func GetRandomKey() ([]byte, error) {
password, err := GenerateSecureRandomBytes(int(chapo.KeySize))
if err != nil {
return nil, err
}
salt, err := GenerateSecureRandomBytes(chapo.NonceSize)
if err != nil {
return nil, err
}
key := argon2.IDKey(password, salt, KeyTime, KeyMemory, KeyThreads, chapo.KeySize)
return key, nil
}
func Encrypt(c *Conf, filename string) error {
info, err := os.Stat(filename)
if err != nil {
return err
}
size := info.Size()
outfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer outfile.Close()
key, err := GetRandomKey()
if err != nil {
return err
}
aead, err := chapo.NewX(key)
if err != nil {
return err
}
for i := 0; i < c.count; i++ {
for {
if size < chunkSize {
EncryptChunk(aead, outfile, size)
break
}
EncryptChunk(aead, outfile, chunkSize)
size = size - chunkSize
if size <= 0 {
break
}
}
}
return nil
}
func EncryptChunk(aead cipher.AEAD, file *os.File, size int64) error {
chunk := make([]byte, size)
nonce, err := GenerateSecureRandomBytes(int(chapo.NonceSize))
if err != nil {
return err
}
cipher := aead.Seal(nil, nonce, chunk, nil)
n, err := file.Write(cipher[:size])
if err != nil {
return err
}
if int64(n) != size {
return errors.New("invalid number of bytes written")
}
return nil
}
/*
func Encrypt(c *Conf, filename string) error {
salt, err := GetRand(KeySize)
if err != nil {
return err
}
salt1, err := GetRand(KeySize)
if err != nil {
return err
}
outfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer outfile.Close()
key := argon2.IDKey(salt1, salt, KeyTime, KeyMemory, KeyThreads, KeySize)
aead, err := chacha20poly1305.NewX(key)
if err != nil {
return err
}
buf := make([]byte, chunkSize)
ad_counter := 0 // associated data is a counter
for {
if n > 0 {
// Select a random nonce, and leave capacity for the ciphertext.
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+n+aead.Overhead())
if m, err := cryptorand.Read(nonce); err != nil || m != aead.NonceSize() {
return err
}
msg := buf[:n]
// Encrypt the message and append the ciphertext to the nonce.
encryptedMsg := aead.Seal(nonce, nonce, msg, []byte(string(ad_counter)))
outfile.Write(encryptedMsg)
ad_counter += 1
}
if err == io.EOF {
break
}
if err != nil {
log.Println("Error when reading input file chunk :", err)
panic(err)
}
}
}
*/

11
go.mod
View File

@@ -1,11 +0,0 @@
module gowipe
go 1.20
require (
github.com/JojiiOfficial/shred v1.2.1 // indirect
github.com/lu4p/shred v0.0.0-20201211173428-0347b645d724 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
)

10
go.sum
View File

@@ -1,10 +0,0 @@
github.com/JojiiOfficial/shred v1.2.1 h1:658CFVTqcAkYVg815vW+guYnyJTLOIoS15tMyPTYhNo=
github.com/JojiiOfficial/shred v1.2.1/go.mod h1:/OAxd6eYOhrXb3KW+2wmDog2BiFlUld8oJEKa+xblxU=
github.com/lu4p/shred v0.0.0-20201211173428-0347b645d724 h1:nLJRUakdvy2j7JsefrtAUqGRbfJUKKqRu/3BCRA9mIQ=
github.com/lu4p/shred v0.0.0-20201211173428-0347b645d724/go.mod h1:6b1kEKx7IPBboPSTnoJZE5sbSDjcNkHHO3Hii8TU8XY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

238
main.go
View File

@@ -1,238 +0,0 @@
/*
Copyright © 2022 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"github.com/JojiiOfficial/shred"
flag "github.com/spf13/pflag"
)
const VERSION string = "0.0.2"
const Usage string = `This is gowipe - destruct files in a non-recoverable way.
Usage: gowipe [-rcvz] <file|directory>...
Options:
-r --recursive Delete <dir> recursively
-c --count <num> Overwrite files <num> times
-m --mode <mode> Use <mode> for overwriting (or use -E, -S, -M, -Z)
-n --nodelete Do not delete files after overwriting
-N --norename Do not rename the files
-v --verbose Verbose output
-V --version Show program version
-h --help Show usage
Available modes:
zero Overwrite with zeroes (-Z)
math Overwrite with math random bytes (-M)
secure Overwrite with secure random bytes (default) (-S)
encrypt Overwrite with ChaCha2Poly1305 encryption (most secure) (-E)`
type Conf struct {
mode string
count int
recurse bool
nodelete bool
norename bool
verbose bool
}
func main() {
showversion := false
showhelp := false
optzero := false
optsecure := false
optmath := false
optencrypt := false
c := Conf{
verbose: false,
mode: `secure`,
count: 30,
recurse: false,
nodelete: false,
norename: false,
}
flag.BoolVarP(&showversion, "version", "V", showversion, "show version")
flag.BoolVarP(&showhelp, "help", "h", showversion, "show help")
flag.BoolVarP(&c.verbose, "verbose", "v", c.verbose, "verbose")
flag.StringVarP(&c.mode, "mode", "m", c.mode, "overwrite mode")
flag.BoolVarP(&optzero, "zero", "Z", optzero, "zero mode")
flag.BoolVarP(&optsecure, "secure", "S", optsecure, "secure mode")
flag.BoolVarP(&optmath, "math", "M", optmath, "math mode")
flag.BoolVarP(&optmath, "encrypt", "E", optmath, "encrypt mode")
flag.BoolVarP(&c.recurse, "recursive", "r", c.recurse, "recursive")
flag.BoolVarP(&c.nodelete, "nodelete", "n", c.nodelete, "don't delete")
flag.BoolVarP(&c.norename, "norename", "N", c.norename, "don't rename")
flag.IntVarP(&c.count, "count", "c", c.count, "overwrite count")
flag.Parse()
if showversion {
fmt.Printf("This is gowipe version %s\n", VERSION)
os.Exit(0)
}
if showhelp {
fmt.Println(Usage)
os.Exit(0)
}
if len(flag.Args()) == 0 {
fmt.Println(Usage)
os.Exit(0)
}
var option shred.WriteOptions
if optzero {
option = shred.WriteZeros
}
if optmath {
option = shred.WriteRand
}
if optsecure {
option = shred.WriteRandSecure
}
if optencrypt {
c.mode = "encrypt"
}
switch c.mode {
case `secure`:
option = shred.WriteRandSecure
case `math`:
option = shred.WriteRand
case `zero`:
option = shred.WriteZeros
case `encrypt`:
optencrypt = true
default:
option = shred.WriteRandSecure
}
shredder := shred.Shredder{}
shredconf := shred.NewShredderConf(&shredder, option, c.count, !c.nodelete)
for _, file := range flag.Args() {
Wipe(file, &c, shredconf)
}
}
func Wipe(file string, c *Conf, wiper *shred.ShredderConf) {
if info, err := os.Stat(file); err == nil {
if info.IsDir() {
if !c.recurse {
fmt.Printf("-r not set, ignoring directory %s\n", file)
return
}
files, err := ioutil.ReadDir(file)
if err != nil {
log.Fatal(err)
}
for _, entry := range files {
Wipe(filepath.Join(file, entry.Name()), c, wiper)
}
if !c.nodelete {
err = os.Remove(Rename(file, c))
if err != nil {
log.Fatal(err)
}
}
} else {
if c.mode == "encrypt" {
err := Encrypt(c, file)
if err != nil {
log.Fatal(err)
}
Rename(file, c)
} else {
wiper.ShredFile(Rename(file, c))
}
}
if c.verbose {
fmt.Printf("Wiped %d times: %s\n", c.count, file)
}
} else {
if os.IsNotExist(err) {
fmt.Printf("No such file or directory: %s\n", file)
} else {
fmt.Println(err)
}
os.Exit(1)
}
}
func Rename(file string, c *Conf) string {
var newname string
dir := filepath.Dir(file)
base := filepath.Base(file)
length := len(base)
for i := 0; i < c.count; i++ {
for {
switch c.mode {
case `secure`:
new, err := GenerateSecureRandomString(length)
if err != nil {
log.Fatal(err)
}
newname = new
case `math`:
newname = GenerateMathRandomString(length)
case `zero`:
newname = strings.Repeat("0", length)
}
if newname != base {
break
}
}
/*
if c.verbose {
fmt.Printf("renaming %s/%s => %s/%s\n", dir, base, dir, newname)
}
*/
err := os.Rename(filepath.Join(dir, base), filepath.Join(dir, newname))
if err != nil {
log.Fatal(err)
}
base = newname
}
return filepath.Join(dir, newname)
}

View File

@@ -1,65 +0,0 @@
#!/bin/bash
# Copyright © 2022 Thomas von Dein
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# get list with: go tool dist list
DIST="darwin/amd64
freebsd/amd64
linux/amd64
netbsd/amd64
openbsd/amd64
windows/amd64"
tool="$1"
version="$2"
if test -z "$version"; then
echo "Usage: $0 <tool name> <release version>"
exit 1
fi
rm -rf releases
mkdir -p releases
for D in $DIST; do
os=${D/\/*/}
arch=${D/*\//}
binfile="releases/${tool}-${os}-${arch}-${version}"
tardir="${tool}-${os}-${arch}-${version}"
tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
set -x
GOOS=${os} GOARCH=${arch} go build -o ${binfile} -ldflags "-X 'github.com/tlinden/tablizer/cfg.VERSION=${version}'"
mkdir -p ${tardir}
cp ${binfile} README.md LICENSE ${tardir}/
echo 'tool = tablizer
PREFIX = /usr/local
UID = root
GID = 0
install:
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
tar cpzf ${tarfile} ${tardir}
sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256
sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256
rm -rf ${tardir}
set +x
done