Add Parallel Mounting options

This commit is contained in:
GRMrGecko 2023-01-05 00:38:11 -06:00
parent 356940fa32
commit 03d5d70e3c
2 changed files with 120 additions and 97 deletions

249
main.go
View File

@ -8,6 +8,7 @@ import (
"os/exec" "os/exec"
"regexp" "regexp"
"strings" "strings"
"sync"
"syscall" "syscall"
"golang.org/x/term" "golang.org/x/term"
@ -21,6 +22,7 @@ type RaidMount struct {
Flags string Flags string
CryptName string CryptName string
Encrypted bool Encrypted bool
Parallel bool
} }
// App: Global application structure. // App: Global application structure.
@ -52,123 +54,16 @@ func isMounted(target string) bool {
return false return false
} }
// main: Starting application function. func mountDrive(mount RaidMount, encryptionPassword string, wg *sync.WaitGroup) {
func main() { // Make sure we tell the wait group that we're done when the mount is done.
// Only allow running as root. defer wg.Done()
if os.Getuid() != 0 {
fmt.Println("You must call this program as root.")
os.Exit(1)
}
// Read configurations.
app = new(App)
app.flags = new(Flags)
app.flags.Init()
app.ReadConfig()
// The raid table is how we know what to mount, and it must exist to start.
if _, err := os.Stat(app.config.RaidTablePath); err != nil {
log.Fatalln("Raid table does not exist.")
}
var raidMounts []RaidMount
hasEncryptedDrives := false // If there are encrypted drives, we require a password to decrypt them.
// Open the raid mountpoint table file.
raidTab, err := os.Open(app.config.RaidTablePath)
if err != nil {
log.Fatalln("Unable to open raid table:", err)
}
// Prepare scanners and regular expressions for parsing raid table.
scanner := bufio.NewScanner(raidTab)
comment := regexp.MustCompile(`#.*`)
uuidMatch := regexp.MustCompile(`^UUID=["]*([0-9a-f-]+)["]*$`)
partuuidMatch := regexp.MustCompile(`^PARTUUID=["]*([0-9a-f-]+)["]*$`)
// Each line item, parse the mountpoint.
for scanner.Scan() {
// Read line, and clean up comments/parse fields.
line := scanner.Text()
line = comment.ReplaceAllString(line, "")
args := strings.Fields(line)
// If line contains no fields, we can ignore it.
if len(args) == 0 {
continue
}
// If line is not 5 fields, some formatting is wrong in the table. We will just log/ignore this line.
if len(args) != 5 {
log.Println("Line does not have correct number of arguments:", line)
continue
}
// Put fields into mountpoint structure.
mount := RaidMount{
Source: strings.ReplaceAll(args[0], "\\040", " "),
Target: strings.ReplaceAll(args[1], "\\040", " "),
FSType: args[2],
Flags: args[3],
CryptName: args[4],
Encrypted: false,
}
// If the CryptName field is not none, then it is an encrypted drive. We must set the variables for logic below to easily determine if it has encryption.
if mount.CryptName != "none" {
mount.Encrypted = true
hasEncryptedDrives = true
}
// If the source drive is a UUID or PARTUUID, expand to device name.
if uuidMatch.MatchString(mount.Source) {
uuid := uuidMatch.FindStringSubmatch(mount.Source)
mount.Source = "/dev/disk/by-uuid/" + uuid[1]
} else if partuuidMatch.MatchString(mount.Source) {
uuid := partuuidMatch.FindStringSubmatch(mount.Source)
mount.Source = "/dev/disk/by-partuuid/" + uuid[1]
}
raidMounts = append(raidMounts, mount)
}
raidTab.Close()
// If the encryption key was passed as a flag, override the configuration file.
if app.flags.EncryptionKey != "" {
app.config.EncryptionKey = app.flags.EncryptionKey
}
// If the encryption key file is set, we need to verify it actually exists.
if app.config.EncryptionKey != "" {
if _, err := os.Stat(app.config.EncryptionKey); err != nil {
log.Fatalln("Encryption key specified does not exist.")
}
}
// If the encryption password was not provided and an encryption key not provided and there is a mountpoint that is encrypted,
// request the password from the user.
encryptionPassword := app.flags.EncryptionPassword
if encryptionPassword == "" && app.config.EncryptionKey == "" && hasEncryptedDrives {
fmt.Print("Please enter the encryption password: ")
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
log.Fatalln("Unable to read password:", err)
}
fmt.Println()
encryptionPassword = string(bytePassword)
}
// With each mountpoint, decrypt and mount.
for _, mount := range raidMounts {
// If encrypted, decrypt the drive. // If encrypted, decrypt the drive.
if mount.Encrypted { if mount.Encrypted {
// Check the device path to see if the encrypted drive is already decrypted. // Check the device path to see if the encrypted drive is already decrypted.
dmPath := "/dev/mapper/" + mount.CryptName dmPath := "/dev/mapper/" + mount.CryptName
if _, err := os.Stat(dmPath); err == nil { if _, err := os.Stat(dmPath); err == nil {
fmt.Println("Already decrypted:", mount.CryptName) fmt.Println("Already decrypted:", mount.CryptName)
continue return
} }
// Decrypt the drive. // Decrypt the drive.
@ -221,7 +116,7 @@ func main() {
// If we're already mounted on this mountpoint, skip to the next one. // If we're already mounted on this mountpoint, skip to the next one.
if isMounted(mount.Target) { if isMounted(mount.Target) {
fmt.Println(mount.Target, "is already mounted") fmt.Println(mount.Target, "is already mounted")
continue return
} }
// Mount the mountpoint. // Mount the mountpoint.
@ -240,7 +135,7 @@ func main() {
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
// Run mount to mount the mountpoint, any error is fatal as we want to ensure that mountpoints mount. // Run mount to mount the mountpoint, any error is fatal as we want to ensure that mountpoints mount.
err = cmd.Start() err := cmd.Start()
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
@ -253,10 +148,138 @@ func main() {
// Verified that it actually mounted. // Verified that it actually mounted.
if !isMounted(mount.Target) { if !isMounted(mount.Target) {
log.Fatalln("Unable to mount:", mount.Target) log.Fatalln("Unable to mount:", mount.Target)
}
}
// main: Starting application function.
func main() {
// Only allow running as root.
if os.Getuid() != 0 {
fmt.Println("You must call this program as root.")
os.Exit(1)
}
// Read configurations.
app = new(App)
app.flags = new(Flags)
app.flags.Init()
app.ReadConfig()
// The raid table is how we know what to mount, and it must exist to start.
if _, err := os.Stat(app.config.RaidTablePath); err != nil {
log.Fatalln("Raid table does not exist.")
}
var raidMounts []RaidMount
hasEncryptedDrives := false // If there are encrypted drives, we require a password to decrypt them.
// Open the raid mountpoint table file.
raidTab, err := os.Open(app.config.RaidTablePath)
if err != nil {
log.Fatalln("Unable to open raid table:", err)
}
// Prepare scanners and regular expressions for parsing raid table.
scanner := bufio.NewScanner(raidTab)
comment := regexp.MustCompile(`#.*`)
uuidMatch := regexp.MustCompile(`^UUID=["]*([0-9a-f-]+)["]*$`)
partuuidMatch := regexp.MustCompile(`^PARTUUID=["]*([0-9a-f-]+)["]*$`)
// Each line item, parse the mountpoint.
for scanner.Scan() {
// Read line, and clean up comments/parse fields.
line := scanner.Text()
line = comment.ReplaceAllString(line, "")
args := strings.Fields(line)
// If line contains no fields, we can ignore it.
if len(args) == 0 {
continue continue
} }
// If line is not 5 fields, some formatting is wrong in the table. We will just log/ignore this line.
if len(args) != 6 {
log.Println("Line does not have correct number of arguments:", line)
continue
} }
// Put fields into mountpoint structure.
mount := RaidMount{
Source: strings.ReplaceAll(args[0], "\\040", " "),
Target: strings.ReplaceAll(args[1], "\\040", " "),
FSType: args[2],
Flags: args[3],
CryptName: args[4],
Encrypted: false,
Parallel: false,
}
// If the CryptName field is not none, then it is an encrypted drive. We must set the variables for logic below to easily determine if it has encryption.
if mount.CryptName != "none" {
mount.Encrypted = true
hasEncryptedDrives = true
}
// Determine if parallel mount.
if args[5] == "1" {
mount.Parallel = true
}
// If the source drive is a UUID or PARTUUID, expand to device name.
if uuidMatch.MatchString(mount.Source) {
uuid := uuidMatch.FindStringSubmatch(mount.Source)
mount.Source = "/dev/disk/by-uuid/" + uuid[1]
} else if partuuidMatch.MatchString(mount.Source) {
uuid := partuuidMatch.FindStringSubmatch(mount.Source)
mount.Source = "/dev/disk/by-partuuid/" + uuid[1]
}
raidMounts = append(raidMounts, mount)
}
raidTab.Close()
// If the encryption key was passed as a flag, override the configuration file.
if app.flags.EncryptionKey != "" {
app.config.EncryptionKey = app.flags.EncryptionKey
}
// If the encryption key file is set, we need to verify it actually exists.
if app.config.EncryptionKey != "" {
if _, err := os.Stat(app.config.EncryptionKey); err != nil {
log.Fatalln("Encryption key specified does not exist.")
}
}
// If the encryption password was not provided and an encryption key not provided and there is a mountpoint that is encrypted,
// request the password from the user.
encryptionPassword := app.flags.EncryptionPassword
if encryptionPassword == "" && app.config.EncryptionKey == "" && hasEncryptedDrives {
fmt.Print("Please enter the encryption password: ")
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
log.Fatalln("Unable to read password:", err)
}
fmt.Println()
encryptionPassword = string(bytePassword)
}
// With each mountpoint, decrypt and mount.
var wg sync.WaitGroup
for _, mount := range raidMounts {
// If this task is not parallel, wait for previous tasks to complete before processing.
if !mount.Parallel {
wg.Wait()
}
// Add 1 to the wait group as we're spawning a task.
wg.Add(1)
// Mount the drive.
go mountDrive(mount, encryptionPassword, &wg)
}
// Now that all mounts are in progress, we wait before starting services.
wg.Wait()
// Now that all mountpoints are mounted, start the services in configuration. // Now that all mountpoints are mounted, start the services in configuration.
for _, service := range app.config.Services { for _, service := range app.config.Services {
// Start the service. // Start the service.

View File

@ -1,6 +1,6 @@
# Source Target FSType Flags CryptName # Source Target FSType Flags CryptName Parallel
/dev/sdb1 /mnt/sdb1 xfs defaults none /dev/sdb1 /mnt/sdb1 xfs defaults none 1
/dev/sdc1 /mnt/sdc1 xfs defaults sdc1 /dev/sdc1 /mnt/sdc1 xfs defaults sdc1 1
# Merged # Merged
/mnt/sdb1:/mnt/sdc1 /mnt/merged mergerfs config=/etc/mergerfs.ini,allow_other,use_ino,fsname=merged none /mnt/sdb1:/mnt/sdc1 /mnt/merged mergerfs config=/etc/mergerfs.ini,allow_other,use_ino,fsname=merged none 0