A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
1package main
2
3import (
4 "fmt"
5 "os"
6
7 "github.com/distribution/distribution/v3/registry"
8 _ "github.com/distribution/distribution/v3/registry/auth/token"
9 _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
10 "github.com/spf13/cobra"
11
12 "atcr.io/pkg/appview"
13
14 // Register our custom middleware
15 _ "atcr.io/pkg/appview/middleware"
16
17 // Register built-in themes
18 _ "atcr.io/themes/seamark"
19)
20
21var configFile string
22
23var serveCmd = &cobra.Command{
24 Use: "serve",
25 Short: "Start the ATCR registry server",
26 Long: `Start the ATCR registry server with authentication endpoints.
27
28Configuration is loaded in layers: defaults -> YAML file -> environment variables.
29Use --config to specify a YAML configuration file.
30Environment variables always override file values.`,
31 Args: cobra.NoArgs,
32 RunE: serveRegistry,
33}
34
35var configCmd = &cobra.Command{
36 Use: "config",
37 Short: "Configuration management commands",
38}
39
40var configInitCmd = &cobra.Command{
41 Use: "init [path]",
42 Short: "Generate an example configuration file",
43 Long: `Generate an example YAML configuration file with all available options.
44If path is provided, writes to that file. Otherwise writes to stdout.`,
45 Args: cobra.MaximumNArgs(1),
46 RunE: func(cmd *cobra.Command, args []string) error {
47 yamlBytes, err := appview.ExampleYAML()
48 if err != nil {
49 return fmt.Errorf("failed to generate example config: %w", err)
50 }
51 if len(args) == 1 {
52 if err := os.WriteFile(args[0], yamlBytes, 0644); err != nil {
53 return fmt.Errorf("failed to write config file: %w", err)
54 }
55 fmt.Fprintf(os.Stderr, "Wrote example config to %s\n", args[0])
56 return nil
57 }
58 fmt.Print(string(yamlBytes))
59 return nil
60 },
61}
62
63func init() {
64 serveCmd.Flags().StringVarP(&configFile, "config", "c", "", "path to YAML configuration file")
65
66 configCmd.AddCommand(configInitCmd)
67
68 // Replace the default serve command with our custom one
69 for i, cmd := range registry.RootCmd.Commands() {
70 if cmd.Name() == "serve" {
71 registry.RootCmd.Commands()[i] = serveCmd
72 break
73 }
74 }
75
76 registry.RootCmd.AddCommand(configCmd)
77}
78
79func serveRegistry(cmd *cobra.Command, args []string) error {
80 cfg, err := appview.LoadConfig(configFile)
81 if err != nil {
82 return fmt.Errorf("failed to load config: %w", err)
83 }
84
85 branding, err := appview.LookupTheme(cfg.UI.Theme)
86 if err != nil {
87 return err
88 }
89
90 server, err := appview.NewAppViewServer(cfg, branding)
91 if err != nil {
92 return fmt.Errorf("failed to initialize server: %w", err)
93 }
94
95 return server.Serve()
96}
97
98func main() {
99 // The serve command is registered above via init()
100 // Just execute the root command
101 if err := registry.RootCmd.Execute(); err != nil {
102 os.Exit(1)
103 }
104}