Skip to content

Commit 17e42de

Browse files
committed
feat(cache): enhance Redis cache configuration with ACL and TLS support
1 parent 46c12ba commit 17e42de

4 files changed

Lines changed: 133 additions & 63 deletions

File tree

apps/docs/docs/cache.mdx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The **Expo Open OTA server** uses a cache to improve performance and reduce serv
1212
The cache is primarily used for:
1313

1414
1. **Storing the computed `lastUpdateId` for a given platform and runtime version**
15+
1516
- This prevents the need to recompute the last update for every request, significantly speeding up responses.
1617

1718
2. **Caching the computed manifest**
@@ -22,19 +23,20 @@ The cache is primarily used for:
2223
The environment variables required for each storage solution are listed below, you can set them in a `.env` file in the root of the project or keep them in a safe place to prepare for deployment.
2324
:::
2425

25-
import Tabs from '@theme/Tabs';
26-
import TabItem from '@theme/TabItem';
26+
import Tabs from "@theme/Tabs";
27+
import TabItem from "@theme/TabItem";
2728

2829
<Tabs queryString="cache" defaultValue="local">
2930
<TabItem value="local" label="Local cache" default>
3031
:::warning
3132

32-
This cache solution is not recommended for production use. It is intended for development and testing purposes only.
33-
If you really want to use it in production, make sure to not have multiple instances of the server running, as the cache is stored locally and not shared between instances.
33+
This cache solution is not recommended for production use. It is intended for development and testing purposes only.
34+
If you really want to use it in production, make sure to not have multiple instances of the server running, as the cache is stored locally and not shared between instances.
35+
36+
:::
37+
Local cache is the default cache solution used by the server. It stores the cache in memory and is not shared between instances of the server. This means that the cache is lost when the server is restarted.
38+
No additional configuration is required to use the local cache.
3439

