diff --git a/.github/workflows/deb-build.yml b/.github/workflows/deb-build.yml index 8e4feca9..23cf95ec 100644 --- a/.github/workflows/deb-build.yml +++ b/.github/workflows/deb-build.yml @@ -23,11 +23,11 @@ jobs: rm -r /run/host${{ runner.tool_cache }} - name: Install needed packages - run: apt update && apt install dpkg-dev build-essential debhelper libbtrfs-dev libdevmapper-dev libgpgme-dev lvm2 dh-golang golang-go gcc pkg-config -y + run: apt update && apt install dpkg-dev build-essential debhelper libbtrfs-dev libdevmapper-dev libgpgme-dev lvm2 dh-golang golang-go gcc pkg-config make -y - name: Build debian package run: | - dpkg-buildpackage --no-sign + make deb mv ../*.deb ../albius.deb - uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c068ea19..49ac7c34 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -22,10 +22,10 @@ jobs: - name: Install build dependencies run: | apt-get update - apt-get install -y gcc pkg-config libbtrfs-dev libdevmapper-dev libgpgme-dev lvm2 + apt-get install -y gcc pkg-config libbtrfs-dev libdevmapper-dev libgpgme-dev lvm2 make - name: Build - run: go build -v ./... + run: make build test: runs-on: ubuntu-latest @@ -40,4 +40,4 @@ jobs: - name: Test run: | - distrobox enter -r albius_test -- sudo go test -v ./... + distrobox enter -r albius_test -- sudo make test diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..465b669a --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +all: build test +all-deb: build test deb + +build: + go build + +deb: + dpkg-buildpackage --no-sign + +test: + sudo go test -v ./... + +.PHONY: clean + +clean: + rm albius diff --git a/core/disk.go b/core/disk/disk.go similarity index 76% rename from core/disk.go rename to core/disk/disk.go index ca301933..92962bd1 100644 --- a/core/disk.go +++ b/core/disk/disk.go @@ -1,14 +1,16 @@ -package albius +package disk import ( "cmp" "encoding/json" "fmt" + "reflect" "slices" "strconv" "strings" "github.com/vanilla-os/albius/core/lvm" + "github.com/vanilla-os/albius/core/util" ) const ( @@ -72,7 +74,7 @@ func (disk *Disk) AvailableSectors() ([]Sector, error) { func LocateDisk(diskname string) (*Disk, error) { findPartitionCmd := "parted -sj %s unit MiB print" - output, err := OutputCommand(fmt.Sprintf(findPartitionCmd, diskname)) + output, err := util.OutputCommand(fmt.Sprintf(findPartitionCmd, diskname)) // If disk is unformatted, parted returns the expected json but also throws an error. // We can assume we have all the necessary information if output isn't empty. if err != nil && output == "" { @@ -91,10 +93,10 @@ func LocateDisk(diskname string) (*Disk, error) { decoded.Disk.Partitions[i].FillPath(decoded.Disk.Path) } - // Partitions may be unordered - slices.SortFunc(decoded.Disk.Partitions, func(a, b Partition) int { - return cmp.Compare(a.Number, b.Number) - }) + // Partitions may be unordered + slices.SortFunc(decoded.Disk.Partitions, func(a, b Partition) int { + return cmp.Compare(a.Number, b.Number) + }) return &decoded.Disk, nil } @@ -121,13 +123,13 @@ func (disk *Disk) Update() error { // inform the OS of changes to the partition table (using `partprobe`) and // ensure the system is aware of it before proceeding. func (disk *Disk) waitForNewPartition() error { - err := RunCommand(fmt.Sprintf("partprobe %s", disk.Path)) + err := util.RunCommand(fmt.Sprintf("partprobe %s", disk.Path)) if err != nil { return err } for { - output, err := OutputCommand(fmt.Sprintf("lsblk -nro NAME %s | wc -l", disk.Path)) + output, err := util.OutputCommand(fmt.Sprintf("lsblk -nro NAME %s | wc -l", disk.Path)) if err != nil { return err } @@ -178,7 +180,7 @@ func (disk *Disk) LabelDisk(label DiskLabel) error { } } - err = RunCommand(fmt.Sprintf(labelDiskCmd, disk.Path, label)) + err = util.RunCommand(fmt.Sprintf(labelDiskCmd, disk.Path, label)) if err != nil { return fmt.Errorf("failed to label disk: %s", err) } @@ -214,7 +216,7 @@ func (target *Disk) NewPartition(name string, fsType PartitionFs, start, end int partName = fmt.Sprintf(" \"%s\"", name) } - err := RunCommand(fmt.Sprintf(createPartCmd, target.Path, partType, partName, fsType, start, endStr)) + err := util.RunCommand(fmt.Sprintf(createPartCmd, target.Path, partType, partName, fsType, start, endStr)) if err != nil { return nil, fmt.Errorf("failed to create partition: %s", err) } @@ -266,17 +268,49 @@ func (target *Disk) NewPartition(name string, fsType PartitionFs, start, end int // // [Issue #44]: https://github.com/Vanilla-OS/Albius/issues/44 func (target *Disk) GetPartition(partNum int) *Partition { - // Happy path: No partitions are missing - if target.Partitions[partNum-1].Number == partNum { - return &target.Partitions[partNum-1] - } - - // Missing partition numbers, find correct partition manually - for _, part := range target.Partitions { - if part.Number == partNum { - return &part - } - } - - return nil + // Happy path: No partitions are missing + if target.Partitions[partNum-1].Number == partNum { + return &target.Partitions[partNum-1] + } + + // Missing partition numbers, find correct partition manually + for _, part := range target.Partitions { + if part.Number == partNum { + return &part + } + } + + return nil +} + +func setField(obj interface{}, name string, value interface{}) error { + structValue := reflect.ValueOf(obj).Elem() + structFieldValue := structValue.FieldByName(name) + + if !structFieldValue.IsValid() { + return fmt.Errorf("no such field: %s in obj", name) + } + + if !structFieldValue.CanSet() { + return fmt.Errorf("cannot set %s field value", name) + } + + structFieldType := structFieldValue.Type() + val := reflect.ValueOf(value) + var convertedVal reflect.Value + if structFieldType != val.Type() { + // Type conversions + if structFieldType.Kind() == reflect.Int && val.Type().Kind() == reflect.Float64 { + convertedVal = reflect.ValueOf(int(val.Interface().(float64))) + } else if structFieldType.Name() == "DiskLabel" && val.Type().Kind() == reflect.String { + convertedVal = reflect.ValueOf(DiskLabel(val.Interface().(string))) + } else { + return fmt.Errorf("provided value type for %s did not match obj field type. Expected %v, got %v", name, structFieldType, val.Type()) + } + } else { + convertedVal = val + } + + structFieldValue.Set(convertedVal) + return nil } diff --git a/core/disk/disk_test.go b/core/disk/disk_test.go new file mode 100644 index 00000000..a5d418f1 --- /dev/null +++ b/core/disk/disk_test.go @@ -0,0 +1,136 @@ +package disk + +import ( + "os" + "os/exec" + "runtime" + "strings" + "testing" + + luks "github.com/vanilla-os/albius/core/disk/luks" +) + +var diskPath string + +func TestMain(m *testing.M) { + _, filename, _, _ := runtime.Caller(0) + projRoot, _, _ := strings.Cut(filename, "core/") + + device, err := exec.Command(projRoot+"utils/create_test_device.sh", "-o", "test.img", "-s", "51200").Output() + if err != nil { + panic(err) + } + diskPath = strings.TrimSpace(string(device)) + + status := m.Run() + + err = exec.Command(projRoot+"utils/remove_test_device.sh", diskPath, "test.img").Run() + if err != nil { + panic(err) + } + os.Exit(status) +} + +func TestLocateDisk(t *testing.T) { + d, err := LocateDisk(diskPath) + if err != nil { + t.Error(err) + } + + if d.Path != diskPath { + t.Errorf("Located incorrect disk: %v", d) + } +} + +func TestLabelDisk(t *testing.T) { + d, err := LocateDisk(diskPath) + if err != nil { + t.Error(err) + } + + err = d.LabelDisk(GPT) + if err != nil { + t.Error(err) + } +} + +func TestNewPartition(t *testing.T) { + d, err := LocateDisk(diskPath) + if err != nil { + t.Error(err) + } + + _, err = d.NewPartition("", EXT4, 1, 25) + if err != nil { + t.Error(err) + } + + _, err = d.NewPartition("", EXT4, 26, -1) + if err != nil { + t.Error(err) + } +} + +func TestGetPartition(t *testing.T) { + d, err := LocateDisk(diskPath) + if err != nil { + t.Error(err) + } + + if p := d.GetPartition(1); p == nil { + t.Error(err) + } +} + +func TestLuksFormat(t *testing.T) { + d, err := LocateDisk(diskPath) + if err != nil { + t.Error(err) + } + + if err = luks.LuksFormat(&d.Partitions[0], "test"); err != nil { + t.Error(err) + } +} + +func TestIsLuks(t *testing.T) { + d, err := LocateDisk(diskPath) + if err != nil { + t.Error(err) + } + + isLuks, err := luks.IsLuks(&d.Partitions[0]) + if err != nil { + t.Error(err) + } + if !isLuks { + t.Error("Failed to detect partition as LUKS-encrypted") + } + + isLuks, err = luks.IsLuks(&d.Partitions[1]) + if err != nil { + t.Error(err) + } + if isLuks { + t.Error("Wrongly detected partition as LUKS-encrypted") + } +} + +func TestLuksOpen(t *testing.T) { + d, err := LocateDisk(diskPath) + if err != nil { + t.Error(err) + } + + err = luks.LuksOpen(&d.Partitions[0], "luks-test", "test") + if err != nil { + t.Error(err) + } +} + +func TestLuksClose(t *testing.T) { + err := luks.LuksClose("luks-test") + if err != nil { + t.Error(err) + } +} diff --git a/core/filesystem.go b/core/disk/filesystem.go similarity index 74% rename from core/filesystem.go rename to core/disk/filesystem.go index 84289395..d816bf5f 100644 --- a/core/filesystem.go +++ b/core/disk/filesystem.go @@ -1,4 +1,4 @@ -package albius +package disk import ( "fmt" @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/vanilla-os/albius/core/util" "github.com/vanilla-os/prometheus" ) @@ -19,7 +20,7 @@ func Unsquashfs(filesystem, destination string, force bool) error { forceFlag = "" } - err := RunCommand(fmt.Sprintf(unsquashfsCmd, forceFlag, destination, filesystem)) + err := util.RunCommand(fmt.Sprintf(unsquashfsCmd, forceFlag, destination, filesystem)) if err != nil { return fmt.Errorf("failed to run unsquashfs: %s", err) } @@ -32,21 +33,21 @@ func MakeFs(part *Partition) error { switch part.Filesystem { case FAT16: makefsCmd := "mkfs.fat -I -F 16 %s" - err = RunCommand(fmt.Sprintf(makefsCmd, part.Path)) + err = util.RunCommand(fmt.Sprintf(makefsCmd, part.Path)) case FAT32: makefsCmd := "mkfs.fat -I -F 32 %s" - err = RunCommand(fmt.Sprintf(makefsCmd, part.Path)) + err = util.RunCommand(fmt.Sprintf(makefsCmd, part.Path)) case EXT2, EXT3, EXT4: makefsCmd := "mkfs.%s -F %s" - err = RunCommand(fmt.Sprintf(makefsCmd, part.Filesystem, part.Path)) + err = util.RunCommand(fmt.Sprintf(makefsCmd, part.Filesystem, part.Path)) case LINUX_SWAP: makefsCmd := "mkswap -f %s" - err = RunCommand(fmt.Sprintf(makefsCmd, part.Path)) + err = util.RunCommand(fmt.Sprintf(makefsCmd, part.Path)) case HFS, HFS_PLUS, UDF: return fmt.Errorf("unsupported filesystem: %s", part.Filesystem) default: makefsCmd := "mkfs.%s -f %s" - err = RunCommand(fmt.Sprintf(makefsCmd, part.Filesystem, part.Path)) + err = util.RunCommand(fmt.Sprintf(makefsCmd, part.Filesystem, part.Path)) } if err != nil { @@ -56,12 +57,27 @@ func MakeFs(part *Partition) error { return nil } +// LUKSMakeFs creates a filesystem inside of a LUKS-formatted partition. Use +// this instead of MakeFs when setting up encrypted filesystems. +func LUKSMakeFs(part Partition) error { + innerPartition := Partition{} + + partUUID, err := part.GetUUID() + if err != nil { + return err + } + innerPartition.Path = fmt.Sprintf("/dev/mapper/luks-%s", partUUID) + innerPartition.Filesystem = part.Filesystem + + return MakeFs(&innerPartition) +} + func GenFstab(targetRoot string, entries [][]string) error { fstabHeader := `# /etc/fstab: static file system information. # # Use 'blkid' to print the universally unique identifier for a # device; this may be used with UUID= as a more robust way to name devices -# that works even if disks are added and removed. See fstab(5). +# that works even if are added and removed. See fstab(5). # # ` @@ -92,13 +108,13 @@ func UpdateInitramfs(root string) error { // Setup mountpoints mountOrder := []string{"/dev", "/dev/pts", "/proc", "/sys"} for _, mount := range mountOrder { - if err := RunCommand(fmt.Sprintf("mount --bind %s %s%s", mount, root, mount)); err != nil { + if err := util.RunCommand(fmt.Sprintf("mount --bind %s %s%s", mount, root, mount)); err != nil { return fmt.Errorf("error mounting %s to chroot: %s", mount, err) } } updInitramfsCmd := "update-initramfs -c -k all" - err := RunInChroot(root, updInitramfsCmd) + err := util.RunInChroot(root, updInitramfsCmd) if err != nil { return fmt.Errorf("failed to run update-initramfs command: %s", err) } @@ -106,7 +122,7 @@ func UpdateInitramfs(root string) error { // Cleanup mountpoints unmountOrder := []string{"/dev/pts", "/dev", "/proc", "/sys"} for _, mount := range unmountOrder { - if err := RunCommand(fmt.Sprintf("umount %s%s", root, mount)); err != nil { + if err := util.RunCommand(fmt.Sprintf("umount %s%s", root, mount)); err != nil { return fmt.Errorf("error unmounting %s fron chroot: %s", mount, err) } } @@ -123,11 +139,11 @@ func OCISetup(imageSource, storagePath, destination string, verbose bool) error // Create tmp directory in root's /var to store podman's temp files, since /var/tmp in // the ISO is tied to the user's RAM and can run out of space pretty quickly storageTmpDir := filepath.Join(storagePath, "tmp") - err = os.Mkdir(storageTmpDir, 0644) + err = os.Mkdir(storageTmpDir, 0o644) if err != nil { return fmt.Errorf("failed to create storage tmp dir: %s", err) } - err = RunCommand(fmt.Sprintf("mount --bind %s %s", storageTmpDir, "/var/tmp")) + err = util.RunCommand(fmt.Sprintf("mount --bind %s %s", storageTmpDir, "/var/tmp")) if err != nil { return fmt.Errorf("failed to mount bind storage tmp dir: %s", err) } @@ -161,19 +177,19 @@ func OCISetup(imageSource, storagePath, destination string, verbose bool) error } else { verboseFlag = "" } - err = RunCommand(fmt.Sprintf("rsync -a%sxHAX --numeric-ids %s/ %s/", verboseFlag, mountPoint, destination)) + err = util.RunCommand(fmt.Sprintf("rsync -a%sxHAX --numeric-ids %s/ %s/", verboseFlag, mountPoint, destination)) if err != nil { return fmt.Errorf("failed to sync image contents to %s: %s", destination, err) } // Remove storage from destination - err = RunCommand(fmt.Sprintf("umount -l %s/storage/graph/overlay", storagePath)) + err = util.RunCommand(fmt.Sprintf("umount -l %s/storage/graph/overlay", storagePath)) if err != nil { return fmt.Errorf("failed to unmount image: %s", err) } // Unmount tmp storage directory - err = RunCommand("umount -l /var/tmp") + err = util.RunCommand("umount -l /var/tmp") if err != nil { return fmt.Errorf("failed to unmount storage tmp dir: %s", err) } @@ -189,7 +205,7 @@ func OCISetup(imageSource, storagePath, destination string, verbose bool) error } // Store the digest in destination as it may be used by the update manager - err = os.WriteFile(filepath.Join(destination, ".oci_digest"), []byte(manifest.Config.Digest), 0644) + err = os.WriteFile(filepath.Join(destination, ".oci_digest"), []byte(manifest.Config.Digest), 0o644) if err != nil { return fmt.Errorf("failed to save digest in %s: %s", destination, err) } diff --git a/core/luks.go b/core/disk/luks/luks.go similarity index 62% rename from core/luks.go rename to core/disk/luks/luks.go index 1ee6750e..56cc713d 100644 --- a/core/luks.go +++ b/core/disk/luks/luks.go @@ -1,16 +1,23 @@ -package albius +package disk import ( "fmt" "os" "os/exec" "strings" + + "github.com/vanilla-os/albius/core/util" ) -func IsLuks(part *Partition) (bool, error) { +type Partition interface { + GetUUID() (string, error) + GetPath() string +} + +func IsLuks(part Partition) (bool, error) { isLuksCmd := "cryptsetup isLuks %s" - cmd := exec.Command("sh", "-c", fmt.Sprintf(isLuksCmd, part.Path)) + cmd := exec.Command("sh", "-c", fmt.Sprintf(isLuksCmd, part.GetPath())) err := cmd.Run() if err != nil { // We expect the command to return exit status 1 if partition isn't LUKS-encrypted @@ -18,22 +25,15 @@ func IsLuks(part *Partition) (bool, error) { if exitError.ExitCode() == 1 { return false, nil } else { - return false, fmt.Errorf("failed to check if %s is LUKS-encrypted: %s", part.Path, string(exitError.Stderr)) + return false, fmt.Errorf("failed to check if %s is LUKS-encrypted: %s", part.GetPath(), string(exitError.Stderr)) } } - return false, fmt.Errorf("failed to check if %s is LUKS-encrypted: %s", part.Path, err) + return false, fmt.Errorf("failed to check if %s is LUKS-encrypted: %s", part.GetPath(), err) } return true, nil } -func IsPathLuks(path string) (bool, error) { - dummyPartition := Partition{} - dummyPartition.Path = path - - return IsLuks(&dummyPartition) -} - // LuksOpen opens a LUKS-encrypted partition, mapping the unencrypted filesystem // to /dev/mapper/. // @@ -42,7 +42,7 @@ func IsPathLuks(path string) (bool, error) { // // WARNING: This function will return an error if mapping already exists, use // LuksTryOpen() to open a device while ignoring existing mappings -func LuksOpen(part *Partition, mapping, password string) error { +func LuksOpen(part Partition, mapping, password string) error { var luksOpenCmd string if password != "" { luksOpenCmd = fmt.Sprintf("echo '%s' | ", password) @@ -52,7 +52,7 @@ func LuksOpen(part *Partition, mapping, password string) error { luksOpenCmd += "cryptsetup open %s %s" - err := RunCommand(fmt.Sprintf(luksOpenCmd, part.Path, mapping)) + err := util.RunCommand(fmt.Sprintf(luksOpenCmd, part.GetPath(), mapping)) if err != nil { return fmt.Errorf("failed to open LUKS-encrypted partition: %s", err) } @@ -68,7 +68,7 @@ func LuksOpen(part *Partition, mapping, password string) error { // open. // // The function still returns other errors, however. -func LuksTryOpen(part *Partition, mapping, password string) error { +func LuksTryOpen(part Partition, mapping, password string) error { _, err := os.Stat(fmt.Sprintf("/dev/mapper/%s", mapping)) if err == nil { // Mapping exists, do nothing return nil @@ -82,7 +82,7 @@ func LuksTryOpen(part *Partition, mapping, password string) error { func LuksClose(mapping string) error { luksCloseCmd := "cryptsetup close %s" - err := RunCommand(fmt.Sprintf(luksCloseCmd, mapping)) + err := util.RunCommand(fmt.Sprintf(luksCloseCmd, mapping)) if err != nil { return fmt.Errorf("failed to close LUKS-encrypted partition: %s", err) } @@ -90,10 +90,10 @@ func LuksClose(mapping string) error { return nil } -func LuksFormat(part *Partition, password string) error { +func LuksFormat(part Partition, password string) error { luksFormatCmd := "echo '%s' | cryptsetup -q luksFormat %s" - err := RunCommand(fmt.Sprintf(luksFormatCmd, password, part.Path)) + err := util.RunCommand(fmt.Sprintf(luksFormatCmd, password, part.GetPath())) if err != nil { return fmt.Errorf("failed to create LUKS-encrypted partition: %s", err) } @@ -122,40 +122,10 @@ func GenCrypttab(targetRoot string, entries [][]string) error { func GetLUKSFilesystemByPath(path string) (string, error) { lsblkCmd := "lsblk -n -o FSTYPE %s | sed '/crypto_LUKS/d'" - output, err := OutputCommand(fmt.Sprintf(lsblkCmd, path)) + output, err := util.OutputCommand(fmt.Sprintf(lsblkCmd, path)) if err != nil { return "", fmt.Errorf("failed to get encrypted partition FSTYPE: %s", err) } return output, nil } - -// LUKSMakeFs creates a filesystem inside of a LUKS-formatted partition. Use -// this instead of MakeFs when setting up encrypted filesystems. -func LUKSMakeFs(part *Partition) error { - innerPartition := Partition{} - - partUUID, err := part.GetUUID() - if err != nil { - return err - } - innerPartition.Path = fmt.Sprintf("/dev/mapper/luks-%s", partUUID) - innerPartition.Filesystem = part.Filesystem - - return MakeFs(&innerPartition) -} - -// LUKSSetLabel labels a LUKS-formatted partition. Use this instead of SetLabel -// when setting up encrypted filesystems. -func LUKSSetLabel(part *Partition, name string) error { - innerPartition := Partition{} - - partUUID, err := part.GetUUID() - if err != nil { - return err - } - innerPartition.Path = fmt.Sprintf("/dev/mapper/luks-%s", partUUID) - innerPartition.Filesystem = part.Filesystem - - return innerPartition.SetLabel(name) -} diff --git a/core/partition.go b/core/disk/partition.go similarity index 77% rename from core/partition.go rename to core/disk/partition.go index e08fd4ef..a649de8c 100644 --- a/core/partition.go +++ b/core/disk/partition.go @@ -1,4 +1,4 @@ -package albius +package disk import ( "fmt" @@ -6,6 +6,9 @@ import ( "slices" "strings" "time" + + luks "github.com/vanilla-os/albius/core/disk/luks" + "github.com/vanilla-os/albius/core/util" ) const ( @@ -36,16 +39,16 @@ func (part *Partition) Mount(location string) error { var mountPath string // If it's a LUKS-encrypted partition, open it first - luks, err := IsLuks(part) + isLuks, err := luks.IsLuks(part) if err != nil { return err } - if luks { + if isLuks { partUUID, err := part.GetUUID() if err != nil { return err } - err = LuksTryOpen(part, fmt.Sprintf("luks-%s", partUUID), "") + err = luks.LuksTryOpen(part, fmt.Sprintf("luks-%s", partUUID), "") if err != nil { return err } @@ -68,7 +71,7 @@ func (part *Partition) Mount(location string) error { } mountCmd := "mount -m %s %s" - err = RunCommand(fmt.Sprintf(mountCmd, mountPath, location)) + err = util.RunCommand(fmt.Sprintf(mountCmd, mountPath, location)) if err != nil { return fmt.Errorf("failed to run mount command: %s", err) } @@ -76,9 +79,13 @@ func (part *Partition) Mount(location string) error { return nil } +func (part *Partition) GetPath() string { + return part.Path +} + func (part *Partition) Mountpoints() ([]string, error) { mountpointsCmd := "lsblk -n -o MOUNTPOINTS %s" - output, err := OutputCommand(fmt.Sprintf(mountpointsCmd, part.Path)) + output, err := util.OutputCommand(fmt.Sprintf(mountpointsCmd, part.Path)) if err != nil { return []string{}, fmt.Errorf("failed to list mountpoints for %s: %s", part.Path, err) } @@ -115,16 +122,16 @@ func (part *Partition) UnmountPartition() error { } // Pass unmount operation to cryptsetup if it's a LUKS-encrypted partition - luks, err := IsLuks(part) + isLuks, err := luks.IsLuks(part) if err != nil { return err } - if luks { + if isLuks { partUUID, err := part.GetUUID() if err != nil { return err } - err = LuksClose(fmt.Sprintf("luks-%s", partUUID)) + err = luks.LuksClose(fmt.Sprintf("luks-%s", partUUID)) if err != nil { return err } @@ -138,7 +145,7 @@ func (part *Partition) UnmountPartition() error { } umountCmd := "umount %s" - err = RunCommand(fmt.Sprintf(umountCmd, mountTarget)) + err = util.RunCommand(fmt.Sprintf(umountCmd, mountTarget)) if err != nil { return fmt.Errorf("failed to run umount command: %s", err) } @@ -149,7 +156,7 @@ func (part *Partition) UnmountPartition() error { func UnmountDirectory(dir string) error { umountCmd := "umount %s" - err := RunCommand(fmt.Sprintf(umountCmd, dir)) + err := util.RunCommand(fmt.Sprintf(umountCmd, dir)) if err != nil { return fmt.Errorf("failed to run umount command: %s", err) } @@ -158,9 +165,9 @@ func UnmountDirectory(dir string) error { } func (target *Partition) RemovePartition() error { - disk, part := SeparateDiskPart(target.Path) + disk, part := util.SeparateDiskPart(target.Path) rmPartCmd := "parted -s %s rm %s" - err := RunCommand(fmt.Sprintf(rmPartCmd, disk, part)) + err := util.RunCommand(fmt.Sprintf(rmPartCmd, disk, part)) if err != nil { return fmt.Errorf("failed to remove partition: %s", err) } @@ -169,9 +176,9 @@ func (target *Partition) RemovePartition() error { } func (target *Partition) ResizePartition(newEnd int) error { - disk, part := SeparateDiskPart(target.Path) + disk, part := util.SeparateDiskPart(target.Path) resizePartCmd := "parted -s %s unit MiB resizepart %s %d" - err := RunCommand(fmt.Sprintf(resizePartCmd, disk, part, newEnd)) + err := util.RunCommand(fmt.Sprintf(resizePartCmd, disk, part, newEnd)) if err != nil { return fmt.Errorf("failed to resize partition: %s", err) } @@ -180,9 +187,9 @@ func (target *Partition) ResizePartition(newEnd int) error { } func (target *Partition) NamePartition(name string) error { - disk, part := SeparateDiskPart(target.Path) + disk, part := util.SeparateDiskPart(target.Path) namePartCmd := "parted -s %s name %s %s" - err := RunCommand(fmt.Sprintf(namePartCmd, disk, part, name)) + err := util.RunCommand(fmt.Sprintf(namePartCmd, disk, part, name)) if err != nil { return fmt.Errorf("failed to name partition: %s", err) } @@ -196,9 +203,9 @@ func (target *Partition) SetPartitionFlag(flag string, state bool) error { stateStr = "on" } - disk, part := SeparateDiskPart(target.Path) + disk, part := util.SeparateDiskPart(target.Path) setPartCmd := "parted -s %s set %s %s %s" - err := RunCommand(fmt.Sprintf(setPartCmd, disk, part, flag, stateStr)) + err := util.RunCommand(fmt.Sprintf(setPartCmd, disk, part, flag, stateStr)) if err != nil { return fmt.Errorf("failed to name partition: %s", err) } @@ -218,7 +225,7 @@ func (target *Partition) FillPath(basePath string) { func (target *Partition) GetUUID() (string, error) { lsblkCmd := "lsblk -d -n -o UUID %s" - output, err := OutputCommand(fmt.Sprintf(lsblkCmd, target.Path)) + output, err := util.OutputCommand(fmt.Sprintf(lsblkCmd, target.Path)) if err != nil { return "", fmt.Errorf("failed to get partition UUID: %s", err) } @@ -229,7 +236,7 @@ func (target *Partition) GetUUID() (string, error) { func GetUUIDByPath(path string) (string, error) { lsblkCmd := "lsblk -d -n -o UUID %s" - output, err := OutputCommand(fmt.Sprintf(lsblkCmd, path)) + output, err := util.OutputCommand(fmt.Sprintf(lsblkCmd, path)) if err != nil { return "", fmt.Errorf("failed to get partition UUID: %s", err) } @@ -240,7 +247,7 @@ func GetUUIDByPath(path string) (string, error) { func GetFilesystemByPath(path string) (string, error) { lsblkCmd := "lsblk -d -n -o FSTYPE %s" - output, err := OutputCommand(fmt.Sprintf(lsblkCmd, path)) + output, err := util.OutputCommand(fmt.Sprintf(lsblkCmd, path)) if err != nil { return "", fmt.Errorf("failed to get partition FSTYPE: %s", err) } @@ -250,11 +257,11 @@ func GetFilesystemByPath(path string) (string, error) { func (part *Partition) GetLUKSMapperPath() (string, error) { // Assert part is a LUKS partition - luks, err := IsLuks(part) + isLuks, err := luks.IsLuks(part) if err != nil { return "", err } - if !luks { + if !isLuks { return "", fmt.Errorf("cannot get mapper path for %s. Partition is not LUKS-formatted", part.Path) } @@ -286,7 +293,7 @@ func (part *Partition) SetLabel(label string) error { return fmt.Errorf("unsupported filesystem: %s", part.Filesystem) } - err := RunCommand(labelCmd) + err := util.RunCommand(labelCmd) if err != nil { return fmt.Errorf("failed to label partition %s: %s", part.Path, err) } @@ -294,6 +301,21 @@ func (part *Partition) SetLabel(label string) error { return nil } +// LUKSSetLabel labels a LUKS-formatted partition. Use this instead of SetLabel +// when setting up encrypted filesystems. +func LUKSSetLabel(part *Partition, name string) error { + innerPartition := Partition{} + + partUUID, err := part.GetUUID() + if err != nil { + return err + } + innerPartition.Path = fmt.Sprintf("/dev/mapper/luks-%s", partUUID) + innerPartition.Filesystem = part.Filesystem + + return innerPartition.SetLabel(name) +} + // WaitUntilAvailable polls the specified partition until it is available. // This is particularly useful to make sure a recently created or modified // partition is recognized by the system. diff --git a/core/lvm_test.go b/core/lvm/lvm_test.go similarity index 52% rename from core/lvm_test.go rename to core/lvm/lvm_test.go index 36c9ac5c..253ca68b 100644 --- a/core/lvm_test.go +++ b/core/lvm/lvm_test.go @@ -1,82 +1,45 @@ -package albius +package lvm import ( "fmt" "os" "os/exec" + "runtime" + "strings" "testing" - - "github.com/vanilla-os/albius/core/lvm" ) -var ( - device string - lvmpart string -) +var lvmpart string func TestMain(m *testing.M) { - // Setup testing device - // Create dummy image - cmd := exec.Command("dd", "if=/dev/zero", "of=test.img", "count=102400") - if err := cmd.Run(); err != nil { - panic("error while creating testing device image: " + err.Error()) - } - // Mount dummy image as loop device - cmd = exec.Command("losetup", "--find", "--show", "test.img") - cmd.Stderr = os.Stderr - ret, err := cmd.Output() - if err != nil { - panic("error while mounting loop device: " + err.Error()) - } - device = string(ret) - device = device[:len(device)-1] + _, filename, _, _ := runtime.Caller(0) + projRoot, _, _ := strings.Cut(filename, "core/") - // Create device label and add some partitions - albiusDevice, err := LocateDisk(device) - if err != nil { - panic("error finding loop device: " + err.Error()) - } - err = albiusDevice.LabelDisk(GPT) + device, err := exec.Command(projRoot+"utils/create_test_device.sh", "-o", "test.img", "-s", "51200", "-p", "\"\"", "ext4", "1", "25", "-p", "\"\"", "26", "100%").Output() if err != nil { - panic("error adding label to loop device: " + err.Error()) + panic(err) } - _, err = albiusDevice.NewPartition("", EXT4, 1, 25) - if err != nil { - panic("error creating partition A in loop device: " + err.Error()) - } - _, err = albiusDevice.NewPartition("", EXT4, 26, -1) - if err != nil { - panic("error creating partition B in loop device: " + err.Error()) - } - lvmpart = device + "p" + deviceStr := strings.TrimSpace(string(device)) + lvmpart = deviceStr + "p" - // Run tests status := m.Run() - // Remove testing device - cmd = exec.Command("losetup", "-d", device) - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - panic("error while detaching testing device: " + err.Error()) - } - err = os.Remove("test.img") + err = exec.Command(projRoot+"utils/remove_test_device.sh", deviceStr, "test.img").Run() if err != nil { - panic("error while removing testing device image: " + err.Error()) + panic(err) } - - // Cleanup os.Exit(status) } func TestPvcreate(t *testing.T) { - err := lvm.Pvcreate(lvmpart + "1") + err := Pvcreate(lvmpart + "1") if err != nil { t.Error(err) } } func TestPvs(t *testing.T) { - pvs, err := lvm.Pvs() + pvs, err := Pvs() fmt.Printf(" -> Returned: %v\n", pvs) if err != nil { t.Error(err) @@ -84,27 +47,27 @@ func TestPvs(t *testing.T) { } func TestPvResize(t *testing.T) { - pvs, err := lvm.Pvs() + pvs, err := Pvs() if err != nil { t.Error(err) } - err = lvm.Pvresize(&pvs[0]) + err = Pvresize(&pvs[0]) if err != nil { t.Error(err) } } func TestPvShrink(t *testing.T) { - pvs, err := lvm.Pvs() + pvs, err := Pvs() if err != nil { t.Error(err) } - err = lvm.Pvresize(&pvs[0], 10.0) + err = Pvresize(&pvs[0], 10.0) if err != nil { t.Error(err) } - pvs, err = lvm.Pvs() + pvs, err = Pvs() fmt.Printf(" -> New size: %v\n", pvs) if err != nil { t.Error(err) @@ -112,7 +75,7 @@ func TestPvShrink(t *testing.T) { } func TestPvRemoveStr(t *testing.T) { - err := lvm.Pvremove(lvmpart + "1") + err := Pvremove(lvmpart + "1") if err != nil { t.Error(err) } @@ -120,17 +83,17 @@ func TestPvRemoveStr(t *testing.T) { func TestPvRemoveStruct(t *testing.T) { // Recreate PV removed by previous test - err := lvm.Pvcreate(lvmpart + "1") + err := Pvcreate(lvmpart + "1") if err != nil { t.Error(err) } - pvs, err := lvm.Pvs(lvmpart + "1") + pvs, err := Pvs(lvmpart + "1") if err != nil { t.Error(err) } - err = lvm.Pvremove(&pvs[0]) + err = Pvremove(&pvs[0]) if err != nil { t.Error(err) } @@ -138,29 +101,29 @@ func TestPvRemoveStruct(t *testing.T) { func TestVgCreate(t *testing.T) { // Create two testing PVs - err := lvm.Pvcreate(lvmpart + "1") + err := Pvcreate(lvmpart + "1") if err != nil { t.Error(err) } - err = lvm.Pvcreate(lvmpart + "2") + err = Pvcreate(lvmpart + "2") if err != nil { t.Error(err) } // Pass one PV as struct and another as string - pvs, err := lvm.Pvs(lvmpart + "1") + pvs, err := Pvs(lvmpart + "1") if err != nil { t.Error(err) } - err = lvm.Vgcreate("MyTestingVG", &pvs[0], lvmpart+"2") + err = Vgcreate("MyTestingVG", &pvs[0], lvmpart+"2") if err != nil { t.Error(err) } } func TestVgs(t *testing.T) { - vgs, err := lvm.Vgs() + vgs, err := Vgs() fmt.Printf(" -> Returned: %v\n", vgs) if err != nil { t.Error(err) @@ -169,7 +132,7 @@ func TestVgs(t *testing.T) { func TestVgrename(t *testing.T) { // Retrieve Vg - vgs, err := lvm.Vgs() + vgs, err := Vgs() if err != nil { t.Error(err) } @@ -179,7 +142,7 @@ func TestVgrename(t *testing.T) { t.Error(err) } - vgs, err = lvm.Vgs("MyTestingVG1") + vgs, err = Vgs("MyTestingVG1") fmt.Printf(" -> Returned: %v\n", vgs) if err != nil { t.Error(err) @@ -188,7 +151,7 @@ func TestVgrename(t *testing.T) { func TestVgReduce(t *testing.T) { // Retrieve Vg - vgs, err := lvm.Vgs() + vgs, err := Vgs() if err != nil { t.Error(err) } @@ -199,7 +162,7 @@ func TestVgReduce(t *testing.T) { } // Retrieve Vg - vgs, err = lvm.Vgs() + vgs, err = Vgs() fmt.Printf(" -> Returned: %v\n", vgs) if err != nil { t.Error(err) @@ -208,7 +171,7 @@ func TestVgReduce(t *testing.T) { func TestVgExtend(t *testing.T) { // Retrieve Vg - vgs, err := lvm.Vgs() + vgs, err := Vgs() if err != nil { t.Error(err) } @@ -219,7 +182,7 @@ func TestVgExtend(t *testing.T) { } // Retrieve Vg - vgs, err = lvm.Vgs() + vgs, err = Vgs() fmt.Printf(" -> Returned: %v\n", vgs) if err != nil { t.Error(err) @@ -227,14 +190,14 @@ func TestVgExtend(t *testing.T) { } func TestLvCreate(t *testing.T) { - err := lvm.Lvcreate("MyLv0", "MyTestingVG1", lvm.LV_TYPE_LINEAR, 30) + err := Lvcreate("MyLv0", "MyTestingVG1", LV_TYPE_LINEAR, 30) if err != nil { t.Error(err) } } func TestLvs(t *testing.T) { - lvs, err := lvm.Lvs() + lvs, err := Lvs() fmt.Printf(" -> Returned: %v\n", lvs) if err != nil { t.Error(err) @@ -243,7 +206,7 @@ func TestLvs(t *testing.T) { func TestLvrename(t *testing.T) { // Retrieve Lv - lv, err := lvm.FindLv("MyTestingVG1", "MyLv0") + lv, err := FindLv("MyTestingVG1", "MyLv0") if err != nil { t.Error(err) } @@ -253,7 +216,7 @@ func TestLvrename(t *testing.T) { t.Error(err) } - lv, err = lvm.FindLv("MyTestingVG1", "MyLv1") + lv, err = FindLv("MyTestingVG1", "MyLv1") fmt.Printf(" -> Returned: %v\n", lv) if err != nil { t.Error(err) @@ -262,7 +225,7 @@ func TestLvrename(t *testing.T) { func TestLvRemove(t *testing.T) { // Retrieve Lv - lv, err := lvm.FindLv("MyTestingVG1", "MyLv1") + lv, err := FindLv("MyTestingVG1", "MyLv1") if err != nil { t.Error(err) } @@ -275,7 +238,7 @@ func TestLvRemove(t *testing.T) { func TestVgRemove(t *testing.T) { // Retrieve Vg - vg, err := lvm.FindVg("MyTestingVG1") + vg, err := FindVg("MyTestingVG1") if err != nil { t.Error(err) } diff --git a/core/recipe.go b/core/recipe.go index b5695228..50f3af50 100644 --- a/core/recipe.go +++ b/core/recipe.go @@ -9,7 +9,11 @@ import ( "strconv" "strings" + "github.com/vanilla-os/albius/core/disk" + luks "github.com/vanilla-os/albius/core/disk/luks" "github.com/vanilla-os/albius/core/lvm" + "github.com/vanilla-os/albius/core/system" + "github.com/vanilla-os/albius/core/util" ) const ( @@ -69,7 +73,7 @@ func ReadRecipe(path string) (*Recipe, error) { } func runSetupOperation(diskLabel, operation string, args []interface{}) error { - disk, err := LocateDisk(diskLabel) + target, err := disk.LocateDisk(diskLabel) if err != nil { return err } @@ -84,8 +88,8 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { * - *LabelType* (`string`): The partitioning scheme. Either `mbr` or `gpt`. */ case "label": - label := DiskLabel(args[0].(string)) - err = disk.LabelDisk(label) + label := disk.DiskLabel(args[0].(string)) + err = target.LabelDisk(label) if err != nil { return err } @@ -108,16 +112,16 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { */ case "mkpart": name := args[0].(string) - fsType := PartitionFs(args[1].(string)) + fsType := disk.PartitionFs(args[1].(string)) start := int(args[2].(float64)) end := int(args[3].(float64)) if len(args) > 4 && strings.HasPrefix(string(fsType), "luks-") { // Encrypted partition luksPassword := args[4].(string) - part, err := disk.NewPartition(name, "", start, end) + part, err := target.NewPartition(name, "", start, end) if err != nil { return err } - err = LuksFormat(part, luksPassword) + err = luks.LuksFormat(part, luksPassword) if err != nil { return err } @@ -128,16 +132,16 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - err = LuksOpen(part, fmt.Sprintf("luks-%s", uuid), luksPassword) + err = luks.LuksOpen(part, fmt.Sprintf("luks-%s", uuid), luksPassword) if err != nil { return err } - part.Filesystem = PartitionFs(strings.TrimPrefix(string(fsType), "luks-")) - err = LUKSMakeFs(part) + part.Filesystem = disk.PartitionFs(strings.TrimPrefix(string(fsType), "luks-")) + err = disk.LUKSMakeFs(*part) if err != nil { return err } - err = LUKSSetLabel(part, name) + err = disk.LUKSSetLabel(part, name) if err != nil { return err } @@ -145,7 +149,7 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if fsType == "none" { fsType = "" } - _, err := disk.NewPartition(name, fsType, start, end) + _, err := target.NewPartition(name, fsType, start, end) if err != nil { return err } @@ -162,7 +166,7 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - err = disk.GetPartition(partNum).RemovePartition() + err = target.GetPartition(partNum).RemovePartition() if err != nil { return err } @@ -183,7 +187,7 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - err = disk.GetPartition(partNum).ResizePartition(partNewSize) + err = target.GetPartition(partNum).ResizePartition(partNewSize) if err != nil { return err } @@ -204,11 +208,11 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - err = disk.GetPartition(partNum).SetLabel(partNewName) + err = target.GetPartition(partNum).SetLabel(partNewName) if err != nil { return err } - err = disk.GetPartition(partNum).NamePartition(partNewName) + err = target.GetPartition(partNum).NamePartition(partNewName) if err != nil { return err } @@ -227,7 +231,7 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - err = disk.GetPartition(partNum).SetPartitionFlag(args[1].(string), args[2].(bool)) + err = target.GetPartition(partNum).SetPartitionFlag(args[1].(string), args[2].(bool)) if err != nil { return err } @@ -246,14 +250,14 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { return err } filesystem := args[1].(string) - disk.GetPartition(partNum).Filesystem = PartitionFs(filesystem) - err = MakeFs(disk.GetPartition(partNum)) + target.GetPartition(partNum).Filesystem = disk.PartitionFs(filesystem) + err = disk.MakeFs(target.GetPartition(partNum)) if err != nil { return err } if len(args) == 3 { label := args[2].(string) - err := disk.GetPartition(partNum).SetLabel(label) + err := target.GetPartition(partNum).SetLabel(label) if err != nil { return err } @@ -275,9 +279,9 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { } filesystem := args[1].(string) password := args[2].(string) - part := disk.GetPartition(partNum) - part.Filesystem = PartitionFs(filesystem) - err = LuksFormat(part, password) + part := target.GetPartition(partNum) + part.Filesystem = disk.PartitionFs(filesystem) + err = luks.LuksFormat(part, password) if err != nil { return err } @@ -288,17 +292,17 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - err = LuksOpen(part, fmt.Sprintf("luks-%s", uuid), password) + err = luks.LuksOpen(part, fmt.Sprintf("luks-%s", uuid), password) if err != nil { return err } - err = LUKSMakeFs(part) + err = disk.LUKSMakeFs(*part) if err != nil { return err } if len(args) == 4 { label := args[3].(string) - err := LUKSSetLabel(part, label) + err := disk.LUKSSetLabel(part, label) if err != nil { return err } @@ -312,7 +316,7 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { */ case "pvcreate": part := args[0].(string) - dummyPart := Partition{Path: part} + dummyPart := disk.Partition{Path: part} dummyPart.WaitUntilAvailable() err := lvm.Pvcreate(part) if err != nil { @@ -364,7 +368,7 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { pvs := []string{} if len(args) > 1 { for _, pv := range args[1].([]interface{}) { - dummyPart := Partition{Path: pv.(string)} + dummyPart := disk.Partition{Path: pv.(string)} dummyPart.WaitUntilAvailable() pvs = append(pvs, pv.(string)) } @@ -548,11 +552,11 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - dummyPart := Partition{ + dummyPart := disk.Partition{ Path: "/dev/" + lv.VgName + "/" + lv.Name, - Filesystem: PartitionFs(filesystem), + Filesystem: disk.PartitionFs(filesystem), } - err = MakeFs(&dummyPart) + err = disk.MakeFs(&dummyPart) if err != nil { return err } @@ -581,11 +585,11 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - dummyPart := Partition{ + dummyPart := disk.Partition{ Path: "/dev/" + lv.VgName + "/" + lv.Name, - Filesystem: PartitionFs(filesystem), + Filesystem: disk.PartitionFs(filesystem), } - err = LuksFormat(&dummyPart, password) + err = luks.LuksFormat(&dummyPart, password) if err != nil { return err } @@ -596,17 +600,17 @@ func runSetupOperation(diskLabel, operation string, args []interface{}) error { if err != nil { return err } - err = LuksOpen(&dummyPart, fmt.Sprintf("luks-%s", uuid), password) + err = luks.LuksOpen(&dummyPart, fmt.Sprintf("luks-%s", uuid), password) if err != nil { return err } - err = LUKSMakeFs(&dummyPart) + err = disk.LUKSMakeFs(dummyPart) if err != nil { return err } if len(args) == 4 { label := args[3].(string) - err := LUKSSetLabel(&dummyPart, label) + err := disk.LUKSSetLabel(&dummyPart, label) if err != nil { return err } @@ -660,9 +664,9 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) var err error if len(args) == 4 { password := args[3].(string) - err = AddUser(targetRoot, username, fullname, groups, password) + err = system.AddUser(targetRoot, username, fullname, groups, password) } else { - err = AddUser(targetRoot, username, fullname, groups) + err = system.AddUser(targetRoot, username, fullname, groups) } if err != nil { return err @@ -676,7 +680,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) */ case "timezone": tz := args[0].(string) - err := SetTimezone(targetRoot, tz) + err := system.SetTimezone(targetRoot, tz) if err != nil { return err } @@ -692,9 +696,9 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) command := arg.(string) var err error if chroot { - err = RunInChroot(targetRoot, command) + err = util.RunInChroot(targetRoot, command) } else { - err = RunCommand(command) + err = util.RunCommand(command) } if err != nil { return err @@ -711,7 +715,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) case "pkgremove": pkgRemovePath := args[0].(string) removeCmd := args[1].(string) - err := RemovePackages(targetRoot, pkgRemovePath, removeCmd) + err := system.RemovePackages(targetRoot, pkgRemovePath, removeCmd) if err != nil { return err } @@ -724,7 +728,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) */ case "hostname": newHostname := args[0].(string) - err := ChangeHostname(targetRoot, newHostname) + err := system.ChangeHostname(targetRoot, newHostname) if err != nil { return err } @@ -737,7 +741,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) */ case "locale": localeCode := args[0].(string) - err := SetLocale(targetRoot, localeCode) + err := system.SetLocale(targetRoot, localeCode) if err != nil { return err } @@ -750,7 +754,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) */ case "swapon": partition := args[0].(string) - err := Swapon(targetRoot, partition) + err := system.Swapon(targetRoot, partition) if err != nil { return err } @@ -767,7 +771,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) layout := args[0].(string) model := args[1].(string) variant := args[2].(string) - err := SetKeyboardLayout(targetRoot, layout, model, variant) + err := system.SetKeyboardLayout(targetRoot, layout, model, variant) if err != nil { return err } @@ -789,16 +793,16 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) if len(args) > 3 { efiDevice = args[3].(string) } - var grubTarget FirmwareType + var grubTarget system.FirmwareType switch target { case "bios": - grubTarget = BIOS + grubTarget = system.BIOS case "efi": - grubTarget = EFI + grubTarget = system.EFI default: return fmt.Errorf("failed to execute operation: %s: Unrecognized firmware type: '%s')", operation, target) } - err := RunGrubInstall(targetRoot, bootDirectory, installDevice, grubTarget, efiDevice) + err := system.RunGrubInstall(targetRoot, bootDirectory, installDevice, grubTarget, efiDevice) if err != nil { return err } @@ -810,7 +814,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) * - *KV(s)* (`...string`): The `KEY=value` pair(s) to add to the GRUB default file. */ case "grub-default-config": - currentConfig, err := GetGrubConfig(targetRoot) + currentConfig, err := system.GetGrubConfig(targetRoot) if err != nil { return err } @@ -818,7 +822,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) kv := strings.SplitN(arg.(string), "=", 2) currentConfig[kv[0]] = kv[1] } - err = WriteGrubConfig(targetRoot, currentConfig) + err = system.WriteGrubConfig(targetRoot, currentConfig) if err != nil { return err } @@ -832,7 +836,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) case "grub-add-script": for _, arg := range args { scriptPath := arg.(string) - err := AddGrubScript(targetRoot, scriptPath) + err := system.AddGrubScript(targetRoot, scriptPath) if err != nil { return err } @@ -847,7 +851,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) case "grub-remove-script": for _, arg := range args { scriptName := arg.(string) - err := RemoveGrubScript(targetRoot, scriptName) + err := system.RemoveGrubScript(targetRoot, scriptName) if err != nil { return err } @@ -861,7 +865,7 @@ func runPostInstallOperation(chroot bool, operation string, args []interface{}) */ case "grub-mkconfig": outputPath := args[0].(string) - err := RunGrubMkconfig(targetRoot, outputPath) + err := system.RunGrubMkconfig(targetRoot, outputPath) if err != nil { return err } @@ -885,7 +889,7 @@ func (recipe *Recipe) RunPostInstall() error { } func (recipe *Recipe) SetupMountpoints() error { - diskCache := map[string]*Disk{} + diskCache := map[string]*disk.Disk{} rootAMounted := false /* We need to mount the partitions in order to prevent one mountpoint @@ -921,7 +925,7 @@ func (recipe *Recipe) SetupMountpoints() error { // LVM partition if lvmExpr.MatchString(mnt.Partition) { - lvmPartition := Partition{ + lvmPartition := disk.Partition{ Number: -1, Path: mnt.Partition, } @@ -933,23 +937,23 @@ func (recipe *Recipe) SetupMountpoints() error { } // Regular partition - diskName, partName := SeparateDiskPart(mnt.Partition) + diskName, partName := util.SeparateDiskPart(mnt.Partition) part, err := strconv.Atoi(partName) if err != nil { return err } - disk, ok := diskCache[diskName] + target, ok := diskCache[diskName] if !ok { - diskPtr, err := LocateDisk(diskName) + diskPtr, err := disk.LocateDisk(diskName) if err != nil { return err } diskCache[diskName] = diskPtr - disk = diskCache[diskName] + target = diskCache[diskName] } - err = disk.GetPartition(part).Mount(baseRoot + mnt.Target) + err = target.GetPartition(part).Mount(baseRoot + mnt.Target) if err != nil { return err } @@ -963,27 +967,29 @@ func (recipe *Recipe) setupFstabEntries() ([][]string, error) { for _, mnt := range recipe.Mountpoints { entry := []string{} - uuid, err := GetUUIDByPath(mnt.Partition) + uuid, err := disk.GetUUIDByPath(mnt.Partition) if err != nil { return [][]string{}, err } // Partition fstype - fstype, err := GetFilesystemByPath(mnt.Partition) + fstype, err := disk.GetFilesystemByPath(mnt.Partition) if err != nil { return [][]string{}, err } // If partition is LUKS-encrypted, use /dev/mapper/xxxx, otherwise // use the partition's UUID + dummyPart := disk.Partition{Path: mnt.Partition} + var fsName string - luks, err := IsPathLuks(mnt.Partition) + isLuks, err := luks.IsLuks(&dummyPart) if err != nil { return [][]string{}, err } - if luks { + if isLuks { fsName = fmt.Sprintf("/dev/mapper/luks-%s", uuid) - encryptedFstype, err := GetLUKSFilesystemByPath(mnt.Partition) + encryptedFstype, err := luks.GetLUKSFilesystemByPath(mnt.Partition) if err != nil { return [][]string{}, err } @@ -1013,17 +1019,18 @@ func (recipe *Recipe) setupFstabEntries() ([][]string, error) { func (recipe *Recipe) setupCrypttabEntries() ([][]string, error) { crypttabEntries := [][]string{} for _, mnt := range recipe.Mountpoints { - luks, err := IsPathLuks(mnt.Partition) + dummyPart := disk.Partition{Path: mnt.Partition} + isLuks, err := luks.IsLuks(&dummyPart) if err != nil { return [][]string{}, err } - if !luks { + if !isLuks { continue } entry := []string{} - partUUID, err := GetUUIDByPath(mnt.Partition) + partUUID, err := disk.GetUUIDByPath(mnt.Partition) if err != nil { return [][]string{}, err } @@ -1044,9 +1051,9 @@ func (recipe *Recipe) Install() error { var err error switch recipe.Installation.Method { case UNSQUASHFS: - err = Unsquashfs(recipe.Installation.Source, RootA, true) + err = disk.Unsquashfs(recipe.Installation.Source, RootA, true) case OCI: - err = OCISetup(recipe.Installation.Source, filepath.Join(RootA, "var"), RootA, false) + err = disk.OCISetup(recipe.Installation.Source, filepath.Join(RootA, "var"), RootA, false) default: return fmt.Errorf("unsupported installation method '%s'", recipe.Installation.Method) } @@ -1060,7 +1067,7 @@ func (recipe *Recipe) Install() error { return fmt.Errorf("failed to generate crypttab entries: %s", err) } if len(crypttabEntries) > 0 { - err = GenCrypttab(RootA, crypttabEntries) + err = luks.GenCrypttab(RootA, crypttabEntries) if err != nil { return fmt.Errorf("failed to generate crypttab: %s", err) } @@ -1071,28 +1078,28 @@ func (recipe *Recipe) Install() error { if err != nil { return fmt.Errorf("failed to generate fstab entries: %s", err) } - err = GenFstab(RootA, fstabEntries) + err = disk.GenFstab(RootA, fstabEntries) if err != nil { return fmt.Errorf("failed to generate fstab: %s", err) } // Initramfs pre-scripts for _, preCmd := range recipe.Installation.InitramfsPre { - err := RunInChroot(RootA, preCmd) + err := util.RunInChroot(RootA, preCmd) if err != nil { return fmt.Errorf("initramfs pre-script '%s' failed: %s", preCmd, err) } } // Update Initramfs - err = UpdateInitramfs(RootA) + err = disk.UpdateInitramfs(RootA) if err != nil { return fmt.Errorf("failed to update initramfs: %s", err) } // Initramfs post-scripts for _, postCmd := range recipe.Installation.InitramfsPost { - err := RunInChroot(RootA, postCmd) + err := util.RunInChroot(RootA, postCmd) if err != nil { return fmt.Errorf("initramfs post-script '%s' failed: %s", postCmd, err) } diff --git a/core/grub.go b/core/system/grub.go similarity index 83% rename from core/grub.go rename to core/system/grub.go index bd6385cf..550a40c8 100644 --- a/core/grub.go +++ b/core/system/grub.go @@ -1,4 +1,4 @@ -package albius +package system import ( "errors" @@ -6,6 +6,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/vanilla-os/albius/core/util" ) type GrubConfig map[string]string @@ -51,7 +53,7 @@ func WriteGrubConfig(targetRoot string, config GrubConfig) error { } targetRootGrubFile := filepath.Join(targetRoot, "/etc/default/grub") - err := os.WriteFile(targetRootGrubFile, fileContents, 0644) + err := os.WriteFile(targetRootGrubFile, fileContents, 0o644) if err != nil { return fmt.Errorf("failed to write GRUB config file: %s", err) } @@ -71,7 +73,7 @@ func AddGrubScript(targetRoot, scriptPath string) error { } targetRootPath := filepath.Join(targetRoot, "/etc/grub.d", filepath.Base(scriptPath)) - err = os.WriteFile(targetRootPath, contents, 0755) // Grub expects script to be executable + err = os.WriteFile(targetRootPath, contents, 0o755) // Grub expects script to be executable if err != nil { return fmt.Errorf("failed to writing GRUB script to %s: %s", targetRootPath, err) } @@ -101,7 +103,7 @@ func RunGrubInstall(targetRoot, bootDirectory, diskPath string, target FirmwareT requiredBinds := []string{"/dev", "/dev/pts", "/proc", "/sys", "/run"} for _, bind := range requiredBinds { targetBind := filepath.Join(targetRoot, bind) - err := RunCommand(fmt.Sprintf("mount --bind %s %s", bind, targetBind)) + err := util.RunCommand(fmt.Sprintf("mount --bind %s %s", bind, targetBind)) if err != nil { return fmt.Errorf("failed to mount %s to %s: %s", bind, targetRoot, err) } @@ -112,9 +114,9 @@ func RunGrubInstall(targetRoot, bootDirectory, diskPath string, target FirmwareT var err error if targetRoot != "" { - err = RunInChroot(targetRoot, fmt.Sprintf(grubInstallCmd, bootDirectory, target, diskPath)) + err = util.RunInChroot(targetRoot, fmt.Sprintf(grubInstallCmd, bootDirectory, target, diskPath)) } else { - err = RunCommand(fmt.Sprintf(grubInstallCmd, bootDirectory, target, diskPath)) + err = util.RunCommand(fmt.Sprintf(grubInstallCmd, bootDirectory, target, diskPath)) } if err != nil { return fmt.Errorf("failed to run grub-install: %s", err) @@ -131,8 +133,8 @@ func RunGrubInstall(targetRoot, bootDirectory, diskPath string, target FirmwareT if len(efiDevice) == 0 || efiDevice[0] == "" { return errors.New("EFI device was not specified") } - diskName, part := SeparateDiskPart(efiDevice[0]) - err = RunCommand(fmt.Sprintf(efibootmgrCmd, diskName, part)) + diskName, part := util.SeparateDiskPart(efiDevice[0]) + err = util.RunCommand(fmt.Sprintf(efibootmgrCmd, diskName, part)) if err != nil { return fmt.Errorf("failed to run grub-install: %s", err) } @@ -146,9 +148,9 @@ func RunGrubMkconfig(targetRoot, output string) error { var err error if targetRoot != "" { - err = RunInChroot(targetRoot, fmt.Sprintf(grubMkconfigCmd, output)) + err = util.RunInChroot(targetRoot, fmt.Sprintf(grubMkconfigCmd, output)) } else { - err = RunCommand(fmt.Sprintf(grubMkconfigCmd, output)) + err = util.RunCommand(fmt.Sprintf(grubMkconfigCmd, output)) } if err != nil { return err diff --git a/core/post_install.go b/core/system/post_install.go similarity index 69% rename from core/post_install.go rename to core/system/post_install.go index a071da9c..189428b5 100644 --- a/core/post_install.go +++ b/core/system/post_install.go @@ -1,25 +1,27 @@ -package albius +package system import ( "fmt" "os" "regexp" "strings" + + "github.com/vanilla-os/albius/core/util" ) func SetTimezone(targetRoot, tz string) error { tzPath := targetRoot + "/etc/timezone" - err := os.WriteFile(tzPath, []byte(tz), 0644) + err := os.WriteFile(tzPath, []byte(tz), 0o644) if err != nil { return fmt.Errorf("failed to set timezone: %s", err) } linkZoneinfoCmd := "ln -sf /usr/share/zoneinfo/%s /etc/localtime" if targetRoot != "" { - err = RunInChroot(targetRoot, fmt.Sprintf(linkZoneinfoCmd, tz)) + err = util.RunInChroot(targetRoot, fmt.Sprintf(linkZoneinfoCmd, tz)) } else { - err = RunCommand(fmt.Sprintf(linkZoneinfoCmd, tz)) + err = util.RunCommand(fmt.Sprintf(linkZoneinfoCmd, tz)) } if err != nil { return fmt.Errorf("failed to set timezone: %s", err) @@ -33,9 +35,9 @@ func AddUser(targetRoot, username, fullname string, groups []string, password .. var err error if targetRoot != "" { - err = RunInChroot(targetRoot, fmt.Sprintf(adduserCmd, username, fullname, username)) + err = util.RunInChroot(targetRoot, fmt.Sprintf(adduserCmd, username, fullname, username)) } else { - err = RunCommand(fmt.Sprintf(adduserCmd, username, fullname, username)) + err = util.RunCommand(fmt.Sprintf(adduserCmd, username, fullname, username)) } if err != nil { return fmt.Errorf("failed to create user: %s", err) @@ -44,9 +46,9 @@ func AddUser(targetRoot, username, fullname string, groups []string, password .. if len(password) == 1 { passwdCmd := "echo \"%s:%s\" | chpasswd" if targetRoot != "" { - err = RunInChroot(targetRoot, fmt.Sprintf(passwdCmd, username, password[0])) + err = util.RunInChroot(targetRoot, fmt.Sprintf(passwdCmd, username, password[0])) } else { - err = RunCommand(fmt.Sprintf(passwdCmd, username, password[0])) + err = util.RunCommand(fmt.Sprintf(passwdCmd, username, password[0])) } if err != nil { return fmt.Errorf("failed to set password: %s", err) @@ -61,9 +63,9 @@ func AddUser(targetRoot, username, fullname string, groups []string, password .. addGroupCmd := "usermod -a -G %s %s" groupList := strings.Join(groups, ",") if targetRoot != "" { - err = RunInChroot(targetRoot, fmt.Sprintf(addGroupCmd, groupList, username)) + err = util.RunInChroot(targetRoot, fmt.Sprintf(addGroupCmd, groupList, username)) } else { - err = RunCommand(fmt.Sprintf(addGroupCmd, groupList, username)) + err = util.RunCommand(fmt.Sprintf(addGroupCmd, groupList, username)) } if err != nil { return fmt.Errorf("failed to add groups to user: %s", err) @@ -81,9 +83,9 @@ func RemovePackages(targetRoot, pkgRemovePath, removeCmd string) error { pkgList := strings.ReplaceAll(string(pkgRemoveContent), "\n", " ") completeCmd := fmt.Sprintf("%s %s", removeCmd, pkgList) if targetRoot != "" { - err = RunInChroot(targetRoot, completeCmd) + err = util.RunInChroot(targetRoot, completeCmd) } else { - err = RunCommand(completeCmd) + err = util.RunCommand(completeCmd) } if err != nil { return fmt.Errorf("failed to remove packages: %s", err) @@ -94,7 +96,7 @@ func RemovePackages(targetRoot, pkgRemovePath, removeCmd string) error { func ChangeHostname(targetRoot, hostname string) error { hostnamePath := targetRoot + "/etc/hostname" - err := os.WriteFile(hostnamePath, []byte(hostname+"\n"), 0644) + err := os.WriteFile(hostnamePath, []byte(hostname+"\n"), 0o644) if err != nil { return fmt.Errorf("failed to change hostname: %s", err) } @@ -104,7 +106,7 @@ func ChangeHostname(targetRoot, hostname string) error { 127.0.1.1 %s.localdomain %s ` hostsPath := targetRoot + "/etc/hosts" - err = os.WriteFile(hostsPath, []byte(fmt.Sprintf(hostsContents, hostname, hostname)), 0644) + err = os.WriteFile(hostsPath, []byte(fmt.Sprintf(hostsContents, hostname, hostname)), 0o644) if err != nil { return fmt.Errorf("failed to change hosts file: %s", err) } @@ -113,20 +115,20 @@ func ChangeHostname(targetRoot, hostname string) error { } func SetLocale(targetRoot, locale string) error { - err := RunCommand(fmt.Sprintf("grep %s %s/usr/share/i18n/SUPPORTED", locale, targetRoot)) + err := util.RunCommand(fmt.Sprintf("grep %s %s/usr/share/i18n/SUPPORTED", locale, targetRoot)) if err != nil { return fmt.Errorf("locale %s is invalid", locale) } - err = RunCommand(fmt.Sprintf("sed -i 's/^\\# \\(%s\\)/\\1/' %s/etc/locale.gen", regexp.QuoteMeta(locale), targetRoot)) + err = util.RunCommand(fmt.Sprintf("sed -i 's/^\\# \\(%s\\)/\\1/' %s/etc/locale.gen", regexp.QuoteMeta(locale), targetRoot)) if err != nil { return fmt.Errorf("failed to set locale: %s", err) } if targetRoot != "" { - err = RunInChroot(targetRoot, "locale-gen") + err = util.RunInChroot(targetRoot, "locale-gen") } else { - err = RunCommand("locale-gen") + err = util.RunCommand("locale-gen") } if err != nil { return fmt.Errorf("failed to set locale: %s", err) @@ -144,7 +146,7 @@ LC_MEASUREMENT=__lang__ LC_IDENTIFICATION=__lang__ ` localePath := targetRoot + "/etc/default/locale" - err = os.WriteFile(localePath, []byte(strings.ReplaceAll(localeContents, "__lang__", locale)), 0644) + err = os.WriteFile(localePath, []byte(strings.ReplaceAll(localeContents, "__lang__", locale)), 0o644) if err != nil { return fmt.Errorf("failed to set locale: %s", err) } @@ -155,9 +157,9 @@ LC_IDENTIFICATION=__lang__ func Swapon(targetRoot, swapPart string) error { swaponCmd := "swapon %s" if targetRoot != "" { - return RunInChroot(targetRoot, fmt.Sprintf(swaponCmd, swapPart)) + return util.RunInChroot(targetRoot, fmt.Sprintf(swaponCmd, swapPart)) } else { - return RunCommand(fmt.Sprintf(swaponCmd, swapPart)) + return util.RunCommand(fmt.Sprintf(swaponCmd, swapPart)) } } @@ -170,15 +172,15 @@ XKBVARIANT="%s" BACKSPACE="guess" ` keyboardPath := targetRoot + "/etc/default/keyboard" - err := os.WriteFile(keyboardPath, []byte(fmt.Sprintf(keyboardContents, kbModel, kbLayout, kbVariant)), 0644) + err := os.WriteFile(keyboardPath, []byte(fmt.Sprintf(keyboardContents, kbModel, kbLayout, kbVariant)), 0o644) if err != nil { return fmt.Errorf("failed to set keyboard layout: %s", err) } if targetRoot != "" { - err = RunInChroot(targetRoot, "setupcon --save-only") + err = util.RunInChroot(targetRoot, "setupcon --save-only") } else { - err = RunCommand("setupcon --save-only") + err = util.RunCommand("setupcon --save-only") } if err != nil { return fmt.Errorf("failed to set keyboard layout: %s", err) diff --git a/core/util.go b/core/util/util.go similarity index 58% rename from core/util.go rename to core/util/util.go index 308d919e..40ccd4ec 100644 --- a/core/util.go +++ b/core/util/util.go @@ -1,12 +1,10 @@ -package albius +package util import ( "bytes" "errors" - "fmt" "os" "os/exec" - "reflect" "regexp" "strings" ) @@ -31,7 +29,6 @@ func RunCommand(command string) error { func OutputCommand(command string) (string, error) { cmd := exec.Command("sh", "-c", command) out, err := cmd.Output() - if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { return strings.TrimSpace(string(out)), errors.New(string(exitErr.Stderr)) @@ -47,7 +44,6 @@ func RunInChroot(root, command string) error { cmd := exec.Command("chroot", root, "sh", "-c", command) cmd.Stdout = os.Stdout err := cmd.Run() - if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { return errors.New(string(exitErr.Stderr)) @@ -58,38 +54,6 @@ func RunInChroot(root, command string) error { return nil } -func setField(obj interface{}, name string, value interface{}) error { - structValue := reflect.ValueOf(obj).Elem() - structFieldValue := structValue.FieldByName(name) - - if !structFieldValue.IsValid() { - return fmt.Errorf("no such field: %s in obj", name) - } - - if !structFieldValue.CanSet() { - return fmt.Errorf("cannot set %s field value", name) - } - - structFieldType := structFieldValue.Type() - val := reflect.ValueOf(value) - var convertedVal reflect.Value - if structFieldType != val.Type() { - // Type conversions - if structFieldType.Kind() == reflect.Int && val.Type().Kind() == reflect.Float64 { - convertedVal = reflect.ValueOf(int(val.Interface().(float64))) - } else if structFieldType.Name() == "DiskLabel" && val.Type().Kind() == reflect.String { - convertedVal = reflect.ValueOf(DiskLabel(val.Interface().(string))) - } else { - return fmt.Errorf("provided value type for %s did not match obj field type. Expected %v, got %v", name, structFieldType, val.Type()) - } - } else { - convertedVal = val - } - - structFieldValue.Set(convertedVal) - return nil -} - // SeparateDiskPart receives a path (e.g. /dev/sda1) and separates it into // the device root and partition number func SeparateDiskPart(path string) (string, string) { diff --git a/utils/create_test_device.sh b/utils/create_test_device.sh new file mode 100755 index 00000000..a1a63ffc --- /dev/null +++ b/utils/create_test_device.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +declare -a partitions + +while :; do + case "$1" in + -o) + output=$2 + shift + shift + ;; + -s) + size=$2 + shift + shift + ;; + -p) + partitions+=("$2;$3;$4;$5") + shift + shift + shift + shift + shift + ;; + *) + break + ;; + esac +done + +dd if=/dev/zero of="$output" bs=1024 count="$size" > /dev/null 2>&1 +device=$(losetup --find --show "$output") + +if [ ${#partitions[@]} != 0 ]; then + parted -s "$device" mklabel gpt +fi + +for partition in "${partitions[@]}"; do + IFS=";" read -r -a args <<< "${partition}" + # name fs start end + parted -s "$device" unit MiB mkpart "${args[0]}" "${args[1]}" "${args[2]}" "${args[3]}" +done + +echo "$device" diff --git a/utils/create_test_env.sh b/utils/create_test_env.sh index 8f9409df..f684fbfc 100755 --- a/utils/create_test_env.sh +++ b/utils/create_test_env.sh @@ -2,4 +2,4 @@ set -e -distrobox-create -r -I -Y -ap "golang libbtrfs-dev libdevmapper-dev libgpgme-dev build-essential pkg-config lvm2 parted udev" -i ghcr.io/vanilla-os/dev:main albius_test +distrobox-create -r -I -Y -ap "golang libbtrfs-dev libdevmapper-dev libgpgme-dev build-essential pkg-config lvm2 cryptsetup parted udev" -i ghcr.io/vanilla-os/dev:main albius_test diff --git a/utils/remove_test_device.sh b/utils/remove_test_device.sh new file mode 100755 index 00000000..5633514d --- /dev/null +++ b/utils/remove_test_device.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +losetup -d "$1" +rm "$2"