Wire most of the work from today together
This commit is contained in:
parent
40aa08c86c
commit
e57519e58d
12 changed files with 328 additions and 132 deletions
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlexSSD7/linsk/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -26,7 +27,7 @@ var cleanCmd = &cobra.Command{
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(string(answer)) != "y\n" {
|
if utils.ClearUnprintableChars(strings.ToLower(string(answer)), false) != "y" {
|
||||||
fmt.Fprintf(os.Stderr, "Aborted.\n")
|
fmt.Fprintf(os.Stderr, "Aborted.\n")
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +38,8 @@ var cleanCmd = &cobra.Command{
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Clean network tap allocations, if any.
|
||||||
|
|
||||||
slog.Info("Deleted data directory", "path", rmPath)
|
slog.Info("Deleted data directory", "path", rmPath)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/AlexSSD7/linsk/share"
|
||||||
"github.com/AlexSSD7/linsk/vm"
|
"github.com/AlexSSD7/linsk/vm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -15,7 +16,7 @@ var lsCmd = &cobra.Command{
|
||||||
Short: "Start a VM and list all user drives within the VM. Uses lsblk command under the hood.",
|
Short: "Start a VM and list all user drives within the VM. Uses lsblk command under the hood.",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
os.Exit(runVM(args[0], func(ctx context.Context, i *vm.VM, fm *vm.FileManager) int {
|
os.Exit(runVM(args[0], func(ctx context.Context, i *vm.VM, fm *vm.FileManager, trc *share.NetTapRuntimeContext) int {
|
||||||
lsblkOut, err := fm.Lsblk()
|
lsblkOut, err := fm.Lsblk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to list block devices in the VM", "error", err.Error())
|
slog.Error("Failed to list block devices in the VM", "error", err.Error())
|
||||||
|
|
@ -29,6 +30,6 @@ var lsCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}, nil, false))
|
}, nil, false, false))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
95
cmd/run.go
95
cmd/run.go
|
|
@ -4,9 +4,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlexSSD7/linsk/share"
|
||||||
"github.com/AlexSSD7/linsk/vm"
|
"github.com/AlexSSD7/linsk/vm"
|
||||||
"github.com/sethvargo/go-password/password"
|
"github.com/sethvargo/go-password/password"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -20,46 +22,30 @@ var runCmd = &cobra.Command{
|
||||||
vmMountDevName := args[1]
|
vmMountDevName := args[1]
|
||||||
fsType := args[2]
|
fsType := args[2]
|
||||||
|
|
||||||
ftpPassivePortCount := uint16(9)
|
newBackendFunc := share.GetBackend(shareBackendFlag)
|
||||||
|
if newBackendFunc == nil {
|
||||||
|
slog.Error("Unknown file share backend", "type", shareBackendFlag)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
networkSharePort, err := getClosestAvailPortWithSubsequent(9000, 10)
|
cfg, err := share.RawUserConfiguration{
|
||||||
|
ListenIP: shareListenIPFlag,
|
||||||
|
|
||||||
|
FTPExtIP: ftpExtIPFlag,
|
||||||
|
SMBExtMode: smbUseExternAddrFlag,
|
||||||
|
}.Process(shareBackendFlag, slog.With("caller", "share-config"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to get closest available host port for network file share", "error", err.Error())
|
slog.Error("Failed to process raw configuration", "error", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ftpListenIP := net.ParseIP(ftpListenAddrFlag)
|
backend, vmOpts, err := newBackendFunc(cfg)
|
||||||
if ftpListenIP == nil {
|
if err != nil {
|
||||||
slog.Error("Invalid FTP listen address specified", "value", ftpListenAddrFlag)
|
slog.Error("Failed to initialize share backend", "backend", shareBackendFlag, "error", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ftpExtIP := net.ParseIP(ftpExtIPFlag)
|
os.Exit(runVM(args[0], func(ctx context.Context, i *vm.VM, fm *vm.FileManager, tapCtx *share.NetTapRuntimeContext) int {
|
||||||
if ftpExtIP == nil {
|
|
||||||
slog.Error("Invalid FTP external IP specified", "value", ftpExtIPFlag)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ftpListenAddrFlag != defaultFTPListenAddr && ftpExtIPFlag == defaultFTPListenAddr {
|
|
||||||
slog.Warn("No external FTP IP address via --ftp-extip was configured. This is a requirement in almost all scenarios if you want to connect remotely.")
|
|
||||||
}
|
|
||||||
|
|
||||||
ports := []vm.PortForwardingRule{{
|
|
||||||
HostIP: ftpListenIP,
|
|
||||||
HostPort: networkSharePort,
|
|
||||||
VMPort: 21,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i := uint16(0); i < ftpPassivePortCount; i++ {
|
|
||||||
p := networkSharePort + 1 + i
|
|
||||||
ports = append(ports, vm.PortForwardingRule{
|
|
||||||
HostIP: ftpListenIP,
|
|
||||||
HostPort: p,
|
|
||||||
VMPort: p,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(runVM(args[0], func(ctx context.Context, i *vm.VM, fm *vm.FileManager) int {
|
|
||||||
slog.Info("Mounting the device", "dev", vmMountDevName, "fs", fsType, "luks", luksFlag)
|
slog.Info("Mounting the device", "dev", vmMountDevName, "fs", fsType, "luks", luksFlag)
|
||||||
|
|
||||||
err := fm.Mount(vmMountDevName, vm.MountOptions{
|
err := fm.Mount(vmMountDevName, vm.MountOptions{
|
||||||
|
|
@ -77,32 +63,53 @@ var runCmd = &cobra.Command{
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fm.StartFTP(sharePWD, networkSharePort+1, ftpPassivePortCount, ftpExtIP)
|
shareURI, err := backend.Apply(ctx, sharePWD, &share.VMShareContext{
|
||||||
|
Instance: i,
|
||||||
|
FileManager: fm,
|
||||||
|
NetTapCtx: tapCtx,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to start FTP server", "error", err.Error())
|
slog.Error("Failed to apply (start) file share backend", "backend", shareBackendFlag, "error", err.Error())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Started the network share successfully", "type", "ftp")
|
slog.Info("Started the network share successfully", "type", "ftp")
|
||||||
|
|
||||||
shareURI := "ftp://linsk:" + sharePWD + "@" + ftpExtIP.String() + ":" + fmt.Sprint(networkSharePort)
|
fmt.Fprintf(os.Stderr, "============================\n[Network File Share Config]\nThe network file share was started. Please use the credentials below to connect to the file server.\n\nType: "+strings.ToUpper(shareBackendFlag)+"\nURL: %v\nUsername: linsk\nPassword: %v\n===========================\n", shareURI, sharePWD)
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "================\n[Network File Share Config]\nThe network file share was started. Please use the credentials below to connect to the file server.\n\nType: FTP\nServer Address: ftp://%v:%v\nUsername: linsk\nPassword: %v\n\nShare URI: %v\n================\n", ftpExtIP.String(), networkSharePort, sharePWD, shareURI)
|
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
return 0
|
return 0
|
||||||
}, ports, unrestrictedNetworkingFlag))
|
}, vmOpts.Ports, unrestrictedNetworkingFlag, vmOpts.EnableTap))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var luksFlag bool
|
var luksFlag bool
|
||||||
var ftpListenAddrFlag string
|
var shareListenIPFlag string
|
||||||
var ftpExtIPFlag string
|
var ftpExtIPFlag string
|
||||||
|
var shareBackendFlag string
|
||||||
const defaultFTPListenAddr = "127.0.0.1"
|
var smbUseExternAddrFlag bool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runCmd.Flags().BoolVarP(&luksFlag, "luks", "l", false, "Use cryptsetup to open a LUKS volume (password will be prompted).")
|
runCmd.Flags().BoolVarP(&luksFlag, "luks", "l", false, "Use cryptsetup to open a LUKS volume (password will be prompted).")
|
||||||
runCmd.Flags().StringVar(&ftpListenAddrFlag, "ftp-listen", defaultFTPListenAddr, "Specifies the address to bind the FTP ports to. NOTE: Changing bind address is not enough to connect remotely. You should also specify --ftp-extip.")
|
|
||||||
runCmd.Flags().StringVar(&ftpExtIPFlag, "ftp-extip", defaultFTPListenAddr, "Specifies the external IP the FTP server should advertise.")
|
var defaultShareType string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
defaultShareType = "smb"
|
||||||
|
default:
|
||||||
|
defaultShareType = "ftp"
|
||||||
|
}
|
||||||
|
|
||||||
|
runCmd.Flags().StringVar(&shareBackendFlag, "share-backend", defaultShareType, "Specifies the file share backend to use. The default value is OS-specific.")
|
||||||
|
runCmd.Flags().StringVar(&shareListenIPFlag, "share-listen", share.GetDefaultListenIPStr(), "Specifies the IP to bind the network share port to. NOTE: For FTP, changing the bind address is not enough to connect remotely. You should also specify --ftp-extip.")
|
||||||
|
|
||||||
|
smbExternDefault := false
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
smbExternDefault = true
|
||||||
|
}
|
||||||
|
|
||||||
|
runCmd.Flags().StringVar(&ftpExtIPFlag, "ftp-extip", share.GetDefaultListenIPStr(), "Specifies the external IP the FTP server should advertise.")
|
||||||
|
runCmd.Flags().BoolVar(&smbUseExternAddrFlag, "smb-extern", smbExternDefault, "Specifies whether Linsk emulate external networking for the VM's SMB server. This is the default for Windows as there is no way to specify ports in Windows SMB client.")
|
||||||
|
|
||||||
|
// TODO: log the use of smbUseExternAddrFlag when SMB is not enabled.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
cmd/shell.go
10
cmd/shell.go
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlexSSD7/linsk/share"
|
||||||
"github.com/AlexSSD7/linsk/vm"
|
"github.com/AlexSSD7/linsk/vm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
@ -39,11 +40,7 @@ var shellCmd = &cobra.Command{
|
||||||
forwardPortRules = append(forwardPortRules, fpr)
|
forwardPortRules = append(forwardPortRules, fpr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !unrestrictedNetworkingFlag {
|
os.Exit(runVM(passthroughArg, func(ctx context.Context, i *vm.VM, fm *vm.FileManager, trc *share.NetTapRuntimeContext) int {
|
||||||
slog.Warn("IMPORTANT: By default, Linsk shell starts a VM with restricted networking as it's done with `run` command. This means that you will have no internet access in the shell. If you want to have access to the internet, please add `--vm-unrestricted-networking` flag to your `linsk shell` command.")
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(runVM(passthroughArg, func(ctx context.Context, i *vm.VM, fm *vm.FileManager) int {
|
|
||||||
sc, err := i.DialSSH()
|
sc, err := i.DialSSH()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to dial VM SSH", "error", err.Error())
|
slog.Error("Failed to dial VM SSH", "error", err.Error())
|
||||||
|
|
@ -130,7 +127,8 @@ var shellCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}, forwardPortRules, unrestrictedNetworkingFlag))
|
}, forwardPortRules, true, false))
|
||||||
|
// TODO: Enable tap option.
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
187
cmd/utils.go
187
cmd/utils.go
|
|
@ -3,7 +3,6 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
|
@ -17,6 +16,8 @@ import (
|
||||||
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/AlexSSD7/linsk/nettap"
|
||||||
|
"github.com/AlexSSD7/linsk/share"
|
||||||
"github.com/AlexSSD7/linsk/storage"
|
"github.com/AlexSSD7/linsk/storage"
|
||||||
"github.com/AlexSSD7/linsk/vm"
|
"github.com/AlexSSD7/linsk/vm"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -64,7 +65,7 @@ func createStore() *storage.Storage {
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManager) int, forwardPortsRules []vm.PortForwardingRule, unrestrictedNetworking bool) int {
|
func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManager, *share.NetTapRuntimeContext) int, forwardPortsRules []vm.PortForwardingRule, unrestrictedNetworking bool, withNetTap bool) int {
|
||||||
store := createStore()
|
store := createStore()
|
||||||
|
|
||||||
vmImagePath, err := store.CheckVMImageExists()
|
vmImagePath, err := store.CheckVMImageExists()
|
||||||
|
|
@ -80,7 +81,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
|
||||||
|
|
||||||
biosPath, err := store.CheckDownloadVMBIOS()
|
biosPath, err := store.CheckDownloadVMBIOS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to check/download VM BIOS", "error", err)
|
slog.Error("Failed to check/download VM BIOS", "error", err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,6 +92,85 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
|
||||||
doUSBRootCheck()
|
doUSBRootCheck()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tapRuntimeCtx *share.NetTapRuntimeContext
|
||||||
|
var tapsConfig []vm.TapConfig
|
||||||
|
|
||||||
|
if withNetTap {
|
||||||
|
tapManager, err := nettap.NewTapManager(slog.With("caller", "nettap-manager"))
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create new network tap manager", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tapNameToUse := nettap.NewRandomTapName()
|
||||||
|
// TODO: Run two instances at the same time and check whether nothing is wrongfully pruned.
|
||||||
|
knownAllocs, err := store.ListNetTapAllocations()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to list net tap allocations", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
removedTaps, err := tapManager.PruneTaps(knownAllocs)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to prune dangling network taps", "error", err.Error())
|
||||||
|
} else {
|
||||||
|
// This is optional, meaning that we won't exit in panic if this fails.
|
||||||
|
for _, removedTap := range removedTaps {
|
||||||
|
err = store.ReleaseNetTapAllocation(removedTap)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to release a danging net tap allocation", "error", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.SaveNetTapAllocation(tapNameToUse, os.Getpid())
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to save net tap allocation", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tapManager, err = nettap.NewTapManager(slog.Default())
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to create net tap manager", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tapManager.CreateNewTap(tapNameToUse)
|
||||||
|
if err != nil {
|
||||||
|
releaseErr := store.ReleaseNetTapAllocation(tapNameToUse)
|
||||||
|
if releaseErr != nil {
|
||||||
|
slog.Error("Failed to release net tap allocation", "error", releaseErr.Error(), "tapname", tapNameToUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Error("Failed to create new tap", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tapNet, err := nettap.GenerateNet()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to generate tap net plan", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tapManager.ConfigureNet(tapNameToUse, tapNet.HostCIDR)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to configure tap net", "error", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
tapRuntimeCtx = &share.NetTapRuntimeContext{
|
||||||
|
Manager: tapManager,
|
||||||
|
Name: tapNameToUse,
|
||||||
|
Net: tapNet,
|
||||||
|
}
|
||||||
|
|
||||||
|
tapsConfig = []vm.TapConfig{{
|
||||||
|
Name: tapNameToUse,
|
||||||
|
}}
|
||||||
|
|
||||||
|
// TODO: Clean the tap up before exiting.
|
||||||
|
}
|
||||||
|
|
||||||
vmCfg := vm.VMConfig{
|
vmCfg := vm.VMConfig{
|
||||||
Drives: []vm.DriveConfig{{
|
Drives: []vm.DriveConfig{{
|
||||||
Path: vmImagePath,
|
Path: vmImagePath,
|
||||||
|
|
@ -103,10 +183,12 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
|
||||||
PassthroughConfig: passthroughConfig,
|
PassthroughConfig: passthroughConfig,
|
||||||
ExtraPortForwardingRules: forwardPortsRules,
|
ExtraPortForwardingRules: forwardPortsRules,
|
||||||
|
|
||||||
|
UnrestrictedNetworking: unrestrictedNetworking,
|
||||||
|
Taps: tapsConfig,
|
||||||
|
|
||||||
OSUpTimeout: time.Duration(vmOSUpTimeoutFlag) * time.Second,
|
OSUpTimeout: time.Duration(vmOSUpTimeoutFlag) * time.Second,
|
||||||
SSHUpTimeout: time.Duration(vmSSHSetupTimeoutFlag) * time.Second,
|
SSHUpTimeout: time.Duration(vmSSHSetupTimeoutFlag) * time.Second,
|
||||||
|
|
||||||
UnrestrictedNetworking: unrestrictedNetworking,
|
|
||||||
ShowDisplay: vmDebugFlag,
|
ShowDisplay: vmDebugFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,7 +259,23 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCode := fn(ctx, vi, fm)
|
startupFailed := false
|
||||||
|
|
||||||
|
if tapRuntimeCtx != nil {
|
||||||
|
err := vi.ConfigureInterfaceStaticNet(context.Background(), "eth1", tapRuntimeCtx.Net.GuestCIDR)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to configure tag interface network", "error", err.Error())
|
||||||
|
startupFailed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitCode int
|
||||||
|
|
||||||
|
if !startupFailed {
|
||||||
|
exitCode = fn(ctx, vi, fm, tapRuntimeCtx)
|
||||||
|
} else {
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
err = vi.Cancel()
|
err = vi.Cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -201,64 +299,6 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPortAvailable(port uint16, subsequent uint16) (bool, error) {
|
|
||||||
if port+subsequent < port {
|
|
||||||
return false, fmt.Errorf("subsequent ports exceed allowed port range")
|
|
||||||
}
|
|
||||||
|
|
||||||
if subsequent == 0 {
|
|
||||||
ln, err := net.Listen("tcp", ":"+fmt.Sprint(port))
|
|
||||||
if err != nil {
|
|
||||||
if opErr, ok := err.(*net.OpError); ok {
|
|
||||||
if sysErr, ok := opErr.Err.(*os.SyscallError); ok {
|
|
||||||
if sysErr.Err == syscall.EADDRINUSE {
|
|
||||||
// The port is in use.
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, errors.Wrapf(err, "net listen (port %v)", port)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ln.Close()
|
|
||||||
if err != nil {
|
|
||||||
return false, errors.Wrap(err, "close ephemeral listener")
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := uint16(0); i < subsequent; i++ {
|
|
||||||
ok, err := checkPortAvailable(port+i, 0)
|
|
||||||
if err != nil {
|
|
||||||
return false, errors.Wrapf(err, "check subsequent port available (base: %v, seq: %v)", port, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClosestAvailPortWithSubsequent(port uint16, subsequent uint16) (uint16, error) {
|
|
||||||
// We use 10 as port range
|
|
||||||
for i := port; i < 65535; i += subsequent {
|
|
||||||
ok, err := checkPortAvailable(i, subsequent)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Wrapf(err, "check port available (%v)", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("no available port (with %v subsequent ones) found", subsequent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDevicePassthroughConfig(val string) vm.PassthroughConfig {
|
func getDevicePassthroughConfig(val string) vm.PassthroughConfig {
|
||||||
valSplit := strings.Split(val, ":")
|
valSplit := strings.Split(val, ":")
|
||||||
if want, have := 2, len(valSplit); want != have {
|
if want, have := 2, len(valSplit); want != have {
|
||||||
|
|
@ -294,17 +334,18 @@ func getDevicePassthroughConfig(val string) vm.PassthroughConfig {
|
||||||
}
|
}
|
||||||
case "dev":
|
case "dev":
|
||||||
devPath := filepath.Clean(valSplit[1])
|
devPath := filepath.Clean(valSplit[1])
|
||||||
stat, err := os.Stat(devPath)
|
// TODO: This is for Linux only. Should support Windows as well.
|
||||||
if err != nil {
|
// stat, err := os.Stat(devPath)
|
||||||
slog.Error("Failed to stat the device path", "error", err.Error(), "path", devPath)
|
// if err != nil {
|
||||||
os.Exit(1)
|
// slog.Error("Failed to stat the device path", "error", err.Error(), "path", devPath)
|
||||||
}
|
// os.Exit(1)
|
||||||
|
// }
|
||||||
|
|
||||||
isDev := stat.Mode()&os.ModeDevice != 0
|
// isDev := stat.Mode()&os.ModeDevice != 0
|
||||||
if !isDev {
|
// if !isDev {
|
||||||
slog.Error("Provided path is not a path to a valid block device", "path", devPath, "file-mode", stat.Mode())
|
// slog.Error("Provided path is not a path to a valid block device", "path", devPath, "file-mode", stat.Mode())
|
||||||
os.Exit(1)
|
// os.Exit(1)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return vm.PassthroughConfig{Block: []vm.BlockDevicePassthroughConfig{{
|
return vm.PassthroughConfig{Block: []vm.BlockDevicePassthroughConfig{{
|
||||||
Path: devPath,
|
Path: devPath,
|
||||||
|
|
|
||||||
6
go.mod
6
go.mod
|
|
@ -7,9 +7,11 @@ require (
|
||||||
github.com/alessio/shellescape v1.4.2
|
github.com/alessio/shellescape v1.4.2
|
||||||
github.com/bramvdbogaerde/go-scp v1.2.1
|
github.com/bramvdbogaerde/go-scp v1.2.1
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
|
github.com/google/uuid v1.3.1
|
||||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/sethvargo/go-password v0.2.0
|
github.com/sethvargo/go-password v0.2.0
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
go.uber.org/multierr v1.11.0
|
go.uber.org/multierr v1.11.0
|
||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.12.0
|
||||||
|
|
@ -17,7 +19,11 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
golang.org/x/sys v0.11.0 // indirect
|
golang.org/x/sys v0.11.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
14
go.sum
14
go.sum
|
|
@ -9,6 +9,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||||
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
|
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
|
||||||
|
|
@ -20,20 +24,30 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
|
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
|
||||||
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||||
|
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,8 @@ func (bc *BuildContext) BuildWithInterruptHandler() error {
|
||||||
|
|
||||||
bc.logger.Info("VM OS installation in progress")
|
bc.logger.Info("VM OS installation in progress")
|
||||||
|
|
||||||
err = runAlpineSetupCmd(sc, []string{"openssh", "lvm2", "util-linux", "cryptsetup", "vsftpd"})
|
// TODO: Compile select features only.
|
||||||
|
err = runAlpineSetupCmd(sc, []string{"openssh", "lvm2", "util-linux", "cryptsetup", "vsftpd", "samba"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "run alpine setup cmd")
|
return errors.Wrap(err, "run alpine setup cmd")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
utils/net.go
Normal file
7
utils/net.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
func IsIPv6IP(ip net.IP) bool {
|
||||||
|
return ip.To4() == nil && ip.To16() != nil
|
||||||
|
}
|
||||||
|
|
@ -247,3 +247,64 @@ pasv_address=` + extIP.String() + `
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fm *FileManager) StartSMB(pwd string) error {
|
||||||
|
// This timeout is for the SCP client exclusively.
|
||||||
|
scpCtx, scpCtxCancel := context.WithTimeout(fm.vm.ctx, time.Second*5)
|
||||||
|
defer scpCtxCancel()
|
||||||
|
|
||||||
|
scpClient, err := fm.vm.DialSCP()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "dial scp")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer scpClient.Close()
|
||||||
|
|
||||||
|
sambaCfg := `[global]
|
||||||
|
workgroup = WORKGROUP
|
||||||
|
dos charset = cp866
|
||||||
|
unix charset = utf-8
|
||||||
|
|
||||||
|
read raw = yes
|
||||||
|
write raw = yes
|
||||||
|
socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=131072 SO_SNDBUF=131072
|
||||||
|
min receivefile size = 16384
|
||||||
|
use sendfile = true
|
||||||
|
aio read size = 16384
|
||||||
|
aio write size = 16384
|
||||||
|
server signing = no
|
||||||
|
|
||||||
|
[linsk]
|
||||||
|
browseable = yes
|
||||||
|
writeable = yes
|
||||||
|
path = /mnt
|
||||||
|
force user = linsk
|
||||||
|
force group = linsk
|
||||||
|
create mask = 0664`
|
||||||
|
|
||||||
|
err = scpClient.CopyFile(scpCtx, strings.NewReader(sambaCfg), "/etc/samba/smb.conf", "0400")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "copy samba config file")
|
||||||
|
}
|
||||||
|
|
||||||
|
scpClient.Close()
|
||||||
|
|
||||||
|
sc, err := fm.vm.DialSSH()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "dial ssh")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = sc.Close() }()
|
||||||
|
|
||||||
|
_, err = sshutil.RunSSHCmd(fm.vm.ctx, sc, "rc-update add samba && rc-service samba start")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "add and start samba service")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sshutil.ChangeSambaPass(fm.vm.ctx, sc, "linsk", pwd)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "change samba pass")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
37
vm/net.go
Normal file
37
vm/net.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/AlexSSD7/linsk/sshutil"
|
||||||
|
"github.com/AlexSSD7/linsk/utils"
|
||||||
|
"github.com/alessio/shellescape"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (vi *VM) ConfigureInterfaceStaticNet(ctx context.Context, iface string, cidr string) error {
|
||||||
|
ip, _, err := net.ParseCIDR(cidr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "invalid cidr")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsIPv6IP(ip) {
|
||||||
|
return fmt.Errorf("ipv6 addresses accepted only (have '%v')", ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
sc, err := vi.DialSSH()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "dial ssh")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = sc.Close() }()
|
||||||
|
|
||||||
|
_, err = sshutil.RunSSHCmd(ctx, sc, "ifconfig "+shellescape.Quote(iface)+" up && ip addr add "+shellescape.Quote(cidr)+" dev "+shellescape.Quote(iface))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "run net conf cmds")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
24
vm/vm.go
24
vm/vm.go
|
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/AlexSSD7/linsk/nettap"
|
||||||
"github.com/AlexSSD7/linsk/sshutil"
|
"github.com/AlexSSD7/linsk/sshutil"
|
||||||
"github.com/AlexSSD7/linsk/utils"
|
"github.com/AlexSSD7/linsk/utils"
|
||||||
"github.com/alessio/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
|
|
@ -64,6 +65,10 @@ type DriveConfig struct {
|
||||||
SnapshotMode bool
|
SnapshotMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TapConfig struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
type VMConfig struct {
|
type VMConfig struct {
|
||||||
CdromImagePath string
|
CdromImagePath string
|
||||||
BIOSPath string
|
BIOSPath string
|
||||||
|
|
@ -74,12 +79,15 @@ type VMConfig struct {
|
||||||
PassthroughConfig PassthroughConfig
|
PassthroughConfig PassthroughConfig
|
||||||
ExtraPortForwardingRules []PortForwardingRule
|
ExtraPortForwardingRules []PortForwardingRule
|
||||||
|
|
||||||
|
// Networking
|
||||||
|
UnrestrictedNetworking bool
|
||||||
|
Taps []TapConfig
|
||||||
|
|
||||||
// Timeouts
|
// Timeouts
|
||||||
OSUpTimeout time.Duration
|
OSUpTimeout time.Duration
|
||||||
SSHUpTimeout time.Duration
|
SSHUpTimeout time.Duration
|
||||||
|
|
||||||
// Mostly debug-related options.
|
// Mostly debug-related options.
|
||||||
UnrestrictedNetworking bool
|
|
||||||
ShowDisplay bool
|
ShowDisplay bool
|
||||||
InstallBaseUtilities bool
|
InstallBaseUtilities bool
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +158,17 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
|
||||||
|
|
||||||
cmdArgs = append(cmdArgs, "-device", "e1000,netdev=net0", "-netdev", netdevOpts)
|
cmdArgs = append(cmdArgs, "-device", "e1000,netdev=net0", "-netdev", netdevOpts)
|
||||||
|
|
||||||
|
for i, tap := range cfg.Taps {
|
||||||
|
err := nettap.ValidateTapName(tap.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "validate network tap #%v name", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
netdevName := "net" + fmt.Sprint(1+i)
|
||||||
|
|
||||||
|
cmdArgs = append(cmdArgs, "-device", "e1000,netdev="+netdevName, "-netdev", "tap,id="+netdevName+",ifname="+shellescape.Quote(tap.Name)+",script=no,downscript=no")
|
||||||
|
}
|
||||||
|
|
||||||
if !cfg.ShowDisplay {
|
if !cfg.ShowDisplay {
|
||||||
cmdArgs = append(cmdArgs, "-display", "none")
|
cmdArgs = append(cmdArgs, "-display", "none")
|
||||||
} else if runtime.GOARCH == "arm64" {
|
} else if runtime.GOARCH == "arm64" {
|
||||||
|
|
@ -193,6 +212,7 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
|
||||||
for _, dev := range cfg.PassthroughConfig.Block {
|
for _, dev := range cfg.PassthroughConfig.Block {
|
||||||
// It's always a user's responsibility to ensure that no drives are mounted
|
// It's always a user's responsibility to ensure that no drives are mounted
|
||||||
// in both host and guest system. This should serve as the last resort.
|
// in both host and guest system. This should serve as the last resort.
|
||||||
|
// TODO: Windows support.
|
||||||
{
|
{
|
||||||
seemsMounted, err := checkDeviceSeemsMounted(dev.Path)
|
seemsMounted, err := checkDeviceSeemsMounted(dev.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -204,7 +224,7 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs = append(cmdArgs, "-drive", "file="+shellescape.Quote(dev.Path)+",format=raw,aio=native,cache=none")
|
cmdArgs = append(cmdArgs, "-drive", "file="+shellescape.Quote(strings.ReplaceAll(dev.Path, "\\", "/"))+",format=raw,cache=none")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're not using clean `cdromImagePath` here because it is set to "."
|
// We're not using clean `cdromImagePath` here because it is set to "."
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue