diff --git a/internal/port/port.go b/internal/port/port.go
new file mode 100644
index 0000000..31aafc5
--- /dev/null
+++ b/internal/port/port.go
@@ -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
+}
diff --git a/proto/proto.go b/proto/proto.go
deleted file mode 100644
index 5290e62..0000000
--- a/proto/proto.go
+++ /dev/null
@@ -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
-}
diff --git a/public/output.css b/public/output.css
deleted file mode 100644
index 7cfe1e7..0000000
--- a/public/output.css
+++ /dev/null
@@ -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));
- }
-}
\ No newline at end of file
diff --git a/server/handler.go b/server/handler.go
index d167a55..bf6fd9e 100644
--- a/server/handler.go
+++ b/server/handler.go
@@ -8,14 +8,29 @@ import (
)
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 {
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
}
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
}
diff --git a/server/http.go b/server/http.go
index 46a00d5..36b17e1 100644
--- a/server/http.go
+++ b/server/http.go
@@ -5,17 +5,14 @@ import (
"bytes"
"errors"
"fmt"
- "golang.org/x/net/context"
"log"
"net"
- "strconv"
"strings"
- "time"
"tunnel_pls/session"
"tunnel_pls/utils"
)
-var redirectTLS bool = false
+var redirectTLS = false
func NewHTTPServer() error {
listener, err := net.Listen("tcp", ":80")
@@ -81,23 +78,10 @@ func Handler(conn net.Conn) {
conn.Close()
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{
- Reader: reader,
- Writer: conn,
- Context: ctx,
- Cancel: cancel,
+ Reader: reader,
+ Writer: conn,
}, sshSession.Connection)
return
}
@@ -131,42 +115,3 @@ func parseHostFromHeader(data []byte) string {
}
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
-}
diff --git a/server/https.go b/server/https.go
index 2d83e5b..0b882b7 100644
--- a/server/https.go
+++ b/server/https.go
@@ -4,11 +4,9 @@ import (
"bufio"
"crypto/tls"
"errors"
- "golang.org/x/net/context"
"log"
"net"
"strings"
- "time"
"tunnel_pls/session"
"tunnel_pls/utils"
)
@@ -70,23 +68,10 @@ func HandlerTLS(conn net.Conn) {
conn.Close()
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{
- Reader: reader,
- Writer: conn,
- Context: ctx,
- Cancel: cancel,
+ Reader: reader,
+ Writer: conn,
}, sshSession.Connection)
return
}
diff --git a/session/handler.go b/session/handler.go
index 178e3b1..fb840d7 100644
--- a/session/handler.go
+++ b/session/handler.go
@@ -3,6 +3,7 @@ package session
import (
"bufio"
"bytes"
+ "context"
"encoding/binary"
"errors"
"fmt"
@@ -13,9 +14,9 @@ import (
"strings"
"sync"
"time"
+ portUtil "tunnel_pls/internal/port"
"golang.org/x/crypto/ssh"
- "golang.org/x/net/context"
"tunnel_pls/utils"
)
@@ -28,10 +29,8 @@ const (
)
type UserConnection struct {
- Reader io.Reader
- Writer net.Conn
- Context context.Context
- Cancel context.CancelFunc
+ Reader io.Reader
+ Writer net.Conn
}
var (
@@ -39,19 +38,6 @@ var (
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 {
clientsMutex.Lock()
defer clientsMutex.Unlock()
@@ -90,45 +76,66 @@ func updateClientSlug(oldSlug, newSlug string) bool {
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 {
- 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 {
- ch.Close()
+ if s.ConnChannel != nil {
+ err := s.ConnChannel.Close()
+ if err != nil && !errors.Is(err, io.EOF) {
+ fmt.Println("2")
+ return err
+ }
}
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 != "" {
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() {
- ticker := time.NewTicker(1 * time.Second)
- for {
- select {
- case req := <-s.GlobalRequest:
- ticker.Stop()
- if req == nil {
- return
- }
- if req.Type == "tcpip-forward" {
- s.handleTCPIPForward(req)
- } else {
- req.Reply(false, nil)
- }
- case <-s.Done:
+func (s *Session) HandleGlobalRequest(GlobalRequest <-chan *ssh.Request) {
+ for req := range GlobalRequest {
+ switch req.Type {
+ case "tcpip-forward":
+ s.handleTCPIPForward(req)
return
- case <-ticker.C:
- 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")))
- s.Close()
+ case "shell", "pty-req", "window-change":
+ req.Reply(true, nil)
+ default:
+ log.Println("Unknown request type:", req.Type)
+ req.Reply(false, nil)
}
}
}
@@ -146,41 +153,69 @@ func (s *Session) handleTCPIPForward(req *ssh.Request) {
return
}
- var portToBind uint32
- if err := binary.Read(reader, binary.BigEndian, &portToBind); err != nil {
+ var rawPortToBind uint32
+ if err := binary.Read(reader, binary.BigEndian, &rawPortToBind); err != nil {
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)
s.Close()
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) {
- 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)
s.Close()
return
}
s.sendMessage("\033[H\033[2J")
- showWelcomeMessage(s.ConnChannels[0])
+
+ showWelcomeMessage(s.ConnChannel)
s.Status = RUNNING
+ go s.handleUserInput()
if portToBind == 80 || portToBind == 443 {
s.handleHTTPForward(req, portToBind)
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)
}
-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 {
return false
}
- if port < 1024 {
+ if port < 1024 && port != 0 {
return true
}
for _, p := range blockedReservedPorts {
@@ -191,7 +226,7 @@ func isBlockedPort(port uint32) bool {
return false
}
-func (s *Session) handleHTTPForward(req *ssh.Request, portToBind uint32) {
+func (s *Session) handleHTTPForward(req *ssh.Request, portToBind uint16) {
s.TunnelType = HTTP
s.ForwardedPort = uint16(portToBind)
@@ -220,7 +255,7 @@ func (s *Session) handleHTTPForward(req *ssh.Request, portToBind uint32) {
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
log.Printf("Requested forwarding on %s:%d", addr, portToBind)
@@ -255,9 +290,8 @@ func (s *Session) acceptTCPConnections() {
}
go s.HandleForwardedConnection(UserConnection{
- Reader: nil,
- Writer: conn,
- Context: context.Background(),
+ Reader: nil,
+ Writer: conn,
}, s.Connection)
}
}
@@ -300,33 +334,19 @@ func (s *Session) waitForRunningStatus() {
}
func (s *Session) sendMessage(message string) {
- if len(s.ConnChannels) > 0 {
- s.ConnChannels[0].Write([]byte(message))
+ if s.ConnChannel != nil {
+ s.ConnChannel.Write([]byte(message))
}
}
-func (s *Session) HandleSessionChannel(newChannel ssh.NewChannel) {
- 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) {
+func (s *Session) handleUserInput() {
var commandBuffer bytes.Buffer
buf := make([]byte, 1)
inSlugEditMode := false
editSlug := s.Slug
for {
- n, err := connection.Read(buf)
+ n, err := s.ConnChannel.Read(buf)
if err != nil {
if err != io.EOF {
log.Printf("Error reading from client: %s", err)
@@ -338,16 +358,16 @@ func (s *Session) handleUserInput(connection ssh.Channel) {
char := buf[0]
if inSlugEditMode {
- s.handleSlugEditMode(connection, &inSlugEditMode, &editSlug, char, &commandBuffer)
+ s.handleSlugEditMode(s.ConnChannel, &inSlugEditMode, &editSlug, char, &commandBuffer)
continue
}
- connection.Write(buf[:n])
+ s.ConnChannel.Write(buf[:n])
if char == 8 || char == 127 {
if commandBuffer.Len() > 0 {
commandBuffer.Truncate(commandBuffer.Len() - 1)
- connection.Write([]byte("\b \b"))
+ s.ConnChannel.Write([]byte("\b \b"))
}
continue
}
@@ -360,7 +380,7 @@ func (s *Session) handleUserInput(connection ssh.Channel) {
if commandBuffer.Len() > 0 {
if char == 13 {
- s.handleCommand(connection, commandBuffer.String(), &inSlugEditMode, &editSlug, &commandBuffer)
+ s.handleCommand(s.ConnChannel, commandBuffer.String(), &inSlugEditMode, &editSlug, &commandBuffer)
continue
}
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"))
case "/clear":
connection.Write([]byte("\033[H\033[2J"))
- showWelcomeMessage(s.ConnChannels[0])
+ showWelcomeMessage(s.ConnChannel)
domain := utils.Getenv("domain")
if s.TunnelType == HTTP {
protocol := "http"
@@ -523,20 +543,6 @@ func (s *Session) handleCommand(connection ssh.Channel, command string, inSlugEd
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) {
defer conn.Writer.Close()
@@ -554,45 +560,74 @@ func (s *Session) HandleForwardedConnection(conn UserConnection, sshConn *ssh.Se
}
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 {
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)
- _, err = reader.Peek(1)
- if err == io.EOF {
- 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"))
+
+ peekChan := make(chan error, 1)
+ 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)
- conn.Writer.Close()
- channel.Close()
+ return
+ case <-ctx.Done():
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)
-}
-
-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
+ _, 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)
}
}
diff --git a/session/session.go b/session/session.go
index 9e05b72..63177c5 100644
--- a/session/session.go
+++ b/session/session.go
@@ -2,6 +2,8 @@ package session
import (
"golang.org/x/crypto/ssh"
+ "net"
+ "sync"
)
type TunnelType string
@@ -13,21 +15,39 @@ const (
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{
- Status: SETUP,
- Slug: "",
- ConnChannels: []ssh.Channel{},
- Connection: conn,
- GlobalRequest: req,
- TunnelType: UNKNOWN,
- SlugChannel: make(chan bool),
- Done: make(chan bool),
+ Status: SETUP,
+ Slug: "",
+ ConnChannel: nil,
+ Connection: conn,
+ TunnelType: UNKNOWN,
+ ChannelChan: make(chan ssh.NewChannel),
+ Done: make(chan bool),
}
go func() {
- for newChannel := range sshChannel {
- go session.HandleSessionChannel(newChannel)
+ for channel := range session.ChannelChan {
+ ch, reqs, _ := channel.Accept()
+ if session.ConnChannel == nil {
+ session.ConnChannel = ch
+ session.Status = RUNNING
+ go session.HandleGlobalRequest(forwardingReq)
+ }
+ go session.HandleGlobalRequest(reqs)
}
}()
diff --git a/staging.bat b/staging.bat
deleted file mode 100644
index 4e60c1f..0000000
--- a/staging.bat
+++ /dev/null
@@ -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"
\ No newline at end of file
diff --git a/tailwind.config.js b/tailwind.config.js
deleted file mode 100644
index bc26aed..0000000
--- a/tailwind.config.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-module.exports = {
- content: [
- "./view/**/*.{html,js,templ}",
- "./public/*.js"
- ],
- theme: {
- extend: {},
- },
- plugins: [],
-}
\ No newline at end of file
diff --git a/view/index/index.templ b/view/index/index.templ
deleted file mode 100644
index a36b1ed..0000000
--- a/view/index/index.templ
+++ /dev/null
@@ -1,87 +0,0 @@
-package indexView
-
-import "tunnel_pls/view/layout"
-
-templ Main(title, domain string) {
- @layout.Base(title) {
-
-
-
- Secure Remote Access Made Simple
-
- SSH Tunnel provides a secure way to access your local services remotely.
- Connect to your development environment from anywhere, safely.
-
-
-
-
- Example Command
-
-
- ssh {domain} -p 2200 -R 80:localhost:8000
-
-
-
-
-
-
-
Secure
-
End-to-end encryption ensures your data remains private and protected.
-
-
-
-
Easy to Use
-
Simple command-line interface for quick setup and connection.
-
-
-
-
Flexible
-
Connect to various local services and ports with ease.
-
-
-
-
-
-
- }
-}
diff --git a/view/index/index_templ.go b/view/index/index_templ.go
deleted file mode 100644
index 26434d6..0000000
--- a/view/index/index_templ.go
+++ /dev/null
@@ -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, "Secure Remote Access Made Simple
SSH Tunnel provides a secure way to access your local services remotely. Connect to your development environment from anywhere, safely.
Example Command
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 Secure
End-to-end encryption ensures your data remains private and protected.
Easy to Use
Simple command-line interface for quick setup and connection.
Flexible
Connect to various local services and ports with ease.
")
- 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
diff --git a/view/index/index_templ.txt b/view/index/index_templ.txt
deleted file mode 100644
index 7ef003d..0000000
--- a/view/index/index_templ.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Secure Remote Access Made Simple
SSH Tunnel provides a secure way to access your local services remotely. Connect to your development environment from anywhere, safely.
Example Command
ssh
- -p 2200 -R 80:localhost:8000 Secure
End-to-end encryption ensures your data remains private and protected.
Easy to Use
Simple command-line interface for quick setup and connection.
Flexible
Connect to various local services and ports with ease.
\ No newline at end of file
diff --git a/view/layout/layout.templ b/view/layout/layout.templ
deleted file mode 100644
index 88a658e..0000000
--- a/view/layout/layout.templ
+++ /dev/null
@@ -1,21 +0,0 @@
-package layout
-
-templ Base(title string){
-
-
-
-
-
-
-
-
-
- { title }
-
-
-
- { children... }
-
-
-
-}
\ No newline at end of file
diff --git a/view/layout/layout_templ.go b/view/layout/layout_templ.go
deleted file mode 100644
index aee9514..0000000
--- a/view/layout/layout_templ.go
+++ /dev/null
@@ -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, "")
- 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, "")
- 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, "
")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return nil
- })
-}
-
-var _ = templruntime.GeneratedTemplate
diff --git a/view/layout/layout_templ.txt b/view/layout/layout_templ.txt
deleted file mode 100644
index bfacbd9..0000000
--- a/view/layout/layout_templ.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file