Compare commits
4 Commits
e602d503e8
...
v0.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
f33de18007
|
|||
|
e136c30266
|
|||
|
ee9c0edf8a
|
|||
|
d9ec7efaae
|
30
README.md
30
README.md
@@ -10,13 +10,13 @@ A Go-based command-line tool to manage SSH tunnels. This tool allows you to:
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```
|
```
|
||||||
go install github.com/yourusername/sshtunnel/cmd@latest
|
go install git.mvl.sh/vleeuwenmenno/sshtunnel@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Or clone this repository and build it yourself:
|
Or clone this repository and build it yourself:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/yourusername/sshtunnel.git
|
git clone https://git.mvl.sh/vleeuwenmenno/sshtunnel.git
|
||||||
cd sshtunnel
|
cd sshtunnel
|
||||||
go build -o sshtunnel ./cmd
|
go build -o sshtunnel ./cmd
|
||||||
```
|
```
|
||||||
@@ -34,31 +34,31 @@ This will display all active SSH tunnels with their IDs, local ports, remote end
|
|||||||
### Starting a new tunnel
|
### Starting a new tunnel
|
||||||
|
|
||||||
```
|
```
|
||||||
sshtunnel start -local 8080 -remote 80 -host example.com -server user@ssh-server.com
|
sshtunnel start -l 8080 -r 80 -H example.com -s user@ssh-server.com
|
||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- `-local`: Local port to forward (required)
|
- `-l`: Local port to forward (required)
|
||||||
- `-remote`: Remote port to forward to (required)
|
- `-r`: Remote port to forward to (required)
|
||||||
- `-host`: Remote host to forward to (default: "localhost")
|
- `-H`: Remote host to forward to (default: "localhost")
|
||||||
- `-server`: SSH server address in the format user@host (required)
|
- `-s`: SSH server address in the format user@host (required)
|
||||||
- `-identity`: Path to SSH identity file (optional)
|
- `-i`: Path to SSH identity file (optional)
|
||||||
|
|
||||||
### Stopping tunnels
|
### Stopping tunnels
|
||||||
|
|
||||||
Stop a specific tunnel by ID:
|
Stop a specific tunnel by ID:
|
||||||
```
|
```
|
||||||
sshtunnel stop -id 1
|
sshtunnel stop -i 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Stop all active tunnels:
|
Stop all active tunnels:
|
||||||
```
|
```
|
||||||
sshtunnel stop -all
|
sshtunnel stop --all
|
||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- `-id`: ID of the tunnel to stop
|
- `-i`: ID of the tunnel to stop
|
||||||
- `-all`: Stop all tunnels
|
- `--all`: Stop all tunnels
|
||||||
|
|
||||||
### Viewing traffic statistics
|
### Viewing traffic statistics
|
||||||
|
|
||||||
@@ -69,12 +69,12 @@ sshtunnel stats --all
|
|||||||
|
|
||||||
View statistics for a specific tunnel:
|
View statistics for a specific tunnel:
|
||||||
```
|
```
|
||||||
sshtunnel stats --id 1
|
sshtunnel stats -i 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Monitor tunnel traffic in real-time:
|
Monitor tunnel traffic in real-time:
|
||||||
```
|
```
|
||||||
sshtunnel stats --id 1 --watch
|
sshtunnel stats -i 1 --watch
|
||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@@ -107,4 +107,4 @@ The tool creates SSH tunnels using the system's SSH client and manages them by t
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
76
bin/scripts/release.sh
Executable file
76
bin/scripts/release.sh
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
source bin/helpers/func.sh
|
||||||
|
|
||||||
|
printfe "%s\n" "green" "SSH Tunnel Manager Release Script"
|
||||||
|
printfe "%s\n" "normal" "==============================="
|
||||||
|
|
||||||
|
# Check if git is installed
|
||||||
|
if ! command -v git &> /dev/null; then
|
||||||
|
printfe "%s\n" "red" "Error: git is not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we're in a git repository
|
||||||
|
if ! git rev-parse --is-inside-work-tree &> /dev/null; then
|
||||||
|
printfe "%s\n" "red" "Error: Not in a git repository"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the latest tag or default to v0.0.0 if no tags exist
|
||||||
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
|
|
||||||
|
# Parse the version number
|
||||||
|
if [[ $LATEST_TAG =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||||
|
MAJOR=${BASH_REMATCH[1]}
|
||||||
|
MINOR=${BASH_REMATCH[2]}
|
||||||
|
PATCH=${BASH_REMATCH[3]}
|
||||||
|
else
|
||||||
|
printfe "%s\n" "red" "Error: Could not parse latest tag: $LATEST_TAG"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Calculate the next patch version
|
||||||
|
NEXT_PATCH=$((PATCH + 1))
|
||||||
|
NEXT_VERSION="v$MAJOR.$MINOR.$NEXT_PATCH"
|
||||||
|
|
||||||
|
# Display current version and suggested next version
|
||||||
|
printfe "%s" "cyan" "Current version: "
|
||||||
|
printfe "%s\n" "yellow" "$LATEST_TAG" false
|
||||||
|
printfe "%s" "cyan" "Suggested next version: "
|
||||||
|
printfe "%s\n" "yellow" "$NEXT_VERSION" false
|
||||||
|
|
||||||
|
# Ask for confirmation or custom version
|
||||||
|
read -p "Accept suggested version? (y/n) " ACCEPT
|
||||||
|
if [[ $ACCEPT != "y" && $ACCEPT != "Y" ]]; then
|
||||||
|
read -p "Enter custom version (format vX.Y.Z): " CUSTOM_VERSION
|
||||||
|
if [[ $CUSTOM_VERSION =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||||
|
NEXT_VERSION=$CUSTOM_VERSION
|
||||||
|
else
|
||||||
|
printfe "%s\n" "red" "Error: Invalid version format. Must be vX.Y.Z where X, Y, Z are numbers."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printfe "%s" "cyan" "Creating and pushing tag: "
|
||||||
|
printfe "%s\n" "yellow" "$NEXT_VERSION" false
|
||||||
|
|
||||||
|
# Make sure we have the latest changes
|
||||||
|
printfe "%s\n" "cyan" "Fetching latest tags..."
|
||||||
|
log_and_run git fetch --tags
|
||||||
|
|
||||||
|
# Create the new tag
|
||||||
|
printfe "%s\n" "cyan" "Creating new tag..."
|
||||||
|
log_and_run git tag "$NEXT_VERSION"
|
||||||
|
|
||||||
|
# Push the new tag
|
||||||
|
printfe "%s\n" "cyan" "Pushing new tag..."
|
||||||
|
log_and_run git push origin "$NEXT_VERSION"
|
||||||
|
|
||||||
|
# Update the latest tag
|
||||||
|
printfe "%s\n" "cyan" "Updating 'latest' tag..."
|
||||||
|
log_and_run git tag -f latest "$NEXT_VERSION"
|
||||||
|
log_and_run git push -f origin latest
|
||||||
|
|
||||||
|
printfe "%s\n" "green" "Successfully tagged and pushed version $NEXT_VERSION"
|
||||||
|
printfe "%s\n" "cyan" "Also updated 'latest' tag to point to $NEXT_VERSION"
|
||||||
186
cmd/start.go
186
cmd/start.go
@@ -8,8 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.mvl.sh/vleeuwenmenno/sshtunnel/pkg/stats"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"sshtunnel/pkg/stats"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -28,106 +28,106 @@ var startCmd = &cobra.Command{
|
|||||||
The tunnel will run in the background and can be managed using the list and stop commands.`,
|
The tunnel will run in the background and can be managed using the list and stop commands.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Check required flags
|
// Check required flags
|
||||||
// Generate the SSH command with appropriate flags for reliable background operation
|
// Generate the SSH command with appropriate flags for reliable background operation
|
||||||
sshArgs := []string{
|
sshArgs := []string{
|
||||||
"-N", // Don't execute remote command
|
"-N", // Don't execute remote command
|
||||||
"-f", // Run in background
|
"-f", // Run in background
|
||||||
"-L", fmt.Sprintf("%d:%s:%d", localPort, remoteHost, remotePort),
|
"-L", fmt.Sprintf("%d:%s:%d", localPort, remoteHost, remotePort),
|
||||||
|
}
|
||||||
|
|
||||||
|
if identity != "" {
|
||||||
|
sshArgs = append(sshArgs, "-i", identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
sshArgs = append(sshArgs, sshServer)
|
||||||
|
sshCmd := exec.Command("ssh", sshArgs...)
|
||||||
|
|
||||||
|
// Capture output for debugging
|
||||||
|
var outputBuffer strings.Builder
|
||||||
|
sshCmd.Stdout = &outputBuffer
|
||||||
|
sshCmd.Stderr = &outputBuffer
|
||||||
|
|
||||||
|
// Run the command (not just Start) - the -f flag means it will return immediately
|
||||||
|
// after going to the background
|
||||||
|
if err := sshCmd.Run(); err != nil {
|
||||||
|
fmt.Printf("Error starting SSH tunnel: %v\n", err)
|
||||||
|
fmt.Printf("SSH output: %s\n", outputBuffer.String())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The PID from cmd.Process is no longer valid since ssh -f forks
|
||||||
|
// We need to find the actual SSH process PID
|
||||||
|
actualPID, err := findSSHTunnelPID(localPort)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Warning: Could not determine tunnel PID: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store tunnel information
|
||||||
|
id := generateTunnelID()
|
||||||
|
pid := 0
|
||||||
|
|
||||||
|
if actualPID > 0 {
|
||||||
|
pid = actualPID
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := Tunnel{
|
||||||
|
ID: id,
|
||||||
|
LocalPort: localPort,
|
||||||
|
RemotePort: remotePort,
|
||||||
|
RemoteHost: remoteHost,
|
||||||
|
SSHServer: sshServer,
|
||||||
|
PID: pid,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := saveTunnel(tunnel); err != nil {
|
||||||
|
fmt.Printf("Error saving tunnel information: %v\n", err)
|
||||||
|
if pid > 0 {
|
||||||
|
// Try to kill the process
|
||||||
|
process, _ := os.FindProcess(pid)
|
||||||
|
if process != nil {
|
||||||
|
process.Kill()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
if identity != "" {
|
// Initialize statistics for this tunnel
|
||||||
sshArgs = append(sshArgs, "-i", identity)
|
statsManager, err := stats.NewStatsManager()
|
||||||
}
|
if err == nil {
|
||||||
|
err = statsManager.InitStats(id, localPort)
|
||||||
sshArgs = append(sshArgs, sshServer)
|
|
||||||
sshCmd := exec.Command("ssh", sshArgs...)
|
|
||||||
|
|
||||||
// Capture output for debugging
|
|
||||||
var outputBuffer strings.Builder
|
|
||||||
sshCmd.Stdout = &outputBuffer
|
|
||||||
sshCmd.Stderr = &outputBuffer
|
|
||||||
|
|
||||||
// Run the command (not just Start) - the -f flag means it will return immediately
|
|
||||||
// after going to the background
|
|
||||||
if err := sshCmd.Run(); err != nil {
|
|
||||||
fmt.Printf("Error starting SSH tunnel: %v\n", err)
|
|
||||||
fmt.Printf("SSH output: %s\n", outputBuffer.String())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The PID from cmd.Process is no longer valid since ssh -f forks
|
|
||||||
// We need to find the actual SSH process PID
|
|
||||||
actualPID, err := findSSHTunnelPID(localPort)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: Could not determine tunnel PID: %v\n", err)
|
fmt.Printf("Warning: Failed to initialize statistics: %v\n", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Store tunnel information
|
// Verify tunnel is actually working
|
||||||
id := generateTunnelID()
|
time.Sleep(500 * time.Millisecond)
|
||||||
pid := 0
|
active := verifyTunnelActive(localPort)
|
||||||
|
status := "ACTIVE"
|
||||||
if actualPID > 0 {
|
if !active {
|
||||||
pid = actualPID
|
status = "UNKNOWN"
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnel := Tunnel{
|
|
||||||
ID: id,
|
|
||||||
LocalPort: localPort,
|
|
||||||
RemotePort: remotePort,
|
|
||||||
RemoteHost: remoteHost,
|
|
||||||
SSHServer: sshServer,
|
|
||||||
PID: pid,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := saveTunnel(tunnel); err != nil {
|
fmt.Printf("Started SSH tunnel (ID: %d): localhost:%d -> %s:%d (%s) [PID: %d] [Status: %s]\n",
|
||||||
fmt.Printf("Error saving tunnel information: %v\n", err)
|
id, localPort, remoteHost, remotePort, sshServer, pid, status)
|
||||||
if pid > 0 {
|
},
|
||||||
// Try to kill the process
|
}
|
||||||
process, _ := os.FindProcess(pid)
|
|
||||||
if process != nil {
|
|
||||||
process.Kill()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize statistics for this tunnel
|
|
||||||
statsManager, err := stats.NewStatsManager()
|
|
||||||
if err == nil {
|
|
||||||
err = statsManager.InitStats(id, localPort)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Warning: Failed to initialize statistics: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify tunnel is actually working
|
func init() {
|
||||||
time.Sleep(500 * time.Millisecond)
|
rootCmd.AddCommand(startCmd)
|
||||||
active := verifyTunnelActive(localPort)
|
|
||||||
status := "ACTIVE"
|
|
||||||
if !active {
|
|
||||||
status = "UNKNOWN"
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Started SSH tunnel (ID: %d): localhost:%d -> %s:%d (%s) [PID: %d] [Status: %s]\n",
|
// Add flags for the start command
|
||||||
id, localPort, remoteHost, remotePort, sshServer, pid, status)
|
startCmd.Flags().IntVarP(&localPort, "local", "l", 0, "Local port to forward")
|
||||||
},
|
startCmd.Flags().IntVarP(&remotePort, "remote", "r", 0, "Remote port to forward to")
|
||||||
}
|
startCmd.Flags().StringVarP(&remoteHost, "host", "H", "localhost", "Remote host to forward to")
|
||||||
|
startCmd.Flags().StringVarP(&sshServer, "server", "s", "", "SSH server address (user@host)")
|
||||||
|
startCmd.Flags().StringVarP(&identity, "identity", "i", "", "Path to SSH identity file")
|
||||||
|
|
||||||
func init() {
|
// Mark required flags
|
||||||
rootCmd.AddCommand(startCmd)
|
startCmd.MarkFlagRequired("local")
|
||||||
|
startCmd.MarkFlagRequired("remote")
|
||||||
// Add flags for the start command
|
startCmd.MarkFlagRequired("server")
|
||||||
startCmd.Flags().IntVarP(&localPort, "local", "l", 0, "Local port to forward")
|
}
|
||||||
startCmd.Flags().IntVarP(&remotePort, "remote", "r", 0, "Remote port to forward to")
|
|
||||||
startCmd.Flags().StringVarP(&remoteHost, "host", "H", "localhost", "Remote host to forward to")
|
|
||||||
startCmd.Flags().StringVarP(&sshServer, "server", "s", "", "SSH server address (user@host)")
|
|
||||||
startCmd.Flags().StringVarP(&identity, "identity", "i", "", "Path to SSH identity file")
|
|
||||||
|
|
||||||
// Mark required flags
|
|
||||||
startCmd.MarkFlagRequired("local")
|
|
||||||
startCmd.MarkFlagRequired("remote")
|
|
||||||
startCmd.MarkFlagRequired("server")
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyTunnelActive checks if the tunnel is actually working
|
// verifyTunnelActive checks if the tunnel is actually working
|
||||||
func verifyTunnelActive(port int) bool {
|
func verifyTunnelActive(port int) bool {
|
||||||
@@ -138,4 +138,4 @@ func verifyTunnelActive(port int) bool {
|
|||||||
}
|
}
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
10
cmd/stats.go
10
cmd/stats.go
@@ -5,8 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sshtunnel/pkg/monitor"
|
"git.mvl.sh/vleeuwenmenno/sshtunnel/pkg/monitor"
|
||||||
"sshtunnel/pkg/stats"
|
"git.mvl.sh/vleeuwenmenno/sshtunnel/pkg/stats"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -181,7 +181,7 @@ func watchTunnelStats(tunnelID int, interval int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: Tunnel #%d may not be active: %v\n", tunnelID, err)
|
fmt.Printf("Warning: Tunnel #%d may not be active: %v\n", tunnelID, err)
|
||||||
fmt.Println("Attempting to monitor anyway...")
|
fmt.Println("Attempting to monitor anyway...")
|
||||||
|
|
||||||
// Try to get port from stats
|
// Try to get port from stats
|
||||||
statsManager, _ := stats.NewStatsManager()
|
statsManager, _ := stats.NewStatsManager()
|
||||||
s, err := statsManager.GetStats(tunnelID)
|
s, err := statsManager.GetStats(tunnelID)
|
||||||
@@ -226,7 +226,7 @@ func watchTunnelStats(tunnelID int, interval int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: %v\n", err)
|
fmt.Printf("Warning: %v\n", err)
|
||||||
fmt.Println("Displaying minimal statistics...")
|
fmt.Println("Displaying minimal statistics...")
|
||||||
statsStr = fmt.Sprintf("Monitoring tunnel #%d on port %d...",
|
statsStr = fmt.Sprintf("Monitoring tunnel #%d on port %d...",
|
||||||
tunnelID, tunnel.LocalPort)
|
tunnelID, tunnel.LocalPort)
|
||||||
}
|
}
|
||||||
fmt.Println(statsStr)
|
fmt.Println(statsStr)
|
||||||
@@ -241,7 +241,7 @@ func watchTunnelStats(tunnelID int, interval int) {
|
|||||||
|
|
||||||
statsStr, err := mon.FormatStats()
|
statsStr, err := mon.FormatStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
statsStr = fmt.Sprintf("Warning: %v\n\nStill monitoring tunnel #%d on port %d...",
|
statsStr = fmt.Sprintf("Warning: %v\n\nStill monitoring tunnel #%d on port %d...",
|
||||||
err, tunnelID, tunnel.LocalPort)
|
err, tunnelID, tunnel.LocalPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"git.mvl.sh/vleeuwenmenno/sshtunnel/pkg/stats"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"sshtunnel/pkg/stats"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -86,7 +86,7 @@ func killTunnel(t Tunnel) {
|
|||||||
|
|
||||||
removeFile(t.ID)
|
removeFile(t.ID)
|
||||||
cleanupStats(t.ID)
|
cleanupStats(t.ID)
|
||||||
fmt.Printf("Stopped SSH tunnel (ID: %d): localhost:%d -> %s:%d\n",
|
fmt.Printf("Stopped SSH tunnel (ID: %d): localhost:%d -> %s:%d\n",
|
||||||
t.ID, t.LocalPort, t.RemoteHost, t.RemotePort)
|
t.ID, t.LocalPort, t.RemoteHost, t.RemotePort)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,4 +102,4 @@ func cleanupStats(tunnelID int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Warning: Failed to delete statistics for tunnel %d: %v\n", tunnelID, err)
|
fmt.Printf("Warning: Failed to delete statistics for tunnel %d: %v\n", tunnelID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module sshtunnel
|
module git.mvl.sh/vleeuwenmenno/sshtunnel
|
||||||
|
|
||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"sshtunnel/cmd"
|
"git.mvl.sh/vleeuwenmenno/sshtunnel/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sshtunnel/pkg/stats"
|
"git.mvl.sh/vleeuwenmenno/sshtunnel/pkg/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Monitor represents a SSH tunnel traffic monitor
|
// Monitor represents a SSH tunnel traffic monitor
|
||||||
|
|||||||
Reference in New Issue
Block a user