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
|
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`
|
## Convex dashboard
|
||||||
- `AUTH_GITHUB_SECRET`
|
|
||||||
- `AUTH_GOOGLE_ID`
|
|
||||||
- `AUTH_GOOGLE_SECRET`
|
|
||||||
- `JWKS`
|
|
||||||
- `JWT_PRIVATE_KEY`
|
|
||||||
|
|
||||||
## 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`)
|
| Name | Workflow |
|
||||||
- `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`
|
| `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`
|
## When adding a variable
|
||||||
- `R2_SECRET_ACCESS_KEY`
|
|
||||||
- `R2_ACCOUNT_ID`
|
|
||||||
- `R2_BUCKET_NAME`
|
|
||||||
- `R2_ENDPOINT_URL`
|
|
||||||
- `R2_PUBLIC_URL`
|
|
||||||
- `R2_CLOUDFLARE_TOKEN`
|
|
||||||
|
|
||||||
**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.
|
Add the name here in the same change as the code that reads it.
|
||||||
|
|
||||||
## 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.
|
|
||||||
@@ -169,7 +169,8 @@ jobs:
|
|||||||
id: pio
|
id: pio
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
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 }}
|
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -179,8 +180,8 @@ jobs:
|
|||||||
BUILD_DIR_ABS="$ROOT/.pio/build/${{ inputs.target_env }}"
|
BUILD_DIR_ABS="$ROOT/.pio/build/${{ inputs.target_env }}"
|
||||||
ARTIFACT_NAME="firmware-${{ inputs.build_key }}-${{ github.run_id }}.tar.gz"
|
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"
|
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}"
|
bun install --frozen-lockfile --production
|
||||||
bunx wrangler r2 object put "$OBJECT_PATH" --file "/tmp/$ARTIFACT_NAME" --remote
|
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"
|
STEP_INDEX=10 LABEL="Firmware bundle uploaded" python3 "${{ github.workspace }}/scripts/ci/report-convex-ci-progress.py"
|
||||||
echo "r2_key=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
|
echo "r2_key=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
|||||||
+17
-9
@@ -1,21 +1,19 @@
|
|||||||
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"
|
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"
|
||||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
||||||
|
|
||||||
export async function generateSignedDownloadUrl(
|
export function createR2Client(): { client: S3Client; bucketName: string } {
|
||||||
objectKey: string,
|
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID
|
||||||
filename: string,
|
|
||||||
contentType: string = "application/octet-stream"
|
|
||||||
): Promise<string> {
|
|
||||||
const accountId = process.env.R2_ACCOUNT_ID
|
|
||||||
const accessKeyId = process.env.R2_ACCESS_KEY_ID
|
const accessKeyId = process.env.R2_ACCESS_KEY_ID
|
||||||
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY
|
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY
|
||||||
const bucketName = process.env.R2_BUCKET_NAME
|
const bucketName = process.env.R2_BUCKET_NAME
|
||||||
|
|
||||||
if (!accountId || !accessKeyId || !secretAccessKey || !bucketName) {
|
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",
|
region: "auto",
|
||||||
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
||||||
credentials: {
|
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({
|
const command = new GetObjectCommand({
|
||||||
Bucket: bucketName,
|
Bucket: bucketName,
|
||||||
Key: objectKey,
|
Key: objectKey,
|
||||||
@@ -32,5 +40,5 @@ export async function generateSignedDownloadUrl(
|
|||||||
})
|
})
|
||||||
|
|
||||||
// URL expires in 1 hour (3600 seconds)
|
// 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