Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimize envs and add support for custom root for data #117 #125

Merged
merged 1 commit into from
Dec 12, 2024
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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ repos
dockerrepos
playwright-report
test-results
config.yml
secrets.yml
10 changes: 6 additions & 4 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ROOT_PATH=/app
#CUSTOM_REPO_ROOT=/app/repos
#CONFIG_LOCATION=/app/config/config.yml
#SECRETS_LOCATION=/app/config/secrets.yml
DRY_RUN=true # not fully supported yet
LOAD_LOCAL_SAMPLES=false
DEBUG_CREATE_FILES=false
LOG_LEVEL=info
#DRY_RUN=true # not fully supported yet
#LOAD_LOCAL_SAMPLES=false
#DEBUG_CREATE_FILES=false
#LOG_LEVEL=info
5 changes: 0 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ COPY esbuild.ts ./
RUN pnpm run build

FROM base AS dev
ENV CONFIG_LOCATION=/app/config/config.yml
ENV SECRETS_LOCATION=/app/config/secrets.yml
# manually mount src etc

CMD [ "pnpm", "start" ]
Expand All @@ -46,8 +44,5 @@ RUN apk add --no-cache libstdc++ dumb-init git

COPY --from=builder /app/bundle.cjs /app/index.js

ENV CONFIG_LOCATION=/app/config/config.yml
ENV SECRETS_LOCATION=/app/config/secrets.yml

# Run with dumb-init to not start node with PID=1, since Node.js was not designed to run as PID 1
CMD ["dumb-init", "node", "index.js"]
4 changes: 0 additions & 4 deletions Dockerfile-deno.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ COPY index.ts esbuild.ts ./
RUN deno --allow-env --allow-read --allow-run esbuild.ts

FROM base AS dev
ENV CONFIG_LOCATION=/app/config/config.yml
ENV SECRETS_LOCATION=/app/config/secrets.yml
ENV DENO_DIR=/app/.deno_cache
# manually mount src etc

Expand All @@ -34,8 +32,6 @@ RUN apk add --no-cache libstdc++ dumb-init git

COPY --from=builder /app/bundle.cjs /app/index.cjs

ENV CONFIG_LOCATION=/app/config/config.yml
ENV SECRETS_LOCATION=/app/config/secrets.yml
ENV DENO_DIR=/app/.deno_cache

# Compile cache / modify for multi-user
Expand Down
37 changes: 37 additions & 0 deletions docs/docs/configuration/environment-variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
sidebar_position: 2
title: Environment Variables
description: "Learn about the environment variables used in our application configuration."
keywords: [environment variables, configuration, setup]
---

# Environment Variables

This document outlines the available environment variables for configuring Configarr besides the config files.
Each variable can be set to customize the behavior of the application.

## Available Environment Variables

| Variable Name | Default Value | Required | Description |
| -------------------- | ------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `LOG_LEVEL` | `"info"` | No | Sets the logging level. Options are `trace`, `debug`, `info`, `warn`, `error`, and `fatal`. |
| `CONFIG_LOCATION` | `"./config/config.yml"` | No | Specifies the path to the configuration file. |
| `SECRETS_LOCATION` | `"./config/secrets.yml"` | No | Specifies the path to the secrets file. |
| `CUSTOM_REPO_ROOT` | `"./repos"` | No | Defines the root directory for custom repositories. |
| `ROOT_PATH` | Current working directory | No | Sets the root path for the application. Defaults to the current working directory. |
| `DRY_RUN` | `"false"` | No | When set to `"true"`, runs the application in dry run mode without making changes. |
| `LOAD_LOCAL_SAMPLES` | `"false"` | No | If `"true"`, loads local sample data for testing purposes. |
| `DEBUG_CREATE_FILES` | `"false"` | No | Enables debugging for file creation processes when set to `"true"`. |

## Usage

To use these environment variables, set them in your shell or include them in your deployment configuration via docker or kubernetes.

## Examples

- For example you change the default path for all configs, repos with the `ROOT_PATH` variables.
As default it would store them inside the application directory (in the container this is `/app`)

## References

