Add ryot and moonrepo
parent
098ca9feff
commit
fc7f47c56c
@ -0,0 +1 @@
|
|||||||
|
{...}: {imports = [./ryot.nix];}
|
@ -0,0 +1,368 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
options,
|
||||||
|
pkgs,
|
||||||
|
craneLib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib;
|
||||||
|
with builtins; let
|
||||||
|
pgHost = {
|
||||||
|
host,
|
||||||
|
socket,
|
||||||
|
name,
|
||||||
|
port,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
...
|
||||||
|
}: "postgres://${user}${
|
||||||
|
if password != ""
|
||||||
|
then ":${password}"
|
||||||
|
else ""
|
||||||
|
}@${
|
||||||
|
if socket == null
|
||||||
|
then "${host}:${port}"
|
||||||
|
else replaceStrings ["/"] ["%%2F"] socket
|
||||||
|
}/${name}";
|
||||||
|
cfg = config.services.ryot;
|
||||||
|
pg = config.services.postgresql;
|
||||||
|
in {
|
||||||
|
options.services.ryot = {
|
||||||
|
enable = mkEnableOption "ryot";
|
||||||
|
|
||||||
|
package = mkOption {
|
||||||
|
default = pkgs.unstable.callPackage ../pkgs/ryot {inherit craneLib;};
|
||||||
|
type = types.package;
|
||||||
|
defaultText = literalExpression "pkgs.callPackage ../pkgs/ryot {}";
|
||||||
|
description = mdDoc "ryot derivation to use";
|
||||||
|
};
|
||||||
|
|
||||||
|
database = {
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = mdDoc "Database host address.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = pg.port or 5432;
|
||||||
|
defaultText = literalExpression "config.postgresql.port or 5432";
|
||||||
|
description = mdDoc "Database host port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
socket = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
example = "/var/run/postgresql";
|
||||||
|
description = mdDoc "Path to the unix socket file to use for authentication.";
|
||||||
|
};
|
||||||
|
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "ryot";
|
||||||
|
description = mdDoc "Database name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "ryot";
|
||||||
|
description = mdDoc "Database user.";
|
||||||
|
};
|
||||||
|
|
||||||
|
password = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = mdDoc ''
|
||||||
|
The password corresponding to {option}`database.user`.
|
||||||
|
Warning: this is stored in cleartext in the Nix store!
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
createDatabase = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = lib.mdDoc "Whether to create a local database automatically.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "ryot";
|
||||||
|
description = lib.mdDoc "User account under which ryot runs.";
|
||||||
|
};
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "ryot";
|
||||||
|
description = lib.mdDoc "Group under which ryot runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8000;
|
||||||
|
description = mdDoc "The port the web interface should listen on";
|
||||||
|
};
|
||||||
|
|
||||||
|
https = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = mdDoc "Whether the site is served over https";
|
||||||
|
};
|
||||||
|
|
||||||
|
cors_urls = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
description = mdDoc ''A list of URLs for CORS.'';
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
audio_books = {
|
||||||
|
audible = {
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://api.audible.com/1.0/catalog/products/";
|
||||||
|
description = mdDoc "The url to make requests for getting metadata from Audible.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
books = {
|
||||||
|
openlibrary = {
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://openlibrary.org";
|
||||||
|
description = mdDoc "The url to make requests for getting metadata from Openlibrary.";
|
||||||
|
};
|
||||||
|
cover_image_url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://covers.openlibrary.org/b";
|
||||||
|
description = mdDoc "The url for getting images from Openlibrary.";
|
||||||
|
};
|
||||||
|
cover_image_size = mkOption {
|
||||||
|
type = types.enum ["S" "M" "L"];
|
||||||
|
default = "M";
|
||||||
|
description = mdDoc "The image sizes to fetch from Openlibrary.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
movies = {
|
||||||
|
tmdb = {
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://api.themoviedb.org/3/";
|
||||||
|
description = mdDoc "The url to make requests for getting metadata about shows/movies.";
|
||||||
|
};
|
||||||
|
access_token = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI4ZGVlOTZjMjc0OGVhY2U0NzU2MGJkMWU4YzE5NTljMCIsInN1YiI6IjY0NDRiYmE4MmM2YjdiMDRiZTdlZDJmNSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.ZZZNJMXStvAOPJlT0hOBVPSTppFAK3mcUpmbJsExIq4";
|
||||||
|
description = mdDoc "The access token for the TMDB API.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
shows = {
|
||||||
|
tmdb = {
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://api.themoviedb.org/3/";
|
||||||
|
description = mdDoc "The url to make requests for getting metadata about shows/movies.";
|
||||||
|
};
|
||||||
|
access_token = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI4ZGVlOTZjMjc0OGVhY2U0NzU2MGJkMWU4YzE5NTljMCIsInN1YiI6IjY0NDRiYmE4MmM2YjdiMDRiZTdlZDJmNSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.ZZZNJMXStvAOPJlT0hOBVPSTppFAK3mcUpmbJsExIq4";
|
||||||
|
description = mdDoc "The access token for the TMDB API.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
podcasts = {
|
||||||
|
listennotes = {
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://listen-api.listennotes.com/api/v2/";
|
||||||
|
description = mdDoc "The url to make requests for getting metadata about podcasts.";
|
||||||
|
};
|
||||||
|
api_token = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = mdDoc "The access token for the Listennotes API.";
|
||||||
|
};
|
||||||
|
user_agent = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = mdDoc "The user agent used for the Listennotes API.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
scheduler = {
|
||||||
|
user_cleanup_every = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 10;
|
||||||
|
description = mdDoc "Deploy a job every x minutes that performs user cleanup and summary calculation.";
|
||||||
|
};
|
||||||
|
rate_limit_num = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 5;
|
||||||
|
description = mdDoc "The number of jobs to process every 5 seconds when updating metadata in the background.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
video_games = {
|
||||||
|
twitch = {
|
||||||
|
client_id = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = mdDoc "The client ID issues by Twitch. Required to enable video games tracking. [More information](https://github.com/IgnisDa/ryot/blob/main/docs/guides/video-games.md)";
|
||||||
|
};
|
||||||
|
client_secret = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = mdDoc "The client secret issued by Twitch.";
|
||||||
|
};
|
||||||
|
access_token_url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://id.twitch.tv/oauth2/token";
|
||||||
|
description = mdDoc "The endpoint that issues access keys for IGDB.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
igdb = {
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://api.igdb.com/v4/";
|
||||||
|
description = mdDoc "The url to make requests for getting metadata about video games.";
|
||||||
|
};
|
||||||
|
image_url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "https://images.igdb.com/igdb/image/upload/";
|
||||||
|
description = mdDoc "The url for getting images from IGDB.";
|
||||||
|
};
|
||||||
|
image_size = mkOption {
|
||||||
|
type = types.enum ["t_original"];
|
||||||
|
default = "t_original";
|
||||||
|
description = mdDoc "The image sizes to fetch from IGDB.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
users = {
|
||||||
|
allow_changing_username = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = mdDoc "Whether users will be allowed to change their username in their profile settings.";
|
||||||
|
};
|
||||||
|
token_valid_for_days = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 90;
|
||||||
|
description = mdDoc "The number of days till login auth token is valid.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services.ryot = rec {
|
||||||
|
description = "ryot";
|
||||||
|
after = ["network.target" "postgresql.service"];
|
||||||
|
requires = ["postgresql.service"];
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
path = [cfg.package];
|
||||||
|
|
||||||
|
preStart = ''mkdir -p /usr/share/ryot && chown ${cfg.user}:${cfg.group} /usr/share/ryot'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
ExecStart = "${getExe cfg.package}";
|
||||||
|
Restart = "always";
|
||||||
|
WorkingDirectory = "/usr/share/ryot";
|
||||||
|
|
||||||
|
ReadWritePaths = ["/usr/share/ryot"];
|
||||||
|
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ProtectHome = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
PrivateUsers = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictAddressFamilies = ["AF_UNIX AF_INET AF_INET6"];
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
PrivateMounts = true;
|
||||||
|
# System Call Filtering
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
|
||||||
|
|
||||||
|
PassEnvironment = concatStringsSep " " (attrNames environment);
|
||||||
|
};
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
AUDIO_BOOKS_AUDIBLE_URL = cfg.settings.audio_books.audible.url;
|
||||||
|
BOOKS_OPENLIBRARY_URL = cfg.settings.books.openlibrary.url;
|
||||||
|
BOOKS_OPENLIBRARY_COVER_IMAGE_URL = cfg.settings.books.openlibrary.cover_image_url;
|
||||||
|
BOOKS_OPENLIBRARY_COVER_IMAGE_SIZE = cfg.settings.books.openlibrary.cover_image_size;
|
||||||
|
MOVIES_TMDB_URL = cfg.settings.movies.tmdb.url;
|
||||||
|
MOVIES_TMDB_ACCESS_TOKEN = cfg.settings.movies.tmdb.access_token;
|
||||||
|
SHOWS_TMDB_URL = cfg.settings.shows.tmdb.url;
|
||||||
|
SHOWS_TMDB_ACCESS_TOKEN = cfg.settings.shows.tmdb.access_token;
|
||||||
|
PODCASTS_LISTENNOTES_URL = cfg.settings.podcasts.listennotes.url;
|
||||||
|
PODCASTS_LISTENNOTES_API_TOKEN = cfg.settings.podcasts.listennotes.api_token;
|
||||||
|
PODCASTS_LISTENNOTES_USER_AGENT = cfg.settings.podcasts.listennotes.user_agent;
|
||||||
|
SCHEDULER_USER_CLEANUP_EVERY = toString cfg.settings.scheduler.user_cleanup_every;
|
||||||
|
SCHEDULER_RATE_LIMIT_NUM = toString cfg.settings.scheduler.rate_limit_num;
|
||||||
|
VIDEO_GAMES_TWITCH_CLIENT_ID = cfg.settings.video_games.twitch.client_id;
|
||||||
|
VIDEO_GAMES_TWITCH_CLIENT_SECRET = cfg.settings.video_games.twitch.client_secret;
|
||||||
|
VIDEO_GAMES_TWITCH_ACCESS_TOKEN_URL = cfg.settings.video_games.twitch.access_token_url;
|
||||||
|
VIDEO_GAMES_IGDB_URL = cfg.settings.video_games.igdb.url;
|
||||||
|
VIDEO_GAMES_IGDB_IMAGE_URL = cfg.settings.video_games.igdb.image_url;
|
||||||
|
VIDEO_GAMES_IGDB_IMAGE_SIZE = cfg.settings.video_games.igdb.image_size;
|
||||||
|
USERS_ALLOW_CHANGING_USERNAME =
|
||||||
|
if cfg.settings.users.allow_changing_username
|
||||||
|
then "true"
|
||||||
|
else null;
|
||||||
|
USERS_TOKEN_VALID_FOR_DAYS = toString cfg.settings.users.token_valid_for_days;
|
||||||
|
WEB_CORS_ORIGINS = concatStringsSep "," cfg.cors_urls;
|
||||||
|
|
||||||
|
DATABASE_SCDB_URL = "/usr/share/ryot";
|
||||||
|
DATABASE_URL = pgHost cfg.database;
|
||||||
|
WEB_INSECURE_COOKIE =
|
||||||
|
if cfg.https
|
||||||
|
then null
|
||||||
|
else "true";
|
||||||
|
|
||||||
|
PORT = toString cfg.port;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.postgresql = optionalAttrs cfg.database.createDatabase {
|
||||||
|
enable = mkDefault true;
|
||||||
|
|
||||||
|
ensureDatabases = [cfg.database.name];
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = cfg.database.user;
|
||||||
|
ensurePermissions = {"DATABASE ${cfg.database.name}" = "ALL PRIVILEGES";};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users = mkIf (cfg.user == "ryot") {
|
||||||
|
ryot = {
|
||||||
|
description = "Ryot Service";
|
||||||
|
useDefaultShell = true;
|
||||||
|
group = cfg.group;
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups = mkIf (cfg.group == "ryot") {
|
||||||
|
ryot = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
meta.maintainers = with maintainers; [nyxkrage];
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
{ ...} : {
|
{...}: {
|
||||||
imports = [
|
imports = [
|
||||||
./gitea.nix
|
./gitea.nix
|
||||||
./postgres.nix
|
./postgres.nix
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
./sshd.nix
|
./sshd.nix
|
||||||
|
./ryot.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
services.ryot = rec {
|
||||||
|
enable = true;
|
||||||
|
database.socket = "/var/run/postgresql";
|
||||||
|
cors_urls = ["https://ryot.pid1.sh" "https://ryot.nyx" "http://192.168.1.7:8001"];
|
||||||
|
port = 8001;
|
||||||
|
https = false;
|
||||||
|
};
|
||||||
|
networking.firewall.allowedTCPPorts = [config.services.ryot.port];
|
||||||
|
}
|
Loading…
Reference in New Issue