Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package goshs for openSUSE:Factory checked 
in at 2026-03-14 22:22:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/goshs (Old)
 and      /work/SRC/openSUSE:Factory/.goshs.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "goshs"

Sat Mar 14 22:22:57 2026 rev:3 rq:1338866 version:1.1.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/goshs/goshs.changes      2026-03-11 
20:59:26.996140154 +0100
+++ /work/SRC/openSUSE:Factory/.goshs.new.8177/goshs.changes    2026-03-14 
22:24:23.298084496 +0100
@@ -1,0 +2,11 @@
+Sat Mar 14 09:25:12 UTC 2026 - Martin Hauke <[email protected]>
+
+- Update to version 1.1.4
+  * add a new feature where you can tunnel goshs to localhost.run
+    to make it available online even if your network can not be
+    reached directly from the internet.
+    For more information look at https://localhost.run/.
+  Bug Fix
+  * Fixed an upload problem tracked in Issue #134.
+
+-------------------------------------------------------------------

Old:
----
  goshs-1.1.3.tar.gz

New:
----
  goshs-1.1.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ goshs.spec ++++++
--- /var/tmp/diff_new_pack.uMd2sl/_old  2026-03-14 22:24:23.930110679 +0100
+++ /var/tmp/diff_new_pack.uMd2sl/_new  2026-03-14 22:24:23.930110679 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package goshs
 #
-# Copyright (c) 2021-2025, Martin Hauke <[email protected]>
+# Copyright (c) 2021-2026, Martin Hauke <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -16,14 +16,14 @@
 #
 
 Name:           goshs
-Version:        1.1.3
+Version:        1.1.4
 Release:        0
 Summary:        A simple HTTP server
 License:        MIT
 Group:          Productivity/Networking/Web/Servers
 URL:            https://goshs.de/
 #Git-Clone:     https://github.com/patrickhener/goshs.git
-Source:         
https://github.com/patrickhener/goshs/archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz
+Source:         
https://github.com/patrickhener/goshs/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
 Source1:        vendor.tar.gz
 BuildRequires:  go >= 1.24.1
 BuildRequires:  golang-packaging

++++++ goshs-1.1.3.tar.gz -> goshs-1.1.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/README.md new/goshs-1.1.4/README.md
--- old/goshs-1.1.3/README.md   2025-12-16 10:29:53.000000000 +0100
+++ new/goshs-1.1.4/README.md   2026-03-13 15:11:55.000000000 +0100
@@ -1,4 +1,4 @@
-![Version](https://img.shields.io/badge/Version-1.1.3-green)
+![Version](https://img.shields.io/badge/Version-v1.1.4-green)
 
[![GitHub](https://img.shields.io/github/license/patrickhener/goshs)](https://github.com/patrickhener/goshs/blob/master/LICENSE)
 ![GitHub go.mod Go 
version](https://img.shields.io/github/go-mod/go-version/patrickhener/goshs)
 [![GitHub 
issues](https://img.shields.io/github/issues-raw/patrickhener/goshs)](https://github.com/patrickhener/goshs/issues)
@@ -74,6 +74,8 @@
   * Share files without authentication
   * Use Download Limit
   * Use Time Limit
+* Tunnel connection via localhost.run
+  * Expose your local goshs to the internet via localhost.run
 
 # Installation
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/config/config.go 
new/goshs-1.1.4/config/config.go
--- old/goshs-1.1.3/config/config.go    2025-12-16 10:29:53.000000000 +0100
+++ new/goshs-1.1.4/config/config.go    2026-03-13 15:11:55.000000000 +0100
@@ -52,6 +52,7 @@
        SFTPHostKeyFile     string   `json:"sftp_host_keyfile"`
        Whitelist           string   `json:"whitelist"`
        TrustedProxies      string   `json:"trusted_proxies"`
+       Tunnel              bool     `json:"tunnel"`
 }
 
 func Load(configpath string) (Config, error) {
@@ -112,6 +113,7 @@
                SFTPHostKeyFile:     "",
                Whitelist:           "",
                TrustedProxies:      "",
+               Tunnel:              false,
        }
 
        b, err := json.MarshalIndent(defaultConfig, "", "  ")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/example/goshs.json.example 
new/goshs-1.1.4/example/goshs.json.example
--- old/goshs-1.1.3/example/goshs.json.example  2025-12-16 10:29:53.000000000 
+0100
+++ new/goshs-1.1.4/example/goshs.json.example  2026-03-13 15:11:55.000000000 
+0100
@@ -41,5 +41,6 @@
   "sftp_keyfile": "",
   "sftp_host_keyfile": "",
   "whitelist": "",
-  "trusted_proxies": ""
+  "trusted_proxies": "",
+  "tunnel": false
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/goshsversion/version.go 
new/goshs-1.1.4/goshsversion/version.go
--- old/goshs-1.1.3/goshsversion/version.go     2025-12-16 10:29:53.000000000 
+0100
+++ new/goshs-1.1.4/goshsversion/version.go     2026-03-13 15:11:55.000000000 
+0100
@@ -1,3 +1,3 @@
 package goshsversion
 
-var GoshsVersion = "1.1.3"
+var GoshsVersion = "v1.1.4"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/httpserver/server.go 
new/goshs-1.1.4/httpserver/server.go
--- old/goshs-1.1.3/httpserver/server.go        2025-12-16 10:29:53.000000000 
+0100
+++ new/goshs-1.1.4/httpserver/server.go        2026-03-13 15:11:55.000000000 
+0100
@@ -9,12 +9,14 @@
        "net/http"
        "os"
        "runtime"
+       "strings"
        "time"
 
        "github.com/howeyc/gopass"
        "github.com/patrickhener/goshs/ca"
        "github.com/patrickhener/goshs/clipboard"
        "github.com/patrickhener/goshs/logger"
+       "github.com/patrickhener/goshs/tunnel"
        "github.com/patrickhener/goshs/ws"
        "golang.org/x/net/webdav"
        "software.sslmate.com/src/go-pkcs12"
@@ -46,17 +48,29 @@
                mux.Use(fs.ServerHeaderMiddleware)
 
                // Define routes
-               mux.HandleFunc("POST /upload", func(w http.ResponseWriter, r 
*http.Request) {
-                       fs.upload(w, r)
-                       runtime.GC()
-               })
                mux.HandleFunc("POST /", func(w http.ResponseWriter, r 
*http.Request) {
-                       fs.logOnly(w, r)
+                       if strings.HasSuffix(r.URL.Path, "/upload") {
+                               fs.upload(w, r)
+                               runtime.GC()
+                       } else {
+                               fs.logOnly(w, r)
+                       }
                })
                mux.HandleFunc("PUT /", func(w http.ResponseWriter, r 
*http.Request) {
                        fs.put(w, r)
                })
-               mux.HandleFunc("/", fs.handler)
+               mux.HandleFunc("/", func(w http.ResponseWriter, r 
*http.Request) {
+                       if r.Method == http.MethodOptions {
+                               // Handle CORS preflight
+                               w.Header().Set("Access-Control-Allow-Origin", 
"*")
+                               w.Header().Set("Access-Control-Allow-Methods", 
"POST, PUT, OPTIONS")
+                               w.Header().Set("Access-Control-Allow-Headers", 
"Content-Type, Authorization")
+                               w.WriteHeader(http.StatusOK)
+                               fs.logOnly(w, r)
+                       } else {
+                               fs.handler(w, r)
+                       }
+               })
 
                addr = fmt.Sprintf("%+v:%+v", fs.IP, fs.Port)
        case "webdav":
@@ -252,6 +266,17 @@
        // Print all embedded files as info to the console
        fs.PrintEmbeddedFiles()
 
+       // Start tunnel if enabled
+       if fs.Tunnel {
+               t, err := tunnel.Start(fs.IP, fs.Port)
+               if err != nil {
+                       logger.Errorf("error starting tunnel: %+v", err)
+               } else {
+                       defer t.Close()
+                       logger.Infof("Public tunnel URL: %s", t.PublicURL)
+               }
+       }
+
        // Create SharedLinks map
        fs.SharedLinks = make(map[string]SharedLink)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/httpserver/structs.go 
new/goshs-1.1.4/httpserver/structs.go
--- old/goshs-1.1.3/httpserver/structs.go       2025-12-16 10:29:53.000000000 
+0100
+++ new/goshs-1.1.4/httpserver/structs.go       2026-03-13 15:11:55.000000000 
+0100
@@ -84,6 +84,7 @@
        Clipboard      *clipboard.Clipboard
        Whitelist      *Whitelist
        SharedLinks    map[string]SharedLink
+       Tunnel         bool
 }
 
 type httperror struct {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/main.go new/goshs-1.1.4/main.go
--- old/goshs-1.1.3/main.go     2025-12-16 10:29:53.000000000 +0100
+++ new/goshs-1.1.4/main.go     2026-03-13 15:11:55.000000000 +0100
@@ -66,6 +66,7 @@
        trustedProxies      = ""
        MDNS                = false
        invisible           = false
+       tunnel              = false
 )
 
 // Man page
@@ -91,6 +92,7 @@
   -c,  --cli            Enable cli (only with auth and tls)     (default: 
false)
   -e,  --embedded       Show embedded files in UI               (default: 
false)
   -o,  --output         Write output to logfile                 (default: 
false)
+  -t,  --tunnel         Enable tunnel                           (default: 
false)
 
 TLS options:
   -s,     --ssl           Use TLS
@@ -240,6 +242,8 @@
        flag.BoolVar(&MDNS, "mdns", MDNS, "Enable zeroconf mDNS registration")
        flag.BoolVar(&invisible, "I", invisible, "Enable invisible mode")
        flag.BoolVar(&invisible, "invisible", invisible, "Enable invisible 
mode")
+       flag.BoolVar(&tunnel, "t", tunnel, "Enable tunnel")
+       flag.BoolVar(&tunnel, "tunnel", tunnel, "Enable tunnel")
 
        updateGoshs := flag.Bool("update", false, "update")
        hash := flag.Bool("H", false, "hash")
@@ -412,6 +416,7 @@
                whitelist = cfg.Whitelist
                trustedProxies = cfg.TrustedProxies
                invisible = cfg.Invisible
+               tunnel = cfg.Tunnel
 
                // Abspath for webroot
                // Trim trailing / for linux/mac and \ for windows
@@ -565,6 +570,7 @@
                Webhook:      *webhook,
                Verbose:      verbose,
                Whitelist:    wl,
+               Tunnel:       tunnel,
                Version:      goshsversion.GoshsVersion,
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/goshs-1.1.3/tunnel/tunnel.go 
new/goshs-1.1.4/tunnel/tunnel.go
--- old/goshs-1.1.3/tunnel/tunnel.go    1970-01-01 01:00:00.000000000 +0100
+++ new/goshs-1.1.4/tunnel/tunnel.go    2026-03-13 15:11:55.000000000 +0100
@@ -0,0 +1,189 @@
+package tunnel
+
+import (
+       "bufio"
+       "fmt"
+       "io"
+       "net"
+       "strings"
+       "time"
+
+       "github.com/patrickhener/goshs/logger"
+       "golang.org/x/crypto/ssh"
+)
+
+type Tunnel struct {
+       PublicURL string
+       client    *ssh.Client
+       session   *ssh.Session
+       stop      chan struct{}
+}
+
+type closeWriter interface {
+       CloseWrite() error
+}
+
+func Start(localIP string, localPort int) (*Tunnel, error) {
+       config := &ssh.ClientConfig{
+               User:            "nokey",
+               Auth:            []ssh.AuthMethod{ssh.Password("")},
+               HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+               Timeout:         10 * time.Second,
+               BannerCallback:  func(banner string) error { return nil },
+       }
+
+       client, err := ssh.Dial("tcp", "localhost.run:22", config)
+       if err != nil {
+               return nil, fmt.Errorf("connecting to localhost.run: %w", err)
+       }
+
+       // Register handler for forwarded-tcpip BEFORE sending the global
+       // request — channels can arrive immediately after the request is
+       // accepted, before we get to Accept() calls
+       chanCh := client.HandleChannelOpen("forwarded-tcpip")
+       if chanCh == nil {
+               client.Close()
+               return nil, fmt.Errorf("could not register forwarded-tcpip 
handler")
+       }
+
+       // Send the tcpip-forward global request directly instead of using
+       // client.Listen() — this avoids the address matching issue entirely
+       ok, _, err := client.SendRequest("tcpip-forward", true, 
ssh.Marshal(struct {
+               BindAddr string
+               BindPort uint32
+       }{"localhost", 80}))
+       if err != nil || !ok {
+               client.Close()
+               return nil, fmt.Errorf("tcpip-forward request failed: %w", err)
+       }
+
+       // Open session for URL capture
+       session, err := client.NewSession()
+       if err != nil {
+               client.Close()
+               return nil, fmt.Errorf("opening session: %w", err)
+       }
+
+       pr, pw := io.Pipe()
+       session.Stdout = pw
+
+       if err := session.RequestPty("xterm", 80, 40, ssh.TerminalModes{
+               ssh.ECHO: 0,
+       }); err != nil {
+               session.Close()
+               client.Close()
+               return nil, fmt.Errorf("requesting pty: %w", err)
+       }
+
+       if err := session.Shell(); err != nil {
+               session.Close()
+               client.Close()
+               return nil, fmt.Errorf("requesting shell: %w", err)
+       }
+
+       urlCh := make(chan string, 1)
+       errCh := make(chan error, 1)
+
+       go func() {
+               found := false
+               scanner := bufio.NewScanner(pr)
+               for scanner.Scan() {
+                       line := strings.TrimSpace(scanner.Text())
+                       if !found && strings.Contains(line, "tunneled with tls 
termination") {
+                               parts := strings.Split(line, ", ")
+                               if len(parts) == 2 && 
strings.HasPrefix(parts[1], "https://";) {
+                                       urlCh <- strings.TrimSpace(parts[1])
+                                       found = true
+                               }
+                       }
+               }
+               if !found {
+                       errCh <- fmt.Errorf("session ended before URL was 
found")
+               }
+       }()
+
+       select {
+       case url := <-urlCh:
+               t := &Tunnel{
+                       PublicURL: url,
+                       client:    client,
+                       session:   session,
+                       stop:      make(chan struct{}),
+               }
+               go t.accept(chanCh, localIP, localPort)
+               return t, nil
+       case err := <-errCh:
+               session.Close()
+               client.Close()
+               return nil, fmt.Errorf("capturing URL: %w", err)
+       case <-time.After(15 * time.Second):
+               session.Close()
+               client.Close()
+               return nil, fmt.Errorf("timed out waiting for tunnel URL")
+       }
+}
+
+// accept handles incoming forwarded-tcpip new channel requests directly,
+// bypassing the address matching in client.Listen()
+func (t *Tunnel) accept(chanCh <-chan ssh.NewChannel, localIP string, 
localPort int) {
+       for {
+               select {
+               case <-t.stop:
+                       return
+               case newChan, ok := <-chanCh:
+                       if !ok {
+                               return
+                       }
+                       logger.Debugf("tunnel: incoming forwarded-tcpip 
channel")
+                       ch, reqs, err := newChan.Accept()
+                       if err != nil {
+                               logger.Debugf("tunnel: accepting channel 
failed: %v", err)
+                               continue
+                       }
+                       // Discard channel-level requests (window adjustments 
etc.)
+                       go ssh.DiscardRequests(reqs)
+                       go proxy(ch, localIP, localPort)
+               }
+       }
+}
+
+func (t *Tunnel) Close() {
+       close(t.stop)
+       t.session.Close()
+       t.client.Close()
+}
+func proxy(remote ssh.Channel, localIP string, localPort int) {
+       defer remote.Close()
+       logger.Debugf("tunnel: proxy connecting to %s:%d", localIP, localPort)
+       local, err := net.DialTimeout("tcp",
+               fmt.Sprintf("%s:%d", localIP, localPort), 5*time.Second)
+       if err != nil {
+               logger.Debugf("tunnel: proxy dial failed: %v", err)
+               return
+       }
+       defer local.Close()
+       logger.Debugf("tunnel: proxy connection established, copying")
+
+       done := make(chan struct{}, 2)
+
+       go func() {
+               n, err := io.Copy(local, remote)
+               logger.Debugf("tunnel: remote→local done: %d bytes, err: %v", 
n, err)
+               if cw, ok := local.(closeWriter); ok {
+                       cw.CloseWrite()
+               }
+               done <- struct{}{}
+       }()
+
+       go func() {
+               n, err := io.Copy(remote, local)
+               logger.Debugf("tunnel: local→remote done: %d bytes, err: %v", 
n, err)
+               if cw, ok := remote.(closeWriter); ok {
+                       cw.CloseWrite()
+               }
+               done <- struct{}{}
+       }()
+
+       <-done
+       <-done
+}

++++++ vendor.tar.gz ++++++

Reply via email to