Add Parallel Mounting options
This commit is contained in:
parent
356940fa32
commit
03d5d70e3c
249
main.go
249
main.go
@ -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.
|
||||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user