Merge pull request #382 from jqshuv/main

feat:  added docker support
This commit is contained in:
Axel Olausson Holtenäs 2022-08-24 15:28:10 +02:00 committed by GitHub
commit 9914b3a4d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 236 additions and 106 deletions

6
.dockerignore Normal file
View file

@ -0,0 +1,6 @@
.vscode
.husky
.github
.cspell
.env
node_modules

30
.env.example Normal file
View file

@ -0,0 +1,30 @@
# THIS FILE SHOULD BE INSIDE OF build/
# Discord
DISCORD_TOKEN=""
DISCORD_CLIENT_ID=""
DISCORD_GUILD_ID=""
# Database
MONGO_URL="mongodb+srv://username:password@host/database?retryWrites=true&w=majority"
# Encryption
ENCRYPTION_ALGORITHM="aes-256-ctr"
ENCRYPTION_SECRET="A RANDOM STRING WITH LENGTH OF 32"
#Embed
EMEBD_COLOR_SUCCESS="#22bb33"
EMBED_COLOR_WAIT="#f0ad4e"
EMBED_COLOR_ERROR="#bb2124"
EMBED_FOOTER_TEXT="https://github.com/ZynerOrg/xyter"
EMBED_FOOTER_ICON="https://github.com/ZynerOrg.png"
# Log
LOG_LEVEL="silly"
# Reputation
REPUTATION_TIMEOUT="86400"
# Bot Hoster
BOT_HOSTER_NAME="Zyner"
BOT_HOSTER_URL="https://xyter.zyner.org/customization/change-hoster"

5
.gitignore vendored
View file

@ -140,3 +140,8 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# Docker
database
docker-compose.local.yml

View file

@ -22,5 +22,8 @@
"addWords": true,
"scope": "workspace"
}
},
"[dotenv]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
}
}

19
Dockerfile Normal file
View file

@ -0,0 +1,19 @@
FROM node:16
LABEL maintainer="xyter@zyner.org"
WORKDIR /build
COPY package* .
RUN npm install
COPY . .
RUN npx -y tsc
WORKDIR /app
RUN cp -r /build/build/* .
RUN cp -r /build/node_modules .
CMD [ "node", "." ]

37
docker-compose.dev.yml Normal file
View file

@ -0,0 +1,37 @@
version: "3"
services:
app:
depends_on:
- mongodb
build: .
restart: unless-stopped
environment:
- DISCORD_TOKEN=DISCORD_TOKEN
- DISCORD_CLIENT_ID=DISCORD_CLIENT_ID
- DISCORD_GUILD_ID=DISCORD_GUILD_ID
- MONGO_URL=mongodb://MONGO_USER:MONGO_PASS@mongodb:27017/admin?retryWrites=true&w=majority
- ENCRYPTION_ALGORITHM=ENCRYPTION_ALGORITHM
- ENCRYPTION_SECRET=ENCRYPTION_SECRET
- EMEBD_COLOR_SUCCESS=EMEBD_COLOR_SUCCESS
- EMBED_COLOR_WAIT=EMBED_COLOR_WAIT
- EMBED_COLOR_ERROR=EMBED_COLOR_ERROR
- EMBED_FOOTER_TEXT=EMBED_FOOTER_TEXT
- EMBED_FOOTER_ICON=EMBED_FOOTER_ICON
- LOG_LEVEL=LOG_LEVEL
- REPUTATION_TIMEOUT=REPUTATION_TIMEOUT
- BOT_HOSTER_NAME=BOT_HOSTER_NAME
- BOT_HOSTER_URL=BOT_HOSTER_URL
- NODE_ENV=development
stdin_open: true
tty: true
volumes:
- ./logs:/app/logs
mongodb:
image: mongo:latest
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: MONGO_USER
MONGO_INITDB_ROOT_PASSWORD: MONGO_PASS
volumes:
- ./database:/data/db

37
docker-compose.yml Normal file
View file

@ -0,0 +1,37 @@
version: "3"
services:
app:
depends_on:
- mongodb
image: zyner/xyter:latest
restart: unless-stopped
environment:
- DISCORD_TOKEN=DISCORD_TOKEN
- DISCORD_CLIENT_ID=DISCORD_CLIENT_ID
- DISCORD_GUILD_ID=DISCORD_GUILD_ID
- MONGO_URL=mongodb://MONGO_USER:MONGO_PASS@mongodb:27017/admin?retryWrites=true&w=majority
- ENCRYPTION_ALGORITHM=ENCRYPTION_ALGORITHM
- ENCRYPTION_SECRET=ENCRYPTION_SECRET
- EMEBD_COLOR_SUCCESS=EMEBD_COLOR_SUCCESS
- EMBED_COLOR_WAIT=EMBED_COLOR_WAIT
- EMBED_COLOR_ERROR=EMBED_COLOR_ERROR
- EMBED_FOOTER_TEXT=EMBED_FOOTER_TEXT
- EMBED_FOOTER_ICON=EMBED_FOOTER_ICON
- LOG_LEVEL=LOG_LEVEL
- REPUTATION_TIMEOUT=REPUTATION_TIMEOUT
- BOT_HOSTER_NAME=BOT_HOSTER_NAME
- BOT_HOSTER_URL=BOT_HOSTER_URL
- NODE_ENV=production
stdin_open: true
tty: true
volumes:
- ./logs:/app/logs
mongodb:
image: mongo:latest
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: MONGO_USER
MONGO_INITDB_ROOT_PASSWORD: MONGO_PASS
volumes:
- ./database:/data/db

View file

@ -21,6 +21,9 @@
"url": "https://github.com/ZynerOrg/xyter.git"
},
"author": "Vermium Sifell <vermium@zyner.org> (https://zyner.org)",
"contributors": [
"Joshua Schmitt <me@jqshuv.xyz> (https://jqshuv.xyz)"
],
"license": "GPL-3.0-only",
"bugs": {
"url": "https://github.com/ZynerOrg/xyter/issues",
@ -34,6 +37,7 @@
"chance": "^1.1.8",
"common": "^0.2.5",
"crypto": "^1.0.1",
"dotenv": "^16.0.1",
"discord-api-types": "^0.37.0",
"discord.js": "^14.0.0",
"i18n": "^0.15.0",

View file

@ -1,3 +0,0 @@
// MongoDB connection string
export const url =
"mongodb+srv://username:password@server/database?retryWrites=true&w=majority";

View file

@ -1,14 +0,0 @@
import { Intents } from "discord.js"; // discord.js
// Discord API token
export const token = "";
// Discord API id
export const clientId = "";
// Discord API intents
export const intents = [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MEMBERS,
];

View file

@ -1,17 +0,0 @@
// Dependencies
import { ColorResolvable } from "discord.js";
// Color for successfully actions
export const successColor: ColorResolvable = "#22bb33";
// Color for waiting actions
export const waitColor: ColorResolvable = "#f0ad4e";
// Color for error actions
export const errorColor: ColorResolvable = "#bb2124";
// Footer text
export const footerText = "https://github.com/ZynerOrg/xyter";
// Footer icon
export const footerIcon = "https://github.com/ZynerOrg.png";

View file

@ -1,5 +0,0 @@
// Encryption algorithm
export const algorithm = "aes-256-ctr";
// Encryption secret (strictly 32 length)
export const secretKey = "";

View file

@ -1,14 +0,0 @@
// Development features
export const devMode = false;
// Development guild
export const guildId = "";
// Hoster name
export const hosterName = "someone";
// Hoster Url
export const hosterUrl = "https://xyter.zyner.org/customization/change-hoster";
// Winston log level
export const logLevel = "info";

View file

@ -1,2 +0,0 @@
// Timeout between repute someone (seconds)
export const timeout = 86400; // One day

View file

@ -1,12 +1,8 @@
import { token, clientId } from "../../config/discord";
import { devMode, guildId } from "../../config/other";
import logger from "../../logger";
import { Client } from "discord.js";
import { REST } from "@discordjs/rest";
import { Routes } from "discord-api-types/v9";
import { RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10";
import { ICommand } from "../../interfaces/Command";
export default async (client: Client) => {
@ -32,10 +28,10 @@ export default async (client: Client) => {
throw new Error(`Could not gather command list: ${error}`);
});
const rest = new REST({ version: "9" }).setToken(token);
const rest = new REST({ version: "9" }).setToken(process.env.DISCORD_TOKEN);
await rest
.put(Routes.applicationCommands(clientId), {
.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), {
body: commandList,
})
.then(async () => {
@ -45,11 +41,17 @@ export default async (client: Client) => {
logger.error(`${error}`);
});
if (devMode) {
if (process.env.NODE_ENV !== "production") {
await rest
.put(Routes.applicationGuildCommands(clientId, guildId), {
body: commandList,
})
.put(
Routes.applicationGuildCommands(
process.env.DISCORD_CLIENT_ID,
process.env.DISCORD_GUILD_ID
),
{
body: commandList,
}
)
.then(async () => logger.info(`Finished updating guild command list.`))
.catch(async (error) => {
logger.error(`${error}`);

View file

@ -1,16 +1,13 @@
// Dependencies
import { Client } from "discord.js";
import logger from "../../logger";
// Configuration
import { devMode, guildId } from "../../config/other";
export default async (client: Client) => {
if (!devMode) {
return client?.application?.commands?.set([], guildId).then(async () => {
return logger.verbose(`Development mode is disabled.`);
});
if (process.env.NODE_ENV !== "production") {
return client?.application?.commands
?.set([], process.env.DISCORD_GUILD_ID)
.then(async () => {
return logger.verbose(`Development mode is disabled.`);
});
}
return logger.info(`Development mode is enabled.`);

View file

@ -1,13 +1,14 @@
import crypto from "crypto";
import { secretKey, algorithm } from "../../config/encryption";
import { IEncryptionData } from "../../interfaces/EncryptionData";
const iv = crypto.randomBytes(16);
const encrypt = (text: crypto.BinaryLike): IEncryptionData => {
const cipher = crypto.createCipheriv(algorithm, secretKey, iv);
const cipher = crypto.createCipheriv(
process.env.ENCRYPTION_ALGORITHM,
process.env.ENCRYPTION_SECRET,
iv
);
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
return {
@ -18,8 +19,8 @@ const encrypt = (text: crypto.BinaryLike): IEncryptionData => {
const decrypt = (hash: IEncryptionData) => {
const decipher = crypto.createDecipheriv(
algorithm,
secretKey,
process.env.ENCRYPTION_ALGORITHM,
process.env.ENCRYPTION_SECRET,
Buffer.from(hash.iv, "hex")
);

View file

@ -1,9 +1,8 @@
import { footerText, footerIcon } from "../../config/embed";
import { MessageEmbed } from "discord.js";
export default new MessageEmbed()
.setFooter({
text: footerText,
iconURL: footerIcon,
text: process.env.EMBED_FOOTER_TEXT,
iconURL: process.env.EMBED_FOOTER_ICON,
})
.setTimestamp(new Date());

View file

@ -1,18 +1,30 @@
import guildSchema from "../../models/guild";
import * as embedConfig from "../../config/embed";
import { Guild } from "discord.js";
export default async (guild?: Guild | null) => {
const {
EMBED_COLOR_SUCCESS,
EMBED_COLOR_WAIT,
EMBED_COLOR_ERROR,
EMBED_FOOTER_TEXT,
EMBED_FOOTER_ICON,
} = process.env;
const defaultEmbedConfig = {
successColor: EMBED_COLOR_SUCCESS,
waitColor: EMBED_COLOR_WAIT,
errorColor: EMBED_COLOR_ERROR,
footerText: EMBED_FOOTER_TEXT,
footerIcon: EMBED_FOOTER_ICON,
};
if (!guild) {
return { ...embedConfig };
return defaultEmbedConfig;
}
const guildConfig = await guildSchema.findOne({ guildId: guild.id });
if (!guildConfig) {
return {
...embedConfig,
};
return defaultEmbedConfig;
}
return guildConfig.embeds;
};

View file

@ -1,5 +1,3 @@
import { token, intents } from "./config/discord";
import { Client, Collection } from "discord.js"; // discord.js
import * as managers from "./managers";
@ -8,7 +6,7 @@ import * as managers from "./managers";
const main = async () => {
// Initiate client object
const client = new Client({
intents,
intents: ["GUILDS", "GUILD_MEMBERS", "GUILD_MESSAGES"],
});
// Create command collection
@ -17,7 +15,7 @@ const main = async () => {
await managers.start(client);
// Authorize with Discord's API
await client.login(token);
await client.login(process.env.DISCORD_TOKEN);
};
main();

View file

@ -1,13 +1,11 @@
import winston from "winston";
import "winston-daily-rotate-file";
import { logLevel } from "../config/other";
const { combine, timestamp, printf, errors, colorize, align, json } =
winston.format;
export default winston.createLogger({
level: logLevel || "info",
level: process.env.LOG_LEVEL || "info",
transports: [
new winston.transports.DailyRotateFile({
filename: "logs/combined-%DATE%.log",

View file

@ -1,6 +1,21 @@
import mongoose from "mongoose";
import { url } from "../../config/database";
import logger from "../../logger";
export const connect = async () => {
await mongoose.connect(url);
await mongoose
.connect(process.env.MONGO_URL)
.then(async (connection) => {
logger.info(`💾 Connected to database: ${connection.connection.name}`);
})
.catch(async (e) => {
logger.error("💾 Could not connect to database", e);
});
mongoose.connection.on("error", async (error) => {
logger.error(`💾 ${error}`);
});
mongoose.connection.on("warn", async (warning) => {
logger.warn(`💾 ${warning}`);
});
};

View file

@ -1,3 +1,4 @@
import "dotenv/config";
import { Client } from "discord.js";
import * as database from "./database";

View file

@ -1,8 +1,5 @@
// Dependencies
import {
CommandInteraction,
Permissions,
} from "discord.js";
import { CommandInteraction, Permissions } from "discord.js";
// Configurations
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";

View file

@ -1,6 +1,5 @@
import { CommandInteraction } from "discord.js";
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
import { timeout } from "../../../../../config/reputation";
import logger from "../../../../../logger";
import fetchUser from "../../../../../helpers/fetchUser";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
@ -53,7 +52,7 @@ export default {
await noSelfReputation(optionTarget, user);
// Check if user is on cooldown otherwise create one
await cooldown.command(interaction, timeout);
await cooldown.command(interaction, process.env.REPUTATION_TIMEOUT);
switch (optionType) {
case "positive":

View file

@ -8,7 +8,6 @@ import {
// Configurations
import getEmbedConfig from "../../../../../helpers/getEmbedConfig";
import { hosterName, hosterUrl } from "../../../../../config/other";
import { SlashCommandSubcommandBuilder } from "@discordjs/builders";
// Function
@ -45,10 +44,10 @@ export default {
.setEmoji("💬")
.setURL("https://discord.zyner.org"),
new MessageButton()
.setLabel(`Hosted by ${hosterName}`)
.setLabel(`Hosted by ${process.env.BOT_HOSTER_NAME}`)
.setStyle("LINK")
.setEmoji("⚒️")
.setURL(`${hosterUrl}`)
.setURL(`${process.env.BOT_HOSTER_URL}`)
);
const interactionEmbed = {

26
src/types/common/environment.d.ts vendored Normal file
View file

@ -0,0 +1,26 @@
import { Snowflake, ColorResolvable } from "discord.js";
declare global {
namespace NodeJS {
interface ProcessEnv {
MONGO_URL: string;
DISCORD_TOKEN: string;
DISCORD_CLIENT_ID: Snowflake;
DISCORD_GUILD_ID: Snowflake;
DEVELOPMENT_MODE: boolean;
ENCRYPTION_ALGORITHM: string;
ENCRYPTION_SECRET: string;
EMBED_COLOR_SUCCESS: ColorResolvable;
EMBED_COLOR_WAIT: ColorResolvable;
EMBED_COLOR_ERROR: ColorResolvable;
EMBED_FOOTER_TEXT: string;
EMBED_FOOTER_ICON: string;
LOG_LEVEL: string;
REPUTATION_TIMEOUT: number;
BOT_HOSTER_NAME: string;
BOT_HOSTER_URL: string;
}
}
}
export {};