Improve concurrency and resource management #2
90
internal/port/port.go
Normal file
90
internal/port/port.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package port
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"tunnel_pls/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PortManager struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
ports map[uint16]bool
|
||||||
|
sortedPorts []uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var Manager = PortManager{
|
||||||
|
ports: make(map[uint16]bool),
|
||||||
|
sortedPorts: []uint16{},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rawRange := utils.Getenv("ALLOWED_PORTS")
|
||||||
|
splitRange := strings.Split(rawRange, "-")
|
||||||
|
if len(splitRange) != 2 {
|
||||||
|
Manager.AddPortRange(30000, 31000)
|
||||||
|
} else {
|
||||||
|
start, err := strconv.ParseUint(splitRange[0], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
start = 30000
|
||||||
|
}
|
||||||
|
end, err := strconv.ParseUint(splitRange[1], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
end = 31000
|
||||||
|
}
|
||||||
|
Manager.AddPortRange(uint16(start), uint16(end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PortManager) AddPortRange(startPort, endPort uint16) error {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
|
if startPort > endPort {
|
||||||
|
return fmt.Errorf("start port cannot be greater than end port")
|
||||||
|
}
|
||||||
|
for port := startPort; port <= endPort; port++ {
|
||||||
|
if _, exists := pm.ports[port]; !exists {
|
||||||
|
pm.ports[port] = false
|
||||||
|
pm.sortedPorts = append(pm.sortedPorts, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(pm.sortedPorts, func(i, j int) bool {
|
||||||
|
return pm.sortedPorts[i] < pm.sortedPorts[j]
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PortManager) GetUnassignedPort() (uint16, bool) {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
|
for _, port := range pm.sortedPorts {
|
||||||
|
if !pm.ports[port] {
|
||||||
|
pm.ports[port] = true
|
||||||
|
return port, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PortManager) SetPortStatus(port uint16, assigned bool) error {
|
||||||
|
pm.mu.Lock()
|
||||||
|
defer pm.mu.Unlock()
|
||||||
|
|
||||||
|
if _, exists := pm.ports[port]; !exists {
|
||||||
|
return fmt.Errorf("port %d is not in the allowed range", port)
|
||||||
|
}
|
||||||
|
pm.ports[port] = assigned
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *PortManager) GetPortStatus(port uint16) (bool, bool) {
|
||||||
|
pm.mu.RLock()
|
||||||
|
defer pm.mu.RUnlock()
|
||||||
|
|
||||||
|
status, exists := pm.ports[port]
|
||||||
|
return status, exists
|
||||||
|
}
|
||||||
@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
Package proto provides byte-level interaction with HTTP request payload.
|
|
||||||
|
|
||||||
Example of HTTP payload for future references, new line symbols escaped:
|
|
||||||
|
|
||||||
POST /upload HTTP/1.1\r\n
|
|
||||||
User-Agent: Gor\r\n
|
|
||||||
Content-Length: 11\r\n
|
|
||||||
\r\n
|
|
||||||
Hello world
|
|
||||||
|
|
||||||
GET /index.html HTTP/1.1\r\n
|
|
||||||
User-Agent: Gor\r\n
|
|
||||||
\r\n
|
|
||||||
\r\n
|
|
||||||
|
|
||||||
https://github.com/buger/goreplay/blob/master/proto/proto.go
|
|
||||||
*/
|
|
||||||
package proto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Methods = [...]string{
|
|
||||||
http.MethodConnect, http.MethodDelete, http.MethodGet,
|
|
||||||
http.MethodHead, http.MethodOptions, http.MethodPatch,
|
|
||||||
http.MethodPost, http.MethodPut, http.MethodTrace,
|
|
||||||
}
|
|
||||||
|
|
||||||
func Method(payload []byte) []byte {
|
|
||||||
end := bytes.IndexByte(payload, ' ')
|
|
||||||
if end == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload[:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsHttpRequest(payload []byte) bool {
|
|
||||||
method := string(Method(payload))
|
|
||||||
var methodFound bool
|
|
||||||
for _, m := range Methods {
|
|
||||||
if methodFound = method == m; methodFound {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return methodFound
|
|
||||||
}
|
|
||||||
@ -1,846 +0,0 @@
|
|||||||
*, ::before, ::after {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
::backdrop {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
||||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
||||||
*/
|
|
||||||
|
|
||||||
*,
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
/* 1 */
|
|
||||||
border-width: 0;
|
|
||||||
/* 2 */
|
|
||||||
border-style: solid;
|
|
||||||
/* 2 */
|
|
||||||
border-color: #e5e7eb;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
--tw-content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use a consistent sensible line-height in all browsers.
|
|
||||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
||||||
3. Use a more readable tab size.
|
|
||||||
4. Use the user's configured `sans` font-family by default.
|
|
||||||
5. Use the user's configured `sans` font-feature-settings by default.
|
|
||||||
6. Use the user's configured `sans` font-variation-settings by default.
|
|
||||||
7. Disable tap highlights on iOS
|
|
||||||
*/
|
|
||||||
|
|
||||||
html,
|
|
||||||
:host {
|
|
||||||
line-height: 1.5;
|
|
||||||
/* 1 */
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
/* 2 */
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
-o-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
/* 4 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 5 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 6 */
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
/* 7 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove the margin in all browsers.
|
|
||||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Add the correct height in Firefox.
|
|
||||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
|
||||||
3. Ensure horizontal rules are visible by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
height: 0;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-top-width: 1px;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr:where([title]) {
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the default font size and weight for headings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset links to optimize for opt-in styling instead of opt-out.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font weight in Edge and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use the user's configured `mono` font-family by default.
|
|
||||||
2. Use the user's configured `mono` font-feature-settings by default.
|
|
||||||
3. Use the user's configured `mono` font-variation-settings by default.
|
|
||||||
4. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp,
|
|
||||||
pre {
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 2 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 3 */
|
|
||||||
font-size: 1em;
|
|
||||||
/* 4 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
|
||||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
|
||||||
3. Remove gaps between table borders by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
text-indent: 0;
|
|
||||||
/* 1 */
|
|
||||||
border-color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-collapse: collapse;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Change the font styles in all browsers.
|
|
||||||
2. Remove the margin in Firefox and Safari.
|
|
||||||
3. Remove default padding in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-variation-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-size: 100%;
|
|
||||||
/* 1 */
|
|
||||||
font-weight: inherit;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 1 */
|
|
||||||
letter-spacing: inherit;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 1 */
|
|
||||||
margin: 0;
|
|
||||||
/* 2 */
|
|
||||||
padding: 0;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inheritance of text transform in Edge and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Remove default button styles.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input:where([type='button']),
|
|
||||||
input:where([type='reset']),
|
|
||||||
input:where([type='submit']) {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
background-color: transparent;
|
|
||||||
/* 2 */
|
|
||||||
background-image: none;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Use the modern Firefox focus style for all focusable elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-focusring {
|
|
||||||
outline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-ui-invalid {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct vertical alignment in Chrome and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Correct the cursor style of increment and decrement buttons in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button,
|
|
||||||
::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the odd appearance in Chrome and Safari.
|
|
||||||
2. Correct the outline style in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type='search'] {
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
/* 1 */
|
|
||||||
outline-offset: -2px;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inner padding in Chrome and Safari on macOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Change font properties to `inherit` in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
font: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct display in Chrome and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Removes the default spacing and border for appropriate elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
dl,
|
|
||||||
dd,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
hr,
|
|
||||||
figure,
|
|
||||||
p,
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
menu {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset default styling for dialogs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
dialog {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent resizing textareas horizontally by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
|
||||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder,
|
|
||||||
textarea::placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the default cursor for buttons.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make sure disabled buttons don't get the pointer cursor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
|
||||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video,
|
|
||||||
canvas,
|
|
||||||
audio,
|
|
||||||
iframe,
|
|
||||||
embed,
|
|
||||||
object {
|
|
||||||
display: block;
|
|
||||||
/* 1 */
|
|
||||||
vertical-align: middle;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
|
||||||
|
|
||||||
[hidden]:where(:not([hidden="until-found"])) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
.container {
|
|
||||||
max-width: 640px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.container {
|
|
||||||
max-width: 768px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1024px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1280px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1280px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1536px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1536px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-auto {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-12 {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-2 {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-4 {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-6 {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-2 {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-2 {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-16 {
|
|
||||||
margin-top: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-4 {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-block {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-12 {
|
|
||||||
height: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-5 {
|
|
||||||
height: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-6 {
|
|
||||||
height: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.min-h-screen {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-12 {
|
|
||||||
width: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-5 {
|
|
||||||
width: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-6 {
|
|
||||||
width: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-2xl {
|
|
||||||
max-width: 42rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-between {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gap-8 {
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
|
||||||
--tw-space-x-reverse: 0;
|
|
||||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
|
||||||
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded {
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded-lg {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-blue-500 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-800 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-900 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gradient-to-b {
|
|
||||||
background-image: linear-gradient(to bottom, var(--tw-gradient-stops));
|
|
||||||
}
|
|
||||||
|
|
||||||
.from-gray-900 {
|
|
||||||
--tw-gradient-from: #111827 var(--tw-gradient-from-position);
|
|
||||||
--tw-gradient-to: rgb(17 24 39 / 0) var(--tw-gradient-to-position);
|
|
||||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
|
||||||
}
|
|
||||||
|
|
||||||
.to-gray-800 {
|
|
||||||
--tw-gradient-to: #1f2937 var(--tw-gradient-to-position);
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-4 {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-6 {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-4 {
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-16 {
|
|
||||||
padding-top: 4rem;
|
|
||||||
padding-bottom: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-2 {
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-8 {
|
|
||||||
padding-top: 2rem;
|
|
||||||
padding-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-2xl {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
line-height: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-4xl {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-lg {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-xl {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
line-height: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-bold {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-semibold {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-400 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(96 165 250 / var(--tw-text-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-400 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-green-400 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(74 222 128 / var(--tw-text-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-purple-400 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(192 132 252 / var(--tw-text-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-white {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-lg {
|
|
||||||
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
||||||
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.transition-colors {
|
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
transition-duration: 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:bg-blue-600:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:text-white:hover {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.md\:grid-cols-3 {
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,14 +8,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) handleConnection(conn net.Conn) {
|
func (s *Server) handleConnection(conn net.Conn) {
|
||||||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, s.Config)
|
sshConn, chans, forwardingReqs, err := ssh.NewServerConn(conn, s.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to establish SSH connection: %v", err)
|
log.Printf("failed to establish SSH connection: %v", err)
|
||||||
conn.Close()
|
err := conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to close SSH connection: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("SSH connection established:", sshConn.User())
|
log.Println("SSH connection established:", sshConn.User())
|
||||||
|
|
||||||
session.New(sshConn, chans, reqs)
|
newSession := session.New(sshConn, forwardingReqs)
|
||||||
|
for ch := range chans {
|
||||||
|
newSession.ChannelChan <- ch
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(newSession *session.Session) {
|
||||||
|
err := newSession.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to close session: %v", err)
|
||||||
|
}
|
||||||
|
}(newSession)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,17 +5,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/context"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"tunnel_pls/session"
|
"tunnel_pls/session"
|
||||||
"tunnel_pls/utils"
|
"tunnel_pls/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var redirectTLS bool = false
|
var redirectTLS = false
|
||||||
|
|
||||||
func NewHTTPServer() error {
|
func NewHTTPServer() error {
|
||||||
listener, err := net.Listen("tcp", ":80")
|
listener, err := net.Listen("tcp", ":80")
|
||||||
@ -81,23 +78,10 @@ func Handler(conn net.Conn) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keepalive, timeout := parseConnectionDetails(headers)
|
|
||||||
var ctx context.Context
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
if keepalive {
|
|
||||||
if timeout >= 300 {
|
|
||||||
timeout = 300
|
|
||||||
}
|
|
||||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Duration(timeout)*time.Second))
|
|
||||||
} else {
|
|
||||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
|
|
||||||
}
|
|
||||||
|
|
||||||
sshSession.HandleForwardedConnection(session.UserConnection{
|
sshSession.HandleForwardedConnection(session.UserConnection{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
Writer: conn,
|
Writer: conn,
|
||||||
Context: ctx,
|
|
||||||
Cancel: cancel,
|
|
||||||
}, sshSession.Connection)
|
}, sshSession.Connection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -131,42 +115,3 @@ func parseHostFromHeader(data []byte) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConnectionDetails(data []byte) (keepAlive bool, timeout int) {
|
|
||||||
keepAlive = false
|
|
||||||
timeout = 30
|
|
||||||
|
|
||||||
lines := strings.Split(string(data), "\r\n")
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.HasPrefix(strings.ToLower(line), "connection:") {
|
|
||||||
value := strings.TrimSpace(strings.TrimPrefix(strings.ToLower(line), "connection:"))
|
|
||||||
keepAlive = (value == "keep-alive")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if keepAlive {
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.HasPrefix(strings.ToLower(line), "keep-alive:") {
|
|
||||||
value := strings.TrimSpace(strings.TrimPrefix(line, "Keep-Alive:"))
|
|
||||||
|
|
||||||
if strings.Contains(value, "timeout=") {
|
|
||||||
parts := strings.Split(value, ",")
|
|
||||||
for _, part := range parts {
|
|
||||||
part = strings.TrimSpace(part)
|
|
||||||
if strings.HasPrefix(part, "timeout=") {
|
|
||||||
timeoutStr := strings.TrimPrefix(part, "timeout=")
|
|
||||||
if t, err := strconv.Atoi(timeoutStr); err == nil {
|
|
||||||
timeout = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return keepAlive, timeout
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,11 +4,9 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"golang.org/x/net/context"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"tunnel_pls/session"
|
"tunnel_pls/session"
|
||||||
"tunnel_pls/utils"
|
"tunnel_pls/utils"
|
||||||
)
|
)
|
||||||
@ -70,23 +68,10 @@ func HandlerTLS(conn net.Conn) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keepalive, timeout := parseConnectionDetails(headers)
|
|
||||||
var ctx context.Context
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
if keepalive {
|
|
||||||
if timeout >= 300 {
|
|
||||||
timeout = 300
|
|
||||||
}
|
|
||||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Duration(timeout)*time.Second))
|
|
||||||
} else {
|
|
||||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
|
|
||||||
}
|
|
||||||
|
|
||||||
sshSession.HandleForwardedConnection(session.UserConnection{
|
sshSession.HandleForwardedConnection(session.UserConnection{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
Writer: conn,
|
Writer: conn,
|
||||||
Context: ctx,
|
|
||||||
Cancel: cancel,
|
|
||||||
}, sshSession.Connection)
|
}, sshSession.Connection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package session
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -13,9 +14,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
portUtil "tunnel_pls/internal/port"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/net/context"
|
|
||||||
"tunnel_pls/utils"
|
"tunnel_pls/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,10 +29,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UserConnection struct {
|
type UserConnection struct {
|
||||||
Reader io.Reader
|
Reader io.Reader
|
||||||
Writer net.Conn
|
Writer net.Conn
|
||||||
Context context.Context
|
|
||||||
Cancel context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -39,19 +38,6 @@ var (
|
|||||||
Clients = make(map[string]*Session)
|
Clients = make(map[string]*Session)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
Connection *ssh.ServerConn
|
|
||||||
ConnChannels []ssh.Channel
|
|
||||||
GlobalRequest <-chan *ssh.Request
|
|
||||||
Listener net.Listener
|
|
||||||
TunnelType TunnelType
|
|
||||||
ForwardedPort uint16
|
|
||||||
Status SessionStatus
|
|
||||||
Slug string
|
|
||||||
SlugChannel chan bool
|
|
||||||
Done chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerClient(slug string, session *Session) bool {
|
func registerClient(slug string, session *Session) bool {
|
||||||
clientsMutex.Lock()
|
clientsMutex.Lock()
|
||||||
defer clientsMutex.Unlock()
|
defer clientsMutex.Unlock()
|
||||||
@ -90,45 +76,66 @@ func updateClientSlug(oldSlug, newSlug string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) Close() {
|
func (s *Session) safeClose() {
|
||||||
|
s.once.Do(func() {
|
||||||
|
close(s.ChannelChan)
|
||||||
|
close(s.Done)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Close() error {
|
||||||
if s.Listener != nil {
|
if s.Listener != nil {
|
||||||
s.Listener.Close()
|
err := s.Listener.Close()
|
||||||
|
if err != nil && !errors.Is(err, net.ErrClosed) {
|
||||||
|
fmt.Println("1")
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ch := range s.ConnChannels {
|
if s.ConnChannel != nil {
|
||||||
ch.Close()
|
err := s.ConnChannel.Close()
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
fmt.Println("2")
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Connection != nil {
|
if s.Connection != nil {
|
||||||
s.Connection.Close()
|
err := s.Connection.Close()
|
||||||
|
if err != nil && !errors.Is(err, net.ErrClosed) {
|
||||||
|
fmt.Println("3")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Slug != "" {
|
if s.Slug != "" {
|
||||||
unregisterClient(s.Slug)
|
unregisterClient(s.Slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
close(s.Done)
|
if s.TunnelType == TCP {
|
||||||
|
err := portUtil.Manager.SetPortStatus(s.ForwardedPort, false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("4")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.safeClose()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) handleGlobalRequest() {
|
func (s *Session) HandleGlobalRequest(GlobalRequest <-chan *ssh.Request) {
|
||||||
ticker := time.NewTicker(1 * time.Second)
|
for req := range GlobalRequest {
|
||||||
for {
|
switch req.Type {
|
||||||
select {
|
case "tcpip-forward":
|
||||||
case req := <-s.GlobalRequest:
|
s.handleTCPIPForward(req)
|
||||||
ticker.Stop()
|
|
||||||
if req == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if req.Type == "tcpip-forward" {
|
|
||||||
s.handleTCPIPForward(req)
|
|
||||||
} else {
|
|
||||||
req.Reply(false, nil)
|
|
||||||
}
|
|
||||||
case <-s.Done:
|
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case "shell", "pty-req", "window-change":
|
||||||
s.sendMessage(fmt.Sprintf("Please specify the forwarding tunnel. For example: 'ssh %s -p %s -R 443:localhost:8080' \r\n\n\n", utils.Getenv("domain"), utils.Getenv("port")))
|
req.Reply(true, nil)
|
||||||
s.Close()
|
default:
|
||||||
|
log.Println("Unknown request type:", req.Type)
|
||||||
|
req.Reply(false, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,41 +153,69 @@ func (s *Session) handleTCPIPForward(req *ssh.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var portToBind uint32
|
var rawPortToBind uint32
|
||||||
if err := binary.Read(reader, binary.BigEndian, &portToBind); err != nil {
|
if err := binary.Read(reader, binary.BigEndian, &rawPortToBind); err != nil {
|
||||||
log.Println("Failed to read port from payload:", err)
|
log.Println("Failed to read port from payload:", err)
|
||||||
s.sendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port.\r\n", portToBind))
|
s.sendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port. (02) \r\n", rawPortToBind))
|
||||||
req.Reply(false, nil)
|
req.Reply(false, nil)
|
||||||
s.Close()
|
s.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rawPortToBind > 65535 {
|
||||||
|
s.sendMessage(fmt.Sprintf("Port %d is larger then allowed port of 65535. (02)\r\n", rawPortToBind))
|
||||||
|
req.Reply(false, nil)
|
||||||
|
s.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
portToBind := uint16(rawPortToBind)
|
||||||
|
|
||||||
if isBlockedPort(portToBind) {
|
if isBlockedPort(portToBind) {
|
||||||
s.sendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port.\r\n", portToBind))
|
s.sendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port. (02)\r\n", portToBind))
|
||||||
req.Reply(false, nil)
|
req.Reply(false, nil)
|
||||||
s.Close()
|
s.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.sendMessage("\033[H\033[2J")
|
s.sendMessage("\033[H\033[2J")
|
||||||
showWelcomeMessage(s.ConnChannels[0])
|
|
||||||
|
showWelcomeMessage(s.ConnChannel)
|
||||||
s.Status = RUNNING
|
s.Status = RUNNING
|
||||||
|
go s.handleUserInput()
|
||||||
|
|
||||||
if portToBind == 80 || portToBind == 443 {
|
if portToBind == 80 || portToBind == 443 {
|
||||||
s.handleHTTPForward(req, portToBind)
|
s.handleHTTPForward(req, portToBind)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
if portToBind == 0 {
|
||||||
|
unassign, success := portUtil.Manager.GetUnassignedPort()
|
||||||
|
portToBind = unassign
|
||||||
|
if !success {
|
||||||
|
s.sendMessage(fmt.Sprintf("No available port\r\n", portToBind))
|
||||||
|
req.Reply(false, nil)
|
||||||
|
s.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if isUse, isExist := portUtil.Manager.GetPortStatus(portToBind); !isExist || isUse {
|
||||||
|
s.sendMessage(fmt.Sprintf("Port %d is already in use or restricted. Please choose a different port. (03)\r\n", portToBind))
|
||||||
|
req.Reply(false, nil)
|
||||||
|
s.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
portUtil.Manager.SetPortStatus(portToBind, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.handleTCPForward(req, addr, portToBind)
|
s.handleTCPForward(req, addr, portToBind)
|
||||||
}
|
}
|
||||||
|
|
||||||
var blockedReservedPorts = []uint32{1080, 1433, 1521, 1900, 2049, 3306, 3389, 5432, 5900, 6379, 8080, 8443, 9000, 9200, 27017}
|
var blockedReservedPorts = []uint16{1080, 1433, 1521, 1900, 2049, 3306, 3389, 5432, 5900, 6379, 8080, 8443, 9000, 9200, 27017}
|
||||||
|
|
||||||
func isBlockedPort(port uint32) bool {
|
func isBlockedPort(port uint16) bool {
|
||||||
if port == 80 || port == 443 {
|
if port == 80 || port == 443 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if port < 1024 {
|
if port < 1024 && port != 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, p := range blockedReservedPorts {
|
for _, p := range blockedReservedPorts {
|
||||||
@ -191,7 +226,7 @@ func isBlockedPort(port uint32) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) handleHTTPForward(req *ssh.Request, portToBind uint32) {
|
func (s *Session) handleHTTPForward(req *ssh.Request, portToBind uint16) {
|
||||||
s.TunnelType = HTTP
|
s.TunnelType = HTTP
|
||||||
s.ForwardedPort = uint16(portToBind)
|
s.ForwardedPort = uint16(portToBind)
|
||||||
|
|
||||||
@ -220,7 +255,7 @@ func (s *Session) handleHTTPForward(req *ssh.Request, portToBind uint32) {
|
|||||||
req.Reply(true, buf.Bytes())
|
req.Reply(true, buf.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) handleTCPForward(req *ssh.Request, addr string, portToBind uint32) {
|
func (s *Session) handleTCPForward(req *ssh.Request, addr string, portToBind uint16) {
|
||||||
s.TunnelType = TCP
|
s.TunnelType = TCP
|
||||||
log.Printf("Requested forwarding on %s:%d", addr, portToBind)
|
log.Printf("Requested forwarding on %s:%d", addr, portToBind)
|
||||||
|
|
||||||
@ -255,9 +290,8 @@ func (s *Session) acceptTCPConnections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go s.HandleForwardedConnection(UserConnection{
|
go s.HandleForwardedConnection(UserConnection{
|
||||||
Reader: nil,
|
Reader: nil,
|
||||||
Writer: conn,
|
Writer: conn,
|
||||||
Context: context.Background(),
|
|
||||||
}, s.Connection)
|
}, s.Connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,33 +334,19 @@ func (s *Session) waitForRunningStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) sendMessage(message string) {
|
func (s *Session) sendMessage(message string) {
|
||||||
if len(s.ConnChannels) > 0 {
|
if s.ConnChannel != nil {
|
||||||
s.ConnChannels[0].Write([]byte(message))
|
s.ConnChannel.Write([]byte(message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) HandleSessionChannel(newChannel ssh.NewChannel) {
|
func (s *Session) handleUserInput() {
|
||||||
connection, requests, err := newChannel.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Could not accept channel: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.ConnChannels = append(s.ConnChannels, connection)
|
|
||||||
|
|
||||||
go s.handleUserInput(connection)
|
|
||||||
|
|
||||||
go s.handleChannelRequests(connection, requests)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) handleUserInput(connection ssh.Channel) {
|
|
||||||
var commandBuffer bytes.Buffer
|
var commandBuffer bytes.Buffer
|
||||||
buf := make([]byte, 1)
|
buf := make([]byte, 1)
|
||||||
inSlugEditMode := false
|
inSlugEditMode := false
|
||||||
editSlug := s.Slug
|
editSlug := s.Slug
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n, err := connection.Read(buf)
|
n, err := s.ConnChannel.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
log.Printf("Error reading from client: %s", err)
|
log.Printf("Error reading from client: %s", err)
|
||||||
@ -338,16 +358,16 @@ func (s *Session) handleUserInput(connection ssh.Channel) {
|
|||||||
char := buf[0]
|
char := buf[0]
|
||||||
|
|
||||||
if inSlugEditMode {
|
if inSlugEditMode {
|
||||||
s.handleSlugEditMode(connection, &inSlugEditMode, &editSlug, char, &commandBuffer)
|
s.handleSlugEditMode(s.ConnChannel, &inSlugEditMode, &editSlug, char, &commandBuffer)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.Write(buf[:n])
|
s.ConnChannel.Write(buf[:n])
|
||||||
|
|
||||||
if char == 8 || char == 127 {
|
if char == 8 || char == 127 {
|
||||||
if commandBuffer.Len() > 0 {
|
if commandBuffer.Len() > 0 {
|
||||||
commandBuffer.Truncate(commandBuffer.Len() - 1)
|
commandBuffer.Truncate(commandBuffer.Len() - 1)
|
||||||
connection.Write([]byte("\b \b"))
|
s.ConnChannel.Write([]byte("\b \b"))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -360,7 +380,7 @@ func (s *Session) handleUserInput(connection ssh.Channel) {
|
|||||||
|
|
||||||
if commandBuffer.Len() > 0 {
|
if commandBuffer.Len() > 0 {
|
||||||
if char == 13 {
|
if char == 13 {
|
||||||
s.handleCommand(connection, commandBuffer.String(), &inSlugEditMode, &editSlug, &commandBuffer)
|
s.handleCommand(s.ConnChannel, commandBuffer.String(), &inSlugEditMode, &editSlug, &commandBuffer)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
commandBuffer.WriteByte(char)
|
commandBuffer.WriteByte(char)
|
||||||
@ -494,7 +514,7 @@ func (s *Session) handleCommand(connection ssh.Channel, command string, inSlugEd
|
|||||||
connection.Write([]byte("\r\nAvailable commands: /bye, /help, /clear, /slug"))
|
connection.Write([]byte("\r\nAvailable commands: /bye, /help, /clear, /slug"))
|
||||||
case "/clear":
|
case "/clear":
|
||||||
connection.Write([]byte("\033[H\033[2J"))
|
connection.Write([]byte("\033[H\033[2J"))
|
||||||
showWelcomeMessage(s.ConnChannels[0])
|
showWelcomeMessage(s.ConnChannel)
|
||||||
domain := utils.Getenv("domain")
|
domain := utils.Getenv("domain")
|
||||||
if s.TunnelType == HTTP {
|
if s.TunnelType == HTTP {
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
@ -523,20 +543,6 @@ func (s *Session) handleCommand(connection ssh.Channel, command string, inSlugEd
|
|||||||
commandBuffer.Reset()
|
commandBuffer.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) handleChannelRequests(connection ssh.Channel, requests <-chan *ssh.Request) {
|
|
||||||
go s.handleGlobalRequest()
|
|
||||||
|
|
||||||
for req := range requests {
|
|
||||||
switch req.Type {
|
|
||||||
case "shell", "pty-req", "window-change":
|
|
||||||
req.Reply(true, nil)
|
|
||||||
default:
|
|
||||||
log.Println("Unknown request type:", req.Type)
|
|
||||||
req.Reply(false, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) HandleForwardedConnection(conn UserConnection, sshConn *ssh.ServerConn) {
|
func (s *Session) HandleForwardedConnection(conn UserConnection, sshConn *ssh.ServerConn) {
|
||||||
defer conn.Writer.Close()
|
defer conn.Writer.Close()
|
||||||
|
|
||||||
@ -554,45 +560,74 @@ func (s *Session) HandleForwardedConnection(conn UserConnection, sshConn *ssh.Se
|
|||||||
}
|
}
|
||||||
defer channel.Close()
|
defer channel.Close()
|
||||||
|
|
||||||
go handleChannelRequests(reqs, conn, channel, s.SlugChannel)
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Panic in request handler: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for req := range reqs {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if conn.Reader == nil {
|
if conn.Reader == nil {
|
||||||
conn.Reader = bufio.NewReader(conn.Writer)
|
conn.Reader = bufio.NewReader(conn.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
go io.Copy(channel, conn.Reader)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("Panic in reader copy: %v", r)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err := io.Copy(channel, conn.Reader)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, net.ErrClosed) {
|
||||||
|
log.Printf("Error copying from conn.Reader to channel: %v", err)
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
reader := bufio.NewReader(channel)
|
reader := bufio.NewReader(channel)
|
||||||
_, err = reader.Peek(1)
|
|
||||||
if err == io.EOF {
|
peekChan := make(chan error, 1)
|
||||||
s.sendMessage(fmt.Sprintf("\033[33m%s -> [%s] WARNING -- \"%s\"\033[0m\r\n", conn.Writer.RemoteAddr().String(), s.TunnelType, "Could not forward request to the tunnel addr"))
|
go func() {
|
||||||
|
_, err := reader.Peek(1)
|
||||||
|
peekChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-peekChan:
|
||||||
|
if err == io.EOF {
|
||||||
|
s.sendMessage(fmt.Sprintf("\033[33m%s -> [%s] WARNING -- \"Could not forward request to the tunnel addr\"\033[0m\r\n", conn.Writer.RemoteAddr().String(), s.TunnelType))
|
||||||
|
sendBadGatewayResponse(conn.Writer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error peeking channel data: %v", err)
|
||||||
|
s.sendMessage(fmt.Sprintf("\033[33m%s -> [%s] WARNING -- \"Could not forward request to the tunnel addr\"\033[0m\r\n", conn.Writer.RemoteAddr().String(), s.TunnelType))
|
||||||
|
sendBadGatewayResponse(conn.Writer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
log.Printf("Timeout waiting for channel data from %s", conn.Writer.RemoteAddr())
|
||||||
|
s.sendMessage(fmt.Sprintf("\033[33m%s -> [%s] WARNING -- \"Could not forward request to the tunnel addr\"\033[0m\r\n", conn.Writer.RemoteAddr().String(), s.TunnelType))
|
||||||
sendBadGatewayResponse(conn.Writer)
|
sendBadGatewayResponse(conn.Writer)
|
||||||
conn.Writer.Close()
|
return
|
||||||
channel.Close()
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.sendMessage(fmt.Sprintf("\033[32m %s -> [%s] TUNNEL ADDRESS -- \"%s\" \r\n \033[0m", conn.Writer.RemoteAddr().String(), s.TunnelType, timestamp))
|
s.sendMessage(fmt.Sprintf("\033[32m%s -> [%s] TUNNEL ADDRESS -- \"%s\"\033[0m\r\n", conn.Writer.RemoteAddr().String(), s.TunnelType, timestamp))
|
||||||
|
|
||||||
io.Copy(conn.Writer, reader)
|
_, err = io.Copy(conn.Writer, reader)
|
||||||
}
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
log.Printf("Error copying from channel to conn.Writer: %v", err)
|
||||||
func handleChannelRequests(reqs <-chan *ssh.Request, conn UserConnection, channel ssh.Channel, slugChannel <-chan bool) {
|
|
||||||
select {
|
|
||||||
case <-reqs:
|
|
||||||
for req := range reqs {
|
|
||||||
req.Reply(false, nil)
|
|
||||||
}
|
|
||||||
case <-conn.Context.Done():
|
|
||||||
conn.Writer.Close()
|
|
||||||
channel.Close()
|
|
||||||
log.Println("Connection closed by timeout")
|
|
||||||
return
|
|
||||||
case <-slugChannel:
|
|
||||||
conn.Writer.Close()
|
|
||||||
channel.Close()
|
|
||||||
log.Println("Connection closed by slug change")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package session
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TunnelType string
|
type TunnelType string
|
||||||
@ -13,21 +15,39 @@ const (
|
|||||||
UNKNOWN TunnelType = "unknown"
|
UNKNOWN TunnelType = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(conn *ssh.ServerConn, sshChannel <-chan ssh.NewChannel, req <-chan *ssh.Request) *Session {
|
type Session struct {
|
||||||
|
Connection *ssh.ServerConn
|
||||||
|
ConnChannel ssh.Channel
|
||||||
|
Listener net.Listener
|
||||||
|
TunnelType TunnelType
|
||||||
|
ForwardedPort uint16
|
||||||
|
Status SessionStatus
|
||||||
|
Slug string
|
||||||
|
ChannelChan chan ssh.NewChannel
|
||||||
|
Done chan bool
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(conn *ssh.ServerConn, forwardingReq <-chan *ssh.Request) *Session {
|
||||||
session := &Session{
|
session := &Session{
|
||||||
Status: SETUP,
|
Status: SETUP,
|
||||||
Slug: "",
|
Slug: "",
|
||||||
ConnChannels: []ssh.Channel{},
|
ConnChannel: nil,
|
||||||
Connection: conn,
|
Connection: conn,
|
||||||
GlobalRequest: req,
|
TunnelType: UNKNOWN,
|
||||||
TunnelType: UNKNOWN,
|
ChannelChan: make(chan ssh.NewChannel),
|
||||||
SlugChannel: make(chan bool),
|
Done: make(chan bool),
|
||||||
Done: make(chan bool),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for newChannel := range sshChannel {
|
for channel := range session.ChannelChan {
|
||||||
go session.HandleSessionChannel(newChannel)
|
ch, reqs, _ := channel.Accept()
|
||||||
|
if session.ConnChannel == nil {
|
||||||
|
session.ConnChannel = ch
|
||||||
|
session.Status = RUNNING
|
||||||
|
go session.HandleGlobalRequest(forwardingReq)
|
||||||
|
}
|
||||||
|
go session.HandleGlobalRequest(reqs)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
10
staging.bat
10
staging.bat
@ -1,10 +0,0 @@
|
|||||||
@echo off
|
|
||||||
|
|
||||||
REM Start the Go server using Air
|
|
||||||
start "" air
|
|
||||||
|
|
||||||
REM Watch for changes in Tailwind CSS
|
|
||||||
start "" npx tailwindcss -i ./public/input.css -o ./public/output.css --watch
|
|
||||||
|
|
||||||
REM Watch for changes in templates and proxy to Go server
|
|
||||||
start "" cmd /k "templ generate -watch"
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./view/**/*.{html,js,templ}",
|
|
||||||
"./public/*.js"
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
package indexView
|
|
||||||
|
|
||||||
import "tunnel_pls/view/layout"
|
|
||||||
|
|
||||||
templ Main(title, domain string) {
|
|
||||||
@layout.Base(title) {
|
|
||||||
<div class="min-h-screen bg-gradient-to-b from-gray-900 to-gray-800 text-white">
|
|
||||||
<header class="container mx-auto px-4 py-8">
|
|
||||||
<nav class="flex justify-between items-center">
|
|
||||||
<h1 class="text-2xl font-bold">Tunnel Pls</h1>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<main class="container mx-auto px-4 py-16 text-center">
|
|
||||||
<h2 class="text-4xl font-bold mb-6">Secure Remote Access Made Simple</h2>
|
|
||||||
<p class="text-xl mb-12 max-w-2xl mx-auto">
|
|
||||||
SSH Tunnel provides a secure way to access your local services remotely.
|
|
||||||
Connect to your development environment from anywhere, safely.
|
|
||||||
</p>
|
|
||||||
<div class="bg-gray-800 p-6 rounded-lg shadow-lg inline-block">
|
|
||||||
<h3 class="text-2xl font-semibold mb-4 flex items-center justify-center">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
Example Command
|
|
||||||
</h3>
|
|
||||||
<code id="sshCommand" class="text-green-400 text-lg block mb-4 bg-gray-900 p-4 rounded">
|
|
||||||
ssh {domain} -p 2200 -R 80:localhost:8000
|
|
||||||
</code>
|
|
||||||
<button id="copyButton" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors flex items-center justify-center mx-auto">
|
|
||||||
Copy Command
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="mt-16 grid md:grid-cols-3 gap-8">
|
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4 text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
|
||||||
</svg>
|
|
||||||
<h3 class="text-xl font-semibold mb-2">Secure</h3>
|
|
||||||
<p>End-to-end encryption ensures your data remains private and protected.</p>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
<h3 class="text-xl font-semibold mb-2">Easy to Use</h3>
|
|
||||||
<p>Simple command-line interface for quick setup and connection.</p>
|
|
||||||
</div>
|
|
||||||
<div class="bg-gray-800 p-6 rounded-lg">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
|
||||||
</svg>
|
|
||||||
<h3 class="text-xl font-semibold mb-2">Flexible</h3>
|
|
||||||
<p>Connect to various local services and ports with ease.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<footer class="container mx-auto px-4 py-8 text-center text-gray-400">
|
|
||||||
<p>© 2025 SSH Tunnel. All rights reserved.</p>
|
|
||||||
<div class="mt-4 flex justify-center space-x-4">
|
|
||||||
<a href="https://github.com/fossyy" target="_blank" rel="noopener noreferrer" class="hover:text-white">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a href="mailto:bagas@fossy.my.id" class="hover:text-white">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<script>
|
|
||||||
document.getElementById('copyButton').addEventListener('click', function() {
|
|
||||||
const sshCommand = document.getElementById('sshCommand').textContent;
|
|
||||||
navigator.clipboard.writeText(sshCommand).then(function() {
|
|
||||||
alert('Command copied to clipboard!');
|
|
||||||
}, function(err) {
|
|
||||||
console.error('Could not copy text: ', err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.833
|
|
||||||
package indexView
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
import "tunnel_pls/view/layout"
|
|
||||||
|
|
||||||
func Main(title, domain string) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"min-h-screen bg-gradient-to-b from-gray-900 to-gray-800 text-white\"><header class=\"container mx-auto px-4 py-8\"><nav class=\"flex justify-between items-center\"><h1 class=\"text-2xl font-bold\">Tunnel Pls</h1></nav></header><main class=\"container mx-auto px-4 py-16 text-center\"><h2 class=\"text-4xl font-bold mb-6\">Secure Remote Access Made Simple</h2><p class=\"text-xl mb-12 max-w-2xl mx-auto\">SSH Tunnel provides a secure way to access your local services remotely. Connect to your development environment from anywhere, safely.</p><div class=\"bg-gray-800 p-6 rounded-lg shadow-lg inline-block\"><h3 class=\"text-2xl font-semibold mb-4 flex items-center justify-center\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 mr-2\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"></path></svg> Example Command</h3><code id=\"sshCommand\" class=\"text-green-400 text-lg block mb-4 bg-gray-900 p-4 rounded\">ssh ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(domain)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/index/index.templ`, Line: 27, Col: 17}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " -p 2200 -R 80:localhost:8000</code> <button id=\"copyButton\" class=\"mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors flex items-center justify-center mx-auto\">Copy Command <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 ml-2\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\"></path></svg></button></div><div class=\"mt-16 grid md:grid-cols-3 gap-8\"><div class=\"bg-gray-800 p-6 rounded-lg\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-12 w-12 mx-auto mb-4 text-blue-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"></path></svg><h3 class=\"text-xl font-semibold mb-2\">Secure</h3><p>End-to-end encryption ensures your data remains private and protected.</p></div><div class=\"bg-gray-800 p-6 rounded-lg\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-12 w-12 mx-auto mb-4 text-green-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"></path></svg><h3 class=\"text-xl font-semibold mb-2\">Easy to Use</h3><p>Simple command-line interface for quick setup and connection.</p></div><div class=\"bg-gray-800 p-6 rounded-lg\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-12 w-12 mx-auto mb-4 text-purple-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17 8l4 4m0 0l-4 4m4-4H3\"></path></svg><h3 class=\"text-xl font-semibold mb-2\">Flexible</h3><p>Connect to various local services and ports with ease.</p></div></div></main><footer class=\"container mx-auto px-4 py-8 text-center text-gray-400\"><p>© 2025 SSH Tunnel. All rights reserved.</p><div class=\"mt-4 flex justify-center space-x-4\"><a href=\"https://github.com/fossyy\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"hover:text-white\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z\"></path></svg></a> <a href=\"mailto:bagas@fossy.my.id\" class=\"hover:text-white\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"></path></svg></a></div></footer><script>\r\n document.getElementById('copyButton').addEventListener('click', function() {\r\n const sshCommand = document.getElementById('sshCommand').textContent;\r\n navigator.clipboard.writeText(sshCommand).then(function() {\r\n alert('Command copied to clipboard!');\r\n }, function(err) {\r\n console.error('Could not copy text: ', err);\r\n });\r\n });\r\n </script></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
templ_7745c5c3_Err = layout.Base(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
<div class=\"min-h-screen bg-gradient-to-b from-gray-900 to-gray-800 text-white\"><header class=\"container mx-auto px-4 py-8\"><nav class=\"flex justify-between items-center\"><h1 class=\"text-2xl font-bold\">Tunnel Pls</h1></nav></header><main class=\"container mx-auto px-4 py-16 text-center\"><h2 class=\"text-4xl font-bold mb-6\">Secure Remote Access Made Simple</h2><p class=\"text-xl mb-12 max-w-2xl mx-auto\">SSH Tunnel provides a secure way to access your local services remotely. Connect to your development environment from anywhere, safely.</p><div class=\"bg-gray-800 p-6 rounded-lg shadow-lg inline-block\"><h3 class=\"text-2xl font-semibold mb-4 flex items-center justify-center\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6 mr-2\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"></path></svg> Example Command</h3><code id=\"sshCommand\" class=\"text-green-400 text-lg block mb-4 bg-gray-900 p-4 rounded\">ssh
|
|
||||||
-p 2200 -R 80:localhost:8000</code> <button id=\"copyButton\" class=\"mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors flex items-center justify-center mx-auto\">Copy Command <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 ml-2\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\"></path></svg></button></div><div class=\"mt-16 grid md:grid-cols-3 gap-8\"><div class=\"bg-gray-800 p-6 rounded-lg\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-12 w-12 mx-auto mb-4 text-blue-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"></path></svg><h3 class=\"text-xl font-semibold mb-2\">Secure</h3><p>End-to-end encryption ensures your data remains private and protected.</p></div><div class=\"bg-gray-800 p-6 rounded-lg\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-12 w-12 mx-auto mb-4 text-green-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"></path></svg><h3 class=\"text-xl font-semibold mb-2\">Easy to Use</h3><p>Simple command-line interface for quick setup and connection.</p></div><div class=\"bg-gray-800 p-6 rounded-lg\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-12 w-12 mx-auto mb-4 text-purple-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17 8l4 4m0 0l-4 4m4-4H3\"></path></svg><h3 class=\"text-xl font-semibold mb-2\">Flexible</h3><p>Connect to various local services and ports with ease.</p></div></div></main><footer class=\"container mx-auto px-4 py-8 text-center text-gray-400\"><p>© 2025 SSH Tunnel. All rights reserved.</p><div class=\"mt-4 flex justify-center space-x-4\"><a href=\"https://github.com/fossyy\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"hover:text-white\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\"><path d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z\"></path></svg></a> <a href=\"mailto:bagas@fossy.my.id\" class=\"hover:text-white\"><svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"></path></svg></a></div></footer><script>\r\n document.getElementById('copyButton').addEventListener('click', function() {\r\n const sshCommand = document.getElementById('sshCommand').textContent;\r\n navigator.clipboard.writeText(sshCommand).then(function() {\r\n alert('Command copied to clipboard!');\r\n }, function(err) {\r\n console.error('Could not copy text: ', err);\r\n });\r\n });\r\n </script></div>
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package layout
|
|
||||||
|
|
||||||
templ Base(title string){
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="description" content="Secure and reliable file hosting service. Upload, organize, and share your documents, images, videos, and more. Sign up now to keep your files always within reach." />
|
|
||||||
<meta name="keywords" content="file hosting, file sharing, cloud storage, data storage, secure file hosting, filekeeper, drive, mega" />
|
|
||||||
<meta name="author" content="Filekeeper" />
|
|
||||||
<link href="/public/output.css" rel="stylesheet"/>
|
|
||||||
<title>{ title }</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content">
|
|
||||||
{ children... }
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.3.833
|
|
||||||
package layout
|
|
||||||
|
|
||||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
|
||||||
|
|
||||||
import "github.com/a-h/templ"
|
|
||||||
import templruntime "github.com/a-h/templ/runtime"
|
|
||||||
|
|
||||||
func Base(title string) templ.Component {
|
|
||||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
|
||||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
|
||||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
|
||||||
return templ_7745c5c3_CtxErr
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
|
||||||
if !templ_7745c5c3_IsBuffer {
|
|
||||||
defer func() {
|
|
||||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err == nil {
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
ctx = templ.InitializeContext(ctx)
|
|
||||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var1 == nil {
|
|
||||||
templ_7745c5c3_Var1 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"Secure and reliable file hosting service. Upload, organize, and share your documents, images, videos, and more. Sign up now to keep your files always within reach.\"><meta name=\"keywords\" content=\"file hosting, file sharing, cloud storage, data storage, secure file hosting, filekeeper, drive, mega\"><meta name=\"author\" content=\"Filekeeper\"><link href=\"/public/output.css\" rel=\"stylesheet\"><title>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 13, Col: 22}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title></head><body><div id=\"content\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></body></html>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = templruntime.GeneratedTemplate
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"description\" content=\"Secure and reliable file hosting service. Upload, organize, and share your documents, images, videos, and more. Sign up now to keep your files always within reach.\"><meta name=\"keywords\" content=\"file hosting, file sharing, cloud storage, data storage, secure file hosting, filekeeper, drive, mega\"><meta name=\"author\" content=\"Filekeeper\"><link href=\"/public/output.css\" rel=\"stylesheet\"><title>
|
|
||||||
</title></head><body><div id=\"content\">
|
|
||||||
</div></body></html>
|
|
||||||
Reference in New Issue
Block a user