Skip to content
This repository was archived by the owner on Dec 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ NEXT_PUBLIC_CACHE_URL=http://localhost:3000
DISABLE_BUILDER=false
NODE_PSK=your_node_psk #Required for a builder scheduler to connect to this controller
LOG_LEVEL=DEBUG
DB_USER=iglu
DB_PASSWORD=iglu
DB_HOST=localhost
DB_PORT=5432
DB_NAME=cache
LOGGER_USE_ENV=true
LOGGER_JSON=false
LOGGER_PREFIX="[Controller]"
LOGGER_PREFIX_COLOR="MAGENTA"
POSTGRES_USER=iglu
POSTGRES_PASSWORD=iglu
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
AUTH_SECRET=your_auth_secret#Used for Auth.js
REDIS_URL=redis://default:verysecret@localhost:6379#Redis URL for caching
REDIS_HOST=localhost
REDIS_USER=default
REDIS_PASSWORD=verysecret
REDIS_PORT=6379
AUTH_TRUST_HOST=true#Instruct Auth.js to trust the x-forwarded-host header
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ This file should be placed in the root directory of the project and should conta
```dotenv
NEXT_PUBLIC_CACHE_URL=http://localhost:3000
LOG_LEVEL=DEBUG
DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=cache
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=cache
LOGGER_USE_ENV=true
LOGGER_JSON=false
LOGGER_PREFIX="[Controller]"
LOGGER_PREFIX_COLOR="MAGENTA"
AUTH_SECRET=your_auth_secret
```
We recommend setting all the LOGGER_ variables like we have set them here, but you can customize them as per your requirements.
Expand All @@ -32,4 +30,4 @@ After installing the packages, you need to set up the environment variables. You
Then, you can run the controller using:
```bash
bun run dev
```
```
2 changes: 1 addition & 1 deletion nix/pkgs/iglu-controller-docker/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
let
archType = if (stdenv.hostPlatform.system == "x86_64-linux") then "amd64" else "arm64";
in
dockerTools.buildLayeredImageWithNixDb {
dockerTools.buildLayeredImage {
name = "iglu-controller";
tag = "v${iglu.iglu-controller.version}-${archType}";

Expand Down
2,233 changes: 1,116 additions & 1,117 deletions nix/pkgs/iglu-controller/bun.nix

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/app/api/v1/node/healthcheck/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export async function GET(req:NextRequest, res:NextResponse){
Logger.debug('Asking for healthcheck');

const editor = redis.createClient({
url: process.env.REDIS_URL
url: `redis://${env.REDIS_USER}:${env.REDIS_PASSWORD}@${env.REDIS_HOST}:${env.REDIS_PORT}`
})
const subscriber = redis.createClient({
url: process.env.REDIS_URL
url: `redis://${env.REDIS_USER}:${env.REDIS_PASSWORD}@${env.REDIS_HOST}:${env.REDIS_PORT}`
})

