A fork of the Cocoon PDS but being made more distributed.
0

Configure Feed

Select the types of activity you want to include in your feed.

1package main 2 3import ( 4 "crypto/ecdsa" 5 "crypto/elliptic" 6 "crypto/rand" 7 "encoding/json" 8 "fmt" 9 "os" 10 "time" 11 12 "github.com/bluesky-social/indigo/atproto/atcrypto" 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 "github.com/haileyok/cocoon/internal/helpers" 15 "github.com/haileyok/cocoon/server" 16 _ "github.com/joho/godotenv/autoload" 17 "github.com/lestrrat-go/jwx/v2/jwk" 18 "github.com/urfave/cli/v2" 19 "golang.org/x/crypto/bcrypt" 20 "gorm.io/driver/sqlite" 21 "gorm.io/gorm" 22) 23 24var Version = "dev" 25 26func main() { 27 app := &cli.App{ 28 Name: "cocoon", 29 Usage: "An atproto PDS", 30 Flags: []cli.Flag{ 31 &cli.StringFlag{ 32 Name: "addr", 33 Value: ":8080", 34 EnvVars: []string{"COCOON_ADDR"}, 35 }, 36 &cli.StringFlag{ 37 Name: "db-name", 38 Value: "cocoon.db", 39 EnvVars: []string{"COCOON_DB_NAME"}, 40 }, 41 &cli.StringFlag{ 42 Name: "did", 43 EnvVars: []string{"COCOON_DID"}, 44 }, 45 &cli.StringFlag{ 46 Name: "hostname", 47 EnvVars: []string{"COCOON_HOSTNAME"}, 48 }, 49 &cli.StringFlag{ 50 Name: "rotation-key-path", 51 EnvVars: []string{"COCOON_ROTATION_KEY_PATH"}, 52 }, 53 &cli.StringFlag{ 54 Name: "jwk-path", 55 EnvVars: []string{"COCOON_JWK_PATH"}, 56 }, 57 &cli.StringFlag{ 58 Name: "contact-email", 59 EnvVars: []string{"COCOON_CONTACT_EMAIL"}, 60 }, 61 &cli.StringSliceFlag{ 62 Name: "relays", 63 EnvVars: []string{"COCOON_RELAYS"}, 64 }, 65 &cli.StringFlag{ 66 Name: "admin-password", 67 EnvVars: []string{"COCOON_ADMIN_PASSWORD"}, 68 }, 69 &cli.StringFlag{ 70 Name: "smtp-user", 71 EnvVars: []string{"COCOON_SMTP_USER"}, 72 }, 73 &cli.StringFlag{ 74 Name: "smtp-pass", 75 EnvVars: []string{"COCOON_SMTP_PASS"}, 76 }, 77 &cli.StringFlag{ 78 Name: "smtp-host", 79 EnvVars: []string{"COCOON_SMTP_HOST"}, 80 }, 81 &cli.StringFlag{ 82 Name: "smtp-port", 83 EnvVars: []string{"COCOON_SMTP_PORT"}, 84 }, 85 &cli.StringFlag{ 86 Name: "smtp-email", 87 EnvVars: []string{"COCOON_SMTP_EMAIL"}, 88 }, 89 &cli.StringFlag{ 90 Name: "smtp-name", 91 EnvVars: []string{"COCOON_SMTP_NAME"}, 92 }, 93 &cli.BoolFlag{ 94 Name: "s3-backups-enabled", 95 EnvVars: []string{"COCOON_S3_BACKUPS_ENABLED"}, 96 }, 97 &cli.BoolFlag{ 98 Name: "s3-blobstore-enabled", 99 EnvVars: []string{"COCOON_S3_BLOBSTORE_ENABLED"}, 100 }, 101 &cli.StringFlag{ 102 Name: "s3-region", 103 EnvVars: []string{"COCOON_S3_REGION"}, 104 }, 105 &cli.StringFlag{ 106 Name: "s3-bucket", 107 EnvVars: []string{"COCOON_S3_BUCKET"}, 108 }, 109 &cli.StringFlag{ 110 Name: "s3-endpoint", 111 EnvVars: []string{"COCOON_S3_ENDPOINT"}, 112 }, 113 &cli.StringFlag{ 114 Name: "s3-access-key", 115 EnvVars: []string{"COCOON_S3_ACCESS_KEY"}, 116 }, 117 &cli.StringFlag{ 118 Name: "s3-secret-key", 119 EnvVars: []string{"COCOON_S3_SECRET_KEY"}, 120 }, 121 &cli.StringFlag{ 122 Name: "session-secret", 123 EnvVars: []string{"COCOON_SESSION_SECRET"}, 124 }, 125 &cli.StringFlag{ 126 Name: "blockstore-variant", 127 EnvVars: []string{"COCOON_BLOCKSTORE_VARIANT"}, 128 Value: "sqlite", 129 }, 130 &cli.StringFlag{ 131 Name: "fallback-proxy", 132 EnvVars: []string{"COCOON_FALLBACK_PROXY"}, 133 }, 134 }, 135 Commands: []*cli.Command{ 136 runServe, 137 runCreateRotationKey, 138 runCreatePrivateJwk, 139 runCreateInviteCode, 140 runResetPassword, 141 }, 142 ErrWriter: os.Stdout, 143 Version: Version, 144 } 145 146 if err := app.Run(os.Args); err != nil { 147 fmt.Printf("Error: %v\n", err) 148 } 149} 150 151var runServe = &cli.Command{ 152 Name: "run", 153 Usage: "Start the cocoon PDS", 154 Flags: []cli.Flag{}, 155 Action: func(cmd *cli.Context) error { 156 157 s, err := server.New(&server.Args{ 158 Addr: cmd.String("addr"), 159 DbName: cmd.String("db-name"), 160 Did: cmd.String("did"), 161 Hostname: cmd.String("hostname"), 162 RotationKeyPath: cmd.String("rotation-key-path"), 163 JwkPath: cmd.String("jwk-path"), 164 ContactEmail: cmd.String("contact-email"), 165 Version: Version, 166 Relays: cmd.StringSlice("relays"), 167 AdminPassword: cmd.String("admin-password"), 168 SmtpUser: cmd.String("smtp-user"), 169 SmtpPass: cmd.String("smtp-pass"), 170 SmtpHost: cmd.String("smtp-host"), 171 SmtpPort: cmd.String("smtp-port"), 172 SmtpEmail: cmd.String("smtp-email"), 173 SmtpName: cmd.String("smtp-name"), 174 S3Config: &server.S3Config{ 175 BackupsEnabled: cmd.Bool("s3-backups-enabled"), 176 BlobstoreEnabled: cmd.Bool("s3-blobstore-enabled"), 177 Region: cmd.String("s3-region"), 178 Bucket: cmd.String("s3-bucket"), 179 Endpoint: cmd.String("s3-endpoint"), 180 AccessKey: cmd.String("s3-access-key"), 181 SecretKey: cmd.String("s3-secret-key"), 182 }, 183 SessionSecret: cmd.String("session-secret"), 184 BlockstoreVariant: server.MustReturnBlockstoreVariant(cmd.String("blockstore-variant")), 185 FallbackProxy: cmd.String("fallback-proxy"), 186 }) 187 if err != nil { 188 fmt.Printf("error creating cocoon: %v", err) 189 return err 190 } 191 192 if err := s.Serve(cmd.Context); err != nil { 193 fmt.Printf("error starting cocoon: %v", err) 194 return err 195 } 196 197 return nil 198 }, 199} 200 201var runCreateRotationKey = &cli.Command{ 202 Name: "create-rotation-key", 203 Usage: "creates a rotation key for your pds", 204 Flags: []cli.Flag{ 205 &cli.StringFlag{ 206 Name: "out", 207 Required: true, 208 Usage: "output file for your rotation key", 209 }, 210 }, 211 Action: func(cmd *cli.Context) error { 212 key, err := atcrypto.GeneratePrivateKeyK256() 213 if err != nil { 214 return err 215 } 216 217 bytes := key.Bytes() 218 219 if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil { 220 return err 221 } 222 223 return nil 224 }, 225} 226 227var runCreatePrivateJwk = &cli.Command{ 228 Name: "create-private-jwk", 229 Usage: "creates a private jwk for your pds", 230 Flags: []cli.Flag{ 231 &cli.StringFlag{ 232 Name: "out", 233 Required: true, 234 Usage: "output file for your jwk", 235 }, 236 }, 237 Action: func(cmd *cli.Context) error { 238 privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 239 if err != nil { 240 return err 241 } 242 243 key, err := jwk.FromRaw(privKey) 244 if err != nil { 245 return err 246 } 247 248 kid := fmt.Sprintf("%d", time.Now().Unix()) 249 250 if err := key.Set(jwk.KeyIDKey, kid); err != nil { 251 return err 252 } 253 254 b, err := json.Marshal(key) 255 if err != nil { 256 return err 257 } 258 259 if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil { 260 return err 261 } 262 263 return nil 264 }, 265} 266 267var runCreateInviteCode = &cli.Command{ 268 Name: "create-invite-code", 269 Usage: "creates an invite code", 270 Flags: []cli.Flag{ 271 &cli.StringFlag{ 272 Name: "for", 273 Usage: "optional did to assign the invite code to", 274 }, 275 &cli.IntFlag{ 276 Name: "uses", 277 Usage: "number of times the invite code can be used", 278 Value: 1, 279 }, 280 }, 281 Action: func(cmd *cli.Context) error { 282 db, err := newDb() 283 if err != nil { 284 return err 285 } 286 287 forDid := "did:plc:123" 288 if cmd.String("for") != "" { 289 did, err := syntax.ParseDID(cmd.String("for")) 290 if err != nil { 291 return err 292 } 293 294 forDid = did.String() 295 } 296 297 uses := cmd.Int("uses") 298 299 code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8)) 300 301 if err := db.Exec("INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", forDid, code, uses).Error; err != nil { 302 return err 303 } 304 305 fmt.Printf("New invite code created with %d uses: %s\n", uses, code) 306 307 return nil 308 }, 309} 310 311var runResetPassword = &cli.Command{ 312 Name: "reset-password", 313 Usage: "resets a password", 314 Flags: []cli.Flag{ 315 &cli.StringFlag{ 316 Name: "did", 317 Usage: "did of the user who's password you want to reset", 318 }, 319 }, 320 Action: func(cmd *cli.Context) error { 321 db, err := newDb() 322 if err != nil { 323 return err 324 } 325 326 didStr := cmd.String("did") 327 did, err := syntax.ParseDID(didStr) 328 if err != nil { 329 return err 330 } 331 332 newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12)) 333 hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10) 334 if err != nil { 335 return err 336 } 337 338 if err := db.Exec("UPDATE repos SET password = ? WHERE did = ?", hashed, did.String()).Error; err != nil { 339 return err 340 } 341 342 fmt.Printf("Password for %s has been reset to: %s", did.String(), newPass) 343 344 return nil 345 }, 346} 347 348func newDb() (*gorm.DB, error) { 349 return gorm.Open(sqlite.Open("cocoon.db"), &gorm.Config{}) 350}