repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
6af3058
parent
06a20ea
author
Eric Bower
date
2023-10-11 03:20:15 +0000 UTC
feat(pastes): extend file expiration from 7 -> 90 days
chore(pastes): update copy
refactor(pastes): move ssh server into `pastes` package
7 files changed,  +148, -145
M Makefile
+1, -1
1@@ -60,7 +60,7 @@ bp-podman-all: bp-podman-prose bp-podman-lists bp-podman-pastes bp-podman-imgs b
2 .PHONY: all
3 
4 build-auth:
5-	go build -o "build/auth" "./cmd/auth"
6+	go build -o "build/auth" "./cmd/auth/web"
7 .PHONY: build-auth
8 
9 build-%:
M cmd/pastes/ssh/main.go
+2, -114
  1@@ -1,119 +1,7 @@
  2 package main
  3 
  4-import (
  5-	"context"
  6-	"fmt"
  7-	"os"
  8-	"os/signal"
  9-	"syscall"
 10-	"time"
 11-
 12-	"github.com/charmbracelet/promwish"
 13-	"github.com/charmbracelet/wish"
 14-	bm "github.com/charmbracelet/wish/bubbletea"
 15-	lm "github.com/charmbracelet/wish/logging"
 16-	"github.com/gliderlabs/ssh"
 17-	"github.com/picosh/pico/db/postgres"
 18-	"github.com/picosh/pico/filehandlers"
 19-	"github.com/picosh/pico/pastes"
 20-	"github.com/picosh/pico/shared"
 21-	"github.com/picosh/pico/shared/storage"
 22-	"github.com/picosh/pico/wish/cms"
 23-	"github.com/picosh/pico/wish/list"
 24-	"github.com/picosh/pico/wish/pipe"
 25-	"github.com/picosh/pico/wish/proxy"
 26-	"github.com/picosh/pico/wish/send/auth"
 27-	wishrsync "github.com/picosh/pico/wish/send/rsync"
 28-	"github.com/picosh/pico/wish/send/scp"
 29-	"github.com/picosh/pico/wish/send/sftp"
 30-)
 31-
 32-type SSHServer struct{}
 33-
 34-func (me *SSHServer) authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 35-	return true
 36-}
 37-
 38-func createRouter(handler *filehandlers.ScpUploadHandler) proxy.Router {
 39-	return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
 40-		return []wish.Middleware{
 41-			pipe.Middleware(handler, ""),
 42-			list.Middleware(handler),
 43-			scp.Middleware(handler),
 44-			wishrsync.Middleware(handler),
 45-			auth.Middleware(handler),
 46-			bm.Middleware(cms.Middleware(&handler.Cfg.ConfigCms, handler.Cfg)),
 47-			lm.Middleware(),
 48-		}
 49-	}
 50-}
 51-
 52-func withProxy(handler *filehandlers.ScpUploadHandler, otherMiddleware ...wish.Middleware) ssh.Option {
 53-	return func(server *ssh.Server) error {
 54-		err := sftp.SSHOption(handler)(server)
 55-		if err != nil {
 56-			return err
 57-		}
 58-
 59-		return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
 60-	}
 61-}
 62+import "github.com/picosh/pico/pastes"
 63 
 64 func main() {
 65-	host := shared.GetEnv("PASTES_HOST", "0.0.0.0")
 66-	port := shared.GetEnv("PASTES_SSH_PORT", "2222")
 67-	promPort := shared.GetEnv("PASTES_PROM_PORT", "9222")
 68-	cfg := pastes.NewConfigSite()
 69-	logger := cfg.Logger
 70-	dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
 71-	defer dbh.Close()
 72-	hooks := &pastes.FileHooks{
 73-		Cfg: cfg,
 74-		Db:  dbh,
 75-	}
 76-
 77-	var st storage.ObjectStorage
 78-	var err error
 79-	if cfg.MinioURL == "" {
 80-		st, err = storage.NewStorageFS(cfg.StorageDir)
 81-	} else {
 82-		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
 83-	}
 84-
 85-	if err != nil {
 86-		logger.Fatal(err)
 87-	}
 88-
 89-	handler := filehandlers.NewScpPostHandler(dbh, cfg, hooks, st)
 90-
 91-	sshServer := &SSHServer{}
 92-	s, err := wish.NewServer(
 93-		wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
 94-		wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
 95-		wish.WithPublicKeyAuth(sshServer.authHandler),
 96-		withProxy(
 97-			handler,
 98-			promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pastes-ssh"),
 99-		),
100-	)
101-	if err != nil {
102-		logger.Fatal(err)
103-	}
104-
105-	done := make(chan os.Signal, 1)
106-	signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
107-	logger.Infof("Starting SSH server on %s:%s", host, port)
108-	go func() {
109-		if err = s.ListenAndServe(); err != nil {
110-			logger.Fatal(err)
111-		}
112-	}()
113-
114-	<-done
115-	logger.Info("Stopping SSH server")
116-	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
117-	defer func() { cancel() }()
118-	if err := s.Shutdown(ctx); err != nil {
119-		logger.Fatal(err)
120-	}
121+	pastes.StartSshServer()
122 }
A pastes/cms.go
+118, -0
  1@@ -0,0 +1,118 @@
  2+package pastes
  3+
  4+import (
  5+	"context"
  6+	"fmt"
  7+	"os"
  8+	"os/signal"
  9+	"syscall"
 10+	"time"
 11+
 12+	"github.com/charmbracelet/promwish"
 13+	"github.com/charmbracelet/wish"
 14+	bm "github.com/charmbracelet/wish/bubbletea"
 15+	lm "github.com/charmbracelet/wish/logging"
 16+	"github.com/gliderlabs/ssh"
 17+	"github.com/picosh/pico/db/postgres"
 18+	"github.com/picosh/pico/filehandlers"
 19+	"github.com/picosh/pico/shared"
 20+	"github.com/picosh/pico/shared/storage"
 21+	"github.com/picosh/pico/wish/cms"
 22+	"github.com/picosh/pico/wish/list"
 23+	"github.com/picosh/pico/wish/pipe"
 24+	"github.com/picosh/pico/wish/proxy"
 25+	"github.com/picosh/pico/wish/send/auth"
 26+	wishrsync "github.com/picosh/pico/wish/send/rsync"
 27+	"github.com/picosh/pico/wish/send/scp"
 28+	"github.com/picosh/pico/wish/send/sftp"
 29+)
 30+
 31+type SSHServer struct{}
 32+
 33+func (me *SSHServer) authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
 34+	return true
 35+}
 36+
 37+func createRouter(handler *filehandlers.ScpUploadHandler) proxy.Router {
 38+	return func(sh ssh.Handler, s ssh.Session) []wish.Middleware {
 39+		return []wish.Middleware{
 40+			pipe.Middleware(handler, ""),
 41+			list.Middleware(handler),
 42+			scp.Middleware(handler),
 43+			wishrsync.Middleware(handler),
 44+			auth.Middleware(handler),
 45+			bm.Middleware(cms.Middleware(&handler.Cfg.ConfigCms, handler.Cfg)),
 46+			lm.Middleware(),
 47+		}
 48+	}
 49+}
 50+
 51+func withProxy(handler *filehandlers.ScpUploadHandler, otherMiddleware ...wish.Middleware) ssh.Option {
 52+	return func(server *ssh.Server) error {
 53+		err := sftp.SSHOption(handler)(server)
 54+		if err != nil {
 55+			return err
 56+		}
 57+
 58+		return proxy.WithProxy(createRouter(handler), otherMiddleware...)(server)
 59+	}
 60+}
 61+
 62+func StartSshServer() {
 63+	host := shared.GetEnv("PASTES_HOST", "0.0.0.0")
 64+	port := shared.GetEnv("PASTES_SSH_PORT", "2222")
 65+	promPort := shared.GetEnv("PASTES_PROM_PORT", "9222")
 66+	cfg := NewConfigSite()
 67+	logger := cfg.Logger
 68+	dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
 69+	defer dbh.Close()
 70+	hooks := &FileHooks{
 71+		Cfg: cfg,
 72+		Db:  dbh,
 73+	}
 74+
 75+	var st storage.ObjectStorage
 76+	var err error
 77+	if cfg.MinioURL == "" {
 78+		st, err = storage.NewStorageFS(cfg.StorageDir)
 79+	} else {
 80+		st, err = storage.NewStorageMinio(cfg.MinioURL, cfg.MinioUser, cfg.MinioPass)
 81+	}
 82+
 83+	if err != nil {
 84+		logger.Fatal(err)
 85+	}
 86+
 87+	handler := filehandlers.NewScpPostHandler(dbh, cfg, hooks, st)
 88+
 89+	sshServer := &SSHServer{}
 90+	s, err := wish.NewServer(
 91+		wish.WithAddress(fmt.Sprintf("%s:%s", host, port)),
 92+		wish.WithHostKeyPath("ssh_data/term_info_ed25519"),
 93+		wish.WithPublicKeyAuth(sshServer.authHandler),
 94+		withProxy(
 95+			handler,
 96+			promwish.Middleware(fmt.Sprintf("%s:%s", host, promPort), "pastes-ssh"),
 97+		),
 98+	)
 99+	if err != nil {
100+		logger.Fatal(err)
101+	}
102+
103+	done := make(chan os.Signal, 1)
104+	signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
105+	logger.Infof("Starting SSH server on %s:%s", host, port)
106+	go func() {
107+		if err = s.ListenAndServe(); err != nil {
108+			logger.Fatal(err)
109+		}
110+	}()
111+
112+	<-done
113+	logger.Info("Stopping SSH server")
114+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
115+	defer func() { cancel() }()
116+	if err := s.Shutdown(ctx); err != nil {
117+		logger.Fatal(err)
118+	}
119+}
M pastes/config.go
+2, -4
 1@@ -23,10 +23,8 @@ func NewConfigSite() *shared.ConfigSite {
 2 	minioPass := shared.GetEnv("MINIO_ROOT_PASSWORD", "")
 3 
 4 	intro := "To get started, enter a username.\n"
 5-	intro += "Then create a folder locally (e.g. ~/pastes).\n"
 6-	intro += "Then write your paste post (e.g. feature.patch).\n"
 7-	intro += "Finally, send your files to us:\n\n"
 8-	intro += fmt.Sprintf("scp ~/pastes/* %s:/", domain)
 9+	intro += "Then all you need to do is send your pastes to us:\n\n"
10+	intro += fmt.Sprintf("scp my.patch %s:/", domain)
11 
12 	return &shared.ConfigSite{
13 		Debug:                debug == "1",
M pastes/html/help.page.tmpl
+11, -23
 1@@ -53,21 +53,21 @@
 2         </ol>
 3     </section>
 4 
 5-    <section id="post-update">
 6+    <section id="paste-update">
 7         <h2 class="text-xl">
 8-            <a href="#post-update" rel="nofollow noopener">#</a>
 9-            How do I update a post?
10+            <a href="#paste-update" rel="nofollow noopener">#</a>
11+            How do I update a paste?
12         </h2>
13         <p>
14-            Updating a post requires that you update the source document and then run the <code>scp</code>
15-            command again.  If the filename remains the same, then the post will be updated.
16+            Updating a paste requires that you update the source document and then run the <code>scp</code>
17+            command again.  If the filename remains the same, then the paste will be updated.
18         </p>
19     </section>
20 
21-    <section id="post-delete">
22+    <section id="paste-delete">
23         <h2 class="text-xl">
24-            <a href="#post-delete" rel="nofollow noopener">#</a>
25-            How do I delete a post?
26+            <a href="#paste-delete" rel="nofollow noopener">#</a>
27+            How do I delete a paste?
28         </h2>
29         <p>
30             Because <code>scp</code> does not natively support deleting files, I didn't want to bake
31@@ -75,7 +75,7 @@
32         </p>
33 
34         <p>
35-            However, if a user wants to delete a post they can delete the contents of the file and
36+            However, if a user wants to delete a paste they can delete the contents of the file and
37             then upload it to our server.  If the file contains 0 bytes, we will remove the post.
38             For example, if you want to delete <code>delete.txt</code> you could:
39         </p>
40@@ -86,23 +86,11 @@ scp ./delete.txt {{.Site.Domain}}:/</pre>
41 
42         <p>
43             Alternatively, you can go to <code>ssh {{.Site.Domain}}</code> and select "Manage posts."
44-            Then you can highlight the post you want to delete and then press "X."  It will ask for
45-            confirmation before actually removing the post.
46+            Then you can highlight the paste you want to delete and then press "X."  It will ask for
47+            confirmation before actually removing the paste.
48         </p>
49     </section>
50 
51-    <section id="post-upload-single-file">
52-        <h2 class="text-xl">
53-            <a href="#post-upload-single-file" rel="nofollow noopener">#</a>
54-            When I want to publish a new post, do I have to upload all posts everytime?
55-        </h2>
56-        <p>
57-            Nope!  Just <code>scp</code> the file you want to publish.  For example, if you created
58-            a new post called <code>taco-tuesday.md</code> then you would publish it like this:
59-        </p>
60-        <pre>scp ./taco-tuesday.md {{.Site.Domain}}:</pre>
61-    </section>
62-
63     <section id="multiple-accounts">
64         <h2 class="text-xl">
65             <a href="#multiple-accounts" rel="nofollow noopener">#</a>
M pastes/html/marketing.page.tmpl
+10, -1
 1@@ -62,6 +62,15 @@
 2         <p>We'll return the URL back to you so you never have to leave the terminal!</p>
 3     </section>
 4 
 5+    <section>
 6+        <h2 class="text-lg font-bold">Pipe Support</h2>
 7+
 8+        <pre>echo "foobar" | ssh pastes.sh</pre>
 9+        <pre>echo "foobar" | ssh pastes.sh FILENAME</pre>
10+        <pre># if the tty warning annoys you
11+echo "foobar" | ssh -T pastes.sh</pre>
12+    </section>
13+
14     <section>
15         <h2 class="text-lg font-bold">Terminal workflow without installation</h2>
16         <p>
17@@ -76,7 +85,7 @@
18     <section>
19         <h2 class="text-lg font-bold">Features</h2>
20         <ul>
21-            <li>Pastes last 7 days</li>
22+            <li>Pastes last 90 days</li>
23             <li>Bring your own editor</li>
24             <li>You control the source files</li>
25             <li>Terminal workflow with no installation</li>
M pastes/scp_hooks.go
+4, -2
 1@@ -9,6 +9,8 @@ import (
 2 	"github.com/picosh/pico/shared"
 3 )
 4 
 5+var DEFAULT_EXPIRES_AT = 90
 6+
 7 type FileHooks struct {
 8 	Cfg *shared.ConfigSite
 9 	Db  db.DB
10@@ -31,8 +33,8 @@ func (p *FileHooks) FileMeta(data *filehandlers.PostMetaData) error {
11 	// we want the slug to be the filename for pastes
12 	data.Slug = data.Filename
13 	if data.Post.ExpiresAt == nil || data.Post.ExpiresAt.IsZero() {
14-		// mark posts for deletion a week after creation
15-		expiresAt := time.Now().AddDate(0, 0, 7)
16+		// mark posts for deletion a X days after creation
17+		expiresAt := time.Now().AddDate(0, 0, DEFAULT_EXPIRES_AT)
18 		data.ExpiresAt = &expiresAt
19 	}
20 	return nil