tenseleyflow/shithub / 17c5f7b

Browse files

Add storage config block (repos_root + s3.*) with all-or-nothing s3 validation

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
17c5f7ba7fba11e1ac5a6e736c328664c418590d
Parents
95986a9
Tree
8d761cc

3 changed files

StatusFile+-
M .env.example 15 0
M docs/internal/config.md 8 0
M internal/infra/config/config.go 59 0
.env.examplemodified
@@ -14,3 +14,18 @@ SHITHUB_DATABASE_URL=postgres://shithub:shithub_dev@127.0.0.1:5432/shithub?sslmo
1414
 # Used only by tests. The dbtest harness creates per-test DBs cloned from a
1515
 # template DB rooted under this server.
1616
 SHITHUB_TEST_DATABASE_URL=postgres://shithub:shithub_dev@127.0.0.1:5432/postgres?sslmode=disable
17
+
18
+# ----- storage (S04) -----
19
+# Filesystem root for bare git repos. Production: /data/repos on the
20
+# block-storage volume. Dev default below points at a per-user dir.
21
+SHITHUB_STORAGE__REPOS_ROOT=/tmp/shithub-dev/repos
22
+
23
+# S3-compatible object storage. Dev defaults target the local MinIO from
24
+# docker-compose. Set non-default credentials in .env (do NOT commit).
25
+SHITHUB_STORAGE__S3__ENDPOINT=127.0.0.1:9000
26
+SHITHUB_STORAGE__S3__REGION=us-east-1
27
+SHITHUB_STORAGE__S3__ACCESS_KEY_ID=shithub-dev
28
+SHITHUB_STORAGE__S3__SECRET_ACCESS_KEY=shithub-dev-secret-please-change
29
+SHITHUB_STORAGE__S3__BUCKET=shithub-dev
30
+SHITHUB_STORAGE__S3__USE_SSL=false
31
+SHITHUB_STORAGE__S3__FORCE_PATH_STYLE=true
docs/internal/config.mdmodified
@@ -47,6 +47,14 @@ shithubd version # includes a one-line summary of which sinks are confi
4747
 | `session.key_b64` | string | `""` | Base64 32-byte AEAD key. Aliased by `SHITHUB_SESSION_KEY`. |
4848
 | `session.max_age` | duration | `720h` | Cookie session lifetime (30 days). |
4949
 | `session.secure` | bool | `false` | Set `Secure` cookie attribute. Enable under TLS (S37 deploy). |
50
+| `storage.repos_root` | string | `/data/repos` | Filesystem root for bare repos. Required. |
51
+| `storage.s3.endpoint` | string | `""` | S3-compatible endpoint host[:port], no scheme. Empty disables S3. |
52
+| `storage.s3.region` | string | `us-east-1` | Region for SigV4 signing. |
53
+| `storage.s3.access_key_id` | string | `""` | |
54
+| `storage.s3.secret_access_key` | string | `""` | Redacted by `config print`. |
55
+| `storage.s3.bucket` | string | `""` | Single bucket per environment. |
56
+| `storage.s3.use_ssl` | bool | `false` | True for Spaces, false for local MinIO. |
57
+| `storage.s3.force_path_style` | bool | `true` | True for MinIO, false for Spaces. |
5058
 
5159
 ## Env-var examples
5260
 
internal/infra/config/config.gomodified
@@ -35,6 +35,7 @@ type Config struct {
3535
 	Tracing        TracingConfig        `toml:"tracing"`
3636
 	ErrorReporting ErrorReportingConfig `toml:"error_reporting"`
3737
 	Session        SessionConfig        `toml:"session"`
38
+	Storage        StorageConfig        `toml:"storage"`
3839
 }
3940
 
4041
 // WebConfig holds HTTP server settings.
@@ -88,6 +89,25 @@ type SessionConfig struct {
8889
 	Secure bool          `toml:"secure"`
8990
 }
9091
 
92
+// StorageConfig configures repo filesystem storage and S3-compatible
93
+// object storage. The S3 block targets MinIO in dev/test and DigitalOcean
94
+// Spaces in prod (force_path_style true for MinIO, false for Spaces).
95
+type StorageConfig struct {
96
+	ReposRoot string          `toml:"repos_root"` // filesystem root for bare repos
97
+	S3        S3StorageConfig `toml:"s3"`
98
+}
99
+
100
+// S3StorageConfig holds the S3-compatible endpoint settings.
101
+type S3StorageConfig struct {
102
+	Endpoint        string `toml:"endpoint"`          // host[:port], no scheme
103
+	Region          string `toml:"region"`            // e.g. "us-east-1", "nyc3"
104
+	AccessKeyID     string `toml:"access_key_id"`     //
105
+	SecretAccessKey string `toml:"secret_access_key"` //
106
+	Bucket          string `toml:"bucket"`            // e.g. "shithub-dev"
107
+	UseSSL          bool   `toml:"use_ssl"`           // true for Spaces, false for local MinIO
108
+	ForcePathStyle  bool   `toml:"force_path_style"`  // true for MinIO, false for Spaces
109
+}
110
+
91111
 // Defaults returns the zero-config baseline.
92112
 func Defaults() Config {
93113
 	return Config{
@@ -119,6 +139,13 @@ func Defaults() Config {
119139
 			MaxAge: 30 * 24 * time.Hour,
120140
 			Secure: false,
121141
 		},
142
+		Storage: StorageConfig{
143
+			ReposRoot: "/data/repos",
144
+			S3: S3StorageConfig{
145
+				Region:         "us-east-1",
146
+				ForcePathStyle: true,
147
+			},
148
+		},
122149
 	}
123150
 }
124151
 
@@ -189,6 +216,38 @@ func Validate(c *Config) error {
189216
 	if c.Tracing.SampleRate < 0 || c.Tracing.SampleRate > 1 {
190217
 		return fmt.Errorf("config: tracing.sample_rate: must be in [0, 1], got %v", c.Tracing.SampleRate)
191218
 	}
219
+	if c.Storage.ReposRoot == "" {
220
+		return errors.New("config: storage.repos_root is required")
221
+	}
222
+	if err := validateS3(c.Storage.S3); err != nil {
223
+		return err
224
+	}
225
+	return nil
226
+}
227
+
228
+// validateS3 enforces all-or-nothing on the S3 block: if any of
229
+// endpoint/bucket/access keys are set, all must be set.
230
+func validateS3(s S3StorageConfig) error {
231
+	any := s.Endpoint != "" || s.Bucket != "" || s.AccessKeyID != "" || s.SecretAccessKey != ""
232
+	if !any {
233
+		return nil
234
+	}
235
+	missing := []string{}
236
+	if s.Endpoint == "" {
237
+		missing = append(missing, "endpoint")
238
+	}
239
+	if s.Bucket == "" {
240
+		missing = append(missing, "bucket")
241
+	}
242
+	if s.AccessKeyID == "" {
243
+		missing = append(missing, "access_key_id")
244
+	}
245
+	if s.SecretAccessKey == "" {
246
+		missing = append(missing, "secret_access_key")
247
+	}
248
+	if len(missing) > 0 {
249
+		return fmt.Errorf("config: storage.s3: incomplete configuration, missing: %s", strings.Join(missing, ", "))
250
+	}
192251
 	return nil
193252
 }
194253