From 242e1c81a38f5770c32a7390ff0c3258a6514c4e Mon Sep 17 00:00:00 2001 From: Gabriel Simmer Date: Tue, 30 Apr 2024 15:17:19 +0100 Subject: [PATCH 1/2] Initial PostgreSQL compatibility --- cmd/tclipd/main.go | 61 +++++++++++++++++++++++++++---------------- cmd/tclipd/schema.sql | 17 +++++------- flake.lock | 6 ++--- flake.nix | 2 +- go.mod | 1 + go.sum | 2 ++ 6 files changed, 53 insertions(+), 36 deletions(-) diff --git a/cmd/tclipd/main.go b/cmd/tclipd/main.go index 1993c20..a20ff85 100644 --- a/cmd/tclipd/main.go +++ b/cmd/tclipd/main.go @@ -27,6 +27,7 @@ import ( "github.com/niklasfasching/go-org/org" "github.com/russross/blackfriday" _ "modernc.org/sqlite" + _ "github.com/lib/pq" "tailscale.com/client/tailscale" "tailscale.com/client/tailscale/apitype" "tailscale.com/ipn" @@ -40,6 +41,7 @@ var ( tsnetLogVerbose = flag.Bool("tsnet-verbose", hasEnv("TSNET_VERBOSE"), "if set, have tsnet log verbosely to standard error") useFunnel = flag.Bool("use-funnel", hasEnv("USE_FUNNEL"), "if set, expose individual pastes to the public internet with Funnel, USE_FUNNEL in the environment") hidePasteUserInfo = flag.Bool("hide-funnel-users", hasEnv("HIDE_FUNNEL_USERS"), "if set, display the username and profile picture of the user who created the paste in funneled pastes") + databaseUrl = flag.String("database-url", envOr("DATABASE_URL", ""), "optional database url if you'd rather use Postgres instead of sqlite") //go:embed schema.sql sqlSchema string @@ -105,7 +107,7 @@ SELECT p.id FROM pastes p INNER JOIN users u ON p.user_id = u.id -ORDER BY p.rowid DESC +ORDER BY p.created_at DESC LIMIT 5 ` @@ -239,11 +241,11 @@ INSERT INTO pastes , data ) VALUES - ( ?1 - , ?2 - , ?3 - , ?4 - , ?5 + ( $1 + , $2 + , $3 + , $4 + , $5 )` _, err = s.db.ExecContext( @@ -296,9 +298,9 @@ SELECT p.id FROM pastes p INNER JOIN users u ON p.user_id = u.id -ORDER BY p.rowid DESC +ORDER BY p.created_at DESC LIMIT 25 -OFFSET ?1 +OFFSET $1 ` uq := r.URL.Query() @@ -434,7 +436,7 @@ func (s *Server) TailnetDeletePost(w http.ResponseWriter, r *http.Request) { q := ` SELECT p.user_id FROM pastes p -WHERE p.id = ?1` +WHERE p.id = $1` row := s.db.QueryRowContext(r.Context(), q, id) var userIDOfPaste int64 @@ -450,7 +452,7 @@ WHERE p.id = ?1` q = ` DELETE FROM pastes -WHERE id = ?1 AND user_id = ?2 +WHERE id = $1 AND user_id = $2 ` if _, err := s.db.ExecContext(r.Context(), q, id, ui.UserProfile.ID); err != nil { @@ -502,7 +504,7 @@ SELECT p.filename FROM pastes p INNER JOIN users u ON p.user_id = u.id -WHERE p.id = ?1` +WHERE p.id = $1` row := s.db.QueryRowContext(r.Context(), q, id) var fname, data, userLoginName, userDisplayName, userProfilePicURL string @@ -684,7 +686,7 @@ func main() { log.Fatal(err) } - db, err := openDB(*dataDir) + db, err := openDB(*dataDir, *databaseUrl) if err != nil { log.Fatal(err) } @@ -822,8 +824,15 @@ func (mch MixedCriticalityHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ panic("unknown security level") } -func openDB(dir string) (*sql.DB, error) { - db, err := sql.Open("sqlite", "file:"+filepath.Join(dir, "data.db")) +func openDB(dir string, databaseUrl string) (*sql.DB, error) { + dbtype := "sqlite" + dbpath := "file:"+filepath.Join(dir, "data.db") + if databaseUrl != "" { + dbtype = "postgres" + dbpath = databaseUrl + } + + db, err := sql.Open(dbtype, dbpath) if err != nil { return nil, err } @@ -833,6 +842,14 @@ func openDB(dir string) (*sql.DB, error) { return nil, err } + // Enable WAL for SQLite + Litestream + if dbtype == "sqlite" { + _, err := db.Exec("PRAGMA journal_mode=WAL;") + if err != nil { + return nil, err + } + } + if _, err := db.Exec(sqlSchema); err != nil { return nil, err } @@ -866,16 +883,16 @@ INSERT INTO users , profile_pic_url ) VALUES - ( ?1 - , ?2 - , ?3 - , ?4 + ( $1 + , $2 + , $3 + , $4 ) -ON CONFLICT DO +ON CONFLICT (id) DO UPDATE SET - login_name = ?2 - , display_name = ?3 - , profile_pic_url = ?4 + login_name = $2 + , display_name = $3 + , profile_pic_url = $4 ` _, err = db.ExecContext( diff --git a/cmd/tclipd/schema.sql b/cmd/tclipd/schema.sql index 44a1435..1cb62b3 100644 --- a/cmd/tclipd/schema.sql +++ b/cmd/tclipd/schema.sql @@ -1,5 +1,10 @@ --- Enable WAL mode to make backups easier with Litestream -PRAGMA journal_mode=WAL; +-- Correlates to tailcfg.UserProfile +CREATE TABLE IF NOT EXISTS users + ( id TEXT PRIMARY KEY NOT NULL + , login_name TEXT NOT NULL + , display_name TEXT NOT NULL + , profile_pic_url TEXT NOT NULL + ); -- Paste data CREATE TABLE IF NOT EXISTS pastes @@ -10,11 +15,3 @@ CREATE TABLE IF NOT EXISTS pastes , data TEXT NOT NULL , FOREIGN KEY(user_id) REFERENCES users(id) ); - --- Correlates to tailcfg.UserProfile -CREATE TABLE IF NOT EXISTS users - ( id TEXT PRIMARY KEY NOT NULL - , login_name TEXT NOT NULL - , display_name TEXT NOT NULL - , profile_pic_url TEXT NOT NULL - ); diff --git a/flake.lock b/flake.lock index aa27517..226a16b 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1711703276, - "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", + "lastModified": 1714253743, + "narHash": "sha256-mdTQw2XlariysyScCv2tTE45QSU9v/ezLcHJ22f0Nxc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", + "rev": "58a1abdbae3217ca6b702f03d3b35125d88a2994", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8768ed4..9f5bd07 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,7 @@ go = pkgs.go; src = ./.; subPackages = "cmd/tclipd"; - vendorHash = "sha256-7iOEp0NrcmvBNrxl5kjrJOAhVfKYPNpI4ssNxaf6g3M="; + vendorHash = "sha256-Ho39aNWTbygA46/9lkXRVLV6FLkWenAJKxOE3MJUb2M="; }; tclip = pkgs.buildGo122Module { diff --git a/go.mod b/go.mod index a54a844..6d8a5ce 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.22.1 require ( github.com/go-enry/go-enry/v2 v2.8.7 github.com/google/uuid v1.5.0 + github.com/lib/pq v1.10.9 github.com/microcosm-cc/bluemonday v1.0.26 github.com/niklasfasching/go-org v1.7.0 github.com/russross/blackfriday v1.6.0 diff --git a/go.sum b/go.sum index 89aa582..8f81f38 100644 --- a/go.sum +++ b/go.sum @@ -125,6 +125,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -- 2.46.0 From 9605a9d6f607eb2a871cca2ac84889245272512a Mon Sep 17 00:00:00 2001 From: Gabriel Simmer Date: Tue, 30 Apr 2024 17:07:14 +0100 Subject: [PATCH 2/2] Formatting --- cmd/tclipd/main.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/tclipd/main.go b/cmd/tclipd/main.go index a20ff85..afe1975 100644 --- a/cmd/tclipd/main.go +++ b/cmd/tclipd/main.go @@ -23,11 +23,11 @@ import ( "github.com/go-enry/go-enry/v2" "github.com/google/uuid" + _ "github.com/lib/pq" "github.com/microcosm-cc/bluemonday" "github.com/niklasfasching/go-org/org" "github.com/russross/blackfriday" _ "modernc.org/sqlite" - _ "github.com/lib/pq" "tailscale.com/client/tailscale" "tailscale.com/client/tailscale/apitype" "tailscale.com/ipn" @@ -36,12 +36,12 @@ import ( ) var ( - hostname = flag.String("hostname", envOr("TSNET_HOSTNAME", "paste"), "hostname to use on your tailnet, TSNET_HOSTNAME in the environment") - dataDir = flag.String("data-location", dataLocation(), "where data is stored, defaults to DATA_DIR or ~/.config/tailscale/paste") - tsnetLogVerbose = flag.Bool("tsnet-verbose", hasEnv("TSNET_VERBOSE"), "if set, have tsnet log verbosely to standard error") - useFunnel = flag.Bool("use-funnel", hasEnv("USE_FUNNEL"), "if set, expose individual pastes to the public internet with Funnel, USE_FUNNEL in the environment") - hidePasteUserInfo = flag.Bool("hide-funnel-users", hasEnv("HIDE_FUNNEL_USERS"), "if set, display the username and profile picture of the user who created the paste in funneled pastes") - databaseUrl = flag.String("database-url", envOr("DATABASE_URL", ""), "optional database url if you'd rather use Postgres instead of sqlite") + hostname = flag.String("hostname", envOr("TSNET_HOSTNAME", "paste"), "hostname to use on your tailnet, TSNET_HOSTNAME in the environment") + dataDir = flag.String("data-location", dataLocation(), "where data is stored, defaults to DATA_DIR or ~/.config/tailscale/paste") + tsnetLogVerbose = flag.Bool("tsnet-verbose", hasEnv("TSNET_VERBOSE"), "if set, have tsnet log verbosely to standard error") + useFunnel = flag.Bool("use-funnel", hasEnv("USE_FUNNEL"), "if set, expose individual pastes to the public internet with Funnel, USE_FUNNEL in the environment") + hidePasteUserInfo = flag.Bool("hide-funnel-users", hasEnv("HIDE_FUNNEL_USERS"), "if set, display the username and profile picture of the user who created the paste in funneled pastes") + databaseUrl = flag.String("database-url", envOr("DATABASE_URL", ""), "optional database url if you'd rather use Postgres instead of sqlite") //go:embed schema.sql sqlSchema string @@ -826,12 +826,12 @@ func (mch MixedCriticalityHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ func openDB(dir string, databaseUrl string) (*sql.DB, error) { dbtype := "sqlite" - dbpath := "file:"+filepath.Join(dir, "data.db") + dbpath := "file:" + filepath.Join(dir, "data.db") if databaseUrl != "" { dbtype = "postgres" dbpath = databaseUrl } - + db, err := sql.Open(dbtype, dbpath) if err != nil { return nil, err @@ -849,7 +849,7 @@ func openDB(dir string, databaseUrl string) (*sql.DB, error) { return nil, err } } - + if _, err := db.Exec(sqlSchema); err != nil { return nil, err } -- 2.46.0