Skip to content

Instantly share code, notes, and snippets.

@hayd1n
Last active November 5, 2025 15:10
Show Gist options
  • Save hayd1n/cd4af4aa82d6fa635080ebe83fbcb84d to your computer and use it in GitHub Desktop.
Save hayd1n/cd4af4aa82d6fa635080ebe83fbcb84d to your computer and use it in GitHub Desktop.
AFFiNE Self-Hosted Patcher

AFFiNE Self-Hosted Patcher

This Node.js script patches the configuration file of a self-hosted AFFiNE instance to modify the pro_plan_v1 limits. It effectively "unlocks" the Pro plan for self-hosted servers by increasing quotas to generous values.

This script is designed to be run against the specific bundled/compiled JavaScript file in your AFFiNE server build that contains the plan definitions.

⚠️ Disclaimer

This script directly modifies AFFiNE's compiled source code. This is not an official or supported method.

What it Changes

This script targets the pro_plan_v1 object in the code and changes the following values:

Setting From To
blobLimit 100 * ...OneMB 10 * ...OneGB
storageQuota 100 * ...OneGB 1000 * ...OneGB
historyPeriod 30 * ...OneDay 3650 * ...OneDay
memberLimit 10 1000

Usage

# Example
node affine-patcher.mjs /app/dist/main.js

The script will log its progress and confirm success or report an error if the content to be replaced is not found.

// Import required modules using ESM syntax
import fs from "fs/promises"; // Use the promises-based API
import path from "path";
import process from "process"; // Required for argv and exit
// --- Configuration ---
// Get the filename from the command line arguments
// process.argv[0] is 'node'
// process.argv[1] is the script file (e.g., 'replace.js')
// process.argv[2] is the first user-provided argument
const FILENAME = process.argv[2];
// --- Validation ---
if (!FILENAME) {
console.error("Error: No filename provided.");
console.log("Usage: node replace.js <your-target-file.js>");
process.exit(1); // Exit with a non-zero status code
}
// Resolve the absolute path based on the current working directory
// This makes the script work regardless of where it's called from.
const targetFilePath = path.resolve(FILENAME);
/**
* Async function to read, replace, and write file content.
*/
async function runRobustReplace() {
try {
console.log(`[INFO] Reading file: ${targetFilePath}`);
let fileContent = await fs.readFile(targetFilePath, "utf8");
// Save original content for comparison
const originalContent = fileContent;
// We will use a series of replacements to ensure we are operating
// within the correct 'pro_plan_v1' block.
// The 'm' flag allows regex to match across multiple lines.
// [\s\S]*? is a non-greedy way to match any character, including newlines.
// Replace blobLimit
fileContent = fileContent.replace(
/(pro_plan_v1:[\s\S]*?blobLimit:\s*)100 \* _base__WEBPACK_IMPORTED_MODULE_1__\.OneMB(,\s*)/m,
"$110 * _base__WEBPACK_IMPORTED_MODULE_1__.OneGB$2"
);
// Replace storageQuota
fileContent = fileContent.replace(
/(pro_plan_v1:[\s\S]*?storageQuota:\s*)100 \* _base__WEBPACK_IMPORTED_MODULE_1__\.OneGB(,\s*)/m,
"$11000 * _base__WEBPACK_IMPORTED_MODULE_1__.OneGB$2"
);
// Replace historyPeriod
// fileContent = fileContent.replace(
// /(pro_plan_v1:[\s\S]*?historyPeriod:\s*)30 \* _base__WEBPACK_IMPORTED_MODULE_1__\.OneDay(,\s*)/m,
// "$13650 * _base__WEBPACK_IMPORTED_MODULE_1__.OneDay$2"
// );
// Replace memberLimit
fileContent = fileContent.replace(
/(pro_plan_v1:[\s\S]*?memberLimit:\s*)10(,\s*)/m,
"$11000$2"
);
// Check if any content actually changed
if (originalContent === fileContent) {
console.error("[ERROR] Could not find any matching lines to replace.");
console.log("Please check your regex patterns and the file content.");
return;
}
// Write the new content back to the file
await fs.writeFile(targetFilePath, fileContent, "utf8");
console.log(
"[SUCCESS] File content was successfully replaced using Regex!"
);
} catch (err) {
// Handle potential errors, like "File Not Found"
if (err.code === "ENOENT") {
console.error(`[ERROR] File not found at path: ${targetFilePath}`);
} else {
console.error("[ERROR] An error occurred during the process:", err);
}
process.exit(1); // Exit with error
}
}
// Run the main function
runRobustReplace();
name: affine
services:
affine:
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
container_name: affine_server
ports:
- '${PORT:-3010}:3010'
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
affine_migration:
condition: service_completed_successfully
volumes:
# custom configurations
- ${UPLOAD_LOCATION}:/root/.affine/storage
- ${CONFIG_LOCATION}:/root/.affine/
- ./scripts:/root/scripts
# Patch before start
command: ['sh', '-c', 'node /root/scripts/crack.mjs ./dist/main.js ; node ./dist/main.js']
env_file:
- .env
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
- AFFINE_INDEXER_ENABLED=false
restart: unless-stopped
affine_migration:
image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
container_name: affine_migration_job
volumes:
# custom configurations
- ${UPLOAD_LOCATION}:/root/.affine/storage
- ${CONFIG_LOCATION}:/root/.affine/config
- ./scripts:/root/scripts
# Patch and run migration
command: ['sh', '-c', 'node /root/scripts/crack.mjs ./dist/main.js ; node ./scripts/self-host-predeploy.js']
env_file:
- .env
environment:
- REDIS_SERVER_HOST=redis
- DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
- AFFINE_INDEXER_ENABLED=false
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
redis:
image: redis
container_name: affine_redis
healthcheck:
test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
postgres:
image: pgvector/pgvector:pg16
container_name: affine_postgres
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_DATABASE:-affine}
POSTGRES_INITDB_ARGS: '--data-checksums'
# you better set a password for you database
# or you may add 'POSTGRES_HOST_AUTH_METHOD=trust' to ignore postgres security policy
POSTGRES_HOST_AUTH_METHOD: trust
healthcheck:
test:
['CMD', 'pg_isready', '-U', "${DB_USERNAME}", '-d', "${DB_DATABASE:-affine}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment