Added nix flake stuff.
This commit is contained in:
parent
ab9969283b
commit
667b324572
4 changed files with 607 additions and 0 deletions
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1733212471,
|
||||
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
38
flake.nix
Normal file
38
flake.nix
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
|
||||
nixosConfigurations.container = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules =
|
||||
[
|
||||
( import ./sharkey-service.nix )
|
||||
({ pkgs, ... }: {
|
||||
boot.isContainer = true;
|
||||
|
||||
# Let 'nixos-version --json' know about the Git revision
|
||||
# of this flake.
|
||||
system.configurationRevision = nixpkgs.lib.mkIf (self ? rev) self.rev;
|
||||
|
||||
# Network configuration.
|
||||
networking.useDHCP = false;
|
||||
networking.firewall.allowedTCPPorts = [ 3000 ];
|
||||
|
||||
system.stateVersion = "24.04";
|
||||
|
||||
services.sharkey = {
|
||||
enable = true;
|
||||
package = (pkgs.callPackage ./sharkey.nix {});
|
||||
settings = {
|
||||
url = "https://sharkey.localhost";
|
||||
};
|
||||
redis.createLocally = true;
|
||||
database.createLocally = true;
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
418
sharkey-service.nix
Normal file
418
sharkey-service.nix
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.sharkey;
|
||||
settingsFormat = pkgs.formats.yaml { };
|
||||
redisType = lib.types.submodule {
|
||||
freeformType = lib.types.attrsOf settingsFormat.type;
|
||||
options = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "localhost";
|
||||
description = "The Redis host.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 6379;
|
||||
description = "The Redis port.";
|
||||
};
|
||||
};
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
description = ''
|
||||
Configuration for Misskey, see
|
||||
[`example.yml`](https://github.com/sharkey-dev/sharkey/blob/develop/.config/example.yml)
|
||||
for all supported options.
|
||||
'';
|
||||
type = lib.types.submodule {
|
||||
freeformType = lib.types.attrsOf settingsFormat.type;
|
||||
options = {
|
||||
url = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "https://example.tld/";
|
||||
description = ''
|
||||
The final user-facing URL. Do not change after running Misskey for the first time.
|
||||
|
||||
This needs to match up with the configured reverse proxy and is automatically configured when using `services.sharkey.reverseProxy`.
|
||||
'';
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 3000;
|
||||
description = "The port your Misskey server should listen on.";
|
||||
};
|
||||
socket = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/path/to/sharkey.sock";
|
||||
description = "The UNIX socket your Misskey server should listen on.";
|
||||
};
|
||||
chmodSocket = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "777";
|
||||
description = "The file access mode of the UNIX socket.";
|
||||
};
|
||||
db = lib.mkOption {
|
||||
description = "Database settings.";
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/run/postgresql";
|
||||
example = "localhost";
|
||||
description = "The PostgreSQL host.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 5432;
|
||||
description = "The PostgreSQL port.";
|
||||
};
|
||||
db = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "sharkey";
|
||||
description = "The database name.";
|
||||
};
|
||||
user = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "sharkey";
|
||||
description = "The user used for database authentication.";
|
||||
};
|
||||
pass = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "The password used for database authentication.";
|
||||
};
|
||||
disableCache = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to disable caching queries.";
|
||||
};
|
||||
extra = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.attrsOf settingsFormat.type);
|
||||
default = null;
|
||||
example = {
|
||||
ssl = true;
|
||||
};
|
||||
description = "Extra connection options.";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
redis = lib.mkOption {
|
||||
type = redisType;
|
||||
default = { };
|
||||
description = "`ioredis` options. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
redisForPubsub = lib.mkOption {
|
||||
type = lib.types.nullOr redisType;
|
||||
default = null;
|
||||
description = "`ioredis` options for pubsub. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
redisForJobQueue = lib.mkOption {
|
||||
type = lib.types.nullOr redisType;
|
||||
default = null;
|
||||
description = "`ioredis` options for the job queue. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
redisForTimelines = lib.mkOption {
|
||||
type = lib.types.nullOr redisType;
|
||||
default = null;
|
||||
description = "`ioredis` options for timelines. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
|
||||
};
|
||||
meilisearch = lib.mkOption {
|
||||
description = "Meilisearch connection options.";
|
||||
type = lib.types.nullOr (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "localhost";
|
||||
description = "The Meilisearch host.";
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 7700;
|
||||
description = "The Meilisearch port.";
|
||||
};
|
||||
apiKey = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "The Meilisearch API key.";
|
||||
};
|
||||
ssl = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to connect via SSL.";
|
||||
};
|
||||
index = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Meilisearch index to use.";
|
||||
};
|
||||
scope = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"local"
|
||||
"global"
|
||||
];
|
||||
default = "local";
|
||||
description = "The search scope.";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = null;
|
||||
};
|
||||
id = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"aid"
|
||||
"aidx"
|
||||
"meid"
|
||||
"ulid"
|
||||
"objectid"
|
||||
];
|
||||
default = "aidx";
|
||||
description = "The ID generation method to use. Do not change after starting Misskey for the first time.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.sharkey = {
|
||||
enable = lib.mkEnableOption "sharkey";
|
||||
package = lib.mkPackageOption pkgs "sharkey" { };
|
||||
inherit settings;
|
||||
database = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Create the PostgreSQL database locally. Sets `services.sharkey.settings.db.{db,host,port,user,pass}`.";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "The path to a file containing the database password. Sets `services.sharkey.settings.db.pass`.";
|
||||
};
|
||||
};
|
||||
redis = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Create and use a local Redis instance. Sets `services.sharkey.settings.redis.host`.";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "The path to a file containing the Redis password. Sets `services.sharkey.settings.redis.pass`.";
|
||||
};
|
||||
};
|
||||
meilisearch = {
|
||||
createLocally = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Create and use a local Meilisearch instance. Sets `services.sharkey.settings.meilisearch.{host,port,ssl}`.";
|
||||
};
|
||||
keyFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "The path to a file containing the Meilisearch API key. Sets `services.sharkey.settings.meilisearch.apiKey`.";
|
||||
};
|
||||
};
|
||||
reverseProxy = {
|
||||
enable = lib.mkEnableOption "a HTTP reverse proxy for Misskey";
|
||||
webserver = lib.mkOption {
|
||||
type = lib.types.attrTag {
|
||||
nginx = lib.mkOption {
|
||||
type = lib.types.submodule (import ../web-servers/nginx/vhost-options.nix);
|
||||
default = { };
|
||||
description = ''
|
||||
Extra configuration for the nginx virtual host of Misskey.
|
||||
Set to `{ }` to use the default configuration.
|
||||
'';
|
||||
};
|
||||
caddy = lib.mkOption {
|
||||
type = lib.types.submodule (
|
||||
import ../web-servers/caddy/vhost-options.nix { cfg = config.services.caddy; }
|
||||
);
|
||||
default = { };
|
||||
description = ''
|
||||
Extra configuration for the caddy virtual host of Misskey.
|
||||
Set to `{ }` to use the default configuration.
|
||||
'';
|
||||
};
|
||||
};
|
||||
description = "The webserver to use as the reverse proxy.";
|
||||
};
|
||||
host = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
The fully qualified domain name to bind to. Sets `services.sharkey.settings.url`.
|
||||
|
||||
This is required when using `services.sharkey.reverseProxy.enable = true`.
|
||||
'';
|
||||
example = "sharkey.example.com";
|
||||
default = null;
|
||||
};
|
||||
ssl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.bool;
|
||||
description = ''
|
||||
Whether to enable SSL for the reverse proxy. Sets `services.sharkey.settings.url`.
|
||||
|
||||
This is required when using `services.sharkey.reverseProxy.enable = true`.
|
||||
'';
|
||||
example = true;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion =
|
||||
cfg.reverseProxy.enable -> ((cfg.reverseProxy.host != null) && (cfg.reverseProxy.ssl != null));
|
||||
message = "`services.sharkey.reverseProxy.enable` requires `services.sharkey.reverseProxy.host` and `services.sharkey.reverseProxy.ssl` to be set.";
|
||||
}
|
||||
];
|
||||
|
||||
services.sharkey.settings = lib.mkMerge [
|
||||
(lib.mkIf cfg.database.createLocally {
|
||||
db = {
|
||||
db = lib.mkDefault "sharkey";
|
||||
# Use unix socket instead of localhost to allow PostgreSQL peer authentication,
|
||||
# required for `services.postgresql.ensureUsers`
|
||||
host = lib.mkDefault "/var/run/postgresql";
|
||||
port = lib.mkDefault config.services.postgresql.settings.port;
|
||||
user = lib.mkDefault "sharkey";
|
||||
pass = lib.mkDefault null;
|
||||
};
|
||||
})
|
||||
(lib.mkIf (cfg.database.passwordFile != null) { db.pass = lib.mkDefault "@DATABASE_PASSWORD@"; })
|
||||
(lib.mkIf cfg.redis.createLocally { redis.host = lib.mkDefault "localhost"; })
|
||||
(lib.mkIf (cfg.redis.passwordFile != null) { redis.pass = lib.mkDefault "@REDIS_PASSWORD@"; })
|
||||
(lib.mkIf cfg.meilisearch.createLocally {
|
||||
meilisearch = {
|
||||
host = lib.mkDefault "localhost";
|
||||
port = lib.mkDefault config.services.meilisearch.listenPort;
|
||||
ssl = lib.mkDefault false;
|
||||
};
|
||||
})
|
||||
(lib.mkIf (cfg.meilisearch.keyFile != null) {
|
||||
meilisearch.apiKey = lib.mkDefault "@MEILISEARCH_KEY@";
|
||||
})
|
||||
(lib.mkIf cfg.reverseProxy.enable {
|
||||
url = lib.mkDefault "${
|
||||
if cfg.reverseProxy.ssl then "https" else "http"
|
||||
}://${cfg.reverseProxy.host}";
|
||||
})
|
||||
];
|
||||
|
||||
systemd.services.sharkey = {
|
||||
after = [
|
||||
"network-online.target"
|
||||
"postgresql.service"
|
||||
];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
MISSKEY_CONFIG_YML = "/run/sharkey/default.yml";
|
||||
};
|
||||
preStart =
|
||||
''
|
||||
install -m 700 ${settingsFormat.generate "sharkey-config.yml" cfg.settings} /run/sharkey/default.yml
|
||||
''
|
||||
+ (lib.optionalString (cfg.database.passwordFile != null) ''
|
||||
${pkgs.replace-secret}/bin/replace-secret '@DATABASE_PASSWORD@' "${cfg.database.passwordFile}" /run/sharkey/default.yml
|
||||
'')
|
||||
+ (lib.optionalString (cfg.redis.passwordFile != null) ''
|
||||
${pkgs.replace-secret}/bin/replace-secret '@REDIS_PASSWORD@' "${cfg.redis.passwordFile}" /run/sharkey/default.yml
|
||||
'')
|
||||
+ (lib.optionalString (cfg.meilisearch.keyFile != null) ''
|
||||
${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/sharkey/default.yml
|
||||
'');
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/sharkey migrateandstart";
|
||||
RuntimeDirectory = "sharkey";
|
||||
RuntimeDirectoryMode = "700";
|
||||
StateDirectory = "sharkey";
|
||||
StateDirectoryMode = "700";
|
||||
TimeoutSec = 60;
|
||||
DynamicUser = true;
|
||||
User = "sharkey";
|
||||
LockPersonality = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql = lib.mkIf cfg.database.createLocally {
|
||||
enable = true;
|
||||
ensureDatabases = [ "sharkey" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "sharkey";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.redis.servers = lib.mkIf cfg.redis.createLocally {
|
||||
sharkey = {
|
||||
enable = true;
|
||||
port = cfg.settings.redis.port;
|
||||
};
|
||||
};
|
||||
|
||||
services.meilisearch = lib.mkIf cfg.meilisearch.createLocally { enable = true; };
|
||||
|
||||
services.caddy = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? caddy) {
|
||||
enable = true;
|
||||
virtualHosts.${cfg.settings.url} = lib.mkMerge [
|
||||
cfg.reverseProxy.webserver.caddy
|
||||
{
|
||||
hostName = lib.mkDefault cfg.settings.url;
|
||||
extraConfig = ''
|
||||
reverse_proxy localhost:${toString cfg.settings.port}
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? nginx) {
|
||||
enable = true;
|
||||
virtualHosts.${cfg.reverseProxy.host} = lib.mkMerge [
|
||||
cfg.reverseProxy.webserver.nginx
|
||||
{
|
||||
locations."/" = {
|
||||
proxyPass = lib.mkDefault "http://localhost:${toString cfg.settings.port}";
|
||||
proxyWebsockets = lib.mkDefault true;
|
||||
recommendedProxySettings = lib.mkDefault true;
|
||||
};
|
||||
}
|
||||
(lib.mkIf (cfg.reverseProxy.ssl != null) { forceSSL = lib.mkDefault cfg.reverseProxy.ssl; })
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = [ lib.maintainers.feathecutie ];
|
||||
};
|
||||
}
|
||||
124
sharkey.nix
Normal file
124
sharkey.nix
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
stdenv,
|
||||
lib,
|
||||
nixosTests,
|
||||
fetchFromGitHub,
|
||||
nodejs,
|
||||
pnpm,
|
||||
makeWrapper,
|
||||
python3,
|
||||
bash,
|
||||
jemalloc,
|
||||
ffmpeg-headless,
|
||||
writeShellScript,
|
||||
xcbuild,
|
||||
...
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "sharkey";
|
||||
|
||||
version = "2024.10.0";
|
||||
|
||||
src = fetchGit {
|
||||
url = "https://activitypub.software/TransFem-org/Sharkey.git";
|
||||
rev = "150d949a3ec2b5162e2dfda10c2cc5dddea8c59a";
|
||||
submodules = true;
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
nodejs
|
||||
pnpm.configHook
|
||||
makeWrapper
|
||||
python3
|
||||
] ++ lib.optionals stdenv.hostPlatform.isDarwin [ xcbuild.xcrun ];
|
||||
|
||||
# https://nixos.org/manual/nixpkgs/unstable/#javascript-pnpm
|
||||
pnpmDeps = pnpm.fetchDeps {
|
||||
inherit (finalAttrs) pname version src;
|
||||
hash = "sha256-PpXmNBO4pWj8AG0we4DpPhzfx/18rwDZHi86+esFghM=";
|
||||
};
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/pull/296697/files#r1617546739
|
||||
(
|
||||
cd node_modules/.pnpm/node_modules/v-code-diff
|
||||
pnpm run postinstall
|
||||
)
|
||||
|
||||
# https://github.com/NixOS/nixpkgs/pull/296697/files#r1617595593
|
||||
export npm_config_nodedir=${nodejs}
|
||||
(
|
||||
cd node_modules/.pnpm/node_modules/re2
|
||||
pnpm run rebuild
|
||||
)
|
||||
(
|
||||
cd node_modules/.pnpm/node_modules/sharp
|
||||
pnpm run install
|
||||
)
|
||||
|
||||
pnpm build
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase =
|
||||
let
|
||||
checkEnvVarScript = writeShellScript "sharkey-check-env-var" ''
|
||||
if [[ -z $MISSKEY_CONFIG_YML ]]; then
|
||||
echo "MISSKEY_CONFIG_YML must be set to the location of the Misskey config file."
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
in
|
||||
''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/data
|
||||
cp -r . $out/data
|
||||
|
||||
# Set up symlink for use at runtime
|
||||
# TODO: Find a better solution for this (potentially patch Misskey to make this configurable?)
|
||||
# Line that would need to be patched: https://github.com/sharkey-dev/sharkey/blob/9849aab40283cbde2184e74d4795aec8ef8ccba3/packages/backend/src/core/InternalStorageService.ts#L18
|
||||
# Otherwise, maybe somehow bindmount a writable directory into <package>/data/files.
|
||||
ln -s /var/lib/sharkey $out/data/files
|
||||
|
||||
makeWrapper ${pnpm}/bin/pnpm $out/bin/sharkey \
|
||||
--run "${checkEnvVarScript} || exit" \
|
||||
--chdir $out/data \
|
||||
--add-flags run \
|
||||
--set-default NODE_ENV production \
|
||||
--prefix PATH : ${
|
||||
lib.makeBinPath [
|
||||
nodejs
|
||||
pnpm
|
||||
bash
|
||||
]
|
||||
} \
|
||||
--prefix LD_LIBRARY_PATH : ${
|
||||
lib.makeLibraryPath [
|
||||
jemalloc
|
||||
ffmpeg-headless
|
||||
stdenv.cc.cc
|
||||
]
|
||||
}
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
inherit (finalAttrs) pnpmDeps;
|
||||
tests.sharkey = nixosTests.sharkey;
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "🌎 An interplanetary microblogging platform 🚀";
|
||||
homepage = "https://sharkey-hub.net/";
|
||||
license = lib.licenses.agpl3Only;
|
||||
maintainers = [ lib.maintainers.feathecutie ];
|
||||
platforms = lib.platforms.unix;
|
||||
mainProgram = "sharkey";
|
||||
};
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue