├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── goreleaser.yml ├── main.go ├── pkg ├── client │ └── client.go ├── namesgenerator │ └── namesgenerator.go ├── server │ └── server.go ├── utils │ └── utils.go └── wsconn │ └── websocket.go ├── static ├── banner.jpg ├── dropzone.css ├── dropzone.js ├── hostyoself.png ├── hostyoself2.png ├── inception.gif ├── main.js └── style.css └── templates ├── files.html └── view.html /.gitattributes: -------------------------------------------------------------------------------- 1 | static/* linguist-vendored 2 | templates/* linguist-vendored -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: schollz 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/server/assets.go 2 | *~ 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ################################### 2 | # 1. Build in a Go-based image # 3 | ################################### 4 | FROM golang:1.12-alpine as builder 5 | RUN apk add --no-cache git ca-certificates # add deps here (like make) if needed 6 | WORKDIR /go/hostyoself 7 | COPY . . 8 | # any pre-requisities to building should be added here 9 | RUN go generate -v 10 | RUN go build -v 11 | 12 | ################################### 13 | # 2. Copy into a clean image # 14 | ################################### 15 | FROM alpine:latest 16 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 17 | COPY --from=builder /go/hostyoself/hostyoself /hostyoself 18 | VOLUME /data 19 | CMD ["sh","-c","/hostyoself host --folder /data"] 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | hostyoself 4 |
5 | Version

9 | 10 |

A hosting service from the browser, because why not. Try it at hostyoself.com.

11 | 12 | ## See it in action 13 | 14 | Here's an example where I use [hostyoself.com](https://hostyoself.com) to host itself. I use `wget` to download [hostyoself.com](https://hostyoself.com) and then host [hostyoself.com](https://hostyoself.com) from [hostyoself.com](https://hostyoself.com): [hostyoself.com/hostyoself/](https://hostyoself.com/hostyoself/). Happy 9th Anniversary [Inception](https://en.wikipedia.org/wiki/Inception) :cake:! 15 | 16 | ![Inception](/static/inception.gif) 17 | 18 | 19 | ## Host from the browser 20 | 21 | Open [hostyoself.com](https://hostyoself.com) and drag and drop a folder, or select a file. Your browser will host the files! 22 | 23 | 24 | ## Host from the command line 25 | 26 | You can host files directly from the terminal! 27 | 28 | ``` 29 | $ hostyoself host 30 | https://hostyoself.com/confidentcat/ 31 | ``` 32 | 33 | Now if you have a file in your folder `README.md` you can access it with the public URL `https://hostyoself.com/confidentcat/README.md`, directly from your computer! 34 | 35 | If you're on a Mac, you can install with Homebrew: 36 | 37 | ``` 38 | brew tap schollz/homebrew https://github.com/schollz/homebrew-tap.git 39 | brew install hostyoself 40 | ``` 41 | 42 | Or you can host your current directory using Docker: 43 | 44 | ``` 45 | $ docker run -v `pwd`:/data schollz/hostyoself 46 | ``` 47 | 48 | ## Run your own relay 49 | 50 | Want to run your own relay? Its easy. 51 | 52 | ``` 53 | $ hostyoself relay --url https://yoururl 54 | ``` 55 | 56 | ## FAQ 57 | 58 | 59 | **How do I start web hosting?** You will need to setup port forwarding, a dynamic DNS, name registration, MySQL, PHP, Apache and take a online course in Javascript. 60 | 61 | Just *kidding*! You don't need any of that crap. Just goto [hostyoself.com](https://hostyoself.com) drag and drop a folder, or select a file. That's literally it. Now you can host a website from your laptop or your phone or your smartwatch or your toaster. 62 | 63 | **How is this possible?** When the server you point at gets a request for a webpage, the server turns back and asks *you* for that content and will use what you provide for the original request. 64 | 65 | **Seriously, how is this possible?** The relay uses websockets in your browser to process GET commands. 66 | 67 | **Won't my website disappear when I close my browser?** Yep! There is a [command-line tool](https://github.com/schollz/hostyoself#host-from-the-command-line) that doesn't require a browser so it can run in the background if you need that. But yes, if your computer turns off then your site is down. Welcome to the joys of hosting a site on the internet. 68 | 69 | **Won't I have to reload my browser if I change a file?** Yep! Welcome to the joys of Javascript. 70 | 71 | **What's the largest file I can host using this?** `¯\_(ツ)_/¯` 72 | 73 | **Should I use this to host a website?** Dear god yes. 74 | 75 | **Does this use AI or blockchain?** Sure, why not. 76 | 77 | **Does it scale?** Horizontally, or vertically? Probably neither! 78 | 79 | **What inspired this?** [beaker browser](https://beakerbrowser.com/), [ngrok](https://ngrok.com/), [localhost.run](http://localhost.run/), [inlets.dev](https://github.com/alexellis/inlets), Parks and Recreation. 80 | 81 | **What's the point of this?** You can host a website! You can share a file! Anything you want, directly from your browser! 82 | 83 | ## Develop 84 | 85 | ``` 86 | $ git clone https://github.com/schollz/hostyoself 87 | $ cd hostyoself 88 | $ go generate -v -x 89 | $ go build -v 90 | ``` 91 | 92 | ## License 93 | 94 | MIT 95 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/schollz/hostyoself 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/fsnotify/fsnotify v1.4.7 7 | github.com/gorilla/websocket v1.4.0 8 | github.com/h2non/filetype v1.0.8 9 | github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect 10 | github.com/schollz/logger v1.0.1 11 | github.com/urfave/cli v1.20.0 12 | github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb 13 | golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 2 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 3 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 4 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 5 | github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14= 6 | github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= 7 | github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts= 8 | github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs= 9 | github.com/schollz/logger v1.0.1 h1:BuBAU+euqphM0Ny9qFVScl4RSxatis4nCHIkOxO2cUU= 10 | github.com/schollz/logger v1.0.1/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM= 11 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 12 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 13 | github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb h1:lyL3z7vYwTWXf4/bI+A01+cCSnfhKIBhy+SQ46Z/ml8= 14 | github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= 15 | golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= 16 | golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | -------------------------------------------------------------------------------- /goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: hostyoself 2 | build: 3 | main: main.go 4 | binary: hostyoself 5 | ldflags: -s -w -X main.Version="v{{.Version}}-{{.Date}}" 6 | env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - darwin 10 | - linux 11 | - windows 12 | - freebsd 13 | - netbsd 14 | - openbsd 15 | - dragonfly 16 | goarch: 17 | - amd64 18 | - 386 19 | - arm 20 | - arm64 21 | goarm: 22 | - 7 23 | nfpm: 24 | formats: 25 | - deb 26 | vendor: "schollz.com" 27 | homepage: "https://schollz.com/software/hostyoself/" 28 | maintainer: "schollz " 29 | description: "A simple, secure, and fast way to transfer data." 30 | license: "MIT" 31 | name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}" 32 | replacements: 33 | amd64: 64bit 34 | 386: 32bit 35 | arm: ARM 36 | arm64: ARM64 37 | darwin: macOS 38 | linux: Linux 39 | windows: Windows 40 | openbsd: OpenBSD 41 | netbsd: NetBSD 42 | freebsd: FreeBSD 43 | dragonfly: DragonFlyBSD 44 | archives: 45 | - 46 | format: tar.gz 47 | format_overrides: 48 | - goos: windows 49 | format: zip 50 | name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}" 51 | replacements: 52 | amd64: 64bit 53 | 386: 32bit 54 | arm: ARM 55 | arm64: ARM64 56 | darwin: macOS 57 | linux: Linux 58 | windows: Windows 59 | openbsd: OpenBSD 60 | netbsd: NetBSD 61 | freebsd: FreeBSD 62 | dragonfly: DragonFlyBSD 63 | files: 64 | - README.md 65 | - LICENSE 66 | 67 | brew: 68 | github: 69 | owner: schollz 70 | name: homebrew-tap 71 | folder: Formula 72 | description: "hostyoself lets you host your files using websockets from the command line or a browser." 73 | homepage: "https://schollz.com/software/hostyoself/" 74 | install: | 75 | bin.install "hostyoself" 76 | 77 | test: | 78 | system "#{bin}/hostyoself --version" 79 | 80 | scoop: 81 | bucket: 82 | owner: schollz 83 | name: scoop-bucket 84 | homepage: "https://schollz.com/software/hostyoself/" 85 | description: "hostyoself lets you host your files using websockets from the command line or a browser." 86 | license: MIT -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go get -v github.com/jteeuwen/go-bindata/go-bindata 4 | //go:generate go-bindata -pkg server -o pkg/server/assets.go templates/ static/ 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | "os" 10 | "runtime" 11 | "strings" 12 | "time" 13 | 14 | "github.com/schollz/hostyoself/pkg/client" 15 | "github.com/schollz/hostyoself/pkg/server" 16 | log "github.com/schollz/logger" 17 | "github.com/urfave/cli" 18 | ) 19 | 20 | func init() { 21 | rand.Seed(time.Now().UTC().UnixNano()) 22 | } 23 | 24 | var Version string 25 | 26 | func main() { 27 | runtime.GOMAXPROCS(runtime.NumCPU()) 28 | 29 | app := cli.NewApp() 30 | app.Name = "hostyoself" 31 | if Version == "" { 32 | Version = "v0.0.0" 33 | } 34 | app.Version = Version 35 | app.Compiled = time.Now() 36 | app.Usage = "host your files using websockets from the command line or a browser" 37 | app.UsageText = "use to transfer files or host an impromptu website" 38 | app.Commands = []cli.Command{ 39 | { 40 | Name: "relay", 41 | Usage: "start a relay", 42 | Description: "relay is used to transit files", 43 | Flags: []cli.Flag{ 44 | cli.StringFlag{Name: "url, u", Value: "localhost", Usage: "public URL to use"}, 45 | cli.StringFlag{Name: "port", Value: "8010", Usage: "ports of the local relay"}, 46 | }, 47 | HelpName: "hostyoself relay", 48 | Action: func(c *cli.Context) error { 49 | return relay(c) 50 | }, 51 | }, 52 | { 53 | Name: "host", 54 | Usage: "host files from your computer", 55 | Description: "host files from your computer", 56 | HelpName: "hostyoself relay", 57 | Flags: []cli.Flag{ 58 | cli.StringFlag{Name: "url, u", Value: "https://hostyoself.com", Usage: "URL of relay to connect"}, 59 | cli.StringFlag{Name: "domain, d", Value: "", Usage: "domain to use (default is random)"}, 60 | cli.StringFlag{Name: "key, k", Value: "", Usage: "key value to use (default is random)"}, 61 | cli.StringFlag{Name: "folder, f", Value: ".", Usage: "folder to serve files"}, 62 | }, 63 | Action: func(c *cli.Context) error { 64 | return host(c) 65 | }, 66 | }, 67 | } 68 | app.Flags = []cli.Flag{ 69 | cli.BoolFlag{Name: "debug", Usage: "increase verbosity"}, 70 | } 71 | app.EnableBashCompletion = true 72 | app.HideHelp = false 73 | app.HideVersion = false 74 | app.BashComplete = func(c *cli.Context) { 75 | fmt.Fprintf(c.App.Writer, "host\nrelay") 76 | } 77 | 78 | err := app.Run(os.Args) 79 | if err != nil { 80 | log.Debug(err) 81 | } 82 | } 83 | 84 | func host(c *cli.Context) (err error) { 85 | if c.GlobalBool("debug") { 86 | log.SetLevel("debug") 87 | } else { 88 | log.SetLevel("info") 89 | } 90 | 91 | cl, err := client.New(c.String("domain"), c.String("key"), c.String("url"), c.String("folder")) 92 | if err != nil { 93 | return 94 | } 95 | for { 96 | log.Info("serving forever") 97 | err = cl.Run() 98 | if err != nil { 99 | log.Debug(err) 100 | } 101 | log.Infof("server disconnected, retrying in 10 seconds") 102 | time.Sleep(10 * time.Second) 103 | } 104 | } 105 | 106 | func relay(c *cli.Context) (err error) { 107 | if c.GlobalBool("debug") { 108 | log.SetLevel("debug") 109 | } else { 110 | log.SetLevel("info") 111 | } 112 | 113 | flagPublicURL := c.String("url") 114 | if flagPublicURL == "localhost" { 115 | flagPublicURL += ":" + c.String("port") 116 | } 117 | if !strings.HasPrefix(flagPublicURL, "http") { 118 | flagPublicURL = "http://" + flagPublicURL 119 | } 120 | 121 | s := server.New(flagPublicURL, c.String("port")) 122 | return s.Run() 123 | } 124 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "strings" 11 | "sync" 12 | 13 | "github.com/schollz/hostyoself/pkg/namesgenerator" 14 | "github.com/schollz/hostyoself/pkg/server" 15 | "github.com/schollz/hostyoself/pkg/utils" 16 | "github.com/schollz/hostyoself/pkg/wsconn" 17 | 18 | "github.com/fsnotify/fsnotify" 19 | "github.com/gorilla/websocket" 20 | log "github.com/schollz/logger" 21 | "github.com/vincent-petithory/dataurl" 22 | ) 23 | 24 | type client struct { 25 | WebsocketURL string 26 | Domain string 27 | Key string 28 | Folder string 29 | fileList map[string]struct{} 30 | sync.Mutex 31 | } 32 | 33 | // New returns a new client 34 | func New(domain, key, webocketURL, folder string) (c *client, err error) { 35 | if strings.HasPrefix(webocketURL, "http") { 36 | webocketURL = strings.Replace(webocketURL, "http", "ws", 1) 37 | } 38 | webocketURL += "/ws" 39 | 40 | if domain == "" { 41 | domain = namesgenerator.GetRandomName() 42 | } 43 | 44 | if key == "" { 45 | key = utils.RandStringBytesMaskImpr(6) 46 | } 47 | 48 | if folder == "" { 49 | folder = "." 50 | } 51 | 52 | folder, _ = filepath.Abs(folder) 53 | folder = filepath.ToSlash(folder) 54 | 55 | if _, err = os.Stat(folder); os.IsNotExist(err) { 56 | log.Error(err) 57 | return 58 | } 59 | 60 | log.Infof("connecting to %s", webocketURL) 61 | log.Infof("using domain '%s'", domain) 62 | log.Infof("using key '%s'", key) 63 | log.Infof("watching folder '%s'", folder) 64 | publicURL := strings.Replace(webocketURL, "ws", "http", 1) 65 | publicURL = strings.Replace(publicURL, "/ws", "/"+domain+"/", 1) 66 | fmt.Printf("\n\t%s\n\n", publicURL) 67 | 68 | c = &client{ 69 | WebsocketURL: webocketURL, 70 | Domain: domain, 71 | Key: key, 72 | Folder: folder, 73 | fileList: make(map[string]struct{}), 74 | } 75 | return 76 | } 77 | 78 | func (c *client) Run() (err error) { 79 | go c.watchFileSystem() 80 | 81 | log.Debugf("dialing %s", c.WebsocketURL) 82 | wsDial, _, err := websocket.DefaultDialer.Dial(c.WebsocketURL, nil) 83 | if err != nil { 84 | log.Error(err) 85 | return 86 | } 87 | defer wsDial.Close() 88 | 89 | ws := wsconn.New(wsDial) 90 | 91 | err = ws.Send(wsconn.Payload{ 92 | Type: "domain", 93 | Message: c.Domain, 94 | Key: c.Key, 95 | }) 96 | if err != nil { 97 | log.Error(err) 98 | return 99 | } 100 | 101 | for { 102 | var p wsconn.Payload 103 | p, err = ws.Receive() 104 | if err != nil { 105 | log.Debug(err) 106 | return 107 | } 108 | log.Debugf("recv: %+v", p) 109 | 110 | if p.Type == "get" { 111 | haveFile := false 112 | c.Lock() 113 | _, haveFile = c.fileList[p.Message] 114 | c.Unlock() 115 | if !haveFile { 116 | err = ws.Send(wsconn.Payload{ 117 | Type: "get", 118 | Success: false, 119 | Message: "no such file", 120 | Key: c.Key, 121 | }) 122 | log.Infof("%s /%s 404", p.IPAddress, p.Message) 123 | } else { 124 | var b []byte 125 | 126 | b, err = ioutil.ReadFile(path.Join(c.Folder, p.Message)) 127 | if err != nil { 128 | log.Error(err) 129 | return 130 | } 131 | err = ws.Send(wsconn.Payload{ 132 | Type: "get", 133 | Success: true, 134 | Message: dataurl.EncodeBytes(b), 135 | Key: c.Key, 136 | }) 137 | log.Infof("%s /%s 200", p.IPAddress, p.Message) 138 | } 139 | } else if p.Type == "files" { 140 | c.Lock() 141 | fs := make([]server.File, len(c.fileList)) 142 | i := 0 143 | for n := range c.fileList { 144 | fs[i] = server.File{ 145 | FullPath: n, 146 | Upload: server.Upload{ 147 | UUID: "", 148 | Total: 0, 149 | Filename: "", 150 | }, 151 | } 152 | i++ 153 | } 154 | c.Unlock() 155 | 156 | b, _ := json.Marshal(fs) 157 | log.Infof("%s sitemap", p.IPAddress) 158 | err = ws.Send(wsconn.Payload{ 159 | Type: "files", 160 | Success: true, 161 | Message: string(b), 162 | Key: c.Key, 163 | }) 164 | } 165 | if err != nil { 166 | log.Debug(err) 167 | return 168 | } 169 | 170 | } 171 | 172 | return 173 | } 174 | 175 | func (c *client) watchFileSystem() (err error) { 176 | // creates a new file watcher 177 | watcher, err := fsnotify.NewWatcher() 178 | if err != nil { 179 | return err 180 | } 181 | defer watcher.Close() 182 | 183 | done := make(chan bool) 184 | go func() { 185 | for { 186 | select { 187 | case event, ok := <-watcher.Events: 188 | if !ok { 189 | return 190 | } 191 | log.Debugf("event: [%s] [%s]", event.Name, strings.ToLower(event.Op.String())) 192 | c.Lock() 193 | switch strings.ToLower(event.Op.String()) { 194 | case "create": 195 | c.fileList[filepath.ToSlash(event.Name)] = struct{}{} 196 | case "remove": 197 | delete(c.fileList, filepath.ToSlash(event.Name)) 198 | } 199 | log.Debugf("map: %+v", c.fileList) 200 | c.Unlock() 201 | case err, ok := <-watcher.Errors: 202 | if !ok { 203 | return 204 | } 205 | log.Error("error:", err) 206 | } 207 | } 208 | }() 209 | 210 | filepath.Walk(c.Folder, func(ppath string, fi os.FileInfo, err error) error { 211 | if err != nil { 212 | log.Errorf("problem with '%s': %s", ppath, err.Error()) 213 | return err 214 | } 215 | ppath = filepath.ToSlash(ppath) 216 | if strings.Contains(ppath, ".git") { 217 | return nil 218 | } 219 | if fi.Mode().IsDir() { 220 | log.Debugf("watching %s", ppath) 221 | return watcher.Add(ppath) 222 | } else { 223 | ppath, _ = filepath.Abs(ppath) 224 | ppath = strings.TrimPrefix(filepath.ToSlash(ppath), c.Folder+"/") 225 | log.Debugf("%s", ppath) 226 | c.Lock() 227 | c.fileList[ppath] = struct{}{} 228 | c.Unlock() 229 | } 230 | return nil 231 | }) 232 | 233 | <-done 234 | return 235 | } 236 | -------------------------------------------------------------------------------- /pkg/namesgenerator/namesgenerator.go: -------------------------------------------------------------------------------- 1 | package namesgenerator 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | var ( 11 | left = [...]string{ 12 | "admiring", 13 | "adoring", 14 | "affectionate", 15 | "agitated", 16 | "amazing", 17 | "angry", 18 | "awesome", 19 | "beautiful", 20 | "blissful", 21 | "bold", 22 | "boring", 23 | "brave", 24 | "busy", 25 | "charming", 26 | "clever", 27 | "cool", 28 | "compassionate", 29 | "competent", 30 | "condescending", 31 | "confident", 32 | "cranky", 33 | "crazy", 34 | "dazzling", 35 | "determined", 36 | "distracted", 37 | "dreamy", 38 | "eager", 39 | "ecstatic", 40 | "elastic", 41 | "elated", 42 | "elegant", 43 | "eloquent", 44 | "epic", 45 | "exciting", 46 | "fervent", 47 | "festive", 48 | "flamboyant", 49 | "focused", 50 | "friendly", 51 | "frosty", 52 | "funny", 53 | "gallant", 54 | "gifted", 55 | "goofy", 56 | "gracious", 57 | "great", 58 | "happy", 59 | "hardcore", 60 | "heuristic", 61 | "hopeful", 62 | "hungry", 63 | "infallible", 64 | "inspiring", 65 | "interesting", 66 | "intelligent", 67 | "jolly", 68 | "jovial", 69 | "keen", 70 | "kind", 71 | "laughing", 72 | "loving", 73 | "lucid", 74 | "magical", 75 | "mystifying", 76 | "modest", 77 | "musing", 78 | "naughty", 79 | "nervous", 80 | "nice", 81 | "nifty", 82 | "nostalgic", 83 | "objective", 84 | "optimistic", 85 | "peaceful", 86 | "pedantic", 87 | "pensive", 88 | "practical", 89 | "priceless", 90 | "quirky", 91 | "quizzical", 92 | "recursing", 93 | "relaxed", 94 | "reverent", 95 | "romantic", 96 | "sad", 97 | "serene", 98 | "sharp", 99 | "silly", 100 | "sleepy", 101 | "stoic", 102 | "strange", 103 | "stupefied", 104 | "suspicious", 105 | "sweet", 106 | "tender", 107 | "thirsty", 108 | "trusting", 109 | "unruffled", 110 | "upbeat", 111 | "vibrant", 112 | "vigilant", 113 | "vigorous", 114 | "wizardly", 115 | "wonderful", 116 | "xenodochial", 117 | "youthful", 118 | "zealous", 119 | "zen", 120 | } 121 | 122 | // Docker, starting from 0.7.x, generates names from notable scientists and hackers. 123 | // Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa. 124 | right = [...]string{ 125 | "Aardvark", 126 | "Albatross", 127 | "Alligator", 128 | "Alpaca", 129 | "Ant", 130 | "Anteater", 131 | "Antelope", 132 | "Ape", 133 | "Armadillo", 134 | "Donkey", 135 | "Baboon", 136 | "Badger", 137 | "Barracuda", 138 | "Bat", 139 | "Bear", 140 | "Beaver", 141 | "Bee", 142 | "Bison", 143 | "Boar", 144 | "Buffalo", 145 | "Butterfly", 146 | "Camel", 147 | "Capybara", 148 | "Caribou", 149 | "Cassowary", 150 | "Cat", 151 | "Caterpillar", 152 | "Cattle", 153 | "Chamois", 154 | "Cheetah", 155 | "Chicken", 156 | "Chimpanzee", 157 | "Chinchilla", 158 | "Chough", 159 | "Clam", 160 | "Cobra", 161 | "Cockroach", 162 | "Cod", 163 | "Cormorant", 164 | "Coyote", 165 | "Crab", 166 | "Crane", 167 | "Crocodile", 168 | "Crow", 169 | "Curlew", 170 | "Deer", 171 | "Dinosaur", 172 | "Dog", 173 | "Dogfish", 174 | "Dolphin", 175 | "Dotterel", 176 | "Dove", 177 | "Dragonfly", 178 | "Duck", 179 | "Dugong", 180 | "Dunlin", 181 | "Eagle", 182 | "Echidna", 183 | "Eel", 184 | "Eland", 185 | "Elephant", 186 | "Elk", 187 | "Emu", 188 | "Falcon", 189 | "Ferret", 190 | "Finch", 191 | "Fish", 192 | "Flamingo", 193 | "Fly", 194 | "Fox", 195 | "Frog", 196 | "Gaur", 197 | "Gazelle", 198 | "Gerbil", 199 | "Giraffe", 200 | "Gnat", 201 | "Gnu", 202 | "Goat", 203 | "Goldfinch", 204 | "Goldfish", 205 | "Goose", 206 | "Gorilla", 207 | "Goshawk", 208 | "Grasshopper", 209 | "Grouse", 210 | "Guanaco", 211 | "Gull", 212 | "Hamster", 213 | "Hare", 214 | "Hawk", 215 | "Hedgehog", 216 | "Heron", 217 | "Herring", 218 | "Hippopotamus", 219 | "Hornet", 220 | "Horse", 221 | "Human", 222 | "Hummingbird", 223 | "Hyena", 224 | "Ibex", 225 | "Ibis", 226 | "Jackal", 227 | "Jaguar", 228 | "Jay", 229 | "Jellyfish", 230 | "Kangaroo", 231 | "Kingfisher", 232 | "Koala", 233 | "Kookabura", 234 | "Kouprey", 235 | "Kudu", 236 | "Lapwing", 237 | "Lark", 238 | "Lemur", 239 | "Leopard", 240 | "Lion", 241 | "Llama", 242 | "Lobster", 243 | "Locust", 244 | "Loris", 245 | "Louse", 246 | "Lyrebird", 247 | "Magpie", 248 | "Mallard", 249 | "Manatee", 250 | "Mandrill", 251 | "Mantis", 252 | "Marten", 253 | "Meerkat", 254 | "Mink", 255 | "Mole", 256 | "Mongoose", 257 | "Monkey", 258 | "Moose", 259 | "Mosquito", 260 | "Mouse", 261 | "Mule", 262 | "Narwhal", 263 | "Newt", 264 | "Nightingale", 265 | "Octopus", 266 | "Okapi", 267 | "Opossum", 268 | "Oryx", 269 | "Ostrich", 270 | "Otter", 271 | "Owl", 272 | "Oyster", 273 | "Panther", 274 | "Parrot", 275 | "Partridge", 276 | "Peafowl", 277 | "Pelican", 278 | "Penguin", 279 | "Pheasant", 280 | "Pig", 281 | "Pigeon", 282 | "Pony", 283 | "Porcupine", 284 | "Porpoise", 285 | "Quail", 286 | "Quelea", 287 | "Quetzal", 288 | "Rabbit", 289 | "Raccoon", 290 | "Rail", 291 | "Ram", 292 | "Rat", 293 | "Raven", 294 | "Red deer", 295 | "Reindeer", 296 | "Rhinoceros", 297 | "Rook", 298 | "Salamander", 299 | "Salmon", 300 | "Sand Dollar", 301 | "Sandpiper", 302 | "Sardine", 303 | "Scorpion", 304 | "Seahorse", 305 | "Seal", 306 | "Shark", 307 | "Sheep", 308 | "Shrew", 309 | "Skunk", 310 | "Snail", 311 | "Snake", 312 | "Sparrow", 313 | "Spider", 314 | "Spoonbill", 315 | "Squid", 316 | "Squirrel", 317 | "Starling", 318 | "Stingray", 319 | "Stinkbug", 320 | "Stork", 321 | "Swallow", 322 | "Swan", 323 | "Tapir", 324 | "Tarsier", 325 | "Termite", 326 | "Tiger", 327 | "Toad", 328 | "Trout", 329 | "Turkey", 330 | "Turtle", 331 | "Viper", 332 | "Vulture", 333 | "Wallaby", 334 | "Walrus", 335 | "Wasp", 336 | "Weasel", 337 | "Whale", 338 | "Wildcat", 339 | "Wolf", 340 | "Wolverine", 341 | "Wombat", 342 | "Woodcock", 343 | "Woodpecker", 344 | "Worm", 345 | "Wren", 346 | "Yak", 347 | } 348 | ) 349 | 350 | func init() { 351 | rand.Seed(time.Now().UTC().UnixNano()) 352 | } 353 | 354 | // GetRandomName generates a random name from the list of adjectives 355 | // and animals in this package 356 | func GetRandomName() string { 357 | begin: 358 | l := left[rand.Intn(len(left))] 359 | r := strings.ToLower(right[rand.Intn(len(right))]) 360 | if l[0] != r[0] || strings.Contains(r," "){ 361 | goto begin 362 | } 363 | 364 | return fmt.Sprintf("%s%s", l, r) 365 | } 366 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "html/template" 7 | "math/rand" 8 | "net/http" 9 | "path/filepath" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/gorilla/websocket" 15 | "github.com/h2non/filetype" 16 | "github.com/schollz/hostyoself/pkg/namesgenerator" 17 | "github.com/schollz/hostyoself/pkg/utils" 18 | "github.com/schollz/hostyoself/pkg/wsconn" 19 | log "github.com/schollz/logger" 20 | "github.com/vincent-petithory/dataurl" 21 | ) 22 | 23 | type server struct { 24 | publicURL string 25 | port string 26 | 27 | // connections stored as map of domain -> connections 28 | conn map[string][]*connection 29 | sync.Mutex 30 | } 31 | 32 | // connection determine what can be held 33 | type connection struct { 34 | ID int 35 | Joined time.Time 36 | Domain string 37 | Key string 38 | LastGet string 39 | ws *wsconn.WebsocketConn 40 | } 41 | 42 | func New(publicURL, port string) *server { 43 | return &server{ 44 | publicURL: publicURL, 45 | port: port, 46 | conn: make(map[string][]*connection), 47 | } 48 | } 49 | 50 | func (s *server) Run() (err error) { 51 | log.Infof("listening on :%s", s.port) 52 | http.HandleFunc("/", s.handler) 53 | return http.ListenAndServe(fmt.Sprintf(":%s", s.port), nil) 54 | } 55 | 56 | func (s *server) handler(w http.ResponseWriter, r *http.Request) { 57 | t := time.Now().UTC() 58 | err := s.handle(w, r) 59 | if err != nil { 60 | http.Error(w, err.Error(), http.StatusInternalServerError) 61 | log.Error(err) 62 | } 63 | log.Infof("%v %v %v %s\n", r.RemoteAddr, r.Method, r.URL.Path, time.Since(t)) 64 | } 65 | 66 | func (s *server) handle(w http.ResponseWriter, r *http.Request) (err error) { 67 | log.Debugf("URL: %s, Referer: %s", r.URL.Path, r.Referer()) 68 | 69 | // very special paths 70 | if r.URL.Path == "/robots.txt" { 71 | // special path 72 | w.Write([]byte(`User-agent: * 73 | Disallow:`)) 74 | } else if r.URL.Path == "/ws" { 75 | return s.handleWebsocket(w, r) 76 | } else if r.URL.Path == "/favicon.ico" { 77 | err = fmt.Errorf("not implemented") 78 | return 79 | } else if strings.HasPrefix(r.URL.Path, "/static") { 80 | var b []byte 81 | b, err = Asset(r.URL.Path[1:]) 82 | if err != nil { 83 | err = fmt.Errorf("resource '%s' not found", r.URL.Path[1:]) 84 | return 85 | } 86 | var contentType string 87 | switch filepath.Ext(r.URL.Path) { 88 | case ".css": 89 | contentType = "text/css" 90 | case ".js": 91 | contentType = "text/javascript" 92 | case ".html": 93 | contentType = "text/html" 94 | case ".png": 95 | contentType = "image/png" 96 | } 97 | w.Header().Set("Content-Type", contentType) 98 | w.Write(b) 99 | return 100 | } else if r.URL.Path == "/" { 101 | var t *template.Template 102 | b, _ := Asset("templates/view.html") 103 | t, err = template.New("view").Parse(string(b)) 104 | if err != nil { 105 | log.Error(err) 106 | return err 107 | } 108 | type view struct { 109 | PublicURL template.JS 110 | GeneratedDomain string 111 | GeneratedKey string 112 | } 113 | return t.Execute(w, view{ 114 | PublicURL: template.JS(s.publicURL), 115 | GeneratedDomain: namesgenerator.GetRandomName(), 116 | GeneratedKey: utils.RandStringBytesMaskImpr(6), 117 | }) 118 | } else { 119 | // get IP address 120 | var ipAddress string 121 | ipAddress, err = utils.GetClientIPHelper(r) 122 | if err != nil { 123 | log.Debugf("could not determine ip: %s", err.Error()) 124 | } 125 | 126 | log.Debugf("attempting to find %s", r.URL.Path) 127 | 128 | // determine file path and the domain 129 | pathToFile := r.URL.Path[1:] 130 | domain := strings.Split(r.URL.Path[1:], "/")[0] 131 | // clean domain 132 | domain = strings.Replace(strings.ToLower(strings.TrimSpace(domain)), " ", "-", -1) 133 | if !s.isdomain(domain) { 134 | log.Debugf("getting referer") 135 | // if there is a referer, try to obtain the domain from referer 136 | piecesOfReferer := strings.Split(r.Referer(), "/") 137 | if len(piecesOfReferer) > 4 && strings.HasPrefix(r.Referer(), s.publicURL) { 138 | domain = piecesOfReferer[3] 139 | domain = strings.Replace(strings.ToLower(strings.TrimSpace(domain)), " ", "-", -1) 140 | } 141 | } 142 | 143 | // prefix the domain if it doesn't exist 144 | if !strings.HasPrefix(pathToFile, domain) { 145 | pathToFile = domain + "/" + pathToFile 146 | if filepath.Ext(pathToFile) == "" { 147 | pathToFile += "/" 148 | } 149 | http.Redirect(w, r, "/"+pathToFile, 302) 150 | return 151 | } 152 | 153 | // add slash if doesn't exist 154 | if filepath.Ext(pathToFile) == "" && string(r.URL.Path[len(r.URL.Path)-1]) != "/" { 155 | http.Redirect(w, r, r.URL.Path+"/", 302) 156 | return 157 | } 158 | 159 | // trim prefix to get the path to file 160 | pathToFile = strings.TrimPrefix(pathToFile, domain) 161 | if len(pathToFile) == 0 || string(pathToFile[0]) == "/" { 162 | if len(pathToFile) <= 1 { 163 | pathToFile = "index.html" 164 | } else { 165 | pathToFile = pathToFile[1:] 166 | } 167 | } 168 | log.Debugf("pathToFile: %s", pathToFile) 169 | 170 | // send GET request to websockets 171 | var data string 172 | var fs []File 173 | data, err = s.get(domain, pathToFile, ipAddress) 174 | if err != nil { 175 | // try index.html if it doesn't exist 176 | if filepath.Ext(pathToFile) == "" { 177 | if string(pathToFile[len(pathToFile)-1]) != "/" { 178 | pathToFile += "/" 179 | } 180 | pathToFile += "index.html" 181 | log.Debugf("trying 2nd try to get: %s", pathToFile) 182 | data, err = s.get(domain, pathToFile, ipAddress) 183 | } 184 | if err != nil { 185 | // try one more time 186 | if strings.HasSuffix(pathToFile, "/index.html") { 187 | pathToFile = strings.TrimSuffix(pathToFile, "/index.html") 188 | log.Debugf("trying 3rd try to get: %s", pathToFile) 189 | data, err = s.get(domain, pathToFile, ipAddress) 190 | } 191 | if err != nil { 192 | if pathToFile == "index.html" { 193 | // just serve files 194 | fs, err = s.getFiles(domain, ipAddress) 195 | log.Debugf("fs: %+v", fs) 196 | if err != nil { 197 | log.Debug(err) 198 | return 199 | } 200 | 201 | b, _ := Asset("templates/files.html") 202 | var t *template.Template 203 | t, err = template.New("files").Parse(string(b)) 204 | if err != nil { 205 | log.Error(err) 206 | return 207 | } 208 | return t.Execute(w, struct { 209 | Files []File 210 | Domain string 211 | }{ 212 | Domain: domain, 213 | Files: fs, 214 | }) 215 | } else { 216 | log.Debugf("problem getting: %s", err.Error()) 217 | err = fmt.Errorf("not found") 218 | return 219 | } 220 | } 221 | } 222 | } 223 | 224 | // decode the data URI 225 | var dataURL *dataurl.DataURL 226 | dataURL, err = dataurl.DecodeString(data) 227 | if err != nil { 228 | log.Errorf("problem decoding '%s': %s", data, err.Error()) 229 | return 230 | } 231 | 232 | // determine the content type 233 | var contentType string 234 | switch filepath.Ext(pathToFile) { 235 | case ".css": 236 | contentType = "text/css" 237 | case ".js": 238 | contentType = "text/javascript" 239 | case ".html": 240 | contentType = "text/html" 241 | } 242 | if contentType == "" { 243 | contentType = dataURL.MediaType.ContentType() 244 | if contentType == "application/octet-stream" || contentType == "" { 245 | pathToFileExt := filepath.Ext(pathToFile) 246 | mimeType := filetype.GetType(pathToFileExt) 247 | contentType = mimeType.MIME.Value 248 | } 249 | } 250 | log.Debugf("%s/%s (%s)", domain, pathToFile, contentType) 251 | 252 | // write the data to the requester 253 | w.Header().Set("Content-Type", contentType) 254 | w.Write(dataURL.Data) 255 | return 256 | } 257 | return 258 | } 259 | 260 | var wsupgrader = websocket.Upgrader{ 261 | ReadBufferSize: 1024, 262 | WriteBufferSize: 1024, 263 | CheckOrigin: func(r *http.Request) bool { 264 | return true 265 | }, 266 | } 267 | 268 | func (s *server) handleWebsocket(w http.ResponseWriter, r *http.Request) (err error) { 269 | // handle websockets on this page 270 | c, errUpgrade := wsupgrader.Upgrade(w, r, nil) 271 | if errUpgrade != nil { 272 | log.Error(errUpgrade) 273 | return nil 274 | } 275 | ws := wsconn.New(c) 276 | 277 | log.Debugf("%s connected", c.RemoteAddr().String()) 278 | 279 | p, errRead := ws.Receive() 280 | if errRead != nil { 281 | log.Debug(errRead) 282 | ws.Close() 283 | return 284 | } 285 | log.Debugf("recv: %s", p) 286 | 287 | if !(p.Type == "domain" && p.Message != "" && p.Key != "") { 288 | err = fmt.Errorf("got wrong type/domain: %s/%s", p.Type, p.Message) 289 | log.Debug(err) 290 | ws.Close() 291 | return nil 292 | } 293 | 294 | domain := strings.Replace(strings.ToLower(strings.TrimSpace(p.Message)), " ", "-", -1) 295 | 296 | // create domain if it doesn't exist 297 | s.Lock() 298 | if _, ok := s.conn[domain]; !ok { 299 | s.conn[domain] = []*connection{} 300 | } 301 | // register the new connection in the domain 302 | s.conn[domain] = append(s.conn[domain], &connection{ 303 | ID: len(s.conn[domain]), 304 | Domain: domain, 305 | Joined: time.Now(), 306 | Key: p.Key, 307 | ws: ws, 308 | }) 309 | log.Debugf("added: %+v", s.conn) 310 | s.Unlock() 311 | 312 | err = ws.Send(wsconn.Payload{ 313 | Type: "domain", 314 | Message: domain, 315 | Success: true, 316 | }) 317 | if err != nil { 318 | log.Error(err) 319 | } 320 | return nil 321 | } 322 | 323 | func (s *server) isdomain(domain string) bool { 324 | s.Lock() 325 | _, ok := s.conn[domain] 326 | s.Unlock() 327 | return ok 328 | } 329 | 330 | type File struct { 331 | FullPath string `json:"fullPath"` 332 | Upload Upload `json:"upload"` 333 | } 334 | type Upload struct { 335 | UUID string `json:"uuid"` 336 | Total int `json:"total"` 337 | Filename string `json:"filename"` 338 | } 339 | 340 | func (s *server) getFiles(domain, ipAddress string) (fs []File, err error) { 341 | var connections []*connection 342 | s.Lock() 343 | if _, ok := s.conn[domain]; ok { 344 | connections = s.conn[domain] 345 | } 346 | s.Unlock() 347 | if connections == nil || len(connections) == 0 { 348 | err = fmt.Errorf("no connections available for domain %s", domain) 349 | log.Debug(err) 350 | return 351 | } 352 | log.Debugf("requesting files of %s from %d connections", domain, len(connections)) 353 | 354 | // any connection that initated with this key is viable 355 | key := connections[0].Key 356 | 357 | // loop through connections randomly and try to get one to serve the file 358 | for _, i := range rand.Perm(len(connections)) { 359 | var p wsconn.Payload 360 | p, err = func() (p wsconn.Payload, err error) { 361 | err = connections[i].ws.Send(wsconn.Payload{ 362 | Type: "files", 363 | Message: "all", 364 | IPAddress: ipAddress, 365 | }) 366 | if err != nil { 367 | return 368 | } 369 | p, err = connections[i].ws.Receive() 370 | return 371 | }() 372 | if err != nil { 373 | log.Debug(err) 374 | s.dumpConnection(domain, connections[i].ID) 375 | continue 376 | } 377 | log.Tracef("recv: %+v", p) 378 | if p.Type == "files" && p.Key == key { 379 | if !p.Success { 380 | err = fmt.Errorf(p.Message) 381 | return 382 | } 383 | 384 | err = json.Unmarshal([]byte(p.Message), &fs) 385 | return 386 | } 387 | log.Debugf("no good data from %d", i) 388 | } 389 | err = fmt.Errorf("invalid response") 390 | return 391 | } 392 | 393 | func (s *server) get(domain, filePath, ipAddress string) (payload string, err error) { 394 | var connections []*connection 395 | s.Lock() 396 | if _, ok := s.conn[domain]; ok { 397 | connections = s.conn[domain] 398 | } 399 | s.Unlock() 400 | if connections == nil || len(connections) == 0 { 401 | err = fmt.Errorf("no connections available for domain %s", domain) 402 | log.Debug(err) 403 | return 404 | } 405 | log.Debugf("requesting %s/%s from %d connections", domain, filePath, len(connections)) 406 | 407 | // any connection that initated with this key is viable 408 | key := connections[0].Key 409 | 410 | // loop through connections randomly and try to get one to serve the file 411 | for _, i := range rand.Perm(len(connections)) { 412 | var p wsconn.Payload 413 | p, err = func() (p wsconn.Payload, err error) { 414 | err = connections[i].ws.Send(wsconn.Payload{ 415 | Type: "get", 416 | Message: filePath, 417 | IPAddress: ipAddress, 418 | }) 419 | if err != nil { 420 | return 421 | } 422 | p, err = connections[i].ws.Receive() 423 | return 424 | }() 425 | if err != nil { 426 | log.Debug(err) 427 | s.dumpConnection(domain, connections[i].ID) 428 | continue 429 | } 430 | log.Tracef("recv: %+v", p) 431 | if p.Type == "get" && p.Key == key { 432 | payload = p.Message 433 | if !p.Success { 434 | err = fmt.Errorf(payload) 435 | } 436 | return 437 | } 438 | log.Debugf("no good data from %d", i) 439 | } 440 | err = fmt.Errorf("invalid response") 441 | return 442 | } 443 | 444 | func (s *server) dumpConnection(domain string, id int) (err error) { 445 | s.Lock() 446 | defer s.Unlock() 447 | if _, ok := s.conn[domain]; !ok { 448 | err = fmt.Errorf("domain %s not found", domain) 449 | log.Debug(err) 450 | return 451 | } 452 | for i, conn := range s.conn[domain] { 453 | if conn.ID == id { 454 | log.Debugf("dumping connection %s/%d", domain, id) 455 | s.conn[domain] = remove(s.conn[domain], i) 456 | return 457 | } 458 | } 459 | err = fmt.Errorf("could not find %s/%d to dump", domain, id) 460 | return 461 | } 462 | 463 | func remove(slice []*connection, s int) []*connection { 464 | return append(slice[:s], slice[s+1:]...) 465 | } 466 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | // GetClientIPHelper gets the client IP using a mixture of techniques. 13 | // This is how it is with golang at the moment. 14 | func GetClientIPHelper(req *http.Request) (ipResult string, errResult error) { 15 | 16 | // Try lots of ways :) Order is important. 17 | // Try Request Headers (X-Forwarder). Client could be behind a Proxy 18 | ip, err := getClientIPByHeaders(req) 19 | if err == nil { 20 | // log.Printf("debug: Found IP using Request Headers sniffing. ip: %v", ip) 21 | return ip, nil 22 | } 23 | 24 | // Try by Request 25 | ip, err = getClientIPByRequestRemoteAddr(req) 26 | if err == nil { 27 | // log.Printf("debug: Found IP using Request sniffing. ip: %v", ip) 28 | return ip, nil 29 | } 30 | 31 | // Try Request Header ("Origin") 32 | url, err := url.Parse(req.Header.Get("Origin")) 33 | if err == nil { 34 | host := url.Host 35 | ip, _, err := net.SplitHostPort(host) 36 | if err == nil { 37 | // log.Printf("debug: Found IP using Header (Origin) sniffing. ip: %v", ip) 38 | return ip, nil 39 | } 40 | } 41 | 42 | err = errors.New("error: Could not find clients IP address") 43 | return "", err 44 | } 45 | 46 | // getClientIPByRequest tries to get directly from the Request. 47 | // https://blog.golang.org/context/userip/userip.go 48 | func getClientIPByRequestRemoteAddr(req *http.Request) (ip string, err error) { 49 | 50 | // Try via request 51 | ip, _, err = net.SplitHostPort(req.RemoteAddr) 52 | if err != nil { 53 | // log.Printf("debug: Getting req.RemoteAddr %v", err) 54 | return "", err 55 | } else { 56 | // log.Printf("debug: With req.RemoteAddr found IP:%v; Port: %v", ip, port) 57 | } 58 | 59 | userIP := net.ParseIP(ip) 60 | if userIP == nil { 61 | message := fmt.Sprintf("debug: Parsing IP from Request.RemoteAddr got nothing.") 62 | // log.Printf(message) 63 | return "", fmt.Errorf(message) 64 | 65 | } 66 | // log.Printf("debug: Found IP: %v", userIP) 67 | return userIP.String(), nil 68 | 69 | } 70 | 71 | // getClientIPByHeaders tries to get directly from the Request Headers. 72 | // This is only way when the client is behind a Proxy. 73 | func getClientIPByHeaders(req *http.Request) (ip string, err error) { 74 | 75 | // Client could be behid a Proxy, so Try Request Headers (X-Forwarder) 76 | ipSlice := []string{} 77 | 78 | ipSlice = append(ipSlice, req.Header.Get("X-Forwarded-For")) 79 | ipSlice = append(ipSlice, req.Header.Get("x-forwarded-for")) 80 | ipSlice = append(ipSlice, req.Header.Get("X-FORWARDED-FOR")) 81 | 82 | for _, v := range ipSlice { 83 | // log.Printf("debug: client request header check gives ip: %v", v) 84 | if v != "" { 85 | return v, nil 86 | } 87 | } 88 | err = errors.New("error: Could not find clients IP address from the Request Headers") 89 | return "", err 90 | 91 | } 92 | 93 | const letterBytes = "abcdefghijklmnopqrstuvwxyz" 94 | const ( 95 | letterIdxBits = 6 // 6 bits to represent a letter index 96 | letterIdxMask = 1<= 0; { 104 | if remain == 0 { 105 | cache, remain = rand.Int63(), letterIdxMax 106 | } 107 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) { 108 | b[i] = letterBytes[idx] 109 | i-- 110 | } 111 | cache >>= letterIdxBits 112 | remain-- 113 | } 114 | 115 | return string(b) 116 | } 117 | -------------------------------------------------------------------------------- /pkg/wsconn/websocket.go: -------------------------------------------------------------------------------- 1 | package wsconn 2 | 3 | import ( 4 | "encoding/json" 5 | "sync" 6 | 7 | "github.com/gorilla/websocket" 8 | log "github.com/schollz/logger" 9 | ) 10 | 11 | // Payload lists the data exchanged 12 | type Payload struct { 13 | Success bool `json:"success"` 14 | Type string `json:"type,omitempty"` 15 | Message string `json:"message,omitempty"` 16 | IPAddress string `json:"ip,omitempty"` 17 | Key string `json:"key,omitempty"` 18 | } 19 | 20 | func (p Payload) String() string { 21 | b, _ := json.Marshal(p) 22 | return string(b) 23 | } 24 | 25 | // WebsocketConn provides convenience functions for sending 26 | // and receiving data with websockets, using mutex to 27 | // make sure only one writer/reader 28 | type WebsocketConn struct { 29 | ws *websocket.Conn 30 | sync.Mutex 31 | } 32 | 33 | // NewWebsocket returns a new websocket 34 | func New(ws *websocket.Conn) *WebsocketConn { 35 | return &WebsocketConn{ 36 | ws: ws, 37 | } 38 | } 39 | 40 | func (ws *WebsocketConn) Close() (err error) { 41 | ws.Lock() 42 | defer ws.Unlock() 43 | ws.Close() 44 | ws = nil 45 | return 46 | } 47 | 48 | func (ws *WebsocketConn) Send(p Payload) (err error) { 49 | ws.Lock() 50 | defer ws.Unlock() 51 | log.Tracef("sending %+v", p) 52 | err = ws.ws.WriteJSON(p) 53 | return 54 | } 55 | 56 | func (ws *WebsocketConn) Receive() (p Payload, err error) { 57 | ws.Lock() 58 | defer ws.Unlock() 59 | err = ws.ws.ReadJSON(&p) 60 | log.Tracef("recv %+v", p) 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /static/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schollz/hostyoself/04ad1997a612b36a11530f321060bd3e31b0b2c4/static/banner.jpg -------------------------------------------------------------------------------- /static/dropzone.css: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * Copyright (c) 2012 Matias Meno 4 | */ 5 | @-webkit-keyframes passing-through { 6 | 0% { 7 | opacity: 0; 8 | -webkit-transform: translateY(40px); 9 | -moz-transform: translateY(40px); 10 | -ms-transform: translateY(40px); 11 | -o-transform: translateY(40px); 12 | transform: translateY(40px); } 13 | 30%, 70% { 14 | opacity: 1; 15 | -webkit-transform: translateY(0px); 16 | -moz-transform: translateY(0px); 17 | -ms-transform: translateY(0px); 18 | -o-transform: translateY(0px); 19 | transform: translateY(0px); } 20 | 100% { 21 | opacity: 0; 22 | -webkit-transform: translateY(-40px); 23 | -moz-transform: translateY(-40px); 24 | -ms-transform: translateY(-40px); 25 | -o-transform: translateY(-40px); 26 | transform: translateY(-40px); } } 27 | @-moz-keyframes passing-through { 28 | 0% { 29 | opacity: 0; 30 | -webkit-transform: translateY(40px); 31 | -moz-transform: translateY(40px); 32 | -ms-transform: translateY(40px); 33 | -o-transform: translateY(40px); 34 | transform: translateY(40px); } 35 | 30%, 70% { 36 | opacity: 1; 37 | -webkit-transform: translateY(0px); 38 | -moz-transform: translateY(0px); 39 | -ms-transform: translateY(0px); 40 | -o-transform: translateY(0px); 41 | transform: translateY(0px); } 42 | 100% { 43 | opacity: 0; 44 | -webkit-transform: translateY(-40px); 45 | -moz-transform: translateY(-40px); 46 | -ms-transform: translateY(-40px); 47 | -o-transform: translateY(-40px); 48 | transform: translateY(-40px); } } 49 | @keyframes passing-through { 50 | 0% { 51 | opacity: 0; 52 | -webkit-transform: translateY(40px); 53 | -moz-transform: translateY(40px); 54 | -ms-transform: translateY(40px); 55 | -o-transform: translateY(40px); 56 | transform: translateY(40px); } 57 | 30%, 70% { 58 | opacity: 1; 59 | -webkit-transform: translateY(0px); 60 | -moz-transform: translateY(0px); 61 | -ms-transform: translateY(0px); 62 | -o-transform: translateY(0px); 63 | transform: translateY(0px); } 64 | 100% { 65 | opacity: 0; 66 | -webkit-transform: translateY(-40px); 67 | -moz-transform: translateY(-40px); 68 | -ms-transform: translateY(-40px); 69 | -o-transform: translateY(-40px); 70 | transform: translateY(-40px); } } 71 | @-webkit-keyframes slide-in { 72 | 0% { 73 | opacity: 0; 74 | -webkit-transform: translateY(40px); 75 | -moz-transform: translateY(40px); 76 | -ms-transform: translateY(40px); 77 | -o-transform: translateY(40px); 78 | transform: translateY(40px); } 79 | 30% { 80 | opacity: 1; 81 | -webkit-transform: translateY(0px); 82 | -moz-transform: translateY(0px); 83 | -ms-transform: translateY(0px); 84 | -o-transform: translateY(0px); 85 | transform: translateY(0px); } } 86 | @-moz-keyframes slide-in { 87 | 0% { 88 | opacity: 0; 89 | -webkit-transform: translateY(40px); 90 | -moz-transform: translateY(40px); 91 | -ms-transform: translateY(40px); 92 | -o-transform: translateY(40px); 93 | transform: translateY(40px); } 94 | 30% { 95 | opacity: 1; 96 | -webkit-transform: translateY(0px); 97 | -moz-transform: translateY(0px); 98 | -ms-transform: translateY(0px); 99 | -o-transform: translateY(0px); 100 | transform: translateY(0px); } } 101 | @keyframes slide-in { 102 | 0% { 103 | opacity: 0; 104 | -webkit-transform: translateY(40px); 105 | -moz-transform: translateY(40px); 106 | -ms-transform: translateY(40px); 107 | -o-transform: translateY(40px); 108 | transform: translateY(40px); } 109 | 30% { 110 | opacity: 1; 111 | -webkit-transform: translateY(0px); 112 | -moz-transform: translateY(0px); 113 | -ms-transform: translateY(0px); 114 | -o-transform: translateY(0px); 115 | transform: translateY(0px); } } 116 | @-webkit-keyframes pulse { 117 | 0% { 118 | -webkit-transform: scale(1); 119 | -moz-transform: scale(1); 120 | -ms-transform: scale(1); 121 | -o-transform: scale(1); 122 | transform: scale(1); } 123 | 10% { 124 | -webkit-transform: scale(1.1); 125 | -moz-transform: scale(1.1); 126 | -ms-transform: scale(1.1); 127 | -o-transform: scale(1.1); 128 | transform: scale(1.1); } 129 | 20% { 130 | -webkit-transform: scale(1); 131 | -moz-transform: scale(1); 132 | -ms-transform: scale(1); 133 | -o-transform: scale(1); 134 | transform: scale(1); } } 135 | @-moz-keyframes pulse { 136 | 0% { 137 | -webkit-transform: scale(1); 138 | -moz-transform: scale(1); 139 | -ms-transform: scale(1); 140 | -o-transform: scale(1); 141 | transform: scale(1); } 142 | 10% { 143 | -webkit-transform: scale(1.1); 144 | -moz-transform: scale(1.1); 145 | -ms-transform: scale(1.1); 146 | -o-transform: scale(1.1); 147 | transform: scale(1.1); } 148 | 20% { 149 | -webkit-transform: scale(1); 150 | -moz-transform: scale(1); 151 | -ms-transform: scale(1); 152 | -o-transform: scale(1); 153 | transform: scale(1); } } 154 | @keyframes pulse { 155 | 0% { 156 | -webkit-transform: scale(1); 157 | -moz-transform: scale(1); 158 | -ms-transform: scale(1); 159 | -o-transform: scale(1); 160 | transform: scale(1); } 161 | 10% { 162 | -webkit-transform: scale(1.1); 163 | -moz-transform: scale(1.1); 164 | -ms-transform: scale(1.1); 165 | -o-transform: scale(1.1); 166 | transform: scale(1.1); } 167 | 20% { 168 | -webkit-transform: scale(1); 169 | -moz-transform: scale(1); 170 | -ms-transform: scale(1); 171 | -o-transform: scale(1); 172 | transform: scale(1); } } 173 | .dropzone, .dropzone * { 174 | box-sizing: border-box; } 175 | 176 | .dropzone { 177 | min-height: 150px; 178 | border: 2px solid rgba(0, 0, 0, 0.3); 179 | background: white; 180 | padding: 20px 20px; } 181 | .dropzone.dz-clickable { 182 | cursor: pointer; } 183 | .dropzone.dz-clickable * { 184 | cursor: default; } 185 | .dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * { 186 | cursor: pointer; } 187 | .dropzone.dz-started .dz-message { 188 | display: none; } 189 | .dropzone.dz-drag-hover { 190 | border-style: solid; } 191 | .dropzone.dz-drag-hover .dz-message { 192 | opacity: 0.5; } 193 | .dropzone .dz-message { 194 | text-align: center; 195 | margin: 2em 0; } 196 | .dropzone .dz-preview { 197 | position: relative; 198 | display: inline-block; 199 | vertical-align: top; 200 | margin: 16px; 201 | min-height: 100px; } 202 | .dropzone .dz-preview:hover { 203 | z-index: 1000; } 204 | .dropzone .dz-preview:hover .dz-details { 205 | opacity: 1; } 206 | .dropzone .dz-preview.dz-file-preview .dz-image { 207 | border-radius: 20px; 208 | background: #999; 209 | background: linear-gradient(to bottom, #eee, #ddd); } 210 | .dropzone .dz-preview.dz-file-preview .dz-details { 211 | opacity: 1; } 212 | .dropzone .dz-preview.dz-image-preview { 213 | background: white; } 214 | .dropzone .dz-preview.dz-image-preview .dz-details { 215 | -webkit-transition: opacity 0.2s linear; 216 | -moz-transition: opacity 0.2s linear; 217 | -ms-transition: opacity 0.2s linear; 218 | -o-transition: opacity 0.2s linear; 219 | transition: opacity 0.2s linear; } 220 | .dropzone .dz-preview .dz-remove { 221 | font-size: 14px; 222 | text-align: center; 223 | display: block; 224 | cursor: pointer; 225 | border: none; } 226 | .dropzone .dz-preview .dz-remove:hover { 227 | text-decoration: underline; } 228 | .dropzone .dz-preview:hover .dz-details { 229 | opacity: 1; } 230 | .dropzone .dz-preview .dz-details { 231 | z-index: 20; 232 | position: absolute; 233 | top: 0; 234 | left: 0; 235 | opacity: 0; 236 | font-size: 13px; 237 | min-width: 100%; 238 | max-width: 100%; 239 | padding: 2em 1em; 240 | text-align: center; 241 | color: rgba(0, 0, 0, 0.9); 242 | line-height: 150%; } 243 | .dropzone .dz-preview .dz-details .dz-size { 244 | margin-bottom: 1em; 245 | font-size: 16px; } 246 | .dropzone .dz-preview .dz-details .dz-filename { 247 | white-space: nowrap; } 248 | .dropzone .dz-preview .dz-details .dz-filename:hover span { 249 | border: 1px solid rgba(200, 200, 200, 0.8); 250 | background-color: rgba(255, 255, 255, 0.8); } 251 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) { 252 | overflow: hidden; 253 | text-overflow: ellipsis; } 254 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) span { 255 | border: 1px solid transparent; } 256 | .dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span { 257 | background-color: rgba(255, 255, 255, 0.4); 258 | padding: 0 0.4em; 259 | border-radius: 3px; } 260 | .dropzone .dz-preview:hover .dz-image img { 261 | -webkit-transform: scale(1.05, 1.05); 262 | -moz-transform: scale(1.05, 1.05); 263 | -ms-transform: scale(1.05, 1.05); 264 | -o-transform: scale(1.05, 1.05); 265 | transform: scale(1.05, 1.05); 266 | -webkit-filter: blur(8px); 267 | filter: blur(8px); } 268 | .dropzone .dz-preview .dz-image { 269 | border-radius: 20px; 270 | overflow: hidden; 271 | width: 120px; 272 | height: 120px; 273 | position: relative; 274 | display: block; 275 | z-index: 10; } 276 | .dropzone .dz-preview .dz-image img { 277 | display: block; } 278 | .dropzone .dz-preview.dz-success .dz-success-mark { 279 | -webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 280 | -moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 281 | -ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 282 | -o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); 283 | animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); } 284 | .dropzone .dz-preview.dz-error .dz-error-mark { 285 | opacity: 1; 286 | -webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 287 | -moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 288 | -ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 289 | -o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); 290 | animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); } 291 | .dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark { 292 | pointer-events: none; 293 | opacity: 0; 294 | z-index: 500; 295 | position: absolute; 296 | display: block; 297 | top: 50%; 298 | left: 50%; 299 | margin-left: -27px; 300 | margin-top: -27px; } 301 | .dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg { 302 | display: block; 303 | width: 54px; 304 | height: 54px; } 305 | .dropzone .dz-preview.dz-processing .dz-progress { 306 | opacity: 1; 307 | -webkit-transition: all 0.2s linear; 308 | -moz-transition: all 0.2s linear; 309 | -ms-transition: all 0.2s linear; 310 | -o-transition: all 0.2s linear; 311 | transition: all 0.2s linear; } 312 | .dropzone .dz-preview.dz-complete .dz-progress { 313 | opacity: 0; 314 | -webkit-transition: opacity 0.4s ease-in; 315 | -moz-transition: opacity 0.4s ease-in; 316 | -ms-transition: opacity 0.4s ease-in; 317 | -o-transition: opacity 0.4s ease-in; 318 | transition: opacity 0.4s ease-in; } 319 | .dropzone .dz-preview:not(.dz-processing) .dz-progress { 320 | -webkit-animation: pulse 6s ease infinite; 321 | -moz-animation: pulse 6s ease infinite; 322 | -ms-animation: pulse 6s ease infinite; 323 | -o-animation: pulse 6s ease infinite; 324 | animation: pulse 6s ease infinite; } 325 | .dropzone .dz-preview .dz-progress { 326 | opacity: 1; 327 | z-index: 1000; 328 | pointer-events: none; 329 | position: absolute; 330 | height: 16px; 331 | left: 50%; 332 | top: 50%; 333 | margin-top: -8px; 334 | width: 80px; 335 | margin-left: -40px; 336 | background: rgba(255, 255, 255, 0.9); 337 | -webkit-transform: scale(1); 338 | border-radius: 8px; 339 | overflow: hidden; } 340 | .dropzone .dz-preview .dz-progress .dz-upload { 341 | background: #333; 342 | background: linear-gradient(to bottom, #666, #444); 343 | position: absolute; 344 | top: 0; 345 | left: 0; 346 | bottom: 0; 347 | width: 0; 348 | -webkit-transition: width 300ms ease-in-out; 349 | -moz-transition: width 300ms ease-in-out; 350 | -ms-transition: width 300ms ease-in-out; 351 | -o-transition: width 300ms ease-in-out; 352 | transition: width 300ms ease-in-out; } 353 | .dropzone .dz-preview.dz-error .dz-error-message { 354 | display: block; } 355 | .dropzone .dz-preview.dz-error:hover .dz-error-message { 356 | opacity: 1; 357 | pointer-events: auto; } 358 | .dropzone .dz-preview .dz-error-message { 359 | pointer-events: none; 360 | z-index: 1000; 361 | position: absolute; 362 | display: block; 363 | display: none; 364 | opacity: 0; 365 | -webkit-transition: opacity 0.3s ease; 366 | -moz-transition: opacity 0.3s ease; 367 | -ms-transition: opacity 0.3s ease; 368 | -o-transition: opacity 0.3s ease; 369 | transition: opacity 0.3s ease; 370 | border-radius: 8px; 371 | font-size: 13px; 372 | top: 130px; 373 | left: -10px; 374 | width: 140px; 375 | background: #be2626; 376 | background: linear-gradient(to bottom, #be2626, #a92222); 377 | padding: 0.5em 1.2em; 378 | color: white; } 379 | .dropzone .dz-preview .dz-error-message:after { 380 | content: ''; 381 | position: absolute; 382 | top: -6px; 383 | left: 64px; 384 | width: 0; 385 | height: 0; 386 | border-left: 6px solid transparent; 387 | border-right: 6px solid transparent; 388 | border-bottom: 6px solid #be2626; } 389 | -------------------------------------------------------------------------------- /static/hostyoself.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schollz/hostyoself/04ad1997a612b36a11530f321060bd3e31b0b2c4/static/hostyoself.png -------------------------------------------------------------------------------- /static/hostyoself2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schollz/hostyoself/04ad1997a612b36a11530f321060bd3e31b0b2c4/static/hostyoself2.png -------------------------------------------------------------------------------- /static/inception.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schollz/hostyoself/04ad1997a612b36a11530f321060bd3e31b0b2c4/static/inception.gif -------------------------------------------------------------------------------- /static/main.js: -------------------------------------------------------------------------------- 1 | var files = []; 2 | var isConnected = false; 3 | var relativeDirectory = ""; 4 | 5 | function consoleLog(s) { 6 | console.log(s); 7 | if (typeof s === 'object') { 8 | s = JSON.stringify(s); 9 | } 10 | 11 | if (!(s.startsWith("[debug]"))) { 12 | document.getElementById("consoleText").value = document.getElementById("consoleText").value + s + "\n"; 13 | document.getElementById("consoleText").scrollTop = document.getElementById("consoleText").scrollHeight; 14 | } 15 | } 16 | 17 | function humanFileSize(bytes, si) { 18 | var thresh = si ? 1000 : 1024; 19 | if (Math.abs(bytes) < thresh) { 20 | return bytes + ' B'; 21 | } 22 | var units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 23 | 'EiB', 'ZiB', 'YiB' 24 | ]; 25 | var u = -1; 26 | do { 27 | bytes /= thresh; 28 | ++u; 29 | } while (Math.abs(bytes) >= thresh && u < units.length - 1); 30 | return bytes.toFixed(1) + ' ' + units[u]; 31 | } 32 | 33 | var Name = ""; 34 | var filesize = 0; 35 | 36 | (function(Dropzone) { 37 | Dropzone.autoDiscover = false; 38 | 39 | let drop = new Dropzone('div#filesBox', { 40 | maxFiles: 1000, 41 | url: '/', 42 | method: 'post', 43 | createImageThumbnails: false, 44 | previewTemplate: "
", 45 | autoProcessQueue: false, 46 | }); 47 | 48 | 49 | drop.on('addedfile', function(file) { 50 | // console.log(file); 51 | var domain = document.getElementById("inputDomain").value; 52 | files.push(file); 53 | if ("webkitRelativePath" in file) { 54 | if (files.length == 1 && file.webkitRelativePath != "") { 55 | relativeDirectory = file.webkitRelativePath.split("/")[0]; 56 | } else if (file.webkitRelativePath.split("/")[0] != relativeDirectory) { 57 | relativeDirectory = ""; 58 | } 59 | } 60 | if ("fullPath" in file) { 61 | if (files.length == 1 && file.fullPath != "") { 62 | relativeDirectory = file.fullPath.split("/")[0]; 63 | } else if (file.fullPath.split("/")[0] != relativeDirectory) { 64 | relativeDirectory = ""; 65 | } 66 | } 67 | 68 | 69 | 70 | if (!(isConnected)) { 71 | isConnected = true; 72 | socketSend({ 73 | type: "domain", 74 | message: domain, 75 | key: document.getElementById("inputKey").value, 76 | }) 77 | } 78 | 79 | var filesString = "files are"; 80 | var domainName = `${window.publicURL}/${domain}/`; 81 | if (files.length == 1) { 82 | filesString = "file is" 83 | domainName += `${file.name}` 84 | } 85 | 86 | document.getElementById("consoleHeader").innerHTML = 87 | `

Your ${filesString} available at:

${domainName}

`; 88 | html = `
    ` 89 | for (i = 0; i < files.length; i++) { 90 | var urlToFile = files[i].name; 91 | if ('fullPath' in files[i]) { 92 | urlToFile = files[i].fullPath; 93 | } 94 | html = html + 95 | `
  • /${urlToFile}
  • ` 96 | } 97 | html = html + `
`; 98 | document.getElementById("fileList").innerHTML = html; 99 | document.getElementById("filesBox").classList.add("hide"); 100 | document.getElementById("console").classList.remove("hide"); 101 | document.getElementById("inputKey").readOnly = "true"; 102 | document.getElementById("inputDomain").readOnly = "true"; 103 | }) 104 | 105 | })(Dropzone); 106 | 107 | var socket; // websocket 108 | 109 | 110 | /* websockets */ 111 | function socketSend(data) { 112 | if (socket == null) { 113 | return 114 | } 115 | if (socket.readyState != 1) { 116 | return 117 | } 118 | jsonData = JSON.stringify(data); 119 | socket.send(jsonData); 120 | if (jsonData.length > 100) { 121 | consoleLog("[debug] ws-> " + jsonData.substring(0, 99)) 122 | } else { 123 | consoleLog("[debug] ws-> " + jsonData) 124 | } 125 | } 126 | 127 | const socketMessageListener = (event) => { 128 | var data = JSON.parse(event.data); 129 | if (!('type' in data && 'message' in data)) { 130 | consoleLog(`[warn] got bad data ${event.data}`); 131 | return 132 | } 133 | console.log(data) 134 | consoleLog(`[debug] ${data.message}`) 135 | if (data.type == "files") { 136 | if (files.length > 0) { 137 | socketSend({ 138 | type: "files", 139 | message: JSON.stringify(files), 140 | success: true, 141 | key: document.getElementById("inputKey").value, 142 | }); 143 | consoleLog( 144 | `${data.ip} [${(new Date()).toUTCString()}] sitemap 200` 145 | ); 146 | } else { 147 | socketSend({ 148 | type: "files", 149 | message: "none found", 150 | success: false, 151 | key: document.getElementById("inputKey").value, 152 | }); 153 | consoleLog( 154 | `${data.ip} [${(new Date()).toUTCString()}] sitemap 404` 155 | ); 156 | } 157 | } else if (data.type == "get") { 158 | var foundFile = false 159 | var iToSend = 0 160 | for (i = 0; i < files.length; i++) { 161 | if (files[i].webkitRelativePath == data.message || files[i].fullPath == data.message || files[i].name == data.message || files[i] 162 | .webkitRelativePath == relativeDirectory + "/" + data.message || files[i] 163 | .fullPath == relativeDirectory + "/" + data.message) { 164 | iToSend = i; 165 | var reader = new FileReader(); 166 | reader.onload = function(theFile) { 167 | socketSend({ 168 | type: "get", 169 | message: reader.result, 170 | success: true, 171 | key: document.getElementById("inputKey").value, 172 | }) 173 | consoleLog( 174 | `${data.ip} [${(new Date()).toUTCString()}] /${data.message} 200 ${files[i].size}` 175 | ); 176 | }; 177 | reader.readAsDataURL(files[i]); 178 | foundFile = true 179 | break 180 | } 181 | } 182 | if (foundFile == false) { 183 | socketSend({ 184 | type: "get", 185 | message: "not found", 186 | success: false, 187 | key: document.getElementById("inputKey").value, 188 | }) 189 | consoleLog(`${data.ip} [${(new Date()).toUTCString()}] /${data.message} 404`); 190 | } 191 | } else if (data.type == "domain") { 192 | console.log(`[info] ${data.message}`); 193 | } else if (data.type == "message") { 194 | console.log(`[info] ${data.message}`); 195 | } else { 196 | consoleLog(`[debug] unknown`); 197 | } 198 | }; 199 | const socketOpenListener = (event) => { 200 | consoleLog('[info] connected'); 201 | if (isConnected == true) { 202 | // reconnect if was connected and got disconnected 203 | socketSend({ 204 | type: "domain", 205 | message: document.getElementById("inputDomain").value, 206 | key: document.getElementById("inputKey").value, 207 | }) 208 | } 209 | }; 210 | 211 | const socketCloseListener = (event) => { 212 | if (socket) { 213 | consoleLog('[info] disconnected'); 214 | } 215 | var url = window.origin.replace("http", "ws") + '/ws'; 216 | try { 217 | socket = new WebSocket(url); 218 | socket.addEventListener('open', socketOpenListener); 219 | socket.addEventListener('message', socketMessageListener); 220 | socket.addEventListener('close', socketCloseListener); 221 | } catch (err) { 222 | consoleLog("[info] no connection available") 223 | } 224 | }; 225 | 226 | 227 | socketCloseListener(); -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --mono-font: San Francisco Mono, Monaco, "Consolas", "Lucida Console", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", monospace; 3 | --sans-font: -apple-system, BlinkMacSystemFont, 'avenir next', avenir, helvetica, 'helvetica neue', roboto, noto, 'segoe ui', arial, sans-serif 4 | } 5 | 6 | * { 7 | /* reset header margines */ 8 | margin: 0; 9 | } 10 | 11 | body { 12 | margin: 1em; 13 | background: rgb(253, 253, 253); 14 | } 15 | 16 | main { 17 | line-height: 1.6em; 18 | font-size: 1em; 19 | max-width: 580px; 20 | margin: 1em auto; 21 | -webkit-font-smoothing: antialiased; 22 | text-rendering: optimizeLegibility; 23 | font-family: var(--sans-font); 24 | 25 | 26 | /* break words URLs */ 27 | overflow-wrap: break-word; 28 | word-wrap: break-word; 29 | word-break: break-word; 30 | 31 | /* Adds a hyphen where the word breaks, if supported (No Blink) */ 32 | -ms-hyphens: auto; 33 | -moz-hyphens: auto; 34 | -webkit-hyphens: auto; 35 | hyphens: auto; 36 | } 37 | 38 | p { 39 | /* hyphenate paragraphs */ 40 | -webkit-hyphens: auto; 41 | -webkit-hyphenate-limit-before: 3; 42 | -webkit-hyphenate-limit-after: 3; 43 | -webkit-hyphenate-limit-chars: 6 3 3; 44 | -webkit-hyphenate-limit-lines: 2; 45 | -webkit-hyphenate-limit-last: always; 46 | -webkit-hyphenate-limit-zone: 8%; 47 | 48 | -moz-hyphens: auto; 49 | -moz-hyphenate-limit-chars: 6 3 3; 50 | -moz-hyphenate-limit-lines: 2; 51 | -moz-hyphenate-limit-last: always; 52 | -moz-hyphenate-limit-zone: 8%; 53 | 54 | -ms-hyphens: auto; 55 | -ms-hyphenate-limit-chars: 6 3 3; 56 | -ms-hyphenate-limit-lines: 2; 57 | -ms-hyphenate-limit-last: always; 58 | -ms-hyphenate-limit-zone: 8%; 59 | 60 | hyphens: auto; 61 | hyphenate-limit-chars: 6 3 3; 62 | hyphenate-limit-lines: 2; 63 | hyphenate-limit-last: always; 64 | hyphenate-limit-zone: 8%; 65 | } 66 | 67 | 68 | ul, 69 | ol { 70 | padding-left: 1.5em; 71 | } 72 | 73 | sup { 74 | line-height: 0em; 75 | font-size: 0.7em; 76 | } 77 | 78 | 79 | figure, 80 | p, 81 | ul, 82 | ol, 83 | pre, 84 | hr { 85 | margin-bottom: 1.1em; 86 | } 87 | 88 | /* headers */ 89 | h2, 90 | h3, 91 | h4, 92 | h5 { 93 | margin-bottom: 0.2em; 94 | margin-top: 1em; 95 | } 96 | 97 | h1 { 98 | font-size: 1.6em; 99 | letter-spacing: .004em; 100 | margin-bottom: 0.4em; 101 | } 102 | 103 | h2 { 104 | font-size: 1.3em; 105 | letter-spacing: .009em 106 | } 107 | 108 | h3 { 109 | font-size: 1.2em; 110 | letter-spacing: .009em 111 | } 112 | 113 | h4 { 114 | font-weight: 600; 115 | font-size: 1.1em; 116 | } 117 | 118 | h5 { 119 | font-weight: 600; 120 | font-size: 1.05em; 121 | } 122 | 123 | 124 | /* quotes */ 125 | blockquote { 126 | margin: 1.5em 1em; 127 | font-style: italic; 128 | quotes: "\201C""\201D""\2018""\2019"; 129 | width: 100%; 130 | font-size: 0.9em; 131 | } 132 | 133 | /* links */ 134 | a { 135 | text-decoration: underline; 136 | cursor: pointer; 137 | color: #000; 138 | text-decoration-skip-ink: auto; 139 | text-decoration: underline; 140 | } 141 | 142 | 143 | a:visited { 144 | color: #333 145 | } 146 | 147 | 148 | /* code */ 149 | code { 150 | font-family: var(--mono-font); 151 | background-color: rgba(27, 31, 35, .05); 152 | border-radius: 3px; 153 | font-size: 85%; 154 | margin: 0; 155 | padding: .2em .4em; 156 | } 157 | 158 | pre { 159 | margin-top: 1em; 160 | word-wrap: normal; 161 | background-color: #f6f8fa; 162 | border-radius: 3px; 163 | font-size: 85%; 164 | line-height: 1.45; 165 | overflow: auto; 166 | padding: 16px; 167 | } 168 | 169 | pre code { 170 | background-color: transparent; 171 | border: 0; 172 | display: inline; 173 | line-height: inherit; 174 | margin: 0; 175 | max-width: auto; 176 | overflow: visible; 177 | padding: 0; 178 | word-wrap: normal; 179 | } 180 | 181 | pre>code { 182 | background: transparent; 183 | border: 0; 184 | font-size: 100%; 185 | margin: 0; 186 | padding: 0; 187 | white-space: pre; 188 | word-break: normal; 189 | } 190 | 191 | img { 192 | width: 100%; 193 | } 194 | 195 | .dropzone { 196 | margin-top: 1em; 197 | margin-bottom: 1em; 198 | min-height: 0px !important; 199 | } 200 | 201 | .tooltip { 202 | position: relative; 203 | 204 | } 205 | 206 | .tooltip .tooltiptext { 207 | visibility: hidden; 208 | width: 140px; 209 | background-color: #555; 210 | color: #fff; 211 | text-align: center; 212 | border-radius: 6px; 213 | padding: 5px; 214 | position: absolute; 215 | z-index: 1; 216 | bottom: 150%; 217 | left: 50%; 218 | margin-left: -75px; 219 | opacity: 0; 220 | transition: opacity 0.3s; 221 | } 222 | 223 | .tooltip .tooltiptext::after { 224 | content: ""; 225 | position: absolute; 226 | top: 100%; 227 | left: 50%; 228 | margin-left: -5px; 229 | border-width: 5px; 230 | border-style: solid; 231 | border-color: #555 transparent transparent transparent; 232 | } 233 | 234 | .tooltip:hover .tooltiptext { 235 | visibility: visible; 236 | opacity: 1; 237 | } 238 | 239 | .error { 240 | color: #dc3545; 241 | } 242 | 243 | /* The snackbar - position it at the bottom and in the middle of the screen */ 244 | #snackbar { 245 | font-size: 80%; 246 | line-height: 1.2em; 247 | visibility: hidden; 248 | /* Hidden by default. Visible on click */ 249 | min-width: 250px; 250 | /* Set a default minimum width */ 251 | margin-left: -125px; 252 | /* Divide value of min-width by 2 */ 253 | background-color: #333; 254 | /* Black background color */ 255 | color: #fff; 256 | /* White text color */ 257 | text-align: center; 258 | /* Centered text */ 259 | border-radius: 2px; 260 | /* Rounded borders */ 261 | padding: 12px; 262 | /* Padding */ 263 | position: fixed; 264 | /* Sit on top of the screen */ 265 | z-index: 1; 266 | /* Add a z-index if needed */ 267 | left: 50%; 268 | /* Center the snackbar */ 269 | bottom: 30px; 270 | /* 30px from the bottom */ 271 | } 272 | 273 | /* Show the snackbar when clicking on a button (class added with JavaScript) */ 274 | .show { 275 | visibility: visible !important; 276 | /* Show the snackbar */ 277 | 278 | /* Add animation: Take 0.5 seconds to fade in and out the snackbar. 279 | However, delay the fade out process for 2.5 seconds */ 280 | -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; 281 | animation: fadein 0.5s, fadeout 0.5s 2.5s; 282 | } 283 | 284 | /* Animations to fade the snackbar in and out */ 285 | @-webkit-keyframes fadein { 286 | from { 287 | bottom: 0; 288 | opacity: 0; 289 | } 290 | 291 | to { 292 | bottom: 30px; 293 | opacity: 1; 294 | } 295 | } 296 | 297 | @keyframes fadein { 298 | from { 299 | bottom: 0; 300 | opacity: 0; 301 | } 302 | 303 | to { 304 | bottom: 30px; 305 | opacity: 1; 306 | } 307 | } 308 | 309 | @-webkit-keyframes fadeout { 310 | from { 311 | bottom: 30px; 312 | opacity: 1; 313 | } 314 | 315 | to { 316 | bottom: 0; 317 | opacity: 0; 318 | } 319 | } 320 | 321 | @keyframes fadeout { 322 | from { 323 | bottom: 30px; 324 | opacity: 1; 325 | } 326 | 327 | to { 328 | bottom: 0; 329 | opacity: 0; 330 | } 331 | } 332 | 333 | 334 | 335 | .main { 336 | padding-top: 20px; 337 | } 338 | 339 | .list { 340 | display: flex; 341 | flex-wrap: wrap; 342 | } 343 | 344 | .list>div { 345 | padding: 0.4em; 346 | } 347 | 348 | body { 349 | text-decoration-skip: ink; 350 | } 351 | 352 | .hide { 353 | display: none; 354 | } 355 | 356 | textarea { 357 | width: 100%; 358 | border: none; 359 | resize: none; 360 | height: 20em; 361 | border: 1px solid #ccc; 362 | background-color: #f5f5f5; 363 | padding: 1em; 364 | font-size: 0.9em; 365 | } 366 | 367 | details>p>code { 368 | background-color: inherit; 369 | } 370 | 371 | .editer { 372 | margin: 0.2em; 373 | padding: 0.5em; 374 | background-color: #f5f5f5; 375 | display: inline; 376 | border: none; 377 | font-weight: bold; 378 | font-family: var(--sans-font); 379 | font-size: 1em; 380 | } 381 | 382 | .banner { 383 | padding-top: 1em; 384 | } 385 | 386 | .p05 { 387 | padding-top: 0.5em; 388 | padding-bottom: 0.5em; 389 | } 390 | 391 | .flexcol { 392 | display: flex; 393 | flex-direction: row; 394 | align-content: center; 395 | } -------------------------------------------------------------------------------- /templates/files.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /templates/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | host yo self 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 42 | 43 |

44 |

Need a web host? Host yo self! Use this page to host a website or a file directly from your computer / phone / smartwatch / toaster!

45 |
46 | Click here for FAQ. 47 |

FAQ

48 |

How do I start web hosting? You will need to setup port forwarding, a dynamic DNS, name 49 | registration, MySQL, PHP, Apache and take a online course in Javascript.

50 |

Just kidding! You don't need any of that crap. Just drag and drop a folder, or select a file. 51 | That's literally it. Now you can host a website from your laptop or your phone or your smartwatch or 52 | your toaster.

53 |

How is this possible? When the server you point at gets a request for a webpage, the 54 | server turns back and asks you for that content and will use what you provide for the original 55 | request.

56 |

Seriously, how is this possible? The relay uses websockets in your browser to process 57 | GET commands.

58 |

Won't my website disappear when I close my browser? Yep! There is a command-line tool 59 | that doesn't require a browser so it can run in the background if you need that. But yes, if your 60 | computer turns off then your site is down. Welcome to the joys of hosting a site on the internet.

61 |

Won't I have to reload my browser if I change a file? Yep! Welcome to the joys of 62 | Javascript.

63 |

What's the largest file I can host using this? ¯\_(ツ)_/¯

64 |

Should I use this to host a website? Dear god yes.

65 |

Does it scale? Horizontally, or vertically? Probably neither!

66 |

What inspired this? beaker browser, ngrok, localhost.run, inlets.dev, Parks and Recreation.

67 |

What's the point of this? You can host a website! You can share a file! Anything you 68 | want, directly from your browser!

69 |

70 |

71 | 72 |

Terms of use

73 |
74 |

Introduction

75 |

These Terms of Service ("Terms") govern your use of hostyoself.com ("The Service").

76 |

Services

77 |

The Service provides a online utility to upload and host files on a temporary basis.

78 |

Your Content in Our Services

79 |

You may upload content as part of the features of the Services. By uploading content, you hereby 80 | grant us a nonexclusive, royalty-free, worldwide license to use your content in connection with 81 | the provision of the Services. You hereby represent and warrant that your content will not 82 | infringe the rights of any third party and will comply with any content guidelines presented by 83 | The Service. To report abusive Screenshots or to report claims of copyright or trademark 84 | infringement, email us a link to the shot at schollz@mg.hostyoself.com.

85 |

Proprietary Rights

86 |

The Service does not grant you any intellectual property rights in the Services that are not 87 | specifically stated in these Terms. For example, these Terms do not provide the right to use any 88 | of The Service's copyrights, trade names, trademarks, service marks, logos, domain names, or 89 | other distinctive brand features. The Services are distributed under and subject to the current 90 | version of the MIT license.

91 |

Term; Termination

92 |

These Terms will continue to apply until ended by either you or The Service. You can choose to 93 | end them at any time for any reason by deleting your account, discontinuing your use of the 94 | Services, and if applicable, unsubscribing from our emails.

95 |

Indemnification

96 |

You agree to defend, indemnify and hold harmless The Service, its contractors, contributors, 97 | licensors, and partners, and their respective directors, officers, employees and agents 98 | ("Indemnified Parties") from and against any and all third party claims and expenses, including 99 | attorneys' fees, arising out of or related to your use of the Services (including, but not 100 | limited to, from any content uploaded by you).

101 |

Disclaimer; Limitation of Liability

102 |

THE SERVICES ARE PROVIDED "AS IS" WITH ALL FAULTS. TO THE EXTENT PERMITTED BY LAW, THE SERVICE 103 | AND THE INDEMNIFIED PARTIES HEREBY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, 104 | INCLUDING WITHOUT LIMITATION WARRANTIES THAT THE SERVICES ARE FREE OF DEFECTS, MERCHANTABLE, FIT 105 | FOR A PARTICULAR PURPOSE, AND NON-INFRINGING. YOU BEAR THE ENTIRE RISK AS TO SELECTING THE 106 | SERVICES FOR YOUR PURPOSES AND AS TO THE QUALITY AND PERFORMANCE OF THE SERVICES, INCLUDING 107 | WITHOUT LIMITATION THE RISK THAT YOUR CONTENT IS DELETED OR CORRUPTED OR THAT SOMEONE ELSE 108 | ACCESSES YOUR ONLINE ACCOUNTS. THIS LIMITATION WILL APPLY NOTWITHSTANDING THE FAILURE OF 109 | ESSENTIAL PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF 110 | IMPLIED WARRANTIES, SO THIS DISCLAIMER MAY NOT APPLY TO YOU.

111 |

EXCEPT AS REQUIRED BY LAW, THE SERVICE AND THE INDEMNIFIED PARTIES WILL NOT BE LIABLE FOR ANY 112 | INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES ARISING OUT OF OR IN ANY WAY 113 | RELATING TO THESE TERMS OR THE USE OF OR INABILITY TO USE THE SERVICES, INCLUDING WITHOUT 114 | LIMITATION DIRECT AND INDIRECT DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, LOST PROFITS, LOSS 115 | OF DATA, AND COMPUTER FAILURE OR MALFUNCTION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES 116 | AND REGARDLESS OF THE THEORY (CONTRACT, TORT, OR OTHERWISE) UPON WHICH SUCH CLAIM IS BASED. THE 117 | COLLECTIVE LIABILITY OF THE SERVICE AND THE INDEMNIFIED PARTIES UNDER THIS AGREEMENT WILL NOT 118 | EXCEED $500 (FIVE HUNDRED DOLLARS). SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION 119 | OF INCIDENTAL, CONSEQUENTIAL, OR SPECIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY 120 | TO YOU.

121 |

Modifications to these Terms

122 |

The Service may update these Terms from time to time to address a new feature of the Services or 123 | to clarify a provision. The updated Terms will be posted online. Your continued use of the 124 | Services after the effective date of such changes constitutes your acceptance of such changes. 125 |

126 |

Miscellaneous

127 |

These Terms constitute the entire agreement between you and The Service concerning the Services 128 | and are governed by the laws of the state of Washington, U.S.A., excluding its conflict of law 129 | provisions. If any portion of these Terms is held to be invalid or unenforceable, the remaining 130 | portions will remain in full force and effect. In the event of a conflict between a translated 131 | version of these terms and the English language version, the English language version shall 132 | control.

133 |
134 |

135 |

136 |

137 | 138 |

Privacy policy

139 |
140 |

Effective date: July 11, 2019

141 |

Host Yo Self ("us", "we", or "our") operates the https://hostyoself.com website (the "Service").

142 |

This page informs you of our policies regarding the collection, use, and disclosure of personal data when you use our Service and the choices you have associated with that data.

143 |

We use your data to provide and improve the Service. By using the Service, you agree to the collection and use of information in accordance with this policy. Unless otherwise defined in this Privacy Policy, terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, accessible from https://hostyoself.com

144 |

Information Collection And Use

145 |

We collect several different types of information for various purposes to provide and improve our Service to you.

146 |

Types of Data Collected

147 |

Personal Data

148 |

While using our Service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you ("Personal Data"). Personally identifiable information may include, but is not limited to:

149 |
    150 |
  • Usage Data
  • 151 |
152 |

Usage Data

153 |

We may also collect information how the Service is accessed and used ("Usage Data"). This Usage Data may include information such as your computer's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.

154 |

Use of Data

155 |

Host Yo Self uses the collected data for various purposes:

156 |
    157 |
  • To provide and maintain the Service
  • 158 |
  • To notify you about changes to our Service
  • 159 |
  • To allow you to participate in interactive features of our Service when you choose to do so
  • 160 |
  • To provide customer care and support
  • 161 |
  • To provide analysis or valuable information so that we can improve the Service
  • 162 |
  • To monitor the usage of the Service
  • 163 |
  • To detect, prevent and address technical issues
  • 164 |
165 |

Transfer Of Data

166 |

Your information, including Personal Data, may be transferred to — and maintained on — computers located outside of your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from your jurisdiction.

167 |

If you are located outside United States and choose to provide information to us, please note that we transfer the data, including Personal Data, to United States and process it there.

168 |

Your consent to this Privacy Policy followed by your submission of such information represents your agreement to that transfer.

169 |

Host Yo Self will take all steps reasonably necessary to ensure that your data is treated securely and in accordance with this Privacy Policy and no transfer of your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of your data and other personal information.

170 |

Disclosure Of Data

171 |

Legal Requirements

172 |

Host Yo Self may disclose your Personal Data in the good faith belief that such action is necessary to:

173 |
    174 |
  • To comply with a legal obligation
  • 175 |
  • To protect and defend the rights or property of Host Yo Self
  • 176 |
  • To prevent or investigate possible wrongdoing in connection with the Service
  • 177 |
  • To protect the personal safety of users of the Service or the public
  • 178 |
  • To protect against legal liability
  • 179 |
180 |

Security Of Data

181 |

The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.

182 |

Service Providers

183 |

We may employ third party companies and individuals to facilitate our Service ("Service Providers"), to provide the Service on our behalf, to perform Service-related services or to assist us in analyzing how our Service is used.

184 |

These third parties have access to your Personal Data only to perform these tasks on our behalf and are obligated not to disclose or use it for any other purpose.

185 |

Links To Other Sites

186 |

Our Service may contain links to other sites that are not operated by us. If you click on a third party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.

187 |

We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.

188 |

Children's Privacy

189 |

Our Service does not address anyone under the age of 18 ("Children").

190 |

We do not knowingly collect personally identifiable information from anyone under the age of 18. If you are a parent or guardian and you are aware that your Children has provided us with Personal Data, please contact us. If we become aware that we have collected Personal Data from children without verification of parental consent, we take steps to remove that information from our servers.

191 |

Changes To This Privacy Policy

192 |

We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.

193 |

We will let you know via email and/or a prominent notice on our Service, prior to the change becoming effective and update the "effective date" at the top of this Privacy Policy.

194 |

You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.

195 |

Contact Us

196 |

If you have any questions about this Privacy Policy, please contact us:

197 |
    198 |
  • By email: schollz@mg.hostyoself.com
  • 199 |
200 |
201 |

202 |
203 |
204 | 205 | 206 |
207 |
208 | 209 | 210 |   (You can spawn multiple hosts with this key). 211 |
212 |
213 |
214 | Drag and drop a folder or click to share a file.
215 |

216 |
217 |
218 |
219 |
220 |
221 |
222 | Files served 223 |
224 |
225 |

Console:

226 | 227 |
228 | 235 |
236 | 239 | 240 | 241 | 242 | 243 | 244 | --------------------------------------------------------------------------------