Check the `.env.template` file in the repository [Github](https://github.com/raydak-labs/configarr/blob/main/.env.template)
2 changes: 1 addition & 1 deletion docs/docs/configuration/experimental-support.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 3
title: Experimental Support
description: "Experimental and testing support for other *Arr tools"
keywords: [configarr configuration, yaml config, custom formats, expermintal, whisparr, readarr]
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/configuration/scheduled.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
title: Scheduling
description: "How to run configarr regulary/schedueld"
keywords: [configarr configuration, schedule, scheduler, regular, cron]
Expand Down
1 change: 1 addition & 0 deletions examples/full/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
!config/*.yml
dockerrepos/
data/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"pino-pretty": "13.0.0",
"simple-git": "3.27.0",
"tsx": "4.19.2",
"yaml": "2.6.1"
"yaml": "2.6.1",
"zod": "^3.24.1"
},
"devDependencies": {
"@hyrious/esbuild-plugin-commonjs": "0.2.4",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 16 additions & 13 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { existsSync, readFileSync } from "node:fs";
import yaml from "yaml";
import { getHelpers } from "./env";
import { logger } from "./logger";
import {
ConfigArrInstance,
Expand All @@ -12,10 +13,6 @@ import {
InputConfigSchema,
MergedConfigInstance,
} from "./types/config.types";
import { ROOT_PATH } from "./util";

const CONFIG_LOCATION = process.env.CONFIG_LOCATION ?? `${ROOT_PATH}/config.yml`;
const SECRETS_LOCATION = process.env.SECRETS_LOCATION ?? `${ROOT_PATH}/secrets.yml`;

let config: ConfigSchema;
let secrets: any;
Expand Down Expand Up @@ -50,12 +47,14 @@ export const getConfig = (): ConfigSchema => {
return config;
}

if (!existsSync(CONFIG_LOCATION)) {
logger.error(`Config file in location "${CONFIG_LOCATION}" does not exists.`);
const configLocation = getHelpers().configLocation;

if (!existsSync(configLocation)) {
logger.error(`Config file in location "${configLocation}" does not exists.`);
throw new Error("Config file not found.");
}

const file = readFileSync(CONFIG_LOCATION, "utf8");
const file = readFileSync(configLocation, "utf8");

const inputConfig = yaml.parse(file, { customTags: [secretsTag, envTag] }) as InputConfigSchema;

Expand All @@ -65,12 +64,14 @@ export const getConfig = (): ConfigSchema => {
};

export const readConfigRaw = (): object => {
if (!existsSync(CONFIG_LOCATION)) {
logger.error(`Config file in location "${CONFIG_LOCATION}" does not exists.`);
const configLocation = getHelpers().configLocation;

if (!existsSync(configLocation)) {
logger.error(`Config file in location "${configLocation}" does not exists.`);
throw new Error("Config file not found.");
}

const file = readFileSync(CONFIG_LOCATION, "utf8");
const file = readFileSync(configLocation, "utf8");

const inputConfig = yaml.parse(file, { customTags: [secretsTag, envTag] });

Expand All @@ -82,12 +83,14 @@ export const getSecrets = () => {
return secrets;
}

if (!existsSync(SECRETS_LOCATION)) {
logger.error(`Secret file in location "${SECRETS_LOCATION}" does not exists.`);
const secretLocation = getHelpers().secretLocation;

if (!existsSync(secretLocation)) {
logger.error(`Secret file in location "${secretLocation}" does not exists.`);
throw new Error("Secret file not found.");
}

const file = readFileSync(SECRETS_LOCATION, "utf8");
const file = readFileSync(secretLocation, "utf8");
config = yaml.parse(file);
return config;
};
Expand Down
10 changes: 6 additions & 4 deletions src/custom-formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import path from "node:path";
import { MergedCustomFormatResource } from "./__generated__/mergedTypes";
import { getUnifiedClient } from "./clients/unified-client";
import { getConfig } from "./config";
import { getEnvs } from "./env";
import { logger } from "./logger";
import { loadTrashCFs } from "./trash-guide";
import { ArrType, CFProcessing, ConfigarrCF } from "./types/common.types";
import { ConfigCustomFormatList, CustomFormatDefinitions } from "./types/config.types";
import { TrashCF } from "./types/trashguide.types";
import { IS_DRY_RUN, IS_LOCAL_SAMPLE_MODE, compareCustomFormats, loadJsonFile, mapImportCfToRequestCf, toCarrCF } from "./util";
import { compareCustomFormats, loadJsonFile, mapImportCfToRequestCf, toCarrCF } from "./util";

export const deleteAllCustomFormats = async () => {
const api = getUnifiedClient();
Expand All @@ -21,7 +22,7 @@ export const deleteAllCustomFormats = async () => {
};

export const loadServerCustomFormats = async (): Promise<MergedCustomFormatResource[]> => {
if (IS_LOCAL_SAMPLE_MODE) {
if (getEnvs().LOAD_LOCAL_SAMPLES) {
return loadJsonFile<MergedCustomFormatResource[]>(path.resolve(__dirname, "../tests/samples/cfs.json"));
}
const api = getUnifiedClient();
Expand Down Expand Up @@ -61,7 +62,7 @@ export const manageCf = async (
logger.info(`Found mismatch for ${tr.requestConfig.name}: ${comparison.changes}`);

try {
if (IS_DRY_RUN) {
if (getEnvs().DRY_RUN) {
logger.info(`DryRun: Would update CF: ${existingCf.id} - ${existingCf.name}`);
updatedCFs.push(existingCf);
} else {
Expand All @@ -83,7 +84,7 @@ export const manageCf = async (
} else {
// Create
try {
if (IS_DRY_RUN) {
if (getEnvs().DRY_RUN) {
logger.info(`Would create CF: ${tr.requestConfig.name}`);
} else {
const createResult = await api.createCustomFormat(tr.requestConfig);
Expand Down Expand Up @@ -114,6 +115,7 @@ export const manageCf = async (

return { createCFs, updatedCFs, validCFs, errorCFs };
};

export const loadLocalCfs = async (): Promise<CFProcessing | null> => {
const config = getConfig();

Expand Down
72 changes: 72 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import path from "node:path";
import { z } from "zod";

const DEFAULT_ROOT_PATH = path.resolve(process.cwd());

const schema = z.object({
// NODE_ENV: z.enum(["production", "development", "test"] as const),
LOG_LEVEL: z
.enum(["trace", "debug", "info", "warn", "error", "fatal"] as const)
.optional()
.default("info"),
CONFIG_LOCATION: z.string().optional(),
SECRETS_LOCATION: z.string().optional(),
// TODO: deprecate?
CUSTOM_REPO_ROOT: z.string().optional(),
ROOT_PATH: z.string().optional().default(DEFAULT_ROOT_PATH),
DRY_RUN: z
.string()
.toLowerCase()
.transform((x) => x === "true")
.pipe(z.boolean())
.default("false"),
LOAD_LOCAL_SAMPLES: z
.string()
.toLowerCase()
.transform((x) => x === "true")
.pipe(z.boolean())
.default("false"),
DEBUG_CREATE_FILES: z
.string()
.toLowerCase()
.transform((x) => x === "true")
.pipe(z.boolean())
.default("false"),
});

// declare global {
// // eslint-disable-next-line @typescript-eslint/no-namespace
// namespace NodeJS {
// // eslint-disable-next-line @typescript-eslint/no-empty-object-type
// interface ProcessEnv extends z.infer<typeof schema> {}
// }
// }

let envs: z.infer<typeof schema>;

export function initEnvs() {
const parsed = schema.safeParse(process.env);

if (parsed.success === false) {
console.error("Invalid environment variables:", parsed.error.flatten().fieldErrors);
throw new Error("Invalid environment variables.");
}

envs = parsed.data;
}

export const getEnvs = () => {
if (envs) return envs;

envs = schema.parse(process.env);

return envs;
};

export const getHelpers = () => ({
configLocation: getEnvs().CONFIG_LOCATION ?? `${getEnvs().ROOT_PATH}/config/config.yml`,
secretLocation: getEnvs().SECRETS_LOCATION ?? `${getEnvs().ROOT_PATH}/config/secrets.yml`,
// TODO: check for different env name
repoPath: getEnvs().CUSTOM_REPO_ROOT ?? `${getEnvs().ROOT_PATH}/repos`,
// TODO: add stuff like isDryRun,...?
});
12 changes: 7 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// those must be run first!
import "dotenv/config";
import { getEnvs, initEnvs } from "./env";
initEnvs();

import fs from "node:fs";
import { MergedCustomFormatResource } from "./__generated__/mergedTypes";
Expand Down Expand Up @@ -26,7 +29,6 @@ import {
MergedConfigInstance,
} from "./types/config.types";
import { TrashQualityDefintion } from "./types/trashguide.types";
import { DEBUG_CREATE_FILES, IS_DRY_RUN } from "./util";

/**
* Load data from trash, recyclarr, custom configs and merge.
Expand Down Expand Up @@ -250,7 +252,7 @@ const pipeline = async (value: InputConfigArrInstance, arrType: ArrType) => {
const { changeMap, create, restData } = calculateQualityDefinitionDiff(serverQD, qdTrash, config.quality_definition?.preferred_ratio);

if (changeMap.size > 0) {
if (IS_DRY_RUN) {
if (getEnvs().DRY_RUN) {
logger.info("DryRun: Would update QualityDefinitions.");
} else {
logger.info(`Diffs in quality definitions found`, changeMap.values());
Expand All @@ -272,15 +274,15 @@ const pipeline = async (value: InputConfigArrInstance, arrType: ArrType) => {
const serverQP = await loadQualityProfilesFromServer();
const { changedQPs, create, noChanges } = await calculateQualityProfilesDiff(mergedCFs, config, serverQP, serverQD, serverCFs);

if (DEBUG_CREATE_FILES) {
if (getEnvs().DEBUG_CREATE_FILES) {
create.concat(changedQPs).forEach((e, i) => {
fs.writeFileSync(`debug/test${i}.json`, JSON.stringify(e, null, 2), "utf-8");
});
}

logger.info(`QualityProfiles: Create: ${create.length}, Update: ${changedQPs.length}, Unchanged: ${noChanges.length}`);

if (!IS_DRY_RUN) {
if (!getEnvs().DRY_RUN) {
for (const element of create) {
try {
const newProfile = await api.createQualityProfile(element);
Expand All @@ -306,7 +308,7 @@ const pipeline = async (value: InputConfigArrInstance, arrType: ArrType) => {
};

const run = async () => {
if (IS_DRY_RUN) {
if (getEnvs().DRY_RUN) {
logger.info("DryRun: Running in dry-run mode!");
}

Expand Down
Loading
Loading