- 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-%:
+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 }
+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+}
+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",
+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>
+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>
+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