Merge branch 'go_interface_library' into 'master'
Go interface library See merge request bboehmke/raspi-alpine-builder!1
This commit is contained in:
commit
e795fe0158
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
.idea/
|
.idea/
|
||||||
|
test_*
|
8
go.mod
Normal file
8
go.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module gitlab.com/bboehmke/raspi-alpine-builder
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46
|
||||||
|
github.com/stretchr/testify v1.4.0
|
||||||
|
)
|
14
go.sum
Normal file
14
go.sum
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46 h1:rs0kDBt2zF4/CM9rO5/iH+U22jnTygPlqWgX55Ufcxg=
|
||||||
|
github.com/GehirnInc/crypt v0.0.0-20190301055215-6c0105aabd46/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
171
network.go
Normal file
171
network.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package alpine_builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// network configuration
|
||||||
|
var networkConfig = "/data/etc/network/interfaces"
|
||||||
|
var networkInterface = "eth0"
|
||||||
|
|
||||||
|
// regular expressions
|
||||||
|
var reSectionStart = regexp.MustCompile("^(iface|mapping|auto|allow-|source)")
|
||||||
|
var reIface = regexp.MustCompile("^iface (\\w+) inet (\\w+)")
|
||||||
|
var reStaticAddress = regexp.MustCompile("^(\\s*)address ([0-9.]+)")
|
||||||
|
var reStaticNetmask = regexp.MustCompile("^(\\s*)netmask ([0-9.]+)")
|
||||||
|
var reStaticGateway = regexp.MustCompile("^(\\s*)gateway ([0-9.]+)")
|
||||||
|
|
||||||
|
// NetworkInfo represents the actual network configuration
|
||||||
|
type NetworkInfo struct {
|
||||||
|
IsStatic bool
|
||||||
|
Address string
|
||||||
|
Netmask string
|
||||||
|
Gateway string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNetworkInfo from config file
|
||||||
|
func GetNetworkInfo() (*NetworkInfo, error) {
|
||||||
|
// load config file
|
||||||
|
file, err := os.Open(networkConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open network config: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var info *NetworkInfo
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
// check for section start
|
||||||
|
if reSectionStart.MatchString(line) {
|
||||||
|
infLine := reIface.FindStringSubmatch(line)
|
||||||
|
if infLine != nil {
|
||||||
|
// interface found
|
||||||
|
if infLine[1] == networkInterface {
|
||||||
|
info = &NetworkInfo{
|
||||||
|
IsStatic: infLine[2] == "static",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// info set -> stop here
|
||||||
|
if info != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if info != nil {
|
||||||
|
// interface config
|
||||||
|
address := reStaticAddress.FindStringSubmatch(line)
|
||||||
|
if address != nil {
|
||||||
|
info.Address = address[2]
|
||||||
|
}
|
||||||
|
netmask := reStaticNetmask.FindStringSubmatch(line)
|
||||||
|
if netmask != nil {
|
||||||
|
info.Netmask = netmask[2]
|
||||||
|
}
|
||||||
|
gateway := reStaticGateway.FindStringSubmatch(line)
|
||||||
|
if gateway != nil {
|
||||||
|
info.Gateway = gateway[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if info != nil {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid network config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkEnableDHCP configures the network to use DHCP client
|
||||||
|
func NetworkEnableDHCP() error {
|
||||||
|
// load config file
|
||||||
|
file, err := os.Open(networkConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open network config: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var infFound bool
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
// check for section start
|
||||||
|
if reSectionStart.MatchString(line) {
|
||||||
|
infLine := reIface.FindStringSubmatch(line)
|
||||||
|
if infLine != nil {
|
||||||
|
// interface found
|
||||||
|
if infLine[1] == networkInterface {
|
||||||
|
infFound = true
|
||||||
|
buffer.WriteString(fmt.Sprintf("iface %s inet dhcp\n", networkInterface))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if infFound {
|
||||||
|
infFound = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.WriteString(line + "\n")
|
||||||
|
|
||||||
|
} else if !infFound {
|
||||||
|
buffer.WriteString(line + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write config file
|
||||||
|
return ioutil.WriteFile(networkConfig, buffer.Bytes(), os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkSetStatic IP configuration
|
||||||
|
func NetworkSetStatic(address, netmask, gateway string) error {
|
||||||
|
// load config file
|
||||||
|
file, err := os.Open(networkConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open network config: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
var infFound bool
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
// check for section start
|
||||||
|
if reSectionStart.MatchString(line) {
|
||||||
|
infLine := reIface.FindStringSubmatch(line)
|
||||||
|
if infLine != nil {
|
||||||
|
// interface found
|
||||||
|
if infLine[1] == networkInterface {
|
||||||
|
infFound = true
|
||||||
|
buffer.WriteString(fmt.Sprintf("iface %s inet static\n", networkInterface))
|
||||||
|
buffer.WriteString(fmt.Sprintf(" address %s\n", address))
|
||||||
|
buffer.WriteString(fmt.Sprintf(" netmask %s\n", netmask))
|
||||||
|
buffer.WriteString(fmt.Sprintf(" gateway %s\n", gateway))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if infFound {
|
||||||
|
infFound = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.WriteString(line + "\n")
|
||||||
|
|
||||||
|
} else if !infFound {
|
||||||
|
buffer.WriteString(line + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write config file
|
||||||
|
return ioutil.WriteFile(networkConfig, buffer.Bytes(), os.ModePerm)
|
||||||
|
}
|
116
network_test.go
Normal file
116
network_test.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package alpine_builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
networkConfig = "test_interfaces"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNetworkInfo(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// file does not exist
|
||||||
|
_, err := GetNetworkInfo()
|
||||||
|
ass.EqualError(err, "failed to open network config: open test_interfaces: no such file or directory")
|
||||||
|
|
||||||
|
// invalid config file
|
||||||
|
ass.NoError(ioutil.WriteFile(networkConfig, []byte(""), os.ModePerm))
|
||||||
|
_, err = GetNetworkInfo()
|
||||||
|
ass.EqualError(err, "invalid network config")
|
||||||
|
|
||||||
|
// dynamic config
|
||||||
|
ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
iface eth0 inet dhcp`), os.ModePerm))
|
||||||
|
info, err := GetNetworkInfo()
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.False(info.IsStatic)
|
||||||
|
|
||||||
|
// static config
|
||||||
|
ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
iface eth0 inet static
|
||||||
|
address 1.2.3.4
|
||||||
|
netmask 255.255.255.0
|
||||||
|
gateway 4.3.2.1`), os.ModePerm))
|
||||||
|
info, err = GetNetworkInfo()
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.True(info.IsStatic)
|
||||||
|
ass.Equal("1.2.3.4", info.Address)
|
||||||
|
ass.Equal("255.255.255.0", info.Netmask)
|
||||||
|
ass.Equal("4.3.2.1", info.Gateway)
|
||||||
|
|
||||||
|
_ = os.Remove(networkConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkEnableDHCP(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// file does not exist
|
||||||
|
_, err := GetNetworkInfo()
|
||||||
|
ass.EqualError(err, "failed to open network config: open test_interfaces: no such file or directory")
|
||||||
|
|
||||||
|
ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
iface eth0 inet static
|
||||||
|
address 1.2.3.4
|
||||||
|
netmask 255.255.255.0
|
||||||
|
gateway 4.3.2.1`), os.ModePerm))
|
||||||
|
|
||||||
|
ass.NoError(NetworkEnableDHCP())
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(networkConfig)
|
||||||
|
ass.NoError(err)
|
||||||
|
|
||||||
|
ass.Equal(`auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
iface eth0 inet dhcp
|
||||||
|
`, string(data))
|
||||||
|
|
||||||
|
_ = os.Remove(networkConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNetworkSetStatic(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// file does not exist
|
||||||
|
_, err := GetNetworkInfo()
|
||||||
|
ass.EqualError(err, "failed to open network config: open test_interfaces: no such file or directory")
|
||||||
|
|
||||||
|
ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
iface eth0 inet dhcp`), os.ModePerm))
|
||||||
|
|
||||||
|
ass.NoError(NetworkSetStatic("1.2.3.4", "255.255.255.0", "4.3.2.1"))
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(networkConfig)
|
||||||
|
ass.NoError(err)
|
||||||
|
|
||||||
|
ass.Equal(`auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
auto eth0
|
||||||
|
iface eth0 inet static
|
||||||
|
address 1.2.3.4
|
||||||
|
netmask 255.255.255.0
|
||||||
|
gateway 4.3.2.1
|
||||||
|
`, string(data))
|
||||||
|
|
||||||
|
_ = os.Remove(networkConfig)
|
||||||
|
}
|
@ -99,7 +99,7 @@ EOF
|
|||||||
|
|
||||||
# prepare network
|
# prepare network
|
||||||
chroot_exec rc-update add networking default
|
chroot_exec rc-update add networking default
|
||||||
ln -fs /data/etc/interfaces ${ROOTFS_PATH}/etc/network/interfaces
|
ln -fs /data/etc/network/interfaces ${ROOTFS_PATH}/etc/network/interfaces
|
||||||
|
|
||||||
# run local before network -> local brings up the interface
|
# run local before network -> local brings up the interface
|
||||||
sed -i '/^\tneed/ s/$/ local/' ${ROOTFS_PATH}/etc/init.d/networking
|
sed -i '/^\tneed/ s/$/ local/' ${ROOTFS_PATH}/etc/init.d/networking
|
||||||
@ -200,8 +200,8 @@ root_pw=\$(mkpasswd -m sha-512 -s "${DEFAULT_ROOT_PASSWORD}")
|
|||||||
echo "root:\${root_pw}:0:0:::::" > /data/etc/shadow
|
echo "root:\${root_pw}:0:0:::::" > /data/etc/shadow
|
||||||
|
|
||||||
# interface
|
# interface
|
||||||
if [ ! -f /data/etc/interfaces ]; then
|
if [ ! -f /data/etc/network/interfaces ]; then
|
||||||
cat > /data/etc/interfaces <<EOF2
|
cat > /data/etc/network/interfaces <<EOF2
|
||||||
auto lo
|
auto lo
|
||||||
iface lo inet loopback
|
iface lo inet loopback
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# get current partition index
|
# get current partition index
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
image_file=$1
|
image_file=$1
|
||||||
|
@ -71,7 +71,7 @@ int main(int argc, char* argv[]) {
|
|||||||
data[1] = 0;
|
data[1] = 0;
|
||||||
|
|
||||||
// boot partition
|
// boot partition
|
||||||
data[2] = 2; // A=2, B=3
|
data[2] = 2; // A=1, B=2
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle commands
|
// handle commands
|
||||||
|
86
system.go
Normal file
86
system.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package alpine_builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GehirnInc/crypt"
|
||||||
|
_ "github.com/GehirnInc/crypt/sha256_crypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// system configurations
|
||||||
|
var systemShadow = "/data/etc/shadow"
|
||||||
|
var systemDropbearConfig = "/data/etc/dropbear/dropbear.conf"
|
||||||
|
var systemDropbearRestart = "rc-service dropbear start"
|
||||||
|
var systemShutdown = "poweroff"
|
||||||
|
var systemReboot = "reboot"
|
||||||
|
|
||||||
|
// SystemSetRootPassword update shadow file
|
||||||
|
func SystemSetRootPassword(password string) error {
|
||||||
|
crypter := crypt.SHA256.New()
|
||||||
|
|
||||||
|
// generate password line
|
||||||
|
hash, err := crypter.Generate([]byte(password), nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate hash: %w", err)
|
||||||
|
}
|
||||||
|
line := fmt.Sprintf("root:%s:0:0:::::", hash)
|
||||||
|
|
||||||
|
// write shadow file
|
||||||
|
return ioutil.WriteFile(systemShadow, []byte(line), os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemEnableSSH server
|
||||||
|
func SystemEnableSSH() error {
|
||||||
|
err := ioutil.WriteFile(systemDropbearConfig, []byte("DROPBEAR_OPTS=\"\""), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write ssh config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdSplit := strings.Split(systemDropbearRestart, " ")
|
||||||
|
cmd := exec.Command(cmdSplit[0], cmdSplit[1:]...)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to restart ssh server: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemDisableSSH server
|
||||||
|
func SystemDisableSSH() error {
|
||||||
|
err := ioutil.WriteFile(systemDropbearConfig, []byte("DROPBEAR_OPTS=\"-p 127.0.0.1:22\""), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write ssh config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdSplit := strings.Split(systemDropbearRestart, " ")
|
||||||
|
cmd := exec.Command(cmdSplit[0], cmdSplit[1:]...)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to restart ssh server: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemShutdown start shutdown of system
|
||||||
|
func SystemShutdown() error {
|
||||||
|
cmd := exec.Command(systemShutdown)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start system shutdown: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemReboot start reboot of system
|
||||||
|
func SystemReboot() error {
|
||||||
|
cmd := exec.Command(systemReboot)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start system reboot: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
75
system_test.go
Normal file
75
system_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package alpine_builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GehirnInc/crypt"
|
||||||
|
_ "github.com/GehirnInc/crypt/sha256_crypt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
systemShadow = "test_shadow"
|
||||||
|
systemDropbearConfig = "test_dropbear"
|
||||||
|
systemDropbearRestart = "restart_dropbear_command"
|
||||||
|
systemReboot = "reboot_command"
|
||||||
|
systemShutdown = "shutdown_command"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemSetRootPassword(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
ass.NoError(SystemSetRootPassword("password"))
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(systemShadow)
|
||||||
|
ass.NoError(err)
|
||||||
|
hash := strings.Split(string(data), ":")[1]
|
||||||
|
|
||||||
|
crypter := crypt.SHA256.New()
|
||||||
|
ass.NoError(crypter.Verify(hash, []byte("password")))
|
||||||
|
|
||||||
|
_ = os.Remove(systemShadow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemEnableSSH(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
ass.EqualError(SystemEnableSSH(), "failed to restart ssh server: exec: \"restart_dropbear_command\": executable file not found in $PATH")
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(systemDropbearConfig)
|
||||||
|
ass.NoError(err)
|
||||||
|
|
||||||
|
ass.Equal("DROPBEAR_OPTS=\"\"", string(data))
|
||||||
|
|
||||||
|
_ = os.Remove(systemDropbearConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemDisableSSH(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
ass.EqualError(SystemDisableSSH(), "failed to restart ssh server: exec: \"restart_dropbear_command\": executable file not found in $PATH")
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(systemDropbearConfig)
|
||||||
|
ass.NoError(err)
|
||||||
|
|
||||||
|
ass.Equal("DROPBEAR_OPTS=\"-p 127.0.0.1:22\"", string(data))
|
||||||
|
|
||||||
|
_ = os.Remove(systemDropbearConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemReboot(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
ass.EqualError(SystemReboot(),
|
||||||
|
"failed to start system reboot: exec: \"reboot_command\": executable file not found in $PATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemShutdown(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
ass.EqualError(SystemShutdown(),
|
||||||
|
"failed to start system shutdown: exec: \"shutdown_command\": executable file not found in $PATH")
|
||||||
|
}
|
143
update.go
Normal file
143
update.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package alpine_builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ubootFile = "/uboot/uboot.dat"
|
||||||
|
var ubootRemountRW = "mount -o remount,rw /uboot"
|
||||||
|
var ubootRemountRO = "mount -o remount,ro /uboot"
|
||||||
|
|
||||||
|
var rootPartitionA = "/dev/mmcblk0p1"
|
||||||
|
var rootPartitionB = "/dev/mmcblk0p2"
|
||||||
|
|
||||||
|
// loadUbootDat file to byte array
|
||||||
|
func loadUbootDat() ([]byte, error) {
|
||||||
|
defaultData := make([]byte, 1024)
|
||||||
|
defaultData[0] = 1 // file version
|
||||||
|
defaultData[1] = 0 // boot counter
|
||||||
|
defaultData[2] = 1 // boot partition A=1, B=2
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(ubootFile)
|
||||||
|
if err != nil {
|
||||||
|
return defaultData, fmt.Errorf("failed to open file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid dat file?
|
||||||
|
if len(data) < 1024 {
|
||||||
|
return defaultData, errors.New("invalid dat file -> fallback to defaults")
|
||||||
|
}
|
||||||
|
|
||||||
|
crc := binary.LittleEndian.Uint32(data[1020:])
|
||||||
|
bla := crc32.ChecksumIEEE(data[:1020])
|
||||||
|
if crc != bla {
|
||||||
|
return defaultData, errors.New("invalid crc -> fallback to defaults")
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveUbootDat from byte array
|
||||||
|
func saveUbootDat(data []byte) error {
|
||||||
|
// update crc
|
||||||
|
binary.LittleEndian.PutUint32(data[1020:],
|
||||||
|
crc32.ChecksumIEEE(data[:1020]))
|
||||||
|
|
||||||
|
// remount boot partition - writable
|
||||||
|
cmd := exec.Command(ubootRemountRW)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remount RW: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update uboot dat file
|
||||||
|
err = ioutil.WriteFile(ubootFile, data[:1024], os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed write uboot dat: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remount boot partition - readonly
|
||||||
|
cmd = exec.Command(ubootRemountRO)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remount RO: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UBootResetCounter to 0
|
||||||
|
func UBootResetCounter() error {
|
||||||
|
data, _ := loadUbootDat()
|
||||||
|
data[1] = 0
|
||||||
|
return saveUbootDat(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UBootActive returns the active partition. A=1, B=2
|
||||||
|
func UBootActive() uint8 {
|
||||||
|
data, _ := loadUbootDat()
|
||||||
|
return data[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// UBootSetActive sets the active partition. A=1, B=2
|
||||||
|
func UBootSetActive(active uint8) error {
|
||||||
|
data, _ := loadUbootDat()
|
||||||
|
if active == 1 {
|
||||||
|
data[2] = 1
|
||||||
|
} else {
|
||||||
|
data[2] = 2
|
||||||
|
}
|
||||||
|
return saveUbootDat(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSystem with the given image
|
||||||
|
func UpdateSystem(image string) error {
|
||||||
|
data, _ := loadUbootDat()
|
||||||
|
rootPart := rootPartitionA
|
||||||
|
if data[2] == 1 {
|
||||||
|
rootPart = rootPartitionB
|
||||||
|
}
|
||||||
|
|
||||||
|
// open image file
|
||||||
|
inFile, err := os.Open(image)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer inFile.Close()
|
||||||
|
|
||||||
|
// decompress image
|
||||||
|
inDecompress, err := gzip.NewReader(inFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer inDecompress.Close()
|
||||||
|
|
||||||
|
// open root partition
|
||||||
|
out, err := os.OpenFile(rootPart,
|
||||||
|
os.O_WRONLY|os.O_TRUNC|os.O_SYNC, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
// write update
|
||||||
|
_, err = io.Copy(out, inDecompress)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch active partition
|
||||||
|
if data[2] == 1 {
|
||||||
|
return UBootSetActive(2)
|
||||||
|
} else {
|
||||||
|
return UBootSetActive(1)
|
||||||
|
}
|
||||||
|
}
|
179
update_test.go
Normal file
179
update_test.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package alpine_builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ubootFile = "test_uboot"
|
||||||
|
ubootRemountRW = "true"
|
||||||
|
ubootRemountRO = "true"
|
||||||
|
rootPartitionA = "test_rootA"
|
||||||
|
rootPartitionB = "test_rootB"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadUbootDat(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// file missing
|
||||||
|
data, err := loadUbootDat()
|
||||||
|
ass.EqualError(err, "failed to open file: open test_uboot: no such file or directory")
|
||||||
|
ass.Equal([]byte{1, 0, 1}, data[:3])
|
||||||
|
|
||||||
|
// file with invalid data
|
||||||
|
ass.NoError(ioutil.WriteFile(ubootFile, []byte{}, os.ModePerm))
|
||||||
|
|
||||||
|
data, err = loadUbootDat()
|
||||||
|
ass.EqualError(err, "invalid dat file -> fallback to defaults")
|
||||||
|
ass.Equal([]byte{1, 0, 1}, data[:3])
|
||||||
|
|
||||||
|
// file with invalid CRC
|
||||||
|
testData := make([]byte, 1024)
|
||||||
|
testData[0] = 1
|
||||||
|
testData[1] = 2
|
||||||
|
testData[2] = 2
|
||||||
|
|
||||||
|
ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm))
|
||||||
|
data, err = loadUbootDat()
|
||||||
|
ass.EqualError(err, "invalid crc -> fallback to defaults")
|
||||||
|
ass.Equal([]byte{1, 0, 1}, data[:3])
|
||||||
|
|
||||||
|
// file with valid CRC
|
||||||
|
binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A)
|
||||||
|
|
||||||
|
ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm))
|
||||||
|
data, err = loadUbootDat()
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.Equal(testData, data)
|
||||||
|
|
||||||
|
_ = os.Remove(ubootFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveUbootDat(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
testData := make([]byte, 1024)
|
||||||
|
testData[0] = 1
|
||||||
|
testData[1] = 2
|
||||||
|
testData[2] = 2
|
||||||
|
ass.NoError(saveUbootDat(testData))
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A)
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(ubootFile)
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.Equal(testData, data)
|
||||||
|
|
||||||
|
_ = os.Remove(ubootFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUBootResetCounter(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// write test file
|
||||||
|
testData := make([]byte, 1024)
|
||||||
|
testData[0] = 1
|
||||||
|
testData[1] = 2
|
||||||
|
testData[2] = 2
|
||||||
|
binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A)
|
||||||
|
ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm))
|
||||||
|
|
||||||
|
ass.NoError(UBootResetCounter())
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(ubootFile)
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.Zero(data[1])
|
||||||
|
|
||||||
|
_ = os.Remove(ubootFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUBootActive(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// write test file
|
||||||
|
testData := make([]byte, 1024)
|
||||||
|
testData[0] = 1
|
||||||
|
testData[1] = 2
|
||||||
|
testData[2] = 2
|
||||||
|
binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A)
|
||||||
|
ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm))
|
||||||
|
|
||||||
|
ass.Equal(uint8(2), UBootActive())
|
||||||
|
|
||||||
|
_ = os.Remove(ubootFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUBootSetActive(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// write test file
|
||||||
|
testData := make([]byte, 1024)
|
||||||
|
testData[0] = 1
|
||||||
|
testData[1] = 2
|
||||||
|
testData[2] = 2
|
||||||
|
binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A)
|
||||||
|
ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm))
|
||||||
|
|
||||||
|
ass.NoError(UBootSetActive(1))
|
||||||
|
|
||||||
|
data, err := ioutil.ReadFile(ubootFile)
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.Equal(uint8(1), data[2])
|
||||||
|
|
||||||
|
_ = os.Remove(ubootFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSystem(t *testing.T) {
|
||||||
|
ass := assert.New(t)
|
||||||
|
|
||||||
|
// test uboot file
|
||||||
|
testData := make([]byte, 1024)
|
||||||
|
testData[0] = 1
|
||||||
|
testData[1] = 2
|
||||||
|
testData[2] = 2
|
||||||
|
binary.LittleEndian.PutUint32(testData[1020:], 0x982E8B7A)
|
||||||
|
ass.NoError(ioutil.WriteFile(ubootFile, testData, os.ModePerm))
|
||||||
|
|
||||||
|
// generate test image content
|
||||||
|
size := int64(1024 * 1024 * 5)
|
||||||
|
testImgData := make([]byte, size)
|
||||||
|
buffer := bytes.NewBuffer(testImgData)
|
||||||
|
_, err := io.CopyN(buffer, rand.Reader, size)
|
||||||
|
ass.NoError(err)
|
||||||
|
|
||||||
|
// write compressed image file
|
||||||
|
file, err := os.Create("test_image.gz")
|
||||||
|
ass.NoError(err)
|
||||||
|
gzipWriter := gzip.NewWriter(file)
|
||||||
|
_, err = gzipWriter.Write(testImgData)
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.NoError(gzipWriter.Close())
|
||||||
|
ass.NoError(file.Close())
|
||||||
|
|
||||||
|
ass.NoError(ioutil.WriteFile(rootPartitionA, nil, os.ModePerm))
|
||||||
|
ass.NoError(UpdateSystem("test_image.gz"))
|
||||||
|
|
||||||
|
// check if image was written
|
||||||
|
data, err := ioutil.ReadFile(rootPartitionA)
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.Equal(testImgData, data)
|
||||||
|
|
||||||
|
// check if uboot dat was updated
|
||||||
|
data, err = ioutil.ReadFile(ubootFile)
|
||||||
|
ass.NoError(err)
|
||||||
|
ass.Equal(uint8(1), data[2])
|
||||||
|
|
||||||
|
_ = os.Remove("test_image.gz")
|
||||||
|
_ = os.Remove(ubootFile)
|
||||||
|
_ = os.Remove(rootPartitionA)
|
||||||
|
_ = os.Remove(rootPartitionB)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user