337 lines
8.2 KiB
Go
337 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/huh"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
type Config struct {
|
|
SSHUser string `mapstructure:"SSH_USER"`
|
|
SSHHost string `mapstructure:"SSH_HOST"`
|
|
JumpUser string `mapstructure:"JUMP_USER"`
|
|
JumpHost string `mapstructure:"JUMP_HOST"`
|
|
WorkstationHost string `mapstructure:"WORKSTATION_HOST"`
|
|
WorkstationUser string `mapstructure:"WORKSTATION_USER"`
|
|
WorkstationMac string `mapstructure:"WORKSTATION_MAC"`
|
|
RDPUser string `mapstructure:"RDP_USER"`
|
|
RDPPassword string `mapstructure:"RDP_PASSWORD"`
|
|
WorkstationIP string `mapstructure:"WORKSTATION_IP"`
|
|
SSHPort int `mapstructure:"SSH_PORT"`
|
|
}
|
|
|
|
type Flags struct {
|
|
ShowWeek bool
|
|
ShowMonth bool
|
|
ShowExport bool
|
|
}
|
|
|
|
type App struct {
|
|
cfg Config
|
|
flags Flags
|
|
}
|
|
|
|
func NewApp() (*App, error) {
|
|
cfg, err := loadConfig()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error loading config: %w", err)
|
|
}
|
|
|
|
return &App{
|
|
cfg: cfg,
|
|
}, nil
|
|
}
|
|
|
|
func loadConfig() (Config, error) {
|
|
var cfg Config
|
|
configPath, err := os.UserConfigDir()
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
|
|
workConfigPath := filepath.Join(configPath, "work")
|
|
configFile := filepath.Join(workConfigPath, "config.toml")
|
|
|
|
viper.SetConfigFile(configFile)
|
|
viper.SetConfigType("toml")
|
|
viper.AddConfigPath(".")
|
|
viper.AutomaticEnv()
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
return cfg, fmt.Errorf("error reading config file: %w", err)
|
|
}
|
|
|
|
if err := viper.UnmarshalKey("default", &cfg); err != nil {
|
|
return cfg, fmt.Errorf("error decoding config: %w", err)
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (a *App) connect() {
|
|
a.runCommand("timew", "start", "work")
|
|
a.wakeWorkstation()
|
|
sshClient, err := ssh.Dial("tcp", a.cfg.SSHHost+":"+fmt.Sprintf("%v", a.cfg.SSHPort), a.makeSSHClient())
|
|
if err != nil {
|
|
log.Fatal("Failed to dial: ", err)
|
|
}
|
|
defer sshClient.Close()
|
|
|
|
session, err := sshClient.NewSession()
|
|
if err != nil {
|
|
log.Fatal("Failed to create session: ", err)
|
|
}
|
|
defer session.Close()
|
|
|
|
go forwardPort(sshClient, "2048", a.cfg.WorkstationIP, "22")
|
|
|
|
go forwardPort(sshClient, "6000", a.cfg.WorkstationIP, "3389")
|
|
|
|
a.connectToWorkstation()
|
|
}
|
|
|
|
func forwardPort(sshConn *ssh.Client, localPort, remoteHost, remotePort string) {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:"+localPort)
|
|
if err != nil {
|
|
log.Printf("Fehler beim Öffnen des lokalen Ports %s: %v", localPort, err)
|
|
return
|
|
}
|
|
defer listener.Close()
|
|
|
|
for {
|
|
localConn, err := listener.Accept()
|
|
if err != nil {
|
|
log.Printf("Fehler beim Akzeptieren der Verbindung: %v", err)
|
|
continue
|
|
}
|
|
|
|
remoteConn, err := sshConn.Dial("tcp", remoteHost+":"+remotePort)
|
|
if err != nil {
|
|
log.Printf("Fehler beim Verbinden zum Remote-Host %s:%s: %v", remoteHost, remotePort, err)
|
|
localConn.Close()
|
|
continue
|
|
}
|
|
|
|
go copyConn(localConn, remoteConn)
|
|
go copyConn(remoteConn, localConn)
|
|
}
|
|
}
|
|
|
|
func copyConn(dst, src net.Conn) {
|
|
defer dst.Close()
|
|
defer src.Close()
|
|
io.Copy(dst, src)
|
|
}
|
|
|
|
func (a *App) makeSSHClient() *ssh.ClientConfig {
|
|
keypath := os.ExpandEnv("$HOME/.ssh/hegenberg")
|
|
keyBytes, err := os.ReadFile(keypath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to read private key: %s", err)
|
|
}
|
|
|
|
key, err := ssh.ParsePrivateKeyWithPassphrase(keyBytes, []byte(a.cfg.RDPPassword))
|
|
if err != nil {
|
|
log.Fatalf("Failed to parse private key: %s", err)
|
|
}
|
|
|
|
return &ssh.ClientConfig{
|
|
User: a.cfg.SSHUser,
|
|
Auth: []ssh.AuthMethod{
|
|
ssh.PublicKeys(key),
|
|
},
|
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
}
|
|
|
|
func (a *App) runCommand(name string, args ...string) {
|
|
cmd := exec.Command(name, args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.Stdin = os.Stdin
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
}
|
|
}
|
|
|
|
func (a *App) wakeWorkstation() {
|
|
sshCommand := fmt.Sprintf("ssh -tt -p %s %s@%s ssh -tt %s@%s \"wakeonlan %s && exit\"",
|
|
fmt.Sprintf("%v", a.cfg.SSHPort),
|
|
a.cfg.SSHUser,
|
|
a.cfg.SSHHost,
|
|
a.cfg.JumpUser,
|
|
a.cfg.JumpHost,
|
|
a.cfg.WorkstationMac)
|
|
args := strings.Split(sshCommand, " ")
|
|
log.Println(args)
|
|
a.runCommand("ssh", args[1:]...)
|
|
}
|
|
|
|
func (a *App) connectToJump() {
|
|
sshCommand := fmt.Sprintf("ssh -tt -L 2048:%s:22 %s@%s",
|
|
a.cfg.WorkstationHost,
|
|
a.cfg.SSHUser,
|
|
a.cfg.SSHHost)
|
|
args := strings.Split(sshCommand, " ")
|
|
a.runCommand("ssh", args[1:]...)
|
|
}
|
|
|
|
func (a *App) connectToWorkstation() {
|
|
sshCommand := fmt.Sprintf("ssh -tt -L 6000:%s:3389 -p 2048 %s@127.0.0.1",
|
|
a.cfg.WorkstationHost,
|
|
a.cfg.SSHUser)
|
|
args := strings.Split(sshCommand, " ")
|
|
a.runCommand("ssh", args[1:]...)
|
|
}
|
|
|
|
func (a *App) startRDPConnection() {
|
|
rdpCommand := fmt.Sprintf("xfreerdp /u:%s /p:%s /v:127.0.0.1:6000 /size:3000x1350",
|
|
a.cfg.RDPUser,
|
|
a.cfg.RDPPassword)
|
|
a.runCommand("bash", "-c", rdpCommand)
|
|
}
|
|
|
|
func (a *App) makeChoice() {
|
|
var choice string
|
|
form := huh.NewForm(
|
|
huh.NewGroup(
|
|
huh.NewSelect[string]().
|
|
Title("What would you like to do?").
|
|
Options(
|
|
huh.NewOption("Start", "Start"),
|
|
huh.NewOption("Stop Work", "stop Work"),
|
|
huh.NewOption("Show Week Summary", "show week summary"),
|
|
huh.NewOption("Show Month Summary", "show month summary"),
|
|
huh.NewOption("Start Break", "start break"),
|
|
huh.NewOption("Stop Break", "stop break"),
|
|
huh.NewOption("Export Timetable", "export"),
|
|
huh.NewOption("Wake Workstation", "wake workstation"),
|
|
huh.NewOption("Connect to Jump", "connect to jump"),
|
|
huh.NewOption("Connect to Workstation", "connect to workstation"),
|
|
huh.NewOption("Start RDP Connection", "start rdp connection"),
|
|
).
|
|
Value(&choice),
|
|
),
|
|
)
|
|
|
|
err := form.Run()
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
return
|
|
}
|
|
|
|
switch choice {
|
|
case "Start":
|
|
a.connect()
|
|
// startSequence()
|
|
case "stop Work":
|
|
a.runCommand("timew", "stop", "work")
|
|
case "start break":
|
|
a.runCommand("timew", "track", "break")
|
|
case "stop break":
|
|
a.runCommand("timew", "track", "work")
|
|
case "show week summary":
|
|
a.runCommand("timew", "summary", ":week", "work")
|
|
case "show month summary":
|
|
a.runCommand("timew", "summary", ":month", "work")
|
|
case "wake workstation":
|
|
a.wakeWorkstation()
|
|
case "connect to jump":
|
|
a.connectToJump()
|
|
case "connect to workstation":
|
|
a.connectToWorkstation()
|
|
case "start rdp connection":
|
|
a.startRDPConnection()
|
|
case "export":
|
|
fmt.Println("Exporting Work times")
|
|
}
|
|
}
|
|
|
|
func (a *App) setupCommands() *cobra.Command {
|
|
rootCmd := &cobra.Command{
|
|
Use: "work",
|
|
Short: "Fast work interactions",
|
|
Long: `A CLI tool to perform basic work cli tasks.`,
|
|
}
|
|
|
|
rootCmd.AddCommand(a.startCommand())
|
|
rootCmd.AddCommand(a.stopCommand())
|
|
rootCmd.AddCommand(a.showCommand())
|
|
return rootCmd
|
|
}
|
|
|
|
func (a *App) startCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "start",
|
|
Short: "start work",
|
|
Long: "command to start the work day",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
a.connect()
|
|
},
|
|
}
|
|
}
|
|
|
|
func (a *App) stopCommand() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "stop",
|
|
Short: "stop work",
|
|
Long: "command to stop the work day",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
a.runCommand("timew", "stop", "work")
|
|
os.Exit(0)
|
|
},
|
|
}
|
|
}
|
|
|
|
func (a *App) showCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "show",
|
|
Short: "show timetracking",
|
|
Long: "show different timetracking",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
if a.flags.ShowExport {
|
|
fmt.Println("Exporting timetable")
|
|
}
|
|
if a.flags.ShowWeek {
|
|
a.runCommand("timew", "summary", ":week", "work")
|
|
}
|
|
if a.flags.ShowMonth {
|
|
a.runCommand("timew", "summary", ":month", "work")
|
|
}
|
|
os.Exit(0)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVarP(&a.flags.ShowWeek, "week", "w", false, "show timewarrior week summary")
|
|
cmd.Flags().BoolVarP(&a.flags.ShowMonth, "month", "m", false, "show timewarrior month summary")
|
|
cmd.Flags().BoolVarP(&a.flags.ShowExport, "export", "e", false, "export timewarrior timetable")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func main() {
|
|
app, err := NewApp()
|
|
if err != nil {
|
|
log.Fatalf("unable to setup application: %v", err)
|
|
}
|
|
if len(os.Args) > 1 {
|
|
if err := app.setupCommands().Execute(); err != nil {
|
|
log.Fatalf("error executing command: %v", err)
|
|
}
|
|
// return
|
|
} else {
|
|
app.makeChoice()
|
|
}
|
|
}
|