// Handle Redis connection errors
Expand Down Expand Up @@ -147,4 +147,4 @@ export async function GET(req:NextRequest, res:NextResponse){
return NextResponse.json({
'status': 'ok'
})
}
}
46 changes: 27 additions & 19 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export const env = createEnv({
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
DB_USER: z.string(),
DB_PASSWORD: z.string(),
DB_HOST: z.string(),
DB_PORT: z.string().optional().default("5432"),
DB_NAME: z.string().default("cache"),
POSTGRES_USER: z.string(),
POSTGRES_PASSWORD: z.string(),
POSTGRES_HOST: z.string(),
POSTGRES_PORT: z.string().optional().default("5432"),
POSTGRES_DB: z.string().default("cache"),
LOG_LEVEL: z.enum(["DEBUG", "INFO", "WARNING", "ERROR"]).optional(),
LOGGER_USE_ENV: z.enum(["true", "false"]).optional().default("false"),
LOGGER_USE_ENV: z.enum(["true", "false"]).optional().default("true"),
LOGGER_JSON: z.enum(["true", "false"]).optional().default("false"),
LOGGER_PREFIX: z.string().optional().default('Controller'),
LOGGER_PREFIX_COLOR: z
Expand All @@ -31,8 +31,12 @@ export const env = createEnv({
.default("MAGENTA"),
AUTH_TRUST_HOST: z.enum(["true", "false"]).optional().default("false"),
DISABLE_BUILDER: z.enum(["true", "false"]).optional().default("false"),
REDIS_URL: z.string(),
REDIS_HOST: z.string(),
REDIS_USER: z.string().optional().default("default"),
REDIS_PASSWORD: z.string().optional().default("default"),
REDIS_PORT: z.string().optional().default("6379"),
NODE_PSK: z.string(),
AUTH_URL: z.string()
},

/**
Expand All @@ -54,24 +58,28 @@ export const env = createEnv({
runtimeEnv: {
AUTH_SECRET: process.env.AUTH_SECRET,
NODE_ENV: process.env.NODE_ENV,
DB_USER: process.env.DB_USER,
DB_PASSWORD: process.env.DB_PASSWORD,
DB_HOST: process.env.DB_HOST,
DB_PORT: process.env.DB_PORT,
DB_NAME: process.env.DB_NAME,
LOG_LEVEL: process.env.LOG_LEVEL,
LOGGER_USE_ENV: process.env.LOGGER_USE_ENV,
LOGGER_JSON: process.env.LOGGER_JSON,
LOGGER_PREFIX: process.env.LOGGER_PREFIX,
LOGGER_PREFIX_COLOR: process.env.LOGGER_PREFIX_COLOR,
POSTGRES_USER: process.env.POSTGRES_USER,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
POSTGRES_HOST: process.env.POSTGRES_HOST,
POSTGRES_PORT: process.env.POSTGRES_PORT ?? "5432",
POSTGRES_DB: process.env.POSTGRES_DB ?? "cache",
REDIS_HOST: process.env.REDIS_HOST,
REDIS_USER: process.env.REDIS_USER ?? "default",
REDIS_PASSWORD: process.env.REDIS_PASSWORD ?? "",
REDIS_PORT: process.env.REDIS_PORT ?? "6379",
LOG_LEVEL: process.env.LOG_LEVEL ?? "INFO",
LOGGER_USE_ENV: process.env.LOGGER_USE_ENV ?? "true",
LOGGER_JSON: process.env.LOGGER_JSON ?? "false",
LOGGER_PREFIX: process.env.LOGGER_PREFIX ?? "Controller",
LOGGER_PREFIX_COLOR: process.env.LOGGER_PREFIX_COLOR ?? "MAGENTA",
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
NEXT_PUBLIC_CACHE_URL: process.env.NEXT_PUBLIC_CACHE_URL,
AUTH_TRUST_HOST: process.env.AUTH_TRUST_HOST,
DISABLE_BUILDER: process.env.DISABLE_BUILDER === "true" ? "true" : "false",
NEXT_PUBLIC_DISABLE_BUILDER: process.env.DISABLE_BUILDER === "true" ? "true" : "false",
NODE_PSK: process.env.NODE_PSK,
REDIS_URL: process.env.REDIS_URL,
NEXT_PUBLIC_VERSION: process.env.NEXT_PUBLIC_VERSION
NEXT_PUBLIC_VERSION: process.env.NEXT_PUBLIC_VERSION,
AUTH_URL: process.env.NEXT_PUBLIC_URL
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
Expand Down
35 changes: 15 additions & 20 deletions src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Client, type QueryResult} from "pg";
import Logger from "@iglu-sh/logger";
import {createClient} from "redis";
import { env } from "@/env";
import type {
apiKeyWithCache,
cache, git_configs,
Expand Down Expand Up @@ -36,11 +37,11 @@ export default class Database{

constructor() {
this.client = new Client({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: parseInt(process.env.DB_PORT ?? "5432", 10),
user: env.POSTGRES_USER,
host: env.POSTGRES_HOST,
database: env.POSTGRES_DB,
password: env.POSTGRES_PASSWORD,
port: parseInt(env.POSTGRES_PORT, 10),
})
}
private async wrap(cl: Database){
Expand Down Expand Up @@ -287,7 +288,7 @@ export default class Database{
Logger.debug('Database tables set up successfully');

// if the DISABLE_BUILDER environment variable is set to false, we can create a cron update to check the health of the nodes
if(!process.env.DISABLE_BUILDERS || process.env.DISABLE_BUILDERS === 'false'){
if(!env.DISABLE_BUILDERS || env.DISABLE_BUILDERS === 'false'){
Logger.debug('Creating cron update for builder health check');

await this.query(`
Expand All @@ -310,32 +311,26 @@ export default class Database{
SELECT cron.schedule('healthcheck','* * * * *', $$
SELECT * FROM http((
'GET',
'${process.env.NEXT_PUBLIC_URL}/api/v1/node/healthcheck',
ARRAY[http_header('Authorization', '${process.env.NODE_PSK}')],
'${env.NEXT_PUBLIC_URL}/api/v1/node/healthcheck',
ARRAY[http_header('Authorization', '${env.NODE_PSK}')],
NULL,
NULL
)::http_request);
$$);
SELECT cron.schedule('redisTasks','* * * * *', $$
SELECT * FROM http((
'GET',
'${process.env.NEXT_PUBLIC_URL}/api/v1/tasks/redis',
ARRAY[http_header('Authorization', '${process.env.NODE_PSK}')],
'${env.NEXT_PUBLIC_URL}/api/v1/tasks/redis',
ARRAY[http_header('Authorization', '${env.NODE_PSK}')],
NULL,
NULL
)::http_request);
$$);
`)
}

// Redis Setup
// TODO: Move this out of here, wtf
if(!process.env.REDIS_URL){
return
}
// Deregister all nodes that may still be connected
const editor = createClient({
url: process.env.REDIS_URL
url: `redis://${env.REDIS_USER}:${env.REDIS_PASSWORD}@${env.REDIS_HOST}:${env.REDIS_PORT}`
})
await editor.connect().catch((err:Error)=>{
Logger.error(`Failed to connect to Redis editor: ${err.message}`);
Expand Down Expand Up @@ -802,7 +797,7 @@ export default class Database{
})
}
// Mainly used for the OOB experience, and it should not be used outside of that
// It does require a lot of processing power, so it should only be used when necessary
// It does require a lot of ng power, so it should only be used when necessary
public async getEverything():Promise<Array<xTheEverythingType>>{
return await this.query(`
SELECT row_to_json(ca.*) as cache,
Expand Down Expand Up @@ -984,7 +979,7 @@ export default class Database{
INSERT INTO cache.caches (githubusername, ispublic, name, permission, preferredcompressionmethod, uri)
VALUES ('', $1, $2, $3, $4, $5)
RETURNING *
`, [cacheToCreate.ispublic, cacheToCreate.name, cacheToCreate.permission, cacheToCreate.preferredcompressionmethod, process.env.NEXT_PUBLIC_CACHE_URL!], userID as uuid)
`, [cacheToCreate.ispublic, cacheToCreate.name, cacheToCreate.permission, cacheToCreate.preferredcompressionmethod, env.NEXT_PUBLIC_CACHE_URL!], userID as uuid)
.then((res)=>{
if(res.rows.length === 0){
throw new Error("Unknown error while creating cache");
Expand Down Expand Up @@ -1635,4 +1630,4 @@ export default class Database{
}


}
}
11 changes: 6 additions & 5 deletions src/lib/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import type {arch, BuildChannelMessage, BuildQueueMessage} from "@iglu-sh/types/
import type {builder_runs, combinedBuilder} from "@iglu-sh/types/core/db";
import Database from "@/lib/db";
import {EventEmitter} from "node:events";
import { env } from "@/env";

export default class Redis{
private redisClient:RedisClientType
constructor() {
Logger.debug("Constructing Redis Client");
this.redisClient = createClient({
url: process.env.REDIS_URL
url: `redis://${env.REDIS_USER}:${env.REDIS_PASSWORD}@${env.REDIS_HOST}:${env.REDIS_PORT}`
})
this.redisClient.on('error', (err) => Logger.error(`Redis Client Error ${err}`));
void this.redisClient.connect().then(()=>{
Expand Down Expand Up @@ -134,14 +135,14 @@ export default class Redis{
gitcommit: "unknown",
duration: "null",
ended_at: new Date(),
log: JSON.stringify({stdout: "No nodes are currently connected to the controller, cannot process build job.", jobStatus: "failed"}),
log: JSON.stringify({stdout: "No nodes are currently connected to the controller, cannot build job.", jobStatus: "failed"}),
}
// Write the dummy run to redis so that the system is aware of it
await this.redisClient.json.set(`run:${jobID}`, '.', dummyRun)
await this.stopJob("failed", jobID)

// Throw to make sure the caller knows the job could not be processed
throw new Error("No nodes are currently connected to the controller, cannot process build job.")
// Throw to make sure the caller knows the job could not be d
throw new Error("No nodes are currently connected to the controller, cannot build job.")
}

// Get the builder from Redis to see if it exists
Expand Down Expand Up @@ -409,4 +410,4 @@ export default class Redis{
}


}
}
6 changes: 4 additions & 2 deletions src/lib/redisHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Logger from "@iglu-sh/logger";
import type {NodeChannelMessage} from "@iglu-sh/types/controller";
import {createClient} from 'redis';
import { env } from "@/env";
export function buildDeregisterMsg(
nodeId:string,
): NodeChannelMessage {
Expand All @@ -13,10 +14,11 @@ export function buildDeregisterMsg(
}
// Create and return a connected Redis client
export async function getRedisClient(){
const env = env
const client = createClient({
url: process.env.REDIS_URL
url: `redis://${env.REDIS_USER}:${env.REDIS_PASSWORD}@${env.REDIS_HOST}:${env.REDIS_PORT}`
})
client.on('error', (err) => Logger.error(`Redis Client Error ${err}`));
await client.connect()
return client
}
}
41 changes: 28 additions & 13 deletions src/server/api/routers/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,28 +221,28 @@ export const admin = createTRPCRouter({
description: "The environment the controller is running in (development/production). For you, this should always be 'production'."
},
{
envVar: "DB_USER",
value: environment.DB_USER,
envVar: "POSTGRES_USER",
value: environment.POSTGRES_USER,
description: "The database user the controller uses to connect to the database."
},
{
envVar: "DB_PASSWORD",
value: environment.DB_PASSWORD ? "********" : null,
envVar: "POSTGRES_PASSWORD",
value: environment.POSTGRES_PASSWORD ? "********" : null,
description: "The database password the controller uses to connect to the database."
},
{
envVar: "DB_HOST",
value: environment.DB_HOST,
envVar: "POSTGRES_HOST",
value: environment.POSTGRES_HOST,
description: "The database host the controller uses to connect to the database."
},
{
envVar: "DB_PORT",
value: environment.DB_PORT,
envVar: "POSTGRES_PORT",
value: environment.POSTGRES_PORT,
description: "The database port the controller uses to connect to the database."
},
{
envVar: "DB_NAME",
value: environment.DB_NAME,
envVar: "POSTGRES_DB",
value: environment.POSTGRES_DB,
description: "The database name the controller uses to connect to the database. This database must already exist (this is done by the Iglu Cache which you should have deployed before starting the controller)."
},
{
Expand Down Expand Up @@ -281,10 +281,25 @@ export const admin = createTRPCRouter({
description: "The pre-shared key used for node-to-controller communication. A node is any Iglu Scheduler Instance which can spin up builders. This key must be the same on all schedulers and the controller."
},
{
envVar: "REDIS_URL",
value: environment.REDIS_URL,
description: "The Redis URL used for caching and communication between the controller and schedulers/builders. This is only needed if you have not disabled the builder functionality."
envVar: "REDIS_HOST",
value: environment.REDIS_HOST,
description: "The hostname or IP address of the Redis server used for caching and communication between the controller and schedulers/builders."
},
{
envVar: "REDIS_USER",
value: environment.REDIS_USER,
description: "The username to use when connecting to the Redis server. Leave empty if authentication is not required or if only password authentication is used."
},
{
envVar: "REDIS_PASSWORD",
value: environment.REDIS_PASSWORD ? "********" : null,
description: "The password to use when connecting to the Redis server. Leave empty if no authentication is configured."
},
{
envVar: "REDIS_PORT",
value: environment.REDIS_PORT,
description: "The port number of the Redis server. Default is 6379 if not specified."
}
],
client: [
{
Expand Down