Compare commits

...

43 commits

Author SHA1 Message Date
Zack
04ad1997a6
Create FUNDING.yml 2020-05-13 15:36:38 -07:00
Zack
caaed23f57
Merge pull request #14 from 0x333333/master
Fix a typo.
2019-07-20 09:07:42 -07:00
0x333
bdd0536998 Fix a typo. 2019-07-16 22:51:04 -07:00
Zack Scholl
47ef115491 fix handling of relative path 2019-07-15 08:58:40 -07:00
Zack Scholl
e98f05e6dc check it webkitRelativePath exists 2019-07-15 08:50:23 -07:00
Zack Scholl
ab215af53e fix dockerfile 2019-07-14 17:12:39 -06:00
Zack Scholl
3a62f092f1 add docker fixes #10 2019-07-14 17:02:32 -06:00
Zack Scholl
d34a5bca3f add Dockerfile 2019-07-14 16:58:44 -06:00
Zack
e96dfb99e7
Merge pull request #8 from dmuth/master
Added Homebrew installation instructions
2019-07-14 05:42:59 -07:00
Douglas Muth
b458bac63f Added Homebrew installation instructions 2019-07-13 14:28:46 -04:00
Zack Scholl
d74ce01023 bump 2019-07-12 21:38:01 -06:00
Zack Scholl
2804887a56 only request all files when index.html is not found at root 2019-07-12 16:37:55 -07:00
Zack Scholl
ef28306e6d more logging 2019-07-12 16:29:49 -07:00
Zack Scholl
d709d471be client servers file list
Fixes #7
2019-07-12 16:25:44 -07:00
Zack Scholl
479838af2a add file listing if nothing is found 2019-07-12 15:20:26 -07:00
Zack Scholl
78f06e76ca add file listing if nothing is found 2019-07-12 15:15:51 -07:00
Zack Scholl
3520f36f72 use fullpath if no webkitRelativePath 2019-07-12 14:33:10 -07:00
Zack Scholl
2670c964d1 bump 2019-07-12 09:51:18 -06:00
Zack Scholl
bd01535efa make the host run forever 2019-07-12 08:50:37 -07:00
Zack Scholl
8ff76da85e bug fix: browser reconnect after disconnect 2019-07-12 08:43:41 -07:00
Zack Scholl
66d39b9503 bug fix: attempt to get files with no extension 2019-07-12 08:52:07 -06:00
Zack Scholl
ef0758e07a bump 2019-07-12 08:15:06 -06:00
Zack Scholl
329667f3b6 Merge branch 'master' of github.com:schollz/hostyoself 2019-07-12 13:54:23 +00:00
Zack Scholl
6f31c5793f debug -> debugf 2019-07-12 13:54:19 +00:00
Zack
ec1ef5b442
Merge pull request #2 from DavidePastore/patch-1
Fix typos
2019-07-12 05:25:54 -07:00
Zack Scholl
ae6121a085 return nil from websocket error so no superfluos writes 2019-07-12 12:18:40 +00:00
Zack Scholl
79bf4213e4 fix superflous response 2019-07-12 12:11:07 +00:00
Davide Pastore
c0c267f5a1 "What's" instead of "Whats" 2019-07-12 12:55:22 +02:00
Zack
7094dd38a4
Update README.md 2019-07-11 21:44:18 -07:00
Zack
c197e117b2
Update README.md 2019-07-11 17:47:35 -07:00
Zack Scholl
839b47dae9 add crazy gif 2019-07-11 10:45:39 -07:00
Zack Scholl
70e81d7281 allow bots 2019-07-11 17:29:49 +00:00
Zack Scholl
b4d13031f4 update style 2019-07-11 08:09:37 -07:00
Zack
e19772c3cf
Update README.md 2019-07-11 08:02:13 -07:00
Zack Scholl
81c1f8fe1e update urls 2019-07-11 07:56:13 -07:00
Zack Scholl
a0392651f5 update urls 2019-07-11 07:48:03 -07:00
Zack Scholl
c4dfc70944 check domain before using referrer 2019-07-11 06:37:04 -07:00
Zack Scholl
a11230bf29 readme 2019-07-11 06:26:23 -07:00
Zack Scholl
d4244e1d1d readme 2019-07-11 06:25:24 -07:00
Zack Scholl
f39a44a31b readme 2019-07-11 06:24:50 -07:00
Zack Scholl
4b33a98996 Merge branch 'master' of github.com:schollz/hostyoself 2019-07-11 06:24:29 -07:00
Zack Scholl
fc8500b523 readme 2019-07-11 06:24:25 -07:00
Zack Scholl
caba186e6e update go.mod 2019-07-11 07:21:19 -06:00
13 changed files with 300 additions and 44 deletions

