mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 00:18:19 +01:00
Replace config manager with unjs/c12
This commit is contained in:
parent
6b2e4044b6
commit
6a54c5b805
|
|
@ -3,9 +3,7 @@
|
|||
*/
|
||||
|
||||
import chalk from "chalk";
|
||||
import { ConfigManager } from "config-manager";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
import { config } from "config-manager";
|
||||
|
||||
const token = process.env.TOKEN;
|
||||
const requestCount = Number(process.argv[2]) || 100;
|
||||
|
|
|
|||
4
cli.ts
4
cli.ts
|
|
@ -7,7 +7,7 @@ import extract from "extract-zip";
|
|||
import { client } from "~database/datasource";
|
||||
import { CliBuilder, CliCommand } from "cli-parser";
|
||||
import { CliParameterType } from "~packages/cli-parser/cli-builder.type";
|
||||
import { ConfigManager } from "~packages/config-manager";
|
||||
import { config } from "~packages/config-manager";
|
||||
import { Parser } from "@json2csv/plainjs";
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import { MediaBackend } from "media-manager";
|
||||
|
|
@ -17,8 +17,6 @@ import { tmpdir } from "os";
|
|||
|
||||
const args = process.argv;
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
|
||||
const filterObjects = <T extends object>(output: T[], fields: string[]) => {
|
||||
if (fields.length === 0) return output;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,28 @@
|
|||
# Lysand Config
|
||||
# All of these values can be changed via the CLI (they will be saved in a file named config.internal.toml
|
||||
# in the same directory as this one)
|
||||
# Changing this file does not require a restart, but might take a few seconds to apply
|
||||
# This file will be merged with the CLI configuration, taking precedence over it
|
||||
|
||||
[database]
|
||||
# Main PostgreSQL database connection
|
||||
host = "localhost"
|
||||
port = 5432
|
||||
username = "lysand"
|
||||
password = "password123"
|
||||
password = "lysand"
|
||||
database = "lysand"
|
||||
|
||||
[redis.queue]
|
||||
# Redis instance for storing the federation queue
|
||||
# Required for federation
|
||||
host = "localhost"
|
||||
port = 6379
|
||||
password = ""
|
||||
database = 0
|
||||
|
||||
[redis.cache]
|
||||
# Redis instance to be used as a timeline cache
|
||||
# Optional, can be the same as the queue instance
|
||||
host = "localhost"
|
||||
port = 6379
|
||||
password = ""
|
||||
|
|
@ -19,14 +30,15 @@ database = 1
|
|||
enabled = false
|
||||
|
||||
[meilisearch]
|
||||
# If Meilisearch is not configured, search will not be enabled
|
||||
host = "localhost"
|
||||
port = 40007
|
||||
api_key = ""
|
||||
enabled = true
|
||||
port = 7700
|
||||
api_key = "______________________________"
|
||||
enabled = false
|
||||
|
||||
[signups]
|
||||
# URL of your Terms of Service
|
||||
tos_url = "https://example.com/tos"
|
||||
tos_url = "https://my-site.com/tos"
|
||||
# Whether to enable registrations or not
|
||||
registration = true
|
||||
rules = [
|
||||
|
|
@ -41,40 +53,64 @@ rules = [
|
|||
# The provider MUST support OpenID Connect with .well-known discovery
|
||||
# Most notably, GitHub does not support this
|
||||
[[oidc.providers]]
|
||||
# Test with custom Authentik instance
|
||||
name = "CPlusPatch ID"
|
||||
id = "cpluspatch-id"
|
||||
url = "https://id.cpluspatch.com/application/o/lysand-testing/"
|
||||
client_id = "XXXXXXXXXXXXXXXX"
|
||||
client_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
client_id = "______________________________"
|
||||
client_secret = "__________________________________"
|
||||
icon = "https://cpluspatch.com/images/icons/logo.svg"
|
||||
|
||||
[http]
|
||||
# The full URL Lysand will be reachable by (paths are not supported)
|
||||
base_url = "https://lysand.social"
|
||||
bind = "http://localhost"
|
||||
# Address to bind to
|
||||
bind = "0.0.0.0"
|
||||
bind_port = "8080"
|
||||
|
||||
# Bans IPv4 or IPv6 IPs (wildcards, networks and ranges are supported)
|
||||
banned_ips = []
|
||||
# Banned user agents, regex format
|
||||
banned_user_agents = [
|
||||
# "curl\/7.68.0",
|
||||
# "wget\/1.20.3",
|
||||
]
|
||||
|
||||
[http.bait]
|
||||
# Enable the bait feature (sends fake data to those who are flagged)
|
||||
enabled = false
|
||||
# Path to file of bait data (if not provided, Lysand will send the entire Bee Movie script)
|
||||
send_file = ""
|
||||
# IPs to send bait data to (wildcards, networks and ranges are supported)
|
||||
bait_ips = ["127.0.0.1", "::1"]
|
||||
# User agents to send bait data to (regex format)
|
||||
bait_user_agents = ["curl", "wget"]
|
||||
|
||||
[smtp]
|
||||
# SMTP server to use for sending emails
|
||||
server = "smtp.example.com"
|
||||
port = 465
|
||||
username = "test@example.com"
|
||||
password = "password123"
|
||||
password = "____________"
|
||||
tls = true
|
||||
# Disable all email functions (this will allow people to sign up without verifying
|
||||
# their email)
|
||||
enabled = false
|
||||
|
||||
[media]
|
||||
# Can be "s3" or "local", where "local" uploads the file to the local filesystem
|
||||
# If you need to change this value after setting up your instance, you must move all the files
|
||||
# from one backend to the other manually
|
||||
backend = "s3"
|
||||
# from one backend to the other manually (the CLI will have an option to do this later)
|
||||
# TODO: Add CLI command to move files
|
||||
backend = "local"
|
||||
# Whether to check the hash of media when uploading to avoid duplication
|
||||
deduplicate_media = true
|
||||
# If media backend is "local", this is the folder where the files will be stored
|
||||
# Can be any path
|
||||
local_uploads_folder = "uploads"
|
||||
|
||||
[media.conversion]
|
||||
# Whether to automatically convert images to another format on upload
|
||||
convert_images = false
|
||||
# Can be: "jxl", "webp", "avif", "png", "jpg", "heif"
|
||||
# JXL support will likely not work
|
||||
|
|
@ -82,26 +118,26 @@ convert_to = "webp"
|
|||
|
||||
[s3]
|
||||
# Can be left blank if you don't use the S3 media backend
|
||||
endpoint = "https://s3-us-west-2.amazonaws.com"
|
||||
access_key = ""
|
||||
secret_access_key = ""
|
||||
region = "us-west-2"
|
||||
endpoint = "myhostname.banana.com"
|
||||
access_key = "_____________"
|
||||
secret_access_key = "_________________"
|
||||
region = ""
|
||||
bucket_name = "lysand"
|
||||
public_url = "https://cdn.example.com"
|
||||
public_url = "https://cdn.test.com"
|
||||
|
||||
[email]
|
||||
# Sends an email to moderators when a report is received
|
||||
# NOT IMPLEMENTED
|
||||
send_on_report = false
|
||||
# Sends an email to moderators when a user is suspended
|
||||
# NOT IMPLEMENTED
|
||||
send_on_suspend = false
|
||||
# Sends an email to moderators when a user is unsuspended
|
||||
# NOT IMPLEMENTED
|
||||
send_on_unsuspend = false
|
||||
# Verify user emails when signing up (except via OIDC)
|
||||
verify_email = false
|
||||
|
||||
[validation]
|
||||
# Self explanatory
|
||||
# Checks user data
|
||||
# Does not retroactively apply to previously entered data
|
||||
max_displayname_size = 50
|
||||
max_bio_size = 160
|
||||
max_note_size = 5000
|
||||
|
|
@ -115,7 +151,7 @@ max_poll_option_size = 500
|
|||
min_poll_duration = 60
|
||||
max_poll_duration = 1893456000
|
||||
max_username_size = 30
|
||||
# An array of strings, defaults are from Akkoma
|
||||
# Forbidden usernames, defaults are from Akkoma
|
||||
username_blacklist = [
|
||||
".well-known",
|
||||
"~",
|
||||
|
|
@ -146,7 +182,7 @@ username_blacklist = [
|
|||
]
|
||||
# Whether to blacklist known temporary email providers
|
||||
blacklist_tempmail = false
|
||||
# Additional email providers to blacklist
|
||||
# Additional email providers to blacklist (list of domains)
|
||||
email_blacklist = []
|
||||
# Valid URL schemes, otherwise the URL is parsed as text
|
||||
url_scheme_whitelist = [
|
||||
|
|
@ -167,8 +203,10 @@ url_scheme_whitelist = [
|
|||
"mumble",
|
||||
"ssb",
|
||||
"gemini",
|
||||
] # NOT IMPLEMENTED
|
||||
|
||||
]
|
||||
# Only allow those MIME types of data to be uploaded
|
||||
# This can easily be spoofed, but if it is spoofed it will appear broken
|
||||
# to normal clients until despoofed
|
||||
enforce_mime_types = false
|
||||
allowed_mime_types = [
|
||||
"image/jpeg",
|
||||
|
|
@ -203,46 +241,38 @@ allowed_mime_types = [
|
|||
|
||||
[defaults]
|
||||
# Default visibility for new notes
|
||||
# Can be public, unlisted, private or direct
|
||||
# Private only sends to followers, unlisted doesn't show up in timelines
|
||||
visibility = "public"
|
||||
# Default language for new notes
|
||||
# Default language for new notes (ISO code)
|
||||
language = "en"
|
||||
# Default avatar, must be a valid URL or ""
|
||||
# Default avatar, must be a valid URL or "" for none
|
||||
avatar = ""
|
||||
# Default header, must be a valid URL or ""
|
||||
# Default header, must be a valid URL or "" for none
|
||||
header = ""
|
||||
|
||||
[activitypub]
|
||||
# Use ActivityPub Tombstones instead of deleting objects
|
||||
use_tombstones = true
|
||||
# Fetch all members of collections (followers, following, etc) when receiving them
|
||||
# WARNING: This can be a lot of data, and is not recommended
|
||||
fetch_all_collection_members = false # NOT IMPLEMENTED
|
||||
[federation]
|
||||
# This is a list of domain names, such as "mastodon.social" or "pleroma.site"
|
||||
# These changes will not retroactively apply to existing data before they were changed
|
||||
# For that, please use the CLI
|
||||
|
||||
# The following values must be instance domain names without "https" or glob patterns
|
||||
# Rejects all activities from these instances (fediblocking)
|
||||
reject_activities = []
|
||||
# Force posts from this instance to be followers only
|
||||
force_followers_only = [] # NOT IMPLEMENTED
|
||||
# Discard all reports from these instances
|
||||
discard_reports = [] # NOT IMPLEMENTED
|
||||
# Discard all deletes from these instances
|
||||
discard_deletes = []
|
||||
# Discard all updates (edits) from these instances
|
||||
discard_updates = []
|
||||
# Discard all banners from these instances
|
||||
discard_banners = [] # NOT IMPLEMENTED
|
||||
# Discard all avatars from these instances
|
||||
discard_avatars = [] # NOT IMPLEMENTED
|
||||
# Discard all follow requests from these instances
|
||||
discard_follows = []
|
||||
# Force set these instances' media as sensitive
|
||||
force_sensitive = [] # NOT IMPLEMENTED
|
||||
# Remove theses instances' media
|
||||
remove_media = [] # NOT IMPLEMENTED
|
||||
# These instances will not be federated with
|
||||
blocked = []
|
||||
# These instances' data will only be shown to followers, not in public timelines
|
||||
followers_only = []
|
||||
|
||||
# Whether to verify HTTP signatures for every request (warning: can slow down your server
|
||||
# significantly depending on processing power)
|
||||
authorized_fetch = false
|
||||
[federation.discard]
|
||||
# These objects will be discarded when received from these instances
|
||||
reports = []
|
||||
deletes = []
|
||||
updates = []
|
||||
media = []
|
||||
follows = []
|
||||
# If instance reactions are blocked, likes will also be discarded
|
||||
likes = []
|
||||
reactions = []
|
||||
banners = []
|
||||
avatars = []
|
||||
|
||||
[instance]
|
||||
name = "Lysand"
|
||||
|
|
@ -254,22 +284,23 @@ banner = ""
|
|||
|
||||
|
||||
[filters]
|
||||
# Drop notes with these regex filters (only applies to new activities)
|
||||
note_filters = [
|
||||
# Regex filters for federated and local data
|
||||
# Does not apply retroactively (try the CLI for that)
|
||||
|
||||
# Note contents
|
||||
note_content = [
|
||||
# "(https?://)?(www\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+",
|
||||
# "(https?://)?(www\\.)?youtu\\.be/[a-zA-Z0-9_-]+",
|
||||
]
|
||||
# Drop users with these regex filters (only applies to new activities)
|
||||
username_filters = []
|
||||
# Drop users with these regex filters (only applies to new activities)
|
||||
displayname_filters = []
|
||||
# Drop users with these regex filters (only applies to new activities)
|
||||
bio_filters = []
|
||||
emoji_filters = [] # NOT IMPLEMENTED
|
||||
emoji = []
|
||||
# These will drop users matching the filters
|
||||
username = []
|
||||
displayname = []
|
||||
bio = []
|
||||
|
||||
[logging]
|
||||
# Log all requests (warning: this is a lot of data)
|
||||
log_requests = true
|
||||
log_requests = false
|
||||
# Log request and their contents (warning: this is a lot of data)
|
||||
log_requests_verbose = false
|
||||
# For GDPR compliance, you can disable logging of IPs
|
||||
|
|
@ -278,12 +309,19 @@ log_ip = false
|
|||
# Log all filtered objects
|
||||
log_filters = true
|
||||
|
||||
[logging.storage]
|
||||
# Path to logfile for requests
|
||||
requests = "logs/requests.log"
|
||||
|
||||
[ratelimits]
|
||||
# These settings apply to every route at once
|
||||
# Amount to multiply every route's duration by
|
||||
duration_coeff = 1.0
|
||||
# Amount to multiply every route's max by
|
||||
# Amount to multiply every route's max requests per [duration] by
|
||||
max_coeff = 1.0
|
||||
|
||||
[custom_ratelimits]
|
||||
# Add in any API route in this style here
|
||||
# Applies before the global ratelimit changes
|
||||
"/api/v1/accounts/:id/block" = { duration = 30, max = 60 }
|
||||
"/api/v1/timelines/public" = { duration = 60, max = 200 }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// import { Queue } from "bullmq";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { ConfigManager } from "config-manager";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
import { config } from "config-manager";
|
||||
|
||||
const client = new PrismaClient({
|
||||
datasourceUrl: `postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}`,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ import type { Like } from "@prisma/client";
|
|||
import { client } from "~database/datasource";
|
||||
import type { UserWithRelations } from "./User";
|
||||
import type { StatusWithRelations } from "./Status";
|
||||
import { ConfigManager } from "config-manager";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
import { config } from "config-manager";
|
||||
|
||||
/**
|
||||
* Represents a Like entity in the database.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// import { Worker } from "bullmq";
|
||||
import { statusToLysand, type StatusWithRelations } from "./Status";
|
||||
import type { User } from "@prisma/client";
|
||||
import { ConfigManager } from "config-manager";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
import { config } from "config-manager";
|
||||
|
||||
/* export const federationWorker = new Worker(
|
||||
"federation",
|
||||
|
|
|
|||
|
|
@ -23,11 +23,9 @@ import { parse } from "marked";
|
|||
import linkifyStr from "linkify-string";
|
||||
import linkifyHtml from "linkify-html";
|
||||
import { addStausToMeilisearch } from "@meilisearch";
|
||||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import { statusAndUserRelations, userRelations } from "./relations";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
|
||||
const statusRelations = Prisma.validator<Prisma.StatusDefaultArgs>()({
|
||||
include: statusAndUserRelations,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { APIAccount } from "~types/entities/account";
|
||||
import type { LysandUser as LysandUser } from "~types/lysand/Object";
|
||||
import type { LysandUser } from "~types/lysand/Object";
|
||||
import { htmlToText } from "html-to-text";
|
||||
import type { User } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
|
|
@ -8,13 +8,10 @@ import { addEmojiIfNotExists, emojiToAPI, emojiToLysand } from "./Emoji";
|
|||
import { addInstanceIfNotExists } from "./Instance";
|
||||
import type { APISource } from "~types/entities/source";
|
||||
import { addUserToMeilisearch } from "@meilisearch";
|
||||
import { ConfigManager, type ConfigType } from "config-manager";
|
||||
import { config, type Config } from "config-manager";
|
||||
import { userRelations } from "./relations";
|
||||
import { MediaBackendType } from "~packages/media-manager";
|
||||
|
||||
const configManager = new ConfigManager({});
|
||||
const config = await configManager.getConfig();
|
||||
|
||||
export interface AuthData {
|
||||
user: UserWithRelations | null;
|
||||
token: string;
|
||||
|
|
@ -36,7 +33,7 @@ export type UserWithRelations = Prisma.UserGetPayload<typeof userRelations2>;
|
|||
* @param config The config to use
|
||||
* @returns The raw URL for the user's avatar
|
||||
*/
|
||||
export const getAvatarUrl = (user: User, config: ConfigType) => {
|
||||
export const getAvatarUrl = (user: User, config: Config) => {
|
||||
if (!user.avatar) return config.defaults.avatar;
|
||||
if (config.media.backend === MediaBackendType.LOCAL) {
|
||||
return `${config.http.base_url}/media/${user.avatar}`;
|
||||
|
|
@ -52,7 +49,7 @@ export const getAvatarUrl = (user: User, config: ConfigType) => {
|
|||
* @param config The config to use
|
||||
* @returns The raw URL for the user's header
|
||||
*/
|
||||
export const getHeaderUrl = (user: User, config: ConfigType) => {
|
||||
export const getHeaderUrl = (user: User, config: Config) => {
|
||||
if (!user.header) return config.defaults.header;
|
||||
if (config.media.backend === MediaBackendType.LOCAL) {
|
||||
return `${config.http.base_url}/media/${user.header}`;
|
||||
|
|
@ -192,8 +189,6 @@ export const createNewLocalUser = async (data: {
|
|||
header?: string;
|
||||
admin?: boolean;
|
||||
}) => {
|
||||
const config = await configManager.getConfig();
|
||||
|
||||
const keys = await generateUserKeys();
|
||||
|
||||
const user = await client.user.create({
|
||||
|
|
|
|||
9
index.ts
9
index.ts
|
|
@ -1,7 +1,7 @@
|
|||
import type { PrismaClientInitializationError } from "@prisma/client/runtime/library";
|
||||
import { initializeRedisCache } from "@redis";
|
||||
import { connectMeili } from "@meilisearch";
|
||||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import { client } from "~database/datasource";
|
||||
import { LogLevel, LogManager, MultiLogManager } from "log-manager";
|
||||
import { moduleIsEntry } from "@module";
|
||||
|
|
@ -10,9 +10,6 @@ import { exists, mkdir } from "fs/promises";
|
|||
|
||||
const timeAtStart = performance.now();
|
||||
|
||||
const configManager = new ConfigManager({});
|
||||
const config = await configManager.getConfig();
|
||||
|
||||
const requests_log = Bun.file(process.cwd() + "/logs/requests.log");
|
||||
const isEntry = moduleIsEntry(import.meta.url);
|
||||
// If imported as a module, redirect logs to /dev/null to not pollute console (e.g. in tests)
|
||||
|
|
@ -22,7 +19,7 @@ const consoleLogger = new LogManager(
|
|||
);
|
||||
const dualLogger = new MultiLogManager([logger, consoleLogger]);
|
||||
|
||||
if (!(await exists(process.cwd() + "/logs/"))) {
|
||||
if (!(await exists(config.logging.storage.requests))) {
|
||||
await consoleLogger.log(
|
||||
LogLevel.WARNING,
|
||||
"Lysand",
|
||||
|
|
@ -59,7 +56,7 @@ try {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const server = createServer(config, configManager, dualLogger, isProd);
|
||||
const server = createServer(config, dualLogger, isProd);
|
||||
|
||||
await dualLogger.log(
|
||||
LogLevel.INFO,
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@
|
|||
"@typescript-eslint/eslint-plugin": "latest",
|
||||
"@typescript-eslint/parser": "latest",
|
||||
"@unocss/cli": "latest",
|
||||
"@vitejs/plugin-vue": "latest",
|
||||
"@vueuse/head": "^2.0.0",
|
||||
"activitypub-types": "^1.0.3",
|
||||
"bun-types": "latest",
|
||||
"eslint": "^8.54.0",
|
||||
|
|
@ -71,8 +73,7 @@
|
|||
"prettier": "^3.1.0",
|
||||
"typescript": "latest",
|
||||
"unocss": "latest",
|
||||
"@vitejs/plugin-vue": "latest",
|
||||
"@vueuse/head": "^2.0.0",
|
||||
"untyped": "^1.4.2",
|
||||
"vite": "latest",
|
||||
"vite-ssr": "^0.17.1",
|
||||
"vue": "^3.3.9",
|
||||
|
|
@ -86,11 +87,12 @@
|
|||
"@aws-sdk/client-s3": "^3.461.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@json2csv/plainjs": "^7.0.6",
|
||||
"cli-parser": "workspace:*",
|
||||
"@prisma/client": "^5.6.0",
|
||||
"blurhash": "^2.0.5",
|
||||
"bullmq": "latest",
|
||||
"c12": "^1.10.0",
|
||||
"chalk": "^5.3.0",
|
||||
"cli-parser": "workspace:*",
|
||||
"cli-table": "^0.3.11",
|
||||
"config-manager": "workspace:*",
|
||||
"eventemitter3": "^5.0.1",
|
||||
|
|
|
|||
|
|
@ -1,377 +0,0 @@
|
|||
import { MediaBackendType } from "media-manager";
|
||||
|
||||
export interface ConfigType {
|
||||
database: {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
database: string;
|
||||
};
|
||||
|
||||
redis: {
|
||||
queue: {
|
||||
host: string;
|
||||
port: number;
|
||||
password: string;
|
||||
database: number | null;
|
||||
};
|
||||
cache: {
|
||||
host: string;
|
||||
port: number;
|
||||
password: string;
|
||||
database: number | null;
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
meilisearch: {
|
||||
host: string;
|
||||
port: number;
|
||||
api_key: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
signups: {
|
||||
tos_url: string;
|
||||
rules: string[];
|
||||
registration: boolean;
|
||||
};
|
||||
|
||||
oidc: {
|
||||
providers: {
|
||||
name: string;
|
||||
id: string;
|
||||
url: string;
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
icon: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
http: {
|
||||
base_url: string;
|
||||
bind: string;
|
||||
bind_port: string;
|
||||
banned_ips: string[];
|
||||
banned_user_agents: string[];
|
||||
bait: {
|
||||
enabled: boolean;
|
||||
send_file?: string;
|
||||
bait_ips: string[];
|
||||
bait_user_agents: string[];
|
||||
};
|
||||
};
|
||||
|
||||
instance: {
|
||||
name: string;
|
||||
description: string;
|
||||
banner: string;
|
||||
logo: string;
|
||||
};
|
||||
|
||||
smtp: {
|
||||
server: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
tls: boolean;
|
||||
};
|
||||
|
||||
validation: {
|
||||
max_displayname_size: number;
|
||||
max_bio_size: number;
|
||||
max_username_size: number;
|
||||
max_note_size: number;
|
||||
max_avatar_size: number;
|
||||
max_header_size: number;
|
||||
max_media_size: number;
|
||||
max_media_attachments: number;
|
||||
max_media_description_size: number;
|
||||
max_poll_options: number;
|
||||
max_poll_option_size: number;
|
||||
min_poll_duration: number;
|
||||
max_poll_duration: number;
|
||||
|
||||
username_blacklist: string[];
|
||||
blacklist_tempmail: boolean;
|
||||
email_blacklist: string[];
|
||||
url_scheme_whitelist: string[];
|
||||
|
||||
enforce_mime_types: boolean;
|
||||
allowed_mime_types: string[];
|
||||
};
|
||||
|
||||
media: {
|
||||
backend: MediaBackendType;
|
||||
deduplicate_media: boolean;
|
||||
conversion: {
|
||||
convert_images: boolean;
|
||||
convert_to: string;
|
||||
};
|
||||
local_uploads_folder: string;
|
||||
};
|
||||
|
||||
s3: {
|
||||
endpoint: string;
|
||||
access_key: string;
|
||||
secret_access_key: string;
|
||||
region: string;
|
||||
bucket_name: string;
|
||||
public_url: string;
|
||||
};
|
||||
|
||||
defaults: {
|
||||
visibility: string;
|
||||
language: string;
|
||||
avatar: string;
|
||||
header: string;
|
||||
};
|
||||
|
||||
email: {
|
||||
send_on_report: boolean;
|
||||
send_on_suspend: boolean;
|
||||
send_on_unsuspend: boolean;
|
||||
};
|
||||
|
||||
activitypub: {
|
||||
use_tombstones: boolean;
|
||||
reject_activities: string[];
|
||||
force_followers_only: string[];
|
||||
discard_reports: string[];
|
||||
discard_deletes: string[];
|
||||
discard_banners: string[];
|
||||
discard_avatars: string[];
|
||||
discard_updates: string[];
|
||||
discard_follows: string[];
|
||||
force_sensitive: string[];
|
||||
remove_media: string[];
|
||||
fetch_all_collection_members: boolean;
|
||||
authorized_fetch: boolean;
|
||||
};
|
||||
|
||||
filters: {
|
||||
note_filters: string[];
|
||||
username_filters: string[];
|
||||
displayname_filters: string[];
|
||||
bio_filters: string[];
|
||||
emoji_filters: string[];
|
||||
};
|
||||
|
||||
logging: {
|
||||
log_requests: boolean;
|
||||
log_requests_verbose: boolean;
|
||||
log_ip: boolean;
|
||||
log_filters: boolean;
|
||||
};
|
||||
|
||||
ratelimits: {
|
||||
duration_coeff: number;
|
||||
max_coeff: number;
|
||||
};
|
||||
|
||||
custom_ratelimits: Record<
|
||||
string,
|
||||
{
|
||||
duration: number;
|
||||
max: number;
|
||||
}
|
||||
>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const configDefaults: ConfigType = {
|
||||
http: {
|
||||
bind: "http://0.0.0.0",
|
||||
bind_port: "8000",
|
||||
base_url: "http://lysand.localhost:8000",
|
||||
banned_ips: [],
|
||||
banned_user_agents: [],
|
||||
bait: {
|
||||
enabled: false,
|
||||
send_file: "",
|
||||
bait_ips: [],
|
||||
bait_user_agents: [],
|
||||
},
|
||||
},
|
||||
database: {
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
username: "postgres",
|
||||
password: "postgres",
|
||||
database: "lysand",
|
||||
},
|
||||
redis: {
|
||||
queue: {
|
||||
host: "localhost",
|
||||
port: 6379,
|
||||
password: "",
|
||||
database: 0,
|
||||
},
|
||||
cache: {
|
||||
host: "localhost",
|
||||
port: 6379,
|
||||
password: "",
|
||||
database: 1,
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
meilisearch: {
|
||||
host: "localhost",
|
||||
port: 1491,
|
||||
api_key: "",
|
||||
enabled: false,
|
||||
},
|
||||
signups: {
|
||||
tos_url: "",
|
||||
rules: [],
|
||||
registration: false,
|
||||
},
|
||||
oidc: {
|
||||
providers: [],
|
||||
},
|
||||
instance: {
|
||||
banner: "",
|
||||
description: "",
|
||||
logo: "",
|
||||
name: "",
|
||||
},
|
||||
smtp: {
|
||||
password: "",
|
||||
port: 465,
|
||||
server: "",
|
||||
tls: true,
|
||||
username: "",
|
||||
},
|
||||
media: {
|
||||
backend: MediaBackendType.LOCAL,
|
||||
deduplicate_media: true,
|
||||
conversion: {
|
||||
convert_images: false,
|
||||
convert_to: "webp",
|
||||
},
|
||||
local_uploads_folder: "uploads",
|
||||
},
|
||||
email: {
|
||||
send_on_report: false,
|
||||
send_on_suspend: false,
|
||||
send_on_unsuspend: false,
|
||||
},
|
||||
s3: {
|
||||
access_key: "",
|
||||
bucket_name: "",
|
||||
endpoint: "",
|
||||
public_url: "",
|
||||
region: "",
|
||||
secret_access_key: "",
|
||||
},
|
||||
validation: {
|
||||
max_displayname_size: 50,
|
||||
max_bio_size: 6000,
|
||||
max_note_size: 5000,
|
||||
max_avatar_size: 5_000_000,
|
||||
max_header_size: 5_000_000,
|
||||
max_media_size: 40_000_000,
|
||||
max_media_attachments: 10,
|
||||
max_media_description_size: 1000,
|
||||
max_poll_options: 20,
|
||||
max_poll_option_size: 500,
|
||||
min_poll_duration: 60,
|
||||
max_poll_duration: 1893456000,
|
||||
max_username_size: 30,
|
||||
|
||||
username_blacklist: [
|
||||
".well-known",
|
||||
"~",
|
||||
"about",
|
||||
"activities",
|
||||
"api",
|
||||
"auth",
|
||||
"dev",
|
||||
"inbox",
|
||||
"internal",
|
||||
"main",
|
||||
"media",
|
||||
"nodeinfo",
|
||||
"notice",
|
||||
"oauth",
|
||||
"objects",
|
||||
"proxy",
|
||||
"push",
|
||||
"registration",
|
||||
"relay",
|
||||
"settings",
|
||||
"status",
|
||||
"tag",
|
||||
"users",
|
||||
"web",
|
||||
"search",
|
||||
"mfa",
|
||||
],
|
||||
|
||||
blacklist_tempmail: false,
|
||||
|
||||
email_blacklist: [],
|
||||
|
||||
url_scheme_whitelist: [
|
||||
"http",
|
||||
"https",
|
||||
"ftp",
|
||||
"dat",
|
||||
"dweb",
|
||||
"gopher",
|
||||
"hyper",
|
||||
"ipfs",
|
||||
"ipns",
|
||||
"irc",
|
||||
"xmpp",
|
||||
"ircs",
|
||||
"magnet",
|
||||
"mailto",
|
||||
"mumble",
|
||||
"ssb",
|
||||
],
|
||||
|
||||
enforce_mime_types: false,
|
||||
allowed_mime_types: [],
|
||||
},
|
||||
defaults: {
|
||||
visibility: "public",
|
||||
language: "en",
|
||||
avatar: "",
|
||||
header: "",
|
||||
},
|
||||
activitypub: {
|
||||
use_tombstones: true,
|
||||
reject_activities: [],
|
||||
force_followers_only: [],
|
||||
discard_reports: [],
|
||||
discard_deletes: [],
|
||||
discard_banners: [],
|
||||
discard_avatars: [],
|
||||
force_sensitive: [],
|
||||
discard_updates: [],
|
||||
discard_follows: [],
|
||||
remove_media: [],
|
||||
fetch_all_collection_members: false,
|
||||
authorized_fetch: false,
|
||||
},
|
||||
filters: {
|
||||
note_filters: [],
|
||||
username_filters: [],
|
||||
displayname_filters: [],
|
||||
bio_filters: [],
|
||||
emoji_filters: [],
|
||||
},
|
||||
logging: {
|
||||
log_requests: false,
|
||||
log_requests_verbose: false,
|
||||
log_ip: false,
|
||||
log_filters: true,
|
||||
},
|
||||
ratelimits: {
|
||||
duration_coeff: 1,
|
||||
max_coeff: 1,
|
||||
},
|
||||
custom_ratelimits: {},
|
||||
};
|
||||
579
packages/config-manager/config.type.ts
Normal file
579
packages/config-manager/config.type.ts
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
import { MediaBackendType } from "~packages/media-manager";
|
||||
|
||||
export interface Config {
|
||||
database: {
|
||||
/** @default "localhost" */
|
||||
host: string;
|
||||
|
||||
/** @default 5432 */
|
||||
port: number;
|
||||
|
||||
/** @default "lysand" */
|
||||
username: string;
|
||||
|
||||
/** @default "lysand" */
|
||||
password: string;
|
||||
|
||||
/** @default "lysand" */
|
||||
database: string;
|
||||
};
|
||||
|
||||
redis: {
|
||||
queue: {
|
||||
/** @default "localhost" */
|
||||
host: string;
|
||||
|
||||
/** @default 6379 */
|
||||
port: number;
|
||||
|
||||
/** @default "" */
|
||||
password: string;
|
||||
|
||||
/** @default 0 */
|
||||
database: number;
|
||||
};
|
||||
|
||||
cache: {
|
||||
/** @default "localhost" */
|
||||
host: string;
|
||||
|
||||
/** @default 6379 */
|
||||
port: number;
|
||||
|
||||
/** @default "" */
|
||||
password: string;
|
||||
|
||||
/** @default 1 */
|
||||
database: number;
|
||||
|
||||
/** @default false */
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
meilisearch: {
|
||||
/** @default "localhost" */
|
||||
host: string;
|
||||
|
||||
/** @default 7700 */
|
||||
port: number;
|
||||
|
||||
/** @default "______________________________" */
|
||||
api_key: string;
|
||||
|
||||
/** @default false */
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
signups: {
|
||||
/** @default "https://my-site.com/tos" */
|
||||
tos_url: string;
|
||||
|
||||
/** @default true */
|
||||
registration: boolean;
|
||||
|
||||
/** @default ["Do not harass others","Be nice to people","Don't spam","Don't post illegal content"] */
|
||||
rules: string[];
|
||||
};
|
||||
|
||||
oidc: {
|
||||
/** @default [] */
|
||||
providers: Record<string, any>[];
|
||||
};
|
||||
|
||||
http: {
|
||||
/** @default "https://lysand.social" */
|
||||
base_url: string;
|
||||
|
||||
/** @default "0.0.0.0" */
|
||||
bind: string;
|
||||
|
||||
/** @default "8080" */
|
||||
bind_port: string;
|
||||
|
||||
banned_ips: any[];
|
||||
|
||||
banned_user_agents: any[];
|
||||
|
||||
bait: {
|
||||
/** @default false */
|
||||
enabled: boolean;
|
||||
|
||||
/** @default "" */
|
||||
send_file: string;
|
||||
|
||||
/** @default ["127.0.0.1","::1"] */
|
||||
bait_ips: string[];
|
||||
|
||||
/** @default ["curl","wget"] */
|
||||
bait_user_agents: string[];
|
||||
};
|
||||
};
|
||||
|
||||
smtp: {
|
||||
/** @default "smtp.example.com" */
|
||||
server: string;
|
||||
|
||||
/** @default 465 */
|
||||
port: number;
|
||||
|
||||
/** @default "test@example.com" */
|
||||
username: string;
|
||||
|
||||
/** @default "____________" */
|
||||
password: string;
|
||||
|
||||
/** @default true */
|
||||
tls: boolean;
|
||||
|
||||
/** @default false */
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
media: {
|
||||
/** @default "local" */
|
||||
backend: MediaBackendType;
|
||||
|
||||
/** @default true */
|
||||
deduplicate_media: boolean;
|
||||
|
||||
/** @default "uploads" */
|
||||
local_uploads_folder: string;
|
||||
|
||||
conversion: {
|
||||
/** @default false */
|
||||
convert_images: boolean;
|
||||
|
||||
/** @default "webp" */
|
||||
convert_to: string;
|
||||
};
|
||||
};
|
||||
|
||||
s3: {
|
||||
/** @default "myhostname.banana.com" */
|
||||
endpoint: string;
|
||||
|
||||
/** @default "_____________" */
|
||||
access_key: string;
|
||||
|
||||
/** @default "_________________" */
|
||||
secret_access_key: string;
|
||||
|
||||
/** @default "" */
|
||||
region: string;
|
||||
|
||||
/** @default "lysand" */
|
||||
bucket_name: string;
|
||||
|
||||
/** @default "https://cdn.test.com" */
|
||||
public_url: string;
|
||||
};
|
||||
|
||||
email: {
|
||||
/** @default false */
|
||||
send_on_report: boolean;
|
||||
|
||||
/** @default false */
|
||||
send_on_suspend: boolean;
|
||||
|
||||
/** @default false */
|
||||
send_on_unsuspend: boolean;
|
||||
|
||||
/** @default false */
|
||||
verify_email: boolean;
|
||||
};
|
||||
|
||||
validation: {
|
||||
/** @default 50 */
|
||||
max_displayname_size: number;
|
||||
|
||||
/** @default 160 */
|
||||
max_bio_size: number;
|
||||
|
||||
/** @default 5000 */
|
||||
max_note_size: number;
|
||||
|
||||
/** @default 5000000 */
|
||||
max_avatar_size: number;
|
||||
|
||||
/** @default 5000000 */
|
||||
max_header_size: number;
|
||||
|
||||
/** @default 40000000 */
|
||||
max_media_size: number;
|
||||
|
||||
/** @default 10 */
|
||||
max_media_attachments: number;
|
||||
|
||||
/** @default 1000 */
|
||||
max_media_description_size: number;
|
||||
|
||||
/** @default 20 */
|
||||
max_poll_options: number;
|
||||
|
||||
/** @default 500 */
|
||||
max_poll_option_size: number;
|
||||
|
||||
/** @default 60 */
|
||||
min_poll_duration: number;
|
||||
|
||||
/** @default 1893456000 */
|
||||
max_poll_duration: number;
|
||||
|
||||
/** @default 30 */
|
||||
max_username_size: number;
|
||||
|
||||
/** @default [".well-known","~","about","activities","api","auth","dev","inbox","internal","main","media","nodeinfo","notice","oauth","objects","proxy","push","registration","relay","settings","status","tag","users","web","search","mfa"] */
|
||||
username_blacklist: string[];
|
||||
|
||||
/** @default false */
|
||||
blacklist_tempmail: boolean;
|
||||
|
||||
email_blacklist: any[];
|
||||
|
||||
/** @default ["http","https","ftp","dat","dweb","gopher","hyper","ipfs","ipns","irc","xmpp","ircs","magnet","mailto","mumble","ssb","gemini"] */
|
||||
url_scheme_whitelist: string[];
|
||||
|
||||
/** @default false */
|
||||
enforce_mime_types: boolean;
|
||||
|
||||
/** @default ["image/jpeg","image/png","image/gif","image/heic","image/heif","image/webp","image/avif","video/webm","video/mp4","video/quicktime","video/ogg","audio/wave","audio/wav","audio/x-wav","audio/x-pn-wave","audio/vnd.wave","audio/ogg","audio/vorbis","audio/mpeg","audio/mp3","audio/webm","audio/flac","audio/aac","audio/m4a","audio/x-m4a","audio/mp4","audio/3gpp","video/x-ms-asf"] */
|
||||
allowed_mime_types: string[];
|
||||
};
|
||||
|
||||
defaults: {
|
||||
/** @default "public" */
|
||||
visibility: string;
|
||||
|
||||
/** @default "en" */
|
||||
language: string;
|
||||
|
||||
/** @default "" */
|
||||
avatar: string;
|
||||
|
||||
/** @default "" */
|
||||
header: string;
|
||||
};
|
||||
|
||||
federation: {
|
||||
blocked: any[];
|
||||
|
||||
followers_only: any[];
|
||||
|
||||
discard: {
|
||||
reports: any[];
|
||||
|
||||
deletes: any[];
|
||||
|
||||
updates: any[];
|
||||
|
||||
media: any[];
|
||||
|
||||
follows: any[];
|
||||
|
||||
likes: any[];
|
||||
|
||||
reactions: any[];
|
||||
|
||||
banners: any[];
|
||||
|
||||
avatars: any[];
|
||||
};
|
||||
};
|
||||
|
||||
instance: {
|
||||
/** @default "Lysand" */
|
||||
name: string;
|
||||
|
||||
/** @default "A test instance of Lysand" */
|
||||
description: string;
|
||||
|
||||
/** @default "" */
|
||||
logo: string;
|
||||
|
||||
/** @default "" */
|
||||
banner: string;
|
||||
};
|
||||
|
||||
filters: {
|
||||
note_content: any[];
|
||||
|
||||
emoji: any[];
|
||||
|
||||
username: any[];
|
||||
|
||||
displayname: any[];
|
||||
|
||||
bio: any[];
|
||||
};
|
||||
|
||||
logging: {
|
||||
/** @default false */
|
||||
log_requests: boolean;
|
||||
|
||||
/** @default false */
|
||||
log_requests_verbose: boolean;
|
||||
|
||||
/** @default false */
|
||||
log_ip: boolean;
|
||||
|
||||
/** @default true */
|
||||
log_filters: boolean;
|
||||
|
||||
storage: {
|
||||
/** @default "logs/requests.log" */
|
||||
requests: string;
|
||||
};
|
||||
};
|
||||
|
||||
ratelimits: {
|
||||
/** @default 1 */
|
||||
duration_coeff: number;
|
||||
|
||||
/** @default 1 */
|
||||
max_coeff: number;
|
||||
};
|
||||
|
||||
/** @default {} */
|
||||
custom_ratelimits: Record<
|
||||
string,
|
||||
{
|
||||
/** @default 30 */
|
||||
duration: number;
|
||||
|
||||
/** @default 60 */
|
||||
max: number;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
export const defaultConfig: Config = {
|
||||
database: {
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
username: "lysand",
|
||||
password: "lysand",
|
||||
database: "lysand",
|
||||
},
|
||||
redis: {
|
||||
queue: {
|
||||
host: "localhost",
|
||||
port: 6379,
|
||||
password: "",
|
||||
database: 0,
|
||||
},
|
||||
cache: {
|
||||
host: "localhost",
|
||||
port: 6379,
|
||||
password: "",
|
||||
database: 1,
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
meilisearch: {
|
||||
host: "localhost",
|
||||
port: 7700,
|
||||
api_key: "______________________________",
|
||||
enabled: false,
|
||||
},
|
||||
signups: {
|
||||
tos_url: "https://my-site.com/tos",
|
||||
registration: true,
|
||||
rules: [
|
||||
"Do not harass others",
|
||||
"Be nice to people",
|
||||
"Don't spam",
|
||||
"Don't post illegal content",
|
||||
],
|
||||
},
|
||||
oidc: {
|
||||
providers: [[]],
|
||||
},
|
||||
http: {
|
||||
base_url: "https://lysand.social",
|
||||
bind: "0.0.0.0",
|
||||
bind_port: "8080",
|
||||
banned_ips: [],
|
||||
banned_user_agents: [],
|
||||
bait: {
|
||||
enabled: false,
|
||||
send_file: "",
|
||||
bait_ips: ["127.0.0.1", "::1"],
|
||||
bait_user_agents: ["curl", "wget"],
|
||||
},
|
||||
},
|
||||
smtp: {
|
||||
server: "smtp.example.com",
|
||||
port: 465,
|
||||
username: "test@example.com",
|
||||
password: "____________",
|
||||
tls: true,
|
||||
enabled: false,
|
||||
},
|
||||
media: {
|
||||
backend: MediaBackendType.LOCAL,
|
||||
deduplicate_media: true,
|
||||
local_uploads_folder: "uploads",
|
||||
conversion: {
|
||||
convert_images: false,
|
||||
convert_to: "webp",
|
||||
},
|
||||
},
|
||||
s3: {
|
||||
endpoint: "myhostname.banana.com",
|
||||
access_key: "_____________",
|
||||
secret_access_key: "_________________",
|
||||
region: "",
|
||||
bucket_name: "lysand",
|
||||
public_url: "https://cdn.test.com",
|
||||
},
|
||||
email: {
|
||||
send_on_report: false,
|
||||
send_on_suspend: false,
|
||||
send_on_unsuspend: false,
|
||||
verify_email: false,
|
||||
},
|
||||
validation: {
|
||||
max_displayname_size: 50,
|
||||
max_bio_size: 160,
|
||||
max_note_size: 5000,
|
||||
max_avatar_size: 5000000,
|
||||
max_header_size: 5000000,
|
||||
max_media_size: 40000000,
|
||||
max_media_attachments: 10,
|
||||
max_media_description_size: 1000,
|
||||
max_poll_options: 20,
|
||||
max_poll_option_size: 500,
|
||||
min_poll_duration: 60,
|
||||
max_poll_duration: 1893456000,
|
||||
max_username_size: 30,
|
||||
username_blacklist: [
|
||||
".well-known",
|
||||
"~",
|
||||
"about",
|
||||
"activities",
|
||||
"api",
|
||||
"auth",
|
||||
"dev",
|
||||
"inbox",
|
||||
"internal",
|
||||
"main",
|
||||
"media",
|
||||
"nodeinfo",
|
||||
"notice",
|
||||
"oauth",
|
||||
"objects",
|
||||
"proxy",
|
||||
"push",
|
||||
"registration",
|
||||
"relay",
|
||||
"settings",
|
||||
"status",
|
||||
"tag",
|
||||
"users",
|
||||
"web",
|
||||
"search",
|
||||
"mfa",
|
||||
],
|
||||
blacklist_tempmail: false,
|
||||
email_blacklist: [],
|
||||
url_scheme_whitelist: [
|
||||
"http",
|
||||
"https",
|
||||
"ftp",
|
||||
"dat",
|
||||
"dweb",
|
||||
"gopher",
|
||||
"hyper",
|
||||
"ipfs",
|
||||
"ipns",
|
||||
"irc",
|
||||
"xmpp",
|
||||
"ircs",
|
||||
"magnet",
|
||||
"mailto",
|
||||
"mumble",
|
||||
"ssb",
|
||||
"gemini",
|
||||
],
|
||||
enforce_mime_types: false,
|
||||
allowed_mime_types: [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/heic",
|
||||
"image/heif",
|
||||
"image/webp",
|
||||
"image/avif",
|
||||
"video/webm",
|
||||
"video/mp4",
|
||||
"video/quicktime",
|
||||
"video/ogg",
|
||||
"audio/wave",
|
||||
"audio/wav",
|
||||
"audio/x-wav",
|
||||
"audio/x-pn-wave",
|
||||
"audio/vnd.wave",
|
||||
"audio/ogg",
|
||||
"audio/vorbis",
|
||||
"audio/mpeg",
|
||||
"audio/mp3",
|
||||
"audio/webm",
|
||||
"audio/flac",
|
||||
"audio/aac",
|
||||
"audio/m4a",
|
||||
"audio/x-m4a",
|
||||
"audio/mp4",
|
||||
"audio/3gpp",
|
||||
"video/x-ms-asf",
|
||||
],
|
||||
},
|
||||
defaults: {
|
||||
visibility: "public",
|
||||
language: "en",
|
||||
avatar: "",
|
||||
header: "",
|
||||
},
|
||||
federation: {
|
||||
blocked: [],
|
||||
followers_only: [],
|
||||
discard: {
|
||||
reports: [],
|
||||
deletes: [],
|
||||
updates: [],
|
||||
media: [],
|
||||
follows: [],
|
||||
likes: [],
|
||||
reactions: [],
|
||||
banners: [],
|
||||
avatars: [],
|
||||
},
|
||||
},
|
||||
instance: {
|
||||
name: "Lysand",
|
||||
description: "A test instance of Lysand",
|
||||
logo: "",
|
||||
banner: "",
|
||||
},
|
||||
filters: {
|
||||
note_content: [],
|
||||
emoji: [],
|
||||
username: [],
|
||||
displayname: [],
|
||||
bio: [],
|
||||
},
|
||||
logging: {
|
||||
log_requests: false,
|
||||
log_requests_verbose: false,
|
||||
log_ip: false,
|
||||
log_filters: true,
|
||||
storage: {
|
||||
requests: "logs/requests.log",
|
||||
},
|
||||
},
|
||||
ratelimits: {
|
||||
duration_coeff: 1,
|
||||
max_coeff: 1,
|
||||
},
|
||||
custom_ratelimits: {},
|
||||
};
|
||||
|
|
@ -5,122 +5,22 @@
|
|||
* Fuses both and provides a way to retrieve individual values
|
||||
*/
|
||||
|
||||
import { parse, stringify, type JsonMap } from "@iarna/toml";
|
||||
import type { ConfigType } from "./config-type.type";
|
||||
import { configDefaults } from "./config-type.type";
|
||||
import merge from "merge-deep-ts";
|
||||
import { watchConfig } from "c12";
|
||||
import { defaultConfig, type Config } from "./config.type";
|
||||
|
||||
export class ConfigManager {
|
||||
constructor(
|
||||
public config: {
|
||||
configPathOverride?: string;
|
||||
internalConfigPathOverride?: string;
|
||||
}
|
||||
) {}
|
||||
const { config } = await watchConfig<Config>({
|
||||
configFile: "./config/config.toml",
|
||||
defaultConfig: defaultConfig,
|
||||
overrides:
|
||||
(
|
||||
await watchConfig<Config>({
|
||||
configFile: "./config/config.internal.toml",
|
||||
defaultConfig: {} as Config,
|
||||
})
|
||||
).config ?? undefined,
|
||||
});
|
||||
|
||||
/**
|
||||
* @summary Reads the config files and returns the merge as a JSON object
|
||||
* @returns {Promise<T = ConfigType>} The merged config file as a JSON object
|
||||
*/
|
||||
async getConfig<T = ConfigType>() {
|
||||
const config = await this.readConfig<T>();
|
||||
const internalConfig = await this.readInternalConfig<T>();
|
||||
const exportedConfig = config ?? defaultConfig;
|
||||
|
||||
return this.mergeConfigs<T>(
|
||||
configDefaults as T,
|
||||
config,
|
||||
internalConfig
|
||||
);
|
||||
}
|
||||
|
||||
getConfigPath() {
|
||||
return (
|
||||
this.config.configPathOverride ||
|
||||
process.cwd() + "/config/config.toml"
|
||||
);
|
||||
}
|
||||
|
||||
getInternalConfigPath() {
|
||||
return (
|
||||
this.config.internalConfigPathOverride ||
|
||||
process.cwd() + "/config/config.internal.toml"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Reads the internal config file and returns it as a JSON object
|
||||
* @returns {Promise<T = ConfigType>} The internal config file as a JSON object
|
||||
*/
|
||||
private async readInternalConfig<T = ConfigType>() {
|
||||
const config = Bun.file(this.getInternalConfigPath());
|
||||
|
||||
if (!(await config.exists())) {
|
||||
await Bun.write(config, "");
|
||||
}
|
||||
|
||||
return this.parseConfig<T>(await config.text());
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Reads the config file and returns it as a JSON object
|
||||
* @returns {Promise<T = ConfigType>} The config file as a JSON object
|
||||
*/
|
||||
private async readConfig<T = ConfigType>() {
|
||||
const config = Bun.file(this.getConfigPath());
|
||||
|
||||
if (!(await config.exists())) {
|
||||
throw new Error(
|
||||
`Error while reading config at path ${this.getConfigPath()}: Config file not found`
|
||||
);
|
||||
}
|
||||
|
||||
return this.parseConfig<T>(await config.text());
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Parses a TOML string and returns it as a JSON object
|
||||
* @param text The TOML string to parse
|
||||
* @returns {T = ConfigType} The parsed TOML string as a JSON object
|
||||
* @throws {Error} If the TOML string is invalid
|
||||
* @private
|
||||
*/
|
||||
private parseConfig<T = ConfigType>(text: string) {
|
||||
try {
|
||||
// To all [Symbol] keys from the object
|
||||
return JSON.parse(JSON.stringify(parse(text))) as T;
|
||||
} catch (e: any) {
|
||||
throw new Error(
|
||||
`Error while parsing config at path ${this.getConfigPath()}: ${e}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes changed values to the internal config
|
||||
* @param config The new config object
|
||||
*/
|
||||
async writeConfig<T = ConfigType>(config: T) {
|
||||
const path = this.getInternalConfigPath();
|
||||
const file = Bun.file(path);
|
||||
|
||||
await Bun.write(
|
||||
file,
|
||||
`# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT IT MANUALLY, EDIT THE STANDARD CONFIG.TOML INSTEAD.\n${stringify(
|
||||
config as JsonMap
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Merges two config objects together, with
|
||||
* the latter configs' values taking precedence
|
||||
* @param configs
|
||||
* @returns
|
||||
*/
|
||||
private mergeConfigs<T = ConfigType>(...configs: T[]) {
|
||||
return merge(configs) as T;
|
||||
}
|
||||
}
|
||||
|
||||
export type { ConfigType };
|
||||
export const defaultConfig = configDefaults;
|
||||
export { exportedConfig as config };
|
||||
export type { Config };
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
// FILEPATH: /home/jessew/Dev/lysand/packages/config-manager/config-manager.test.ts
|
||||
import { stringify } from "@iarna/toml";
|
||||
import { ConfigManager } from "..";
|
||||
import { describe, beforeEach, spyOn, it, expect } from "bun:test";
|
||||
|
||||
describe("ConfigManager", () => {
|
||||
let configManager: ConfigManager;
|
||||
|
||||
beforeEach(() => {
|
||||
configManager = new ConfigManager({
|
||||
configPathOverride: "./config/config.toml",
|
||||
internalConfigPathOverride: "./config/config.internal.toml",
|
||||
});
|
||||
});
|
||||
|
||||
it("should get the correct config path", () => {
|
||||
expect(configManager.getConfigPath()).toEqual("./config/config.toml");
|
||||
});
|
||||
|
||||
it("should get the correct internal config path", () => {
|
||||
expect(configManager.getInternalConfigPath()).toEqual(
|
||||
"./config/config.internal.toml"
|
||||
);
|
||||
});
|
||||
|
||||
it("should read the config file correctly", async () => {
|
||||
const mockConfig = { key: "value" };
|
||||
|
||||
// @ts-expect-error This is a mock
|
||||
spyOn(Bun, "file").mockImplementationOnce(() => ({
|
||||
exists: () =>
|
||||
new Promise(resolve => {
|
||||
resolve(true);
|
||||
}),
|
||||
text: () =>
|
||||
new Promise(resolve => {
|
||||
resolve(stringify(mockConfig));
|
||||
}),
|
||||
}));
|
||||
|
||||
const config = await configManager.getConfig<typeof mockConfig>();
|
||||
|
||||
expect(config).toContainKeys(Object.keys(mockConfig));
|
||||
});
|
||||
|
||||
it("should read the internal config file correctly", async () => {
|
||||
const mockConfig = { key: "value" };
|
||||
|
||||
// @ts-expect-error This is a mock
|
||||
spyOn(Bun, "file").mockImplementationOnce(() => ({
|
||||
exists: () =>
|
||||
new Promise(resolve => {
|
||||
resolve(true);
|
||||
}),
|
||||
text: () =>
|
||||
new Promise(resolve => {
|
||||
resolve(stringify(mockConfig));
|
||||
}),
|
||||
}));
|
||||
|
||||
const config =
|
||||
// @ts-expect-error Force call private function for testing
|
||||
await configManager.readInternalConfig<typeof mockConfig>();
|
||||
|
||||
expect(config).toEqual(mockConfig);
|
||||
});
|
||||
|
||||
it("should write to the internal config file correctly", async () => {
|
||||
const mockConfig = { key: "value" };
|
||||
|
||||
spyOn(Bun, "write").mockImplementationOnce(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolve(10);
|
||||
})
|
||||
);
|
||||
|
||||
await configManager.writeConfig(mockConfig);
|
||||
});
|
||||
|
||||
it("should merge configs correctly", () => {
|
||||
const config1 = { key1: "value1", key2: "value2" };
|
||||
const config2 = { key2: "newValue2", key3: "value3" };
|
||||
// @ts-expect-error Force call private function for testing
|
||||
const mergedConfig = configManager.mergeConfigs<Record<string, string>>(
|
||||
config1,
|
||||
config2
|
||||
);
|
||||
|
||||
expect(mergedConfig).toEqual({
|
||||
key1: "value1",
|
||||
key2: "newValue2",
|
||||
key3: "value3",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
|
||||
// Proxies all `bunx prisma` commands with an environment variable
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
|
||||
process.stdout.write(
|
||||
`postgresql://${config.database.username}:${config.database.password}@${config.database.host}:${config.database.port}/${config.database.database}\n`
|
||||
|
|
|
|||
10
server.ts
10
server.ts
|
|
@ -1,15 +1,14 @@
|
|||
import { errorResponse, jsonResponse } from "@response";
|
||||
import { matches } from "ip-matching";
|
||||
import { getFromRequest } from "~database/entities/User";
|
||||
import type { ConfigManager, ConfigType } from "config-manager";
|
||||
import { type Config } from "config-manager";
|
||||
import type { LogManager, MultiLogManager } from "log-manager";
|
||||
import { LogLevel } from "log-manager";
|
||||
import { RequestParser } from "request-parser";
|
||||
import { matchRoute } from "~routes";
|
||||
|
||||
export const createServer = (
|
||||
config: ConfigType,
|
||||
configManager: ConfigManager,
|
||||
config: Config,
|
||||
logger: LogManager | MultiLogManager,
|
||||
isProd: boolean
|
||||
) =>
|
||||
|
|
@ -182,8 +181,11 @@ export const createServer = (
|
|||
|
||||
return await file.default(req.clone(), matchedRoute, {
|
||||
auth,
|
||||
configManager,
|
||||
parsedRequest,
|
||||
// To avoid having to rewrite each route
|
||||
configManager: {
|
||||
getConfig: () => Promise.resolve(config),
|
||||
},
|
||||
});
|
||||
} else if (matchedRoute?.name === "/[...404]" || !matchedRoute) {
|
||||
if (new URL(req.url).pathname.startsWith("/api")) {
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default apiRoute<{
|
|||
|
||||
// Check if display name doesnt match filters
|
||||
if (
|
||||
config.filters.displayname_filters.some(filter =>
|
||||
config.filters.displayname.some(filter =>
|
||||
sanitizedDisplayName.match(filter)
|
||||
)
|
||||
) {
|
||||
|
|
@ -126,11 +126,7 @@ export default apiRoute<{
|
|||
}
|
||||
|
||||
// Check if bio doesnt match filters
|
||||
if (
|
||||
config.filters.bio_filters.some(filter =>
|
||||
sanitizedNote.match(filter)
|
||||
)
|
||||
) {
|
||||
if (config.filters.bio.some(filter => sanitizedNote.match(filter))) {
|
||||
return errorResponse("Bio contains blocked words", 422);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export default apiRoute<{
|
|||
content_type,
|
||||
"poll[expires_in]": expires_in,
|
||||
"poll[options]": options,
|
||||
media_ids: media_ids,
|
||||
media_ids,
|
||||
spoiler_text,
|
||||
sensitive,
|
||||
} = extraData.parsedRequest;
|
||||
|
|
@ -181,7 +181,7 @@ export default apiRoute<{
|
|||
|
||||
// Check if status body doesnt match filters
|
||||
if (
|
||||
config.filters.note_filters.some(filter =>
|
||||
config.filters.note_content.some(filter =>
|
||||
statusText?.match(filter)
|
||||
)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export default apiRoute<{
|
|||
}
|
||||
|
||||
// Check if status body doesnt match filters
|
||||
if (config.filters.note_filters.some(filter => status?.match(filter))) {
|
||||
if (config.filters.note_content.some(filter => status?.match(filter))) {
|
||||
return errorResponse("Status contains blocked words", 422);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { MatchedRoute } from "bun";
|
||||
import type { ConfigManager } from "config-manager";
|
||||
import type { Config } from "config-manager";
|
||||
import type { AuthData } from "~database/entities/User";
|
||||
|
||||
export type RouteHandler<T> = (
|
||||
|
|
@ -8,6 +8,8 @@ export type RouteHandler<T> = (
|
|||
extraData: {
|
||||
auth: AuthData;
|
||||
parsedRequest: Partial<T>;
|
||||
configManager: ConfigManager;
|
||||
configManager: {
|
||||
getConfig: () => Promise<Config>;
|
||||
};
|
||||
}
|
||||
) => Response | Promise<Response>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Token } from "@prisma/client";
|
||||
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
||||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import { client } from "~database/datasource";
|
||||
import { TokenType } from "~database/entities/Token";
|
||||
import {
|
||||
|
|
@ -11,7 +11,6 @@ import type { APIEmoji } from "~types/entities/emoji";
|
|||
import type { APIInstance } from "~types/entities/instance";
|
||||
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
const base_url = config.http.base_url;
|
||||
|
||||
let token: Token;
|
||||
|
|
|
|||
|
|
@ -9,10 +9,9 @@ import {
|
|||
import type { APIAccount } from "~types/entities/account";
|
||||
import type { APIRelationship } from "~types/entities/relationship";
|
||||
import type { APIStatus } from "~types/entities/status";
|
||||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import { sendTestRequest, wrapRelativeUrl } from "~tests/utils";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
const base_url = config.http.base_url;
|
||||
|
||||
let token: Token;
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ import type { APIAccount } from "~types/entities/account";
|
|||
import type { APIAsyncAttachment } from "~types/entities/async_attachment";
|
||||
import type { APIContext } from "~types/entities/context";
|
||||
import type { APIStatus } from "~types/entities/status";
|
||||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import { sendTestRequest, wrapRelativeUrl } from "~tests/utils";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
const base_url = config.http.base_url;
|
||||
|
||||
let token: Token;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { client } from "~database/datasource";
|
|||
import { createNewLocalUser } from "~database/entities/User";
|
||||
import { sendTestRequest, wrapRelativeUrl } from "./utils";
|
||||
|
||||
// const config = await new ConfigManager({}).getConfig();
|
||||
const base_url = "http://lysand.localhost:8080"; //config.http.base_url;
|
||||
|
||||
let client_id: string;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import type { RouteHandler } from "~server/api/routes.type";
|
||||
import type { APIRouteMeta } from "~types/api";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
|
||||
export const applyConfig = (routeMeta: APIRouteMeta) => {
|
||||
const newMeta = routeMeta;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { ConfigManager } from "config-manager";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
import { config } from "config-manager";
|
||||
|
||||
export const oauthRedirectUri = (issuer: string) =>
|
||||
`${config.http.base_url}/oauth/callback/${issuer}`;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@ import chalk from "chalk";
|
|||
import { client } from "~database/datasource";
|
||||
import { Meilisearch } from "meilisearch";
|
||||
import type { Status, User } from "@prisma/client";
|
||||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import { LogLevel, type LogManager, type MultiLogManager } from "log-manager";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
|
||||
export const meilisearch = new Meilisearch({
|
||||
host: `${config.meilisearch.host}:${config.meilisearch.port}`,
|
||||
apiKey: config.meilisearch.api_key,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
import type { Prisma } from "@prisma/client";
|
||||
import chalk from "chalk";
|
||||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import Redis from "ioredis";
|
||||
import { createPrismaRedisCache } from "prisma-redis-middleware";
|
||||
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
|
||||
const cacheRedis = config.redis.cache.enabled
|
||||
? new Redis({
|
||||
host: config.redis.cache.host,
|
||||
port: Number(config.redis.cache.port),
|
||||
password: config.redis.cache.password,
|
||||
db: Number(config.redis.cache.database ?? 0),
|
||||
db: Number(config.redis.cache.database),
|
||||
})
|
||||
: null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { ConfigManager } from "config-manager";
|
||||
import { config } from "config-manager";
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
|
||||
export const sanitizeHtml = async (html: string) => {
|
||||
const config = await new ConfigManager({}).getConfig();
|
||||
|
||||
const sanitizedHtml = sanitize(html, {
|
||||
ALLOWED_TAGS: [
|
||||
"a",
|
||||
|
|
|
|||
Loading…
Reference in a new issue