35-
:::
36-
Local cache is the default cache solution used by the server. It stores the cache in memory and is not shared between instances of the server. This means that the cache is lost when the server is restarted.
37-
No additional configuration is required to use the local cache.
3840
</TabItem>
3941
<TabItem value="redis" label="Redis">
4042
To use Redis as your cache solution, you need to set the following environment variables:
@@ -43,6 +45,25 @@ import TabItem from '@theme/TabItem';
4345
REDIS_PORT=your-redis-port
4446
REDIS_PASSWORD=your-redis-password
4547
REDIS_USE_TLS=true // optional if you are using a TLS connection
48+
REDIS_USERNAME=your-redis-username // optional for ACL authentication
49+
REDIS_CA_CERT_B64=base64-encoded-ca-certificate // optional for TLS with custom CA
4650
```
51+
52+
:::tip TLS/SSL Configuration
53+
54+
If you're using Redis with TLS/SSL and a custom CA certificate:
55+
- Set `REDIS_USE_TLS=true`
56+
- Set `REDIS_CA_CERT_B64` to your base64-encoded PEM certificate
57+
- To encode your certificate: `cat certificate.pem | base64 -w 0`
58+
59+
:::
60+
61+
:::info ACL Authentication
62+
63+
If your Redis server uses ACL (Access Control Lists):
64+
- Set `REDIS_USERNAME` to your Redis username
65+
- Ensure `REDIS_PASSWORD` is set to the corresponding password
66+
67+
:::
4768
</TabItem>
4869
</Tabs>

apps/docs/docs/environment.mdx

Lines changed: 67 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,86 +9,100 @@ You can set these variables in a `.env` file for local development or in your de
99

1010
## Supported Environment Variables
1111

12-
1312
### 🌍 **API Configuration**
14-
| Name | Required | Description | Example | Reference |
15-
| --- | --- | --- | --- | --- |
16-
| `BASE_URL` || Root URL of your server | `https://ota.mysite.com` | [Ref](/docs/prerequisites#base-url) |
13+
14+
| Name | Required | Description | Example | Reference |
15+
| ---------- | -------- | ----------------------- | ------------------------ | ----------------------------------- |
16+
| `BASE_URL` || Root URL of your server | `https://ota.mysite.com` | [Ref](/docs/prerequisites#base-url) |
1717

1818
### 🔑 **Authentication & Security**
19-
| Name | Required | Description | Example | Reference |
20-
| --- | --- | --- | --- | --- |
21-
| `JWT_SECRET` || JWT secret used to sign some endpoints | `Random string` | [Ref](/docs/prerequisites#jwt-secret) |
19+
20+
| Name | Required | Description | Example | Reference |
21+
| ------------ | -------- | -------------------------------------- | --------------- | ------------------------------------- |
22+
| `JWT_SECRET` || JWT secret used to sign some endpoints | `Random string` | [Ref](/docs/prerequisites#jwt-secret) |
2223

2324
### 📱 **Expo Configuration**
24-
| Name | Required | Description | Example | Reference |
25-
| --- | --- | --- | --- | --- |
26-
| `EXPO_APP_ID` || The ID of the Expo project | `Random string` | [Ref](/docs/prerequisites#how-to-get-your-project-id) |
27-
| `EXPO_ACCESS_TOKEN` || Expo access token | `Random string` | [Ref](/docs/prerequisites#how-to-get-your-expo-token) |
25+
26+
| Name | Required | Description | Example | Reference |
27+
| ------------------- | -------- | -------------------------- | --------------- | ----------------------------------------------------- |
28+
| `EXPO_APP_ID` || The ID of the Expo project | `Random string` | [Ref](/docs/prerequisites#how-to-get-your-project-id) |
29+
| `EXPO_ACCESS_TOKEN` || Expo access token | `Random string` | [Ref](/docs/prerequisites#how-to-get-your-expo-token) |
2830

2931
### **Cache Configuration**
30-
| Name | Required | Description | Example | Reference |
31-
| --- | --- | --- | --- | --- |
32-
| `CACHE_MODE` || `local` or `redis` | `local` | [Ref](/docs/cache) |
33-
| `REDIS_HOST` | ✅ if CACHE_MODE = `redis` | Redis host | `127.0.0.1` | [Ref](/docs/cache?cache=redis) |
34-
| `REDIS_PORT` | ✅ if CACHE_MODE = `redis` | Redis port | `6379` | [Ref](/docs/cache?cache=redis) |
35-
| `REDIS_PASSWORD` | ✅ if CACHE_MODE = `redis` | Redis password | `password` | [Ref](/docs/cache?cache=redis) |
3632

33+
| Name | Required | Description | Example | Reference |
34+
| ------------------- | -------------------------- | ------------------------------------- | --------------- | ------------------------------ |
35+
| `CACHE_MODE` || `local` or `redis` | `local` | [Ref](/docs/cache) |
36+
| `REDIS_HOST` | ✅ if CACHE_MODE = `redis` | Redis host | `127.0.0.1` | [Ref](/docs/cache?cache=redis) |
37+
| `REDIS_PORT` | ✅ if CACHE_MODE = `redis` | Redis port | `6379` | [Ref](/docs/cache?cache=redis) |
38+
| `REDIS_PASSWORD` | ✅ if CACHE_MODE = `redis` | Redis password | `password` | [Ref](/docs/cache?cache=redis) |
39+
| `REDIS_USE_TLS` || Enable TLS/SSL connection | `true` | [Ref](/docs/cache?cache=redis) |
40+
| `REDIS_USERNAME` || Redis username for ACL authentication | `myuser` | [Ref](/docs/cache?cache=redis) |
41+
| `REDIS_CA_CERT_B64` || Base64-encoded CA certificate for TLS | `Base64 string` | [Ref](/docs/cache?cache=redis) |
3742

3843
### 📦 **Storage Configuration**
39-
| Name | Required | Description | Example | Reference |
40-
| --- | --- | --- | --- | --- |
41-
| `STORAGE_MODE` || `local` or `s3` | `local` | [Ref](/docs/storage) |
42-
| `S3_BUCKET_NAME` | ✅ if STORAGE_MODE = `s3` | S3 bucket name | `my-bucket` | [Ref](/docs/storage?storage=s3) |
44+
45+
| Name | Required | Description | Example | Reference |
46+
| ------------------------ | ---------------------------- | -------------------- | ----------------- | ---------------------------------- |
47+
| `STORAGE_MODE` || `local` or `s3` | `local` | [Ref](/docs/storage) |
48+
| `S3_BUCKET_NAME` | ✅ if STORAGE_MODE = `s3` | S3 bucket name | `my-bucket` | [Ref](/docs/storage?storage=s3) |
4349
| `LOCAL_BUCKET_BASE_PATH` | ✅ if STORAGE_MODE = `local` | Path to store assets | `/path/to/assets` | [Ref](/docs/storage?storage=local) |
4450

4551
### 🔐 **Key store Configuration**
46-
| Name | Required | Description | Example | Reference |
47-
| --- | --- | --- | --- | --- |
48-
| `KEYS_STORAGE_TYPE` || `environment`, `aws-secrets-manager`, or `local` | `environment` | [Ref](/docs/key-store) |
52+
53+
| Name | Required | Description | Example | Reference |
54+
| ------------------- | -------- | ------------------------------------------------ | ------------- | ---------------------- |
55+
| `KEYS_STORAGE_TYPE` || `environment`, `aws-secrets-manager`, or `local` | `environment` | [Ref](/docs/key-store) |
4956

5057
#### **AWS Secrets Manager Key Store**
51-
| Name | Required | Description | Example | Reference |
52-
| --- | --- | --- | --- | --- |
53-
| `AWSSM_EXPO_PUBLIC_KEY_SECRET_ID` | ✅ if KEYS_STORAGE_TYPE = `aws-secrets-manager` | Expo public key secret name in AWS | `my-expo-public-key` | [Ref](/docs/key-store#expo-signing-certificate) |
58+
59+
| Name | Required | Description | Example | Reference |
60+
| ---------------------------------- | ----------------------------------------------- | ----------------------------------- | --------------------- | ----------------------------------------------- |
61+
| `AWSSM_EXPO_PUBLIC_KEY_SECRET_ID` | ✅ if KEYS_STORAGE_TYPE = `aws-secrets-manager` | Expo public key secret name in AWS | `my-expo-public-key` | [Ref](/docs/key-store#expo-signing-certificate) |
5462
| `AWSSM_EXPO_PRIVATE_KEY_SECRET_ID` | ✅ if KEYS_STORAGE_TYPE = `aws-secrets-manager` | Expo private key secret name in AWS | `my-expo-private-key` | [Ref](/docs/key-store#expo-signing-certificate) |
5563

5664
#### **Environment-Based Key Store**
57-
| Name | Required | Description | Example | Reference |
58-
| --- | --- | --- | --- | --- |
59-
| `PUBLIC_EXPO_KEY_B64` | ✅ if KEYS_STORAGE_TYPE = `environment` | Base64-encoded Expo public key | `Base64 string` | [Ref](/docs/key-store#expo-signing-certificate) |
65+
66+
| Name | Required | Description | Example | Reference |
67+
| ---------------------- | --------------------------------------- | ------------------------------- | --------------- | ----------------------------------------------- |
68+
| `PUBLIC_EXPO_KEY_B64` | ✅ if KEYS_STORAGE_TYPE = `environment` | Base64-encoded Expo public key | `Base64 string` | [Ref](/docs/key-store#expo-signing-certificate) |
6069
| `PRIVATE_EXPO_KEY_B64` | ✅ if KEYS_STORAGE_TYPE = `environment` | Base64-encoded Expo private key | `Base64 string` | [Ref](/docs/key-store#expo-signing-certificate) |
6170

6271
#### **Local Key Store**
63-
| Name | Required | Description | Example | Reference |
64-
| --- | --- | --- | --- | --- |
72+
73+
| Name | Required | Description | Example | Reference |
74+
| ----------------------------- | --------------------------------- | ---------------------------- | -------------------------- | ----------------------------------------------- |
6575
| `PRIVATE_LOCAL_EXPO_KEY_PATH` | ✅ if KEYS_STORAGE_TYPE = `local` | Path to the Expo private key | `/path/to/private-key.pem` | [Ref](/docs/key-store#expo-signing-certificate) |
66-
| `PUBLIC_LOCAL_EXPO_KEY_PATH` | ✅ if KEYS_STORAGE_TYPE = `local` | Path to the Expo public key | `/path/to/public-key.pem` | [Ref](/docs/key-store#expo-signing-certificate) |
76+
| `PUBLIC_LOCAL_EXPO_KEY_PATH` | ✅ if KEYS_STORAGE_TYPE = `local` | Path to the Expo public key | `/path/to/public-key.pem` | [Ref](/docs/key-store#expo-signing-certificate) |
6777

6878
### ☁️ **AWS & CloudFront Configuration**
69-
| Name | Required | Description | Example | Reference |
70-
| --- | --- | --- | --- | --- |
71-
| `AWS_REGION` | ✅ if using `aws-secrets-manager` or `s3` | AWS Region | `us-east-1` | [Ref](/docs/key-store?keyStore=aws-secrets-manager#key-store-configuration), [Storage](/docs/storage?storage=s3) |
72-
| `AWS_BASE_ENDPOINT` || Custom S3-compatible endpoint for alternative object storage | `https://account-id.r2.cloudflarestorage.com` | [Storage](/docs/storage?storage=s3) |
73-
| `AWS_ACCESS_KEY_ID` | ✅ if using `aws-secrets-manager` or `s3` without IAM roles | AWS Access Key ID | `ACCESSKEYID` | [Ref](/docs/key-store?keyStore=aws-secrets-manager#key-store-configuration), [Storage](/docs/storage?storage=s3) |
74-
| `AWS_SECRET_ACCESS_KEY` | ✅ if using `aws-secrets-manager` or `s3` without IAM roles | AWS Secret Access Key | `SECRETACCESSKEY` | [Ref](/docs/key-store?keyStore=aws-secrets-manager#key-store-configuration), [Storage](/docs/storage?storage=s3) |
79+
80+
| Name | Required | Description | Example | Reference |
81+
| ----------------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
82+
| `AWS_REGION` | ✅ if using `aws-secrets-manager` or `s3` | AWS Region | `us-east-1` | [Ref](/docs/key-store?keyStore=aws-secrets-manager#key-store-configuration), [Storage](/docs/storage?storage=s3) |
83+
| `AWS_BASE_ENDPOINT` || Custom S3-compatible endpoint for alternative object storage | `https://account-id.r2.cloudflarestorage.com` | [Storage](/docs/storage?storage=s3) |
84+
| `AWS_ACCESS_KEY_ID` | ✅ if using `aws-secrets-manager` or `s3` without IAM roles | AWS Access Key ID | `ACCESSKEYID` | [Ref](/docs/key-store?keyStore=aws-secrets-manager#key-store-configuration), [Storage](/docs/storage?storage=s3) |
85+
| `AWS_SECRET_ACCESS_KEY` | ✅ if using `aws-secrets-manager` or `s3` without IAM roles | AWS Secret Access Key | `SECRETACCESSKEY` | [Ref](/docs/key-store?keyStore=aws-secrets-manager#key-store-configuration), [Storage](/docs/storage?storage=s3) |
7586

7687
#### **CloudFront Settings**
77-
| Name | Required | Description | Example | Reference |
78-
| --- | --- | --- | --- | --- |
79-
| `CLOUDFRONT_DOMAIN` || CloudFront domain | `https://XXX.cloudfront.net` | [Ref](/docs/cdn/cloudfront) |
80-
| `CLOUDFRONT_KEY_PAIR_ID` | ✅ if CLOUDFRONT_DOMAIN is set | CloudFront key pair ID | `Random string` | [Ref](/docs/cdn/cloudfront) |
81-
| `CLOUDFRONT_PRIVATE_KEY_B64` | ✅ if using `environment` & CLOUDFRONT_DOMAIN is set | Base64 CloudFront private key | `Base64 string` | [Ref](/docs/cdn/cloudfront) |
82-
| `AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID` | ✅ if using `aws-secrets-manager` & CLOUDFRONT_DOMAIN is set | CloudFront private key in AWS Secrets Manager | `my-cloudfront-private-key` | [Ref](/docs/cdn/cloudfront) |
83-
| `PRIVATE_LOCAL_CLOUDFRONT_KEY_PATH` | ✅ if using `local` & CLOUDFRONT_DOMAIN is set | Path to CloudFront private key | `/path/to/cloudfront-private-key.pem` | [Ref](/docs/cdn/cloudfront) |
88+
89+
| Name | Required | Description | Example | Reference |
90+
| ---------------------------------------- | ------------------------------------------------------------ | --------------------------------------------- | ------------------------------------- | --------------------------- |
91+
| `CLOUDFRONT_DOMAIN` || CloudFront domain | `https://XXX.cloudfront.net` | [Ref](/docs/cdn/cloudfront) |
92+
| `CLOUDFRONT_KEY_PAIR_ID` | ✅ if CLOUDFRONT_DOMAIN is set | CloudFront key pair ID | `Random string` | [Ref](/docs/cdn/cloudfront) |
93+
| `CLOUDFRONT_PRIVATE_KEY_B64` | ✅ if using `environment` & CLOUDFRONT_DOMAIN is set | Base64 CloudFront private key | `Base64 string` | [Ref](/docs/cdn/cloudfront) |
94+
| `AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID` | ✅ if using `aws-secrets-manager` & CLOUDFRONT_DOMAIN is set | CloudFront private key in AWS Secrets Manager | `my-cloudfront-private-key` | [Ref](/docs/cdn/cloudfront) |
95+
| `PRIVATE_LOCAL_CLOUDFRONT_KEY_PATH` | ✅ if using `local` & CLOUDFRONT_DOMAIN is set | Path to CloudFront private key | `/path/to/cloudfront-private-key.pem` | [Ref](/docs/cdn/cloudfront) |
8496

8597
#### **Prometheus Configuration**
86-
| Name | Required | Description | Example | Reference |
87-
| --- | --- | --- | --- | --- |
88-
| `PROMETHEUS_ENABLED` | ❌ (Automatic) | Automatically set to `true` if `prometheus.io/scrape: "true"` is present in `podAnnotations`, otherwise must be explicitly set to `true` | `true` | |
98+
99+
| Name | Required | Description | Example | Reference |
100+
| -------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------- |
101+
| `PROMETHEUS_ENABLED` | ❌ (Automatic) | Automatically set to `true` if `prometheus.io/scrape: "true"` is present in `podAnnotations`, otherwise must be explicitly set to `true` | `true` | |
89102

90103
#### **Dashboard Configuration**
91-
| Name | Required | Description | Example | Reference |
92-
| --- | --- | --- | --- | --- |
93-
| `USE_DASHBOARD` || Enable the dashboard | `true` | [Ref](/docs/dashboard) |
94-
| `ADMIN_PASSWORD` | ✅ if USE_DASHBOARD is set | Admin password | `Random string` | [Ref](/docs/dashboard) |
104+
105+
| Name | Required | Description | Example | Reference |
106+
| ---------------- | -------------------------- | -------------------- | --------------- | ---------------------- |
107+
| `USE_DASHBOARD` || Enable the dashboard | `true` | [Ref](/docs/dashboard) |
108+
| `ADMIN_PASSWORD` | ✅ if USE_DASHBOARD is set | Admin password | `Random string` | [Ref](/docs/dashboard) |

internal/cache/cache.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ func GetCache() Cache {
5656
password := config.GetEnv("REDIS_PASSWORD")
5757
port := config.GetEnv("REDIS_PORT")
5858
useTLS := config.GetEnv("REDIS_USE_TLS") == "true"
59-
cacheInstance = NewRedisCache(host, password, port, useTLS)
59+
// ACL and TLS configuration
60+
username := config.GetEnv("REDIS_USERNAME")
61+
caCertB64 := config.GetEnv("REDIS_CA_CERT_B64")
62+
cacheInstance = NewRedisCache(host, password, port, useTLS, username, caCertB64)
6063
default:
6164
panic("Unknown cache type")
6265
}

0 commit comments

Comments
 (0)