3
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: schollz

20
Dockerfile Normal file
View file

@ -0,0 +1,20 @@
###################################
# 1. Build in a Go-based image #
###################################
FROM golang:1.12-alpine as builder
RUN apk add --no-cache git ca-certificates # add deps here (like make) if needed
WORKDIR /go/hostyoself
COPY . .
# any pre-requisities to building should be added here
RUN go generate -v
RUN go build -v
###################################
# 2. Copy into a clean image #
###################################
FROM alpine:latest
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /go/hostyoself/hostyoself /hostyoself
VOLUME /data
CMD ["sh","-c","/hostyoself host --folder /data"]

View file

@ -1,21 +1,26 @@
<p align="center">
<img
src="/static/hostyoself2.png" border="0" alt="hostyoself">
src="/static/banner.jpg" border="0" alt="hostyoself">
<br>
<a href="https://travis-ci.org/schollz/hostyoself"><img
src="https://img.shields.io/travis/schollz/hostyoself.svg?style=flat-square"
alt="Build Status"></a> <a
<a
href="https://github.com/schollz/hostyoself/releases/latest"><img
src="https://img.shields.io/badge/version-0.0.0-brightgreen.svg?style=flat-square"
src="https://img.shields.io/badge/version-0.0.6-brightgreen.svg?style=flat-square"
alt="Version"></a> </p>
<p align="center">A hosting service for absolute minimalists. Try it at <a href="https://hostyoself.com">hostyoself.com</a>.</p>
<p align="center">A hosting service from the browser, because why not. Try it at <a href="https://hostyoself.com">hostyoself.com</a>.</p>
## See it in action
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:!
![Inception](/static/inception.gif)
## Host from the browser
Open [hostyoself.com](https://hostyoself.com) and drag and drop a folder, or select a file. Your browser will host the files!
## Host from the command line
You can host files directly from the terminal!
@ -27,13 +32,25 @@ https://hostyoself.com/confidentcat/
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!
If you're on a Mac, you can install with Homebrew:
```
brew tap schollz/homebrew https://github.com/schollz/homebrew-tap.git
brew install hostyoself
```
Or you can host your current directory using Docker:
```
$ docker run -v `pwd`:/data schollz/hostyoself
```
## Run your own relay
Want to run your own relay? Its easy.
```
$ hostyoself relay --url https://yoururl
$ hostyoself relay --url https://yoururl
```
## FAQ
@ -47,16 +64,18 @@ Just *kidding*! You don't need any of that crap. Just goto [hostyoself.com](http
**Seriously, how is this possible?** The relay uses websockets in your browser to process GET commands.
**Won't my website disappear when I close my browser?** Yep! There is a command-line tool 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.
**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.
**Won't I have to reload my browser if I change a file?** Yep! Welcome to the joys of Javascript.
**Whats the largest file I can host using this?** `¯\_(ツ)_/¯`
**What's the largest file I can host using this?** `¯\_(ツ)_/¯`
**Should I use this to host a website?** Dear god yes.
**Does this use AI or blockchain?** Sure, why not.
**Does it scale?** Horizontally, or vertically? Probably neither!
**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.
**What's the point of this?** You can host a website! You can share a file! Anything you want, directly from your browser!
@ -72,4 +91,4 @@ $ go build -v
## License
MIT
MIT

1
go.mod
View file

@ -6,6 +6,7 @@ require (
github.com/fsnotify/fsnotify v1.4.7
github.com/gorilla/websocket v1.4.0
github.com/h2non/filetype v1.0.8
github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect
github.com/schollz/logger v1.0.1
github.com/urfave/cli v1.20.0
github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb

2
go.sum
View file

@ -4,6 +4,8 @@ github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14=
github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU=
github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=
github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
github.com/schollz/logger v1.0.1 h1:BuBAU+euqphM0Ny9qFVScl4RSxatis4nCHIkOxO2cUU=
github.com/schollz/logger v1.0.1/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=

12
main.go
View file

@ -34,7 +34,7 @@ func main() {
app.Version = Version
app.Compiled = time.Now()
app.Usage = "host your files using websockets from the command line or a browser"
app.UsageText = "use to transfer files or host a impromptu website"
app.UsageText = "use to transfer files or host an impromptu website"
app.Commands = []cli.Command{
{
Name: "relay",
@ -92,7 +92,15 @@ func host(c *cli.Context) (err error) {
if err != nil {
return
}
return cl.Run()
for {
log.Info("serving forever")
err = cl.Run()
if err != nil {
log.Debug(err)
}
log.Infof("server disconnected, retrying in 10 seconds")
time.Sleep(10 * time.Second)
}
}
func relay(c *cli.Context) (err error) {

View file

@ -1,6 +1,7 @@
package client
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
@ -10,6 +11,7 @@ import (
"sync"
"github.com/schollz/hostyoself/pkg/namesgenerator"
"github.com/schollz/hostyoself/pkg/server"
"github.com/schollz/hostyoself/pkg/utils"
"github.com/schollz/hostyoself/pkg/wsconn"
@ -134,6 +136,31 @@ func (c *client) Run() (err error) {
})
log.Infof("%s /%s 200", p.IPAddress, p.Message)
}
} else if p.Type == "files" {
c.Lock()
fs := make([]server.File, len(c.fileList))
i := 0
for n := range c.fileList {
fs[i] = server.File{
FullPath: n,
Upload: server.Upload{
UUID: "",
Total: 0,
Filename: "",
},
}
i++
}
c.Unlock()
b, _ := json.Marshal(fs)
log.Infof("%s sitemap", p.IPAddress)
err = ws.Send(wsconn.Payload{
Type: "files",
Success: true,
Message: string(b),
Key: c.Key,
})
}
if err != nil {
log.Debug(err)
@ -195,6 +222,7 @@ func (c *client) watchFileSystem() (err error) {
} else {
ppath, _ = filepath.Abs(ppath)
ppath = strings.TrimPrefix(filepath.ToSlash(ppath), c.Folder+"/")
log.Debugf("%s", ppath)
c.Lock()
c.fileList[ppath] = struct{}{}
c.Unlock()

View file

@ -1,6 +1,7 @@
package server
import (
"encoding/json"
"fmt"
"html/template"
"math/rand"
@ -69,7 +70,7 @@ func (s *server) handle(w http.ResponseWriter, r *http.Request) (err error) {
if r.URL.Path == "/robots.txt" {
// special path
w.Write([]byte(`User-agent: *
Disallow: /`))
Disallow:`))
} else if r.URL.Path == "/ws" {
return s.handleWebsocket(w, r)
} else if r.URL.Path == "/favicon.ico" {
@ -79,7 +80,7 @@ Disallow: /`))
var b []byte
b, err = Asset(r.URL.Path[1:])
if err != nil {
http.Error(w, "file not found", 404)
err = fmt.Errorf("resource '%s' not found", r.URL.Path[1:])
return
}
var contentType string
@ -127,13 +128,17 @@ Disallow: /`))
// determine file path and the domain
pathToFile := r.URL.Path[1:]
domain := strings.Split(r.URL.Path[1:], "/")[0]
// if there is a referer, try to obtain the domain from referer
piecesOfReferer := strings.Split(r.Referer(), "/")
if len(piecesOfReferer) > 4 && strings.HasPrefix(r.Referer(), s.publicURL) {
domain = piecesOfReferer[3]
}
// clean domain
domain = strings.Replace(strings.ToLower(strings.TrimSpace(domain)), " ", "-", -1)
if !s.isdomain(domain) {
log.Debugf("getting referer")
// if there is a referer, try to obtain the domain from referer
piecesOfReferer := strings.Split(r.Referer(), "/")
if len(piecesOfReferer) > 4 && strings.HasPrefix(r.Referer(), s.publicURL) {
domain = piecesOfReferer[3]
domain = strings.Replace(strings.ToLower(strings.TrimSpace(domain)), " ", "-", -1)
}
}
// prefix the domain if it doesn't exist
if !strings.HasPrefix(pathToFile, domain) {
@ -164,6 +169,7 @@ Disallow: /`))
// send GET request to websockets
var data string
var fs []File
data, err = s.get(domain, pathToFile, ipAddress)
if err != nil {
// try index.html if it doesn't exist
@ -172,11 +178,46 @@ Disallow: /`))
pathToFile += "/"
}
pathToFile += "index.html"
log.Debugf("trying 2nd try to get: %s", pathToFile)
data, err = s.get(domain, pathToFile, ipAddress)
}
if err != nil {
log.Debug("problem getting: %s", err.Error())
return
// try one more time
if strings.HasSuffix(pathToFile, "/index.html") {
pathToFile = strings.TrimSuffix(pathToFile, "/index.html")
log.Debugf("trying 3rd try to get: %s", pathToFile)
data, err = s.get(domain, pathToFile, ipAddress)
}
if err != nil {
if pathToFile == "index.html" {
// just serve files
fs, err = s.getFiles(domain, ipAddress)
log.Debugf("fs: %+v", fs)
if err != nil {
log.Debug(err)
return
}
b, _ := Asset("templates/files.html")
var t *template.Template
t, err = template.New("files").Parse(string(b))
if err != nil {
log.Error(err)
return
}
return t.Execute(w, struct {
Files []File
Domain string
}{
Domain: domain,
Files: fs,
})
} else {
log.Debugf("problem getting: %s", err.Error())
err = fmt.Errorf("not found")
return
}
}
}
}
@ -228,7 +269,8 @@ func (s *server) handleWebsocket(w http.ResponseWriter, r *http.Request) (err er
// handle websockets on this page
c, errUpgrade := wsupgrader.Upgrade(w, r, nil)
if errUpgrade != nil {
return errUpgrade
log.Error(errUpgrade)
return nil
}
ws := wsconn.New(c)
@ -246,7 +288,7 @@ func (s *server) handleWebsocket(w http.ResponseWriter, r *http.Request) (err er
err = fmt.Errorf("got wrong type/domain: %s/%s", p.Type, p.Message)
log.Debug(err)
ws.Close()
return
return nil
}
domain := strings.Replace(strings.ToLower(strings.TrimSpace(p.Message)), " ", "-", -1)
@ -272,6 +314,79 @@ func (s *server) handleWebsocket(w http.ResponseWriter, r *http.Request) (err er
Message: domain,
Success: true,
})
if err != nil {
log.Error(err)
}
return nil
}
func (s *server) isdomain(domain string) bool {
s.Lock()
_, ok := s.conn[domain]
s.Unlock()
return ok
}
type File struct {
FullPath string `json:"fullPath"`
Upload Upload `json:"upload"`
}
type Upload struct {
UUID string `json:"uuid"`
Total int `json:"total"`
Filename string `json:"filename"`
}
func (s *server) getFiles(domain, ipAddress string) (fs []File, err error) {
var connections []*connection
s.Lock()
if _, ok := s.conn[domain]; ok {
connections = s.conn[domain]
}
s.Unlock()
if connections == nil || len(connections) == 0 {
err = fmt.Errorf("no connections available for domain %s", domain)
log.Debug(err)
return
}
log.Debugf("requesting files of %s from %d connections", domain, len(connections))
// any connection that initated with this key is viable
key := connections[0].Key
// loop through connections randomly and try to get one to serve the file
for _, i := range rand.Perm(len(connections)) {
var p wsconn.Payload
p, err = func() (p wsconn.Payload, err error) {
err = connections[i].ws.Send(wsconn.Payload{
Type: "files",
Message: "all",
IPAddress: ipAddress,
})
if err != nil {
return
}
p, err = connections[i].ws.Receive()
return
}()
if err != nil {
log.Debug(err)
s.dumpConnection(domain, connections[i].ID)
continue
}
log.Tracef("recv: %+v", p)
if p.Type == "files" && p.Key == key {
if !p.Success {
err = fmt.Errorf(p.Message)
return
}
err = json.Unmarshal([]byte(p.Message), &fs)
return
}
log.Debugf("no good data from %d", i)
}
err = fmt.Errorf("invalid response")
return
}

BIN
static/inception.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

58
static/main.js vendored
View file

@ -48,13 +48,24 @@ var filesize = 0;
drop.on('addedfile', function(file) {
// console.log(file);
var domain = document.getElementById("inputDomain").value
var domain = document.getElementById("inputDomain").value;
files.push(file);
if (files.length == 1) {
relativeDirectory = file.webkitRelativePath.split("/")[0];
} else if (file.webkitRelativePath.split("/")[0] != relativeDirectory) {
relativeDirectory = "";
if ("webkitRelativePath" in file) {
if (files.length == 1 && file.webkitRelativePath != "") {
relativeDirectory = file.webkitRelativePath.split("/")[0];
} else if (file.webkitRelativePath.split("/")[0] != relativeDirectory) {
relativeDirectory = "";
}
}
if ("fullPath" in file) {
if (files.length == 1 && file.fullPath != "") {
relativeDirectory = file.fullPath.split("/")[0];
} else if (file.fullPath.split("/")[0] != relativeDirectory) {
relativeDirectory = "";
}
}
if (!(isConnected)) {
isConnected = true;
@ -121,12 +132,35 @@ const socketMessageListener = (event) => {
}
console.log(data)
consoleLog(`[debug] ${data.message}`)
if (data.type == "get") {
if (data.type == "files") {
if (files.length > 0) {
socketSend({
type: "files",
message: JSON.stringify(files),
success: true,
key: document.getElementById("inputKey").value,
});
consoleLog(
`${data.ip} [${(new Date()).toUTCString()}] sitemap 200`
);
} else {
socketSend({
type: "files",
message: "none found",
success: false,
key: document.getElementById("inputKey").value,
});
consoleLog(
`${data.ip} [${(new Date()).toUTCString()}] sitemap 404`
);
}
} else if (data.type == "get") {
var foundFile = false
var iToSend = 0
for (i = 0; i < files.length; i++) {
if (files[i].webkitRelativePath == data.message || files[i].name == data.message || files[i]
.webkitRelativePath == relativeDirectory + "/" + data.message) {
if (files[i].webkitRelativePath == data.message || files[i].fullPath == data.message || files[i].name == data.message || files[i]
.webkitRelativePath == relativeDirectory + "/" + data.message || files[i]
.fullPath == relativeDirectory + "/" + data.message) {
iToSend = i;
var reader = new FileReader();
reader.onload = function(theFile) {
@ -164,6 +198,14 @@ const socketMessageListener = (event) => {
};
const socketOpenListener = (event) => {
consoleLog('[info] connected');
if (isConnected == true) {
// reconnect if was connected and got disconnected
socketSend({
type: "domain",
message: document.getElementById("inputDomain").value,
key: document.getElementById("inputKey").value,
})
}
};
const socketCloseListener = (event) => {

2
static/style.css vendored
View file

@ -380,7 +380,7 @@ details>p>code {
}
.banner {
padding-top: 3em;
padding-top: 1em;
}
.p05 {

20
templates/files.html vendored Normal file
View file

@ -0,0 +1,20 @@
<html>
<head>
</head>
<body>
<ul>
{{range .Files}}
<li>
{{ if .FullPath }}
<a href="/{{$.Domain}}/{{.FullPath}}">{{.FullPath}}</a>
{{else}}
<a href="/{{$.Domain}}/{{.Upload.Filename}}">{{.Upload.Filename}}</a>
{{end}}
</li>
{{end}}
</ul>
</body>
</html>

22
templates/view.html vendored
View file

@ -15,10 +15,10 @@
<meta name='application-name' content='Host Yo Self'>
<meta name='apple-mobile-web-app-status-bar-style' content='black'>
<meta name='apple-mobile-web-app-title' content='Host Yo Self'>
<link rel='icon' sizes='192x192' href='/static/images/touch/android-launchericon-192-192.png'>
<link rel='apple-touch-icon' href='/static/images/touch/android-launchericon-192-192.png'>
<meta property='og:image' content='{{.PublicURL}}/static/images/touch/android-launchericon-192-192.png'>
<meta name='msapplication-TileImage' content='/static/images/touch/android-launchericon-144-144.png'>
<link rel='icon' sizes='192x192' href='/static/hostyoself2.png'>
<link rel='apple-touch-icon' href='/static/hostyoself2.png'>
<meta property='og:image' content='{{.PublicURL}}/static/hostyoself2.png'>
<meta name='msapplication-TileImage' content='/static/hostyoself2.png'>
<meta name='msapplication-TileColor' content='#375EAB'>
<meta name='theme-color' content='#375EAB'>
<meta property='og:title' content='Host Yo Self'>
@ -29,7 +29,7 @@
<meta name='twitter:url' content='{{.PublicURL}}/'>
<meta name='twitter:title' content='Host Yo Self'>
<meta name='twitter:description' content='Need a web host? Host your self! Use this page to host a website or a file directly from your computer / phone / smartwatch / toaster!'>
<meta name='twitter:image' content='{{.PublicURL}}/static/images/touch/android-launchericon-192-192.png'>
<meta name='twitter:image' content='{{.PublicURL}}/static/hostyoself2.png'>
<meta name='twitter:creator' content='@yakczar'>
<link rel="stylesheet" href="/static/dropzone.css">
<link rel="stylesheet" href="/static/style.css">
@ -39,7 +39,7 @@
<main>
<!-- <h1 align="center"><a href="/">hostyoself</a> </h1>
-->
<img src="/static/banner.jpg" class="banner">
<a href="{{.PublicURL}}"><img src="/static/banner.jpg" class="banner"></a>
<p id="errormessage" class="error"></p>
<p>Need a web host? <em>Host yo self!</em> Use this page to host a website or a file directly from your computer / phone / smartwatch / toaster!</p>
<details>
@ -60,12 +60,10 @@
computer turns off then your site is down. Welcome to the joys of hosting a site on the internet.</p>
<p><strong>Won't I have to reload my browser if I change a file?</strong> Yep! Welcome to the joys of
Javascript.</p>
<p><strong>Whats the largest file I can host using this?</strong> <code>¯\_(ツ)_/¯</code></p>
<p><strong>What's the largest file I can host using this?</strong> <code>¯\_(ツ)_/¯</code></p>
<p><strong>Should I use this to host a website?</strong> Dear god yes.</p>
<p><strong>Does this use AI or blockchain?</strong> Sure, why not. </p>
<p><strong>What inspired this?</strong> <a href="https://github.com/joewalnes/websocketd">websocketd</a>
which shows the magic of websockets and <a href="https://beakerbrowser.com/">beaker browser</a> which
shows the magic of browser hosting.</p>
<p><strong>Does it scale?</strong> Horizontally, or vertically? Probably neither!</p>
<p><strong>What inspired this?</strong> <a href="https://beakerbrowser.com/">beaker browser</a>, <a href="https://ngrok.com/">ngrok</a>, <a href="http://localhost.run/">localhost.run</a>, <a href="https://github.com/alexellis/inlets">inlets.dev</a>, Parks and Recreation.</p>
<p><strong>What's the point of this?</strong> You can host a website! You can share a file! Anything you
want, directly from your browser!</p>
<p>
@ -242,4 +240,4 @@
<script src="/static/main.js"></script>
</body>
</html>
</html>