work/main.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()
}
}