mirror of
https://github.com/MeshEnvy/mesh-forge.git
synced 2026-10-31 02:11:58 +01:00
fix(r2): upload and sign with s3 keys
This commit is contained in:
1 parent
b55aafb742
commit
c84bf442d3
4 files changed
+75
-42
No files matched your search
@@ -1,45 +1,48 @@
|
||||
---
|
||||
description: Convex deployment environment variable names (MeshEnvy mesh-forge); values are secrets — never commit them.
|
||||
description: Environment variable names for MeshForge. Values are secrets. Never commit them.
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Convex environment variables (this deployment)
|
||||
# Environment variables
|
||||
|
||||
These names are configured in the Convex dashboard for this project. **Do not put real values in the repo or in rules.**
|
||||
Names only. The Convex dashboard and GitHub Actions secrets are separate stores. A name that appears in both must hold the same value.
|
||||
|
||||
## Auth (OAuth / Convex Auth)
|
||||
Convex injects `CONVEX_SITE_URL`. `convex deploy` injects `VITE_CONVEX_URL` at build time. Do not add those to either secret list.
|
||||
|
||||
- `AUTH_GITHUB_ID`
|
||||
- `AUTH_GITHUB_SECRET`
|
||||
- `AUTH_GOOGLE_ID`
|
||||
- `AUTH_GOOGLE_SECRET`
|
||||
- `JWKS`
|
||||
- `JWT_PRIVATE_KEY`
|
||||
## Convex dashboard
|
||||
|
||||
## App / Convex Auth config
|
||||
| Name | Read by |
|
||||
| --- | --- |
|
||||
| `AUTH_GITHUB_ID` | Convex Auth |
|
||||
| `AUTH_GITHUB_SECRET` | Convex Auth |
|
||||
| `JWKS` | Convex Auth |
|
||||
| `JWT_PRIVATE_KEY` | Convex Auth |
|
||||
| `SITE_URL` | Convex Auth |
|
||||
| `GITHUB_TOKEN` | `convex/actions.ts`, `repoScans.ts`, `repoTags.ts` |
|
||||
| `CONVEX_BUILD_TOKEN` | `convex/http.ts` |
|
||||
| `CLOUDFLARE_ACCOUNT_ID` | `convex/lib/r2.ts` |
|
||||
| `R2_ACCESS_KEY_ID` | `convex/lib/r2.ts` |
|
||||
| `R2_SECRET_ACCESS_KEY` | `convex/lib/r2.ts` |
|
||||
| `R2_BUCKET_NAME` | `convex/lib/r2.ts` |
|
||||
|
||||
- `SITE_URL` — canonical site URL (often used by auth / redirects; set in dashboard as provided)
|
||||
`CONVEX_ENV=dev` makes `dispatchRepoBuild` use workflow ref `develop`. Unset means `main`. Both use `.github/workflows/custom_build.yml`.
|
||||
|
||||
**Code note:** Convex code in this repo also reads `CONVEX_SITE_URL` for `auth.config.ts` (JWT domain) and `actions.ts` (GitHub Actions `convex_url` input). If something breaks around auth domain or workflow callbacks, confirm both dashboard vars match what Convex and the frontend expect.
|
||||
`CLOUDFLARE_ACCOUNT_ID` is the Cloudflare account id. `r2.ts` uses it as the R2 S3 endpoint host.
|
||||
|
||||
## CI / GitHub integration
|
||||
## GitHub Actions secrets
|
||||
|
||||
- `GITHUB_TOKEN` — GitHub API (repo scans, branches, dispatching `custom_build*.yml`)
|
||||
- `CONVEX_ENV` — when `dev`, workflow dispatch uses **`custom_build_test.yml`** on git ref **`v2`** on `MeshEnvy/mesh-forge`; otherwise **`custom_build.yml`** on **`main`**
|
||||
- `CONVEX_BUILD_TOKEN` — shared with GitHub Actions secret of the same name; bearer auth for `POST /ingest-repo-build`
|
||||
| Name | Workflow |
|
||||
| --- | --- |
|
||||
| `CLOUDFLARE_ACCOUNT_ID` | `custom_build.yml` |
|
||||
| `CLOUDFLARE_API_TOKEN` | `deploy.yml` |
|
||||
| `CONVEX_BUILD_TOKEN` | `custom_build.yml` |
|
||||
| `CONVEX_DEPLOY_KEY` | `deploy.yml` |
|
||||
| `R2_ACCESS_KEY_ID` | `custom_build.yml` |
|
||||
| `R2_SECRET_ACCESS_KEY` | `custom_build.yml` |
|
||||
| `R2_BUCKET_NAME` | `custom_build.yml` |
|
||||
|
||||
## R2 (firmware artifacts / signed downloads)
|
||||
Shared with Convex, same value: `CLOUDFLARE_ACCOUNT_ID`, `CONVEX_BUILD_TOKEN`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME`.
|
||||
|
||||
- `R2_ACCESS_KEY_ID`
|
||||
- `R2_SECRET_ACCESS_KEY`
|
||||
- `R2_ACCOUNT_ID`
|
||||
- `R2_BUCKET_NAME`
|
||||
- `R2_ENDPOINT_URL`
|
||||
- `R2_PUBLIC_URL`
|
||||
- `R2_CLOUDFLARE_TOKEN`
|
||||
## When adding a variable
|
||||
|
||||
**Code note:** `convex/lib/r2.ts` currently uses `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET_NAME` and builds the S3 endpoint from account id. Extra R2-related vars may be used elsewhere or reserved for future use.
|
||||
|
||||
## When editing Convex code
|
||||
|
||||
Prefer reading env vars that already exist in dashboard; if you introduce a new required variable, document it here and in any operator-facing setup notes.
|
||||
Add the name here in the same change as the code that reads it.
|
||||
@@ -169,7 +169,8 @@ jobs:
|
||||
id: pio
|
||||
env:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
|
||||
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
|
||||
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -179,8 +180,8 @@ jobs:
|
||||
BUILD_DIR_ABS="$ROOT/.pio/build/${{ inputs.target_env }}"
|
||||
ARTIFACT_NAME="firmware-${{ inputs.build_key }}-${{ github.run_id }}.tar.gz"
|
||||
MESHTASTIC_OTA=1 bash "${{ github.workspace }}/scripts/ci/common/fw-bundle-pipeline.sh" "$ROOT" "$BUILD_DIR_ABS" "/tmp/$ARTIFACT_NAME"
|
||||
OBJECT_PATH="${R2_BUCKET_NAME}/${ARTIFACT_NAME}"
|
||||
bunx wrangler r2 object put "$OBJECT_PATH" --file "/tmp/$ARTIFACT_NAME" --remote
|
||||
bun install --frozen-lockfile --production
|
||||
bun scripts/ci/upload-firmware-r2.ts "/tmp/$ARTIFACT_NAME" "$ARTIFACT_NAME"
|
||||
STEP_INDEX=10 LABEL="Firmware bundle uploaded" python3 "${{ github.workspace }}/scripts/ci/report-convex-ci-progress.py"
|
||||
echo "r2_key=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
+17
-9
@@ -1,21 +1,19 @@
|
||||
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"
|
||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
||||
|
||||
export async function generateSignedDownloadUrl(
|
||||
objectKey: string,
|
||||
filename: string,
|
||||
contentType: string = "application/octet-stream"
|
||||
): Promise<string> {
|
||||
const accountId = process.env.R2_ACCOUNT_ID
|
||||
export function createR2Client(): { client: S3Client; bucketName: string } {
|
||||
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID
|
||||
const accessKeyId = process.env.R2_ACCESS_KEY_ID
|
||||
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY
|
||||
const bucketName = process.env.R2_BUCKET_NAME
|
||||
|
||||
if (!accountId || !accessKeyId || !secretAccessKey || !bucketName) {
|
||||
throw new Error("R2 credentials are not set")
|
||||
throw new Error(
|
||||
"CLOUDFLARE_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, and R2_BUCKET_NAME must be set"
|
||||
)
|
||||
}
|
||||
|
||||
const s3 = new S3Client({
|
||||
const client = new S3Client({
|
||||
region: "auto",
|
||||
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
@@ -24,6 +22,16 @@ export async function generateSignedDownloadUrl(
|
||||
},
|
||||
})
|
||||
|
||||
return { client, bucketName }
|
||||
}
|
||||
|
||||
export async function generateSignedDownloadUrl(
|
||||
objectKey: string,
|
||||
filename: string,
|
||||
contentType: string = "application/octet-stream"
|
||||
): Promise<string> {
|
||||
const { client, bucketName } = createR2Client()
|
||||
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: objectKey,
|
||||
@@ -32,5 +40,5 @@ export async function generateSignedDownloadUrl(
|
||||
})
|
||||
|
||||
// URL expires in 1 hour (3600 seconds)
|
||||
return await getSignedUrl(s3, command, { expiresIn: 3600 })
|
||||
return await getSignedUrl(client, command, { expiresIn: 3600 })
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createReadStream } from "node:fs"
|
||||
import { PutObjectCommand } from "@aws-sdk/client-s3"
|
||||
import { createR2Client } from "../../convex/lib/r2"
|
||||
|
||||
const filePath = process.argv[2]
|
||||
const objectKey = process.argv[3]
|
||||
if (!filePath || !objectKey) {
|
||||
console.error("usage: upload-firmware-r2.ts <file> <objectKey>")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const { client, bucketName } = createR2Client()
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: objectKey,
|
||||
Body: createReadStream(filePath),
|
||||
ContentType: "application/gzip",
|
||||
})
|
||||
)
|
||||
console.log(`uploaded ${objectKey} to ${bucketName}`)
|
||||
Reference in new issue
Block a user