A URL shortener service that uses ATProto to allow self hosting and ensuring the user owns their data

Getting ready to deploy

+51 -6
+4
.dockerignore
···
··· 1 + at-shorter 2 + database.db 3 + .env 4 + example.env
+19
Dockerfile
···
··· 1 + FROM golang:latest AS builder 2 + 3 + WORKDIR /app 4 + 5 + COPY . . 6 + RUN go mod download 7 + 8 + COPY . . 9 + 10 + RUN CGO_ENABLED=0 go build -o at-shorter . 11 + 12 + FROM alpine:latest 13 + 14 + RUN apk --no-cache add ca-certificates 15 + 16 + WORKDIR /root/ 17 + COPY --from=builder /app/at-shorter . 18 + 19 + CMD ["./at-shorter"]
+14 -4
cmd/atshorter/main.go
··· 24 defaultServerAddr = "wss://jetstream.atproto.tools/subscribe" 25 httpClientTimeoutDuration = time.Second * 5 26 transportIdleConnTimeoutDuration = time.Second * 90 27 ) 28 29 func main() { 30 - err := godotenv.Load(".env") 31 if err != nil { 32 if !os.IsNotExist(err) { 33 log.Fatal("Error loading .env file") ··· 54 defer db.Close() 55 56 var config oauth.ClientConfig 57 - bind := ":8080" 58 scopes := []string{ 59 "atproto", 60 "repo:com.atshorter.shorturl?action=create", ··· 63 } 64 if host == "" { 65 config = oauth.NewLocalhostConfig( 66 - fmt.Sprintf("http://127.0.0.1%s/oauth-callback", bind), 67 scopes, 68 ) 69 slog.Info("configuring localhost OAuth client", "CallbackURL", config.CallbackURL) ··· 83 }, 84 } 85 86 - server, err := atshorter.NewServer(host, 8080, db, oauthClient, httpClient) 87 if err != nil { 88 slog.Error("create new server", "error", err) 89 return
··· 24 defaultServerAddr = "wss://jetstream.atproto.tools/subscribe" 25 httpClientTimeoutDuration = time.Second * 5 26 transportIdleConnTimeoutDuration = time.Second * 90 27 + defaultPort = "8080" 28 ) 29 30 func main() { 31 + envLocation := os.Getenv("ENV_LOCATION") 32 + if envLocation == "" { 33 + envLocation = ".env" 34 + } 35 + 36 + err := godotenv.Load(envLocation) 37 if err != nil { 38 if !os.IsNotExist(err) { 39 log.Fatal("Error loading .env file") ··· 60 defer db.Close() 61 62 var config oauth.ClientConfig 63 + port := os.Getenv("PORT") 64 + if port == "" { 65 + port = defaultPort 66 + } 67 + 68 scopes := []string{ 69 "atproto", 70 "repo:com.atshorter.shorturl?action=create", ··· 73 } 74 if host == "" { 75 config = oauth.NewLocalhostConfig( 76 + fmt.Sprintf("http://127.0.0.1:%s/oauth-callback", port), 77 scopes, 78 ) 79 slog.Info("configuring localhost OAuth client", "CallbackURL", config.CallbackURL) ··· 93 }, 94 } 95 96 + server, err := atshorter.NewServer(host, port, db, oauthClient, httpClient) 97 if err != nil { 98 slog.Error("create new server", "error", err) 99 return
+11
docker-compose.yaml
···
··· 1 + services: 2 + knot: 3 + container_name: at-shorter 4 + image: willdot/at-shorter:latest 5 + environment: 6 + ENV_LOCATION: "/data/at-shorter.env" 7 + volumes: 8 + - ./data:/app/data 9 + ports: 10 + - "3005:3005" 11 + restart: always
+1
example.env
··· 3 HOST="the host of the service such as https://my-url-shortner.com" 4 DATABASE_PATH="./" 5 JS_SERVER_ADDR="set to a different Jetstream instance"
··· 3 HOST="the host of the service such as https://my-url-shortner.com" 4 DATABASE_PATH="./" 5 JS_SERVER_ADDR="set to a different Jetstream instance" 6 + PORT="3002"
+2 -2
server.go
··· 40 mu sync.Mutex 41 } 42 43 - func NewServer(host string, port int, store Store, oauthClient *oauth.ClientApp, httpClient *http.Client) (*Server, error) { 44 sessionStore := sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) 45 46 homeTemplate, err := template.ParseFiles("./html/home.html") ··· 83 mux.HandleFunc("GET /oauth-client-metadata.json", srv.serveClientMetadata) 84 mux.HandleFunc("GET /oauth-callback", srv.handleOauthCallback) 85 86 - addr := fmt.Sprintf("0.0.0.0:%d", port) 87 srv.httpserver = &http.Server{ 88 Addr: addr, 89 Handler: mux,
··· 40 mu sync.Mutex 41 } 42 43 + func NewServer(host string, port string, store Store, oauthClient *oauth.ClientApp, httpClient *http.Client) (*Server, error) { 44 sessionStore := sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) 45 46 homeTemplate, err := template.ParseFiles("./html/home.html") ··· 83 mux.HandleFunc("GET /oauth-client-metadata.json", srv.serveClientMetadata) 84 mux.HandleFunc("GET /oauth-callback", srv.handleOauthCallback) 85 86 + addr := fmt.Sprintf("0.0.0.0:%s", port) 87 srv.httpserver = &http.Server{ 88 Addr: addr, 89 Handler: mux,