fix(storage): close S3 deployment gaps

Honor shared URL addressing in the Git probe, expose the signing region through Helm, and make storage validation guidance match the actual startup probe behavior.

Co-authored-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: Kalvin Chau <kalvin@block.xyz>
This commit is contained in:
npub122y0pqkertljmedu303rl0aqrj3w8pvu43t6jxm6875lzg6f2pwqegc3xc
2026-07-28 15:29:19 -07:00
co-authored by Kalvin Chau
parent bd7b275fb4
commit 6879f48152
11 changed files with 119 additions and 36 deletions
Generated
+1
View File
@@ -1239,6 +1239,7 @@ dependencies = [
"anyhow",
"base64",
"buzz-core",
"buzz-media",
"buzz-sdk",
"buzz-ws-client",
"chrono",
+1
View File
@@ -36,6 +36,7 @@ sha2 = { workspace = true }
sqlx = { workspace = true }
chrono = { workspace = true }
s3 = { version = "0.37", package = "rust-s3", default-features = false, features = ["tokio-rustls-tls", "fail-on-err", "tags"] }
buzz-media = { workspace = true }
buzz-sdk = { workspace = true }
[[bin]]
+62 -9
View File
@@ -21,6 +21,7 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use buzz_media::S3AddressingStyle;
use nostr::{EventBuilder, Keys, Kind, Tag};
use s3::creds::Credentials;
use s3::{Bucket, Region};
@@ -115,6 +116,27 @@ struct PointerSnapshot {
}
impl GitS3Probe {
fn bucket(
endpoint: String,
access_key: &str,
secret_key: &str,
bucket_name: &str,
region_name: String,
addressing_style: S3AddressingStyle,
) -> Box<Bucket> {
let region = Region::Custom {
region: region_name,
endpoint,
};
let creds = Credentials::new(Some(access_key), Some(secret_key), None, None, None)
.expect("S3 credentials");
let bucket = Bucket::new(bucket_name, region, creds).expect("S3 bucket");
match addressing_style {
S3AddressingStyle::Path => bucket.with_path_style(),
S3AddressingStyle::Virtual => bucket,
}
}
fn from_env() -> Self {
let endpoint = std::env::var("BUZZ_GIT_S3_ENDPOINT")
.or_else(|_| std::env::var("BUZZ_S3_ENDPOINT"))
@@ -125,19 +147,25 @@ impl GitS3Probe {
let secret_key = std::env::var("BUZZ_GIT_S3_SECRET_KEY")
.or_else(|_| std::env::var("BUZZ_S3_SECRET_KEY"))
.unwrap_or_else(|_| "buzz_dev_secret".to_string());
let bucket = std::env::var("BUZZ_GIT_S3_BUCKET")
let bucket_name = std::env::var("BUZZ_GIT_S3_BUCKET")
.or_else(|_| std::env::var("BUZZ_S3_BUCKET"))
.unwrap_or_else(|_| "buzz-media".to_string());
let region_name = std::env::var("BUZZ_GIT_S3_REGION")
.or_else(|_| std::env::var("BUZZ_S3_REGION"))
.unwrap_or_else(|_| "us-east-1".to_string());
let addressing_style = std::env::var("BUZZ_S3_ADDRESSING_STYLE")
.unwrap_or_else(|_| "path".to_string())
.parse::<S3AddressingStyle>()
.expect("BUZZ_S3_ADDRESSING_STYLE must be 'path' or 'virtual'");
let region = Region::Custom {
region: "us-east-1".into(),
let bucket = Self::bucket(
endpoint,
};
let creds = Credentials::new(Some(&access_key), Some(&secret_key), None, None, None)
.expect("S3 credentials");
let bucket = Bucket::new(&bucket, region, creds)
.expect("S3 bucket")
.with_path_style();
&access_key,
&secret_key,
&bucket_name,
region_name,
addressing_style,
);
Self { bucket }
}
@@ -192,6 +220,31 @@ impl GitS3Probe {
}
}
#[test]
fn git_s3_probe_builds_both_addressing_styles() {
let path = GitS3Probe::bucket(
"https://storage.example".to_string(),
"access",
"secret",
"buzz-media",
"us-east-1".to_string(),
S3AddressingStyle::Path,
);
assert!(path.is_path_style());
assert_eq!(path.url(), "https://storage.example/buzz-media");
let virtual_hosted = GitS3Probe::bucket(
"https://storage.example".to_string(),
"access",
"secret",
"buzz-media",
"auto".to_string(),
S3AddressingStyle::Virtual,
);
assert!(virtual_hosted.is_subdomain_style());
assert_eq!(virtual_hosted.url(), "https://buzz-media.storage.example");
}
#[tokio::test]
#[ignore = "requires live relay + MinIO + git"]
async fn git_clone_push_fetch_force_roundtrip() {
+26 -18
View File
@@ -61,28 +61,36 @@ Buzz uses one URL style for both media and Git/CAS object-store requests:
| `path` (default) | `https://endpoint/bucket/key` | Bundled MinIO and endpoints whose DNS does not resolve bucket subdomains |
| `virtual` | `https://bucket.endpoint/key` | AWS-style providers and new Railway Storage Buckets |
The chart renders this value as `BUZZ_S3_ADDRESSING_STYLE`. Only `path` and
`virtual` are accepted; invalid values fail chart rendering and relay startup.
The bundled MinIO quickstart deliberately keeps `path` because its Service DNS
resolves one endpoint hostname, not arbitrary `<bucket>.<service>` names.
The chart renders `s3.region` and `s3.addressingStyle` as `BUZZ_S3_REGION`
and `BUZZ_S3_ADDRESSING_STYLE`. Only `path` and `virtual` addressing styles are
accepted; invalid values fail chart rendering and relay startup. The bundled
MinIO quickstart deliberately keeps `path` because its Service DNS resolves one
endpoint hostname, not arbitrary `<bucket>.<service>` names.
For a Railway Storage Bucket named `Object Storage`, map its variables onto the
Buzz service and set the style explicitly:
For a Railway Storage Bucket, map its variables to chart values in the service
or generated Helm configuration:
```text
BUZZ_S3_ENDPOINT=${{Object Storage.ENDPOINT}}
BUZZ_S3_BUCKET=${{Object Storage.BUCKET}}
BUZZ_S3_ACCESS_KEY=${{Object Storage.ACCESS_KEY_ID}}
BUZZ_S3_SECRET_KEY=${{Object Storage.SECRET_ACCESS_KEY}}
BUZZ_S3_REGION=${{Object Storage.REGION}}
BUZZ_S3_ADDRESSING_STYLE=virtual
```yaml
s3:
endpoint: "${{Object Storage.ENDPOINT}}"
bucket: "${{Object Storage.BUCKET}}"
region: "${{Object Storage.REGION}}"
addressingStyle: virtual
```
Railway's Credentials tab is authoritative for older buckets, which may still
require `path`. The setting changes request routing and SigV4 signing, so do not
put the bucket into `BUZZ_S3_ENDPOINT`; pass Railway's base `ENDPOINT` and
`BUCKET` separately. The relay validates storage before becoming ready, and the
Git conformance probe remains startup-fatal when enabled.
Store `BUZZ_S3_ACCESS_KEY=${{Object Storage.ACCESS_KEY_ID}}` and
`BUZZ_S3_SECRET_KEY=${{Object Storage.SECRET_ACCESS_KEY}}` in the Secret named by
`secrets.existingSecret`. Railway's Credentials tab is authoritative for older
buckets, which may still require `path`. The setting changes request routing and
SigV4 signing, so do not put the bucket into `s3.endpoint`; pass Railway's base
`ENDPOINT` and `BUCKET` separately.
Object storage is contacted during relay startup only when
`BUZZ_GIT_CONFORMANCE_PROBE` is enabled (the relay default). A probe failure is
startup-fatal, so Kubernetes readiness never opens. If an operator explicitly
disables that probe through `relay.extraEnv`, `/_readiness` does not test object
storage; configuration is still parsed strictly, but reachability and addressing
errors surface on the first storage operation.
## Relay Pod extensions
@@ -41,6 +41,7 @@ spec:
s3:
endpoint: "https://s3.us-east-1.amazonaws.com"
bucket: "buzz-media"
region: "us-east-1"
addressingStyle: virtual
# accessKey / secretKey live in buzz-secrets
@@ -41,6 +41,7 @@ spec:
s3:
endpoint: "https://s3.us-east-1.amazonaws.com"
bucket: "buzz-media"
region: "us-east-1"
addressingStyle: virtual
persistence:
+5 -9
View File
@@ -75,16 +75,12 @@ surface at template time regardless of which manifest helm renders first.
{{- fail "Postgres source missing: enable postgresql.enabled=true, set externalPostgresql.url, or provide secrets.existingSecret with key DATABASE_URL." -}}
{{- end -}}
{{/* S3 addressing style is duplicated here in addition to values.schema.json
because Helm unittest does not consistently apply schema validation. */}}
{{- if not (has .Values.s3.addressingStyle (list "path" "virtual")) -}}
{{- fail "s3.addressingStyle must be 'path' or 'virtual'. Use path for bundled MinIO and virtual for providers such as new Railway Storage Buckets." -}}
{{- end -}}
{{/* S3 / object-storage source must exist somewhere (relay hard-fails its
startup conformance probe without a reachable bucket). */}}
{{/* S3 / object-storage source must exist somewhere. With the default
BUZZ_GIT_CONFORMANCE_PROBE behavior, an unreachable bucket is detected
before the relay opens its listener; operators can explicitly disable that
startup gate. */}}
{{- if not (or .Values.minio.enabled .Values.s3.endpoint .Values.secrets.existingSecret) -}}
{{- fail "S3/object-storage source missing: enable minio.enabled=true (quickstart in-cluster), set s3.endpoint + s3.bucket + credentials, or provide secrets.existingSecret with keys BUZZ_S3_ACCESS_KEY + BUZZ_S3_SECRET_KEY. The relay runs a startup S3 conformance probe and exits if storage is unreachable." -}}
{{- fail "S3/object-storage source missing: enable minio.enabled=true (quickstart in-cluster), set s3.endpoint + s3.bucket + credentials, or provide secrets.existingSecret with keys BUZZ_S3_ACCESS_KEY + BUZZ_S3_SECRET_KEY. By default the relay runs a startup S3 conformance probe and exits if storage is unreachable; disabling BUZZ_GIT_CONFORMANCE_PROBE also removes that startup storage check." -}}
{{- end -}}
{{- end -}}
@@ -170,6 +170,7 @@ spec:
- { name: BUZZ_S3_ENDPOINT, value: {{ $s3Endpoint | quote }} }
{{- end }}
- { name: BUZZ_S3_BUCKET, value: {{ .Values.s3.bucket | quote }} }
- { name: BUZZ_S3_REGION, value: {{ .Values.s3.region | quote }} }
- { name: BUZZ_S3_ADDRESSING_STYLE, value: {{ .Values.s3.addressingStyle | quote }} }
# ── Secrets (from chart-managed or existing) ─────────────
+13
View File
@@ -30,6 +30,12 @@ tests:
path: kind
value: Service
template: templates/service.yaml
- contains:
path: spec.template.spec.containers[0].env
content:
name: BUZZ_S3_REGION
value: "us-east-1"
template: templates/deployment.yaml
- contains:
path: spec.template.spec.containers[0].env
content:
@@ -61,10 +67,17 @@ tests:
externalRedis.url: redis://h:6379
s3.endpoint: https://storage.railway.app
s3.bucket: buzz-media-example
s3.region: auto
s3.addressingStyle: virtual
s3.accessKey: a
s3.secretKey: s
asserts:
- contains:
path: spec.template.spec.containers[0].env
content:
name: BUZZ_S3_REGION
value: "auto"
template: templates/deployment.yaml
- contains:
path: spec.template.spec.containers[0].env
content:
+5
View File
@@ -198,6 +198,11 @@
"properties": {
"endpoint": { "type": "string", "pattern": "^(https?://.+)?$" },
"bucket": { "type": "string", "minLength": 1 },
"region": {
"type": "string",
"minLength": 1,
"description": "S3 region used for SigV4 signing. Defaults to us-east-1 for bundled MinIO compatibility."
},
"addressingStyle": {
"type": "string",
"enum": ["path", "virtual"],
+3
View File
@@ -338,6 +338,9 @@ externalRedis:
s3:
endpoint: ""
bucket: "buzz-media"
# SigV4 signing region. Keep us-east-1 for bundled MinIO; set this to the
# provider's credential value for managed S3 (for example Railway's REGION).
region: "us-east-1"
# path: https://endpoint/bucket/key (bundled MinIO-compatible default)
# virtual: https://bucket.endpoint/key (standard S3; required by new Railway buckets)
addressingStyle: path