mobilizon: init at 3.1.3

Co-Authored-By: Minijackson <minijackson@riseup.net>
Co-Authored-By: summersamara <summersamara@proton.me>
This commit is contained in:
Kerstin Humm 2023-09-05 17:53:09 +02:00 committed by Yt
parent e5b6a21a70
commit 36ff7d5d5d
10 changed files with 2854 additions and 0 deletions

View File

@ -1260,6 +1260,7 @@
./services/web-apps/node-red.nix
./services/web-apps/onlyoffice.nix
./services/web-apps/openvscode-server.nix
./services/web-apps/mobilizon.nix
./services/web-apps/openwebrx.nix
./services/web-apps/outline.nix
./services/web-apps/peering-manager.nix

View File

@ -0,0 +1,442 @@
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.services.mobilizon;
user = "mobilizon";
group = "mobilizon";
settingsFormat = pkgs.formats.elixirConf { elixir = pkgs.elixir_1_14; };
configFile = settingsFormat.generate "mobilizon-config.exs" cfg.settings;
# Make a package containing launchers with the correct envirenment, instead of
# setting it with systemd services, so that the user can also use them without
# troubles
launchers = pkgs.stdenv.mkDerivation rec {
pname = "${cfg.package.pname}-launchers";
inherit (cfg.package) version;
src = cfg.package;
nativeBuildInputs = with pkgs; [ makeWrapper ];
dontBuild = true;
installPhase = ''
mkdir -p $out/bin
makeWrapper \
$src/bin/mobilizon \
$out/bin/mobilizon \
--run '. ${secretEnvFile}' \
--set MOBILIZON_CONFIG_PATH "${configFile}" \
--set-default RELEASE_TMP "/tmp"
makeWrapper \
$src/bin/mobilizon_ctl \
$out/bin/mobilizon_ctl \
--run '. ${secretEnvFile}' \
--set MOBILIZON_CONFIG_PATH "${configFile}" \
--set-default RELEASE_TMP "/tmp"
'';
};
repoSettings = cfg.settings.":mobilizon"."Mobilizon.Storage.Repo";
instanceSettings = cfg.settings.":mobilizon".":instance";
isLocalPostgres = repoSettings.socket_dir != null;
dbUser = if repoSettings.username != null then repoSettings.username else "mobilizon";
postgresql = config.services.postgresql.package;
postgresqlSocketDir = "/var/run/postgresql";
secretEnvFile = "/var/lib/mobilizon/secret-env.sh";
in
{
options = {
services.mobilizon = {
enable = mkEnableOption
"Mobilizon federated organization and mobilization platform";
nginx.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether an <literal>nginx</literal> virtual host should be
set up to serve Mobilizon.
'';
};
package = mkPackageOptionMD pkgs "mobilizon" { };
settings = mkOption {
type =
let
elixirTypes = settingsFormat.lib.types;
in
types.submodule {
freeformType = settingsFormat.type;
options = {
":mobilizon" = {
"Mobilizon.Web.Endpoint" = {
url.host = mkOption {
type = elixirTypes.str;
defaultText = literalExpression ''
''${settings.":mobilizon".":instance".hostname}
'';
description = ''
Your instance's hostname for generating URLs throughout the app
'';
};
http = {
port = mkOption {
type = elixirTypes.port;
default = 4000;
description = ''
The port to run the server
'';
};
ip = mkOption {
type = elixirTypes.tuple;
default = settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ];
description = ''
The IP address to listen on. Defaults to [::1] notated as a byte tuple.
'';
};
};
has_reverse_proxy = mkOption {
type = elixirTypes.bool;
default = true;
description = ''
Whether you use a reverse proxy
'';
};
};
":instance" = {
name = mkOption {
type = elixirTypes.str;
description = ''
The fallback instance name if not configured into the admin UI
'';
};
hostname = mkOption {
type = elixirTypes.str;
description = ''
Your instance's hostname
'';
};
email_from = mkOption {
type = elixirTypes.str;
defaultText = literalExpression ''
noreply@''${settings.":mobilizon".":instance".hostname}
'';
description = ''
The email for the From: header in emails
'';
};
email_reply_to = mkOption {
type = elixirTypes.str;
defaultText = literalExpression ''
''${email_from}
'';
description = ''
The email for the Reply-To: header in emails
'';
};
};
"Mobilizon.Storage.Repo" = {
socket_dir = mkOption {
type = types.nullOr elixirTypes.str;
default = postgresqlSocketDir;
description = ''
Path to the postgres socket directory.
Set this to null if you want to connect to a remote database.
If non-null, the local PostgreSQL server will be configured with
the configured database, permissions, and required extensions.
If connecting to a remote database, please follow the
instructions on how to setup your database:
<link xlink:href="https://docs.joinmobilizon.org/administration/install/release/#database-setup"/>
'';
};
username = mkOption {
type = types.nullOr elixirTypes.str;
default = user;
description = ''
User used to connect to the database
'';
};
database = mkOption {
type = types.nullOr elixirTypes.str;
default = "mobilizon_prod";
description = ''
Name of the database
'';
};
};
};
};
};
default = { };
description = ''
Mobilizon Elixir documentation, see
<link xlink:href="https://docs.joinmobilizon.org/administration/configure/reference/"/>
for supported values.
'';
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.nginx.enable -> (cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.ip == settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ]);
message = "Setting the IP mobilizon listens on is only possible when the nginx config is not used, as it is hardcoded there.";
}
];
services.mobilizon.settings = {
":mobilizon" = {
"Mobilizon.Web.Endpoint" = {
server = true;
url.host = mkDefault instanceSettings.hostname;
secret_key_base =
settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_INSTANCE_SECRET"; };
};
"Mobilizon.Web.Auth.Guardian".secret_key =
settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_AUTH_SECRET"; };
":instance" = {
registrations_open = mkDefault false;
demo = mkDefault false;
email_from = mkDefault "noreply@${instanceSettings.hostname}";
email_reply_to = mkDefault instanceSettings.email_from;
};
"Mobilizon.Storage.Repo" = {
# Forced by upstream since it uses PostgreSQL-specific extensions
adapter = settingsFormat.lib.mkAtom "Ecto.Adapters.Postgres";
pool_size = mkDefault 10;
};
};
":tzdata".":data_dir" = "/var/lib/mobilizon/tzdata/";
};
# This somewhat follows upstream's systemd service here:
# https://framagit.org/framasoft/mobilizon/-/blob/master/support/systemd/mobilizon.service
systemd.services.mobilizon = {
description = "Mobilizon federated organization and mobilization platform";
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
gawk
imagemagick
libwebp
file
# Optional:
gifsicle
jpegoptim
optipng
pngquant
];
serviceConfig = {
ExecStartPre = "${launchers}/bin/mobilizon_ctl migrate";
ExecStart = "${launchers}/bin/mobilizon start";
ExecStop = "${launchers}/bin/mobilizon stop";
User = user;
Group = group;
StateDirectory = "mobilizon";
Restart = "on-failure";
PrivateTmp = true;
ProtectSystem = "full";
NoNewPrivileges = true;
ReadWritePaths = mkIf isLocalPostgres postgresqlSocketDir;
};
};
# Create the needed secrets before running Mobilizon, so that they are not
# in the nix store
#
# Since some of these tasks are quite common for Elixir projects (COOKIE for
# every BEAM project, Phoenix and Guardian are also quite common), this
# service could be abstracted in the future, and used by other Elixir
# projects.
systemd.services.mobilizon-setup-secrets = {
description = "Mobilizon setup secrets";
before = [ "mobilizon.service" ];
wantedBy = [ "mobilizon.service" ];
script =
let
# Taken from here:
# https://framagit.org/framasoft/mobilizon/-/blob/1.0.7/lib/mix/tasks/mobilizon/instance.ex#L132-133
genSecret =
"IO.puts(:crypto.strong_rand_bytes(64)" +
"|> Base.encode64()" +
"|> binary_part(0, 64))";
# Taken from here:
# https://github.com/elixir-lang/elixir/blob/v1.11.3/lib/mix/lib/mix/release.ex#L499
genCookie = "IO.puts(Base.encode32(:crypto.strong_rand_bytes(32)))";
evalElixir = str: ''
${pkgs.elixir_1_14}/bin/elixir --eval '${str}'
'';
in
''
set -euxo pipefail
if [ ! -f "${secretEnvFile}" ]; then
install -m 600 /dev/null "${secretEnvFile}"
cat > "${secretEnvFile}" <<EOF
# This file was automatically generated by mobilizon-setup-secrets.service
export MOBILIZON_AUTH_SECRET='$(${evalElixir genSecret})'
export MOBILIZON_INSTANCE_SECRET='$(${evalElixir genSecret})'
export RELEASE_COOKIE='$(${evalElixir genCookie})'
EOF
fi
'';
serviceConfig = {
Type = "oneshot";
User = user;
Group = group;
StateDirectory = "mobilizon";
};
};
# Add the required PostgreSQL extensions to the local PostgreSQL server,
# if local PostgreSQL is configured.
systemd.services.mobilizon-postgresql = mkIf isLocalPostgres {
description = "Mobilizon PostgreSQL setup";
after = [ "postgresql.service" ];
before = [ "mobilizon.service" "mobilizon-setup-secrets.service" ];
wantedBy = [ "mobilizon.service" ];
path = [ postgresql ];
# Taken from here:
# https://framagit.org/framasoft/mobilizon/-/blob/1.1.0/priv/templates/setup_db.eex
script =
''
psql "${repoSettings.database}" -c "\
CREATE EXTENSION IF NOT EXISTS postgis; \
CREATE EXTENSION IF NOT EXISTS pg_trgm; \
CREATE EXTENSION IF NOT EXISTS unaccent;"
'';
serviceConfig = {
Type = "oneshot";
User = config.services.postgresql.superUser;
};
};
systemd.tmpfiles.rules = [
"d /var/lib/mobilizon/uploads/exports/csv 700 mobilizon mobilizon - -"
"Z /var/lib/mobilizon 700 mobilizon mobilizon - -"
];
services.postgresql = mkIf isLocalPostgres {
enable = true;
ensureDatabases = [ repoSettings.database ];
ensureUsers = [
{
name = dbUser;
ensurePermissions = {
"DATABASE \"${repoSettings.database}\"" = "ALL PRIVILEGES";
};
}
];
extraPlugins = with postgresql.pkgs; [ postgis ];
};
# Nginx config taken from support/nginx/mobilizon-release.conf
services.nginx =
let
inherit (cfg.settings.":mobilizon".":instance") hostname;
proxyPass = "http://[::1]:"
+ toString cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.port;
in
lib.mkIf cfg.nginx.enable {
enable = true;
virtualHosts."${hostname}" = {
enableACME = lib.mkDefault true;
forceSSL = lib.mkDefault true;
extraConfig = ''
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
'';
locations."/" = {
inherit proxyPass;
};
locations."~ ^/(js|css|img)" = {
root = "${cfg.package}/lib/mobilizon-${cfg.package.version}/priv/static";
extraConfig = ''
etag off;
access_log off;
add_header Cache-Control "public, max-age=31536000, immutable";
'';
};
locations."~ ^/(media|proxy)" = {
inherit proxyPass;
extraConfig = ''
etag off;
access_log off;
add_header Cache-Control "public, max-age=31536000, immutable";
'';
};
};
};
users.users.${user} = {
description = "Mobilizon daemon user";
group = group;
isSystemUser = true;
};
users.groups.${group} = { };
# So that we have the `mobilizon` and `mobilizon_ctl` commands.
# The `mobilizon remote` command is useful for dropping a shell into the
# running Mobilizon instance, and `mobilizon_ctl` is used for common
# management tasks (e.g. adding users).
environment.systemPackages = [ launchers ];
};
meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
}

View File

@ -485,6 +485,7 @@ in {
miriway = handleTest ./miriway.nix {};
misc = handleTest ./misc.nix {};
mjolnir = handleTest ./matrix/mjolnir.nix {};
mobilizon = handleTest ./mobilizon.nix {};
mod_perl = handleTest ./mod_perl.nix {};
molly-brown = handleTest ./molly-brown.nix {};
monica = handleTest ./web-apps/monica.nix {};

44
nixos/tests/mobilizon.nix Normal file
View File

@ -0,0 +1,44 @@
import ./make-test-python.nix ({ lib, ... }:
let
certs = import ./common/acme/server/snakeoil-certs.nix;
mobilizonDomain = certs.domain;
port = 41395;
in
{
name = "mobilizon";
meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
nodes.server =
{ ... }:
{
services.mobilizon = {
enable = true;
settings = {
":mobilizon" = {
":instance" = {
name = "Test Mobilizon";
hostname = mobilizonDomain;
};
"Mobilizon.Web.Endpoint".http.port = port;
};
};
};
security.pki.certificateFiles = [ certs.ca.cert ];
services.nginx.virtualHosts."${mobilizonDomain}" = {
enableACME = lib.mkForce false;
sslCertificate = certs.${mobilizonDomain}.cert;
sslCertificateKey = certs.${mobilizonDomain}.key;
};
networking.hosts."::1" = [ mobilizonDomain ];
};
testScript = ''
server.wait_for_unit("mobilizon.service")
server.wait_for_open_port(${toString port})
server.succeed("curl --fail https://${mobilizonDomain}/")
'';
})

View File

@ -0,0 +1,14 @@
{ fetchFromGitLab }: rec {
pname = "mobilizon";
version = "3.1.3";
src = fetchFromGitLab {
domain = "framagit.org";
owner = "framasoft";
repo = pname;
rev = version;
sha256 = "sha256-vYn8wE3cwOH3VssPDKKWAV9ZLKMSGg6XVWFZzJ9HSw0=";
};
}

View File

@ -0,0 +1,165 @@
{ lib
, beam
, callPackage
, writeShellScriptBin
, writeText
, yarn2nix
, mix2nix
, fetchFromGitLab
, fetchFromGitHub
, fetchgit
, fetchurl
, git
, cmake
, nixosTests
, mobilizon-frontend
}:
let
beamPackages = beam.packages.erlangR25.extend (self: super: {
elixir = super.elixir_1_14;
});
inherit (beamPackages) mixRelease buildMix buildRebar3 fetchHex;
common = callPackage ./common.nix { };
in
mixRelease rec {
inherit (common) pname version src;
# See https://github.com/whitfin/cachex/issues/205
# This circumvents a startup error for now
stripDebug = false;
nativeBuildInputs = [ git cmake ];
mixNixDeps = import ./mix.nix {
inherit beamPackages lib;
overrides = (final: prev:
(lib.mapAttrs
(_: value: value.override {
appConfigPath = src + "/config";
})
prev) // {
fast_html = prev.fast_html.override {
nativeBuildInputs = [ cmake ];
};
ex_cldr = prev.ex_cldr.overrideAttrs (old: {
preBuild = "touch config/prod.exs";
# We have to use the GitHub sources, as it otherwise tries to download
# the locales at build time.
src = fetchFromGitHub {
owner = "erictapen";
repo = "cldr";
# tip of 2.37.1/compile_env-fix
rev = "3a0dcf91132542a739f7b2450c6df12d40edeb0a";
sha256 = "sha256-QQRt1HOuajCANbKxikdgN3oK9BdZJjg1qg+WHm4DuqY=";
};
postInstall = ''
cp $src/priv/cldr/locales/* $out/lib/erlang/lib/ex_cldr-${old.version}/priv/cldr/locales/
'';
});
ex_cldr_currencies = prev.ex_cldr_currencies.override {
preBuild = "touch config/prod.exs";
};
ex_cldr_numbers = prev.ex_cldr_numbers.override {
preBuild = "touch config/prod.exs";
};
ex_cldr_dates_times = prev.ex_cldr_dates_times.override {
preBuild = "touch config/prod.exs";
};
# Upstream issue: https://github.com/bryanjos/geo_postgis/pull/87
geo_postgis = prev.geo_postgis.overrideAttrs (old: {
propagatedBuildInputs = old.propagatedBuildInputs ++ [ final.ecto ];
});
# The remainder are Git dependencies (and their deps) that are not supported by mix2nix currently.
web_push_encryption = buildMix rec {
name = "web_push_encryption";
version = "0.3.1";
src = fetchFromGitHub {
owner = "danhper";
repo = "elixir-web-push-encryption";
rev = "70f00d06cbd88c9ac382e0ad2539e54448e9d8da";
sha256 = "sha256-b4ZMrt/8n2sPUFtCDRTwXS1qWm5VlYdbx8qC0R0boOA=";
};
beamDeps = with final; [ httpoison jose ];
};
icalendar = buildMix rec {
name = "icalendar";
version = "unstable-2022-04-10";
src = fetchFromGitHub {
owner = "tcitworld";
repo = name;
rev = "1033d922c82a7223db0ec138e2316557b70ff49f";
sha256 = "sha256-N3bJZznNazLewHS4c2B7LP1lgxd1wev+EWVlQ7rOwfU=";
};
beamDeps = with final; [ mix_test_watch ex_doc timex ];
};
erlport = buildRebar3 rec {
name = "erlport";
version = "0.10.1-compat";
src = fetchFromGitHub {
owner = "tcitworld";
repo = name;
rev = "1f8f4b1a50ecdf7e959090fb566ac45c63c39b0b";
sha256 = "sha256-NkoGAW+1MTL0p7uUHl89GcQsbcfyAg/sMr417jUWMNM=";
};
};
exkismet = buildMix rec {
name = "exkismet";
version = "0.0.1";
src = fetchFromGitHub {
owner = "tcitworld";
repo = name;
rev = "8b5485fde00fafbde20f315bec387a77f7358334";
sha256 = "sha256-ttgCWoBKU7VTjZJBhZNtqVF4kN7psBr/qOeR65MbTqw=";
};
beamDeps = with final; [ httpoison ];
};
rajska = buildMix rec {
name = "rajska";
version = "0.0.1";
src = fetchFromGitHub {
owner = "tcitworld";
repo = name;
rev = "0c036448e261e8be6a512581c592fadf48982d84";
sha256 = "sha256-4pfply1vTAIT2Xvm3kONmrCK05xKfXFvcb8EKoSCXBE=";
};
beamDeps = with final; [ httpoison absinthe ];
};
});
};
preConfigure = ''
export LANG=C.UTF-8 # fix elixir locale warning
'';
# Install the compiled js part
preBuild =
''
cp -a "${mobilizon-frontend}/libexec/mobilizon/deps/priv/static" ./priv
chmod 770 -R ./priv
'';
postBuild = ''
mix phx.digest --no-deps-check
'';
passthru = {
tests.smoke-test = nixosTests.mobilizon;
updateScript = writeShellScriptBin "update.sh" ''
set -eou pipefail
SRC=$(nix path-info .#mobilizon.src)
${mix2nix}/bin/mix2nix $SRC/mix.lock > pkgs/servers/mobilizon/mix.nix
cat $SRC/js/package.json > pkgs/servers/mobilizon/package.json
'';
};
meta = with lib; {
description = "Mobilizon is an online tool to help manage your events, your profiles and your groups";
homepage = "https://joinmobilizon.org/";
license = licenses.agpl3Plus;
maintainers = with maintainers; [ minijackson erictapen ];
};
}

View File

@ -0,0 +1,42 @@
{ lib, callPackage, mkYarnPackage, fetchYarnDeps, imagemagick }:
let
common = callPackage ./common.nix { };
in
mkYarnPackage rec {
src = "${common.src}/js";
offlineCache = fetchYarnDeps {
yarnLock = src + "/yarn.lock";
sha256 = "sha256-yvId4NG1RABQd27RbSYki6AOFWWr5C97QPWEcnK77OE=";
};
packageJSON = ./package.json;
# Somehow $out/deps/mobilizon/node_modules ends up only containing nothing
# more than a .bin directory otherwise.
yarnPostBuild = ''
rm -rf $out/deps/mobilizon/node_modules
ln -s $out/node_modules $out/deps/mobilizon/node_modules
'';
buildPhase = ''
runHook preBuild
yarn run build
runHook postBuild
'';
doCheck = true;
checkPhase = "yarn run test";
nativeBuildInputs = [ imagemagick ];
meta = with lib; {
description = "Frontend for the Mobilizon server";
homepage = "https://joinmobilizon.org/";
license = licenses.agpl3Plus;
maintainers = with maintainers; [ minijackson erictapen ];
};
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
{
"name": "mobilizon",
"version": "3.1.3",
"private": true,
"scripts": {
"dev": "vite",
"preview": "vite preview",
"build": "yarn run build:assets && yarn run build:pictures",
"lint": "eslint --ext .ts,.vue --ignore-path .gitignore --fix src",
"format": "prettier . --write",
"build:assets": "vite build",
"build:pictures": "bash ./scripts/build/pictures.sh",
"story:dev": "histoire dev",
"story:build": "histoire build",
"story:preview": "histoire preview",
"test": "vitest",
"coverage": "vitest run --coverage",
"prepare": "cd ../ && husky install"
},
"lint-staged": {
"**/*.{js,ts,vue}": [
"eslint --fix",
"prettier --write"
]
},
"dependencies": {
"@absinthe/socket": "^0.2.1",
"@absinthe/socket-apollo-link": "^0.2.1",
"@apollo/client": "^3.3.16",
"@oruga-ui/oruga-next": "^0.6.0",
"@sentry/tracing": "^7.1",
"@sentry/vue": "^7.1",
"@tiptap/core": "^2.0.0-beta.41",
"@tiptap/extension-blockquote": "^2.0.0-beta.25",
"@tiptap/extension-bold": "^2.0.0-beta.24",
"@tiptap/extension-bubble-menu": "^2.0.0-beta.9",
"@tiptap/extension-bullet-list": "^2.0.0-beta.23",
"@tiptap/extension-document": "^2.0.0-beta.15",
"@tiptap/extension-dropcursor": "^2.0.0-beta.25",
"@tiptap/extension-gapcursor": "^2.0.0-beta.33",
"@tiptap/extension-heading": "^2.0.0-beta.23",
"@tiptap/extension-history": "^2.0.0-beta.21",
"@tiptap/extension-image": "^2.0.0-beta.6",
"@tiptap/extension-italic": "^2.0.0-beta.24",
"@tiptap/extension-link": "^2.0.0-beta.8",
"@tiptap/extension-list-item": "^2.0.0-beta.19",
"@tiptap/extension-mention": "^2.0.0-beta.42",
"@tiptap/extension-ordered-list": "^2.0.0-beta.24",
"@tiptap/extension-paragraph": "^2.0.0-beta.22",
"@tiptap/extension-placeholder": "^2.0.0-beta.199",
"@tiptap/extension-strike": "^2.0.0-beta.26",
"@tiptap/extension-text": "^2.0.0-beta.15",
"@tiptap/extension-underline": "^2.0.0-beta.7",
"@tiptap/pm": "^2.0.0-beta.220",
"@tiptap/suggestion": "^2.0.0-beta.195",
"@tiptap/vue-3": "^2.0.0-beta.96",
"@vue-a11y/announcer": "^2.1.0",
"@vue-a11y/skip-to": "^2.1.2",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"@vue/apollo-composable": "^4.0.0-beta.5",
"@vue/compiler-sfc": "^3.2.37",
"@vueuse/core": "^10.0.2",
"@vueuse/head": "^1.0",
"@vueuse/router": "^10.0.2",
"apollo-absinthe-upload-link": "^1.5.0",
"autoprefixer": "^10",
"blurhash": "^2.0.0",
"date-fns": "^2.16.0",
"date-fns-tz": "^2.0.0",
"floating-vue": "^2.0.0-beta.17",
"graphql": "^15.8.0",
"graphql-tag": "^2.10.3",
"hammerjs": "^2.0.8",
"intersection-observer": "^0.12.0",
"jwt-decode": "^3.1.2",
"leaflet": "^1.4.0",
"leaflet.locatecontrol": "^0.79",
"leaflet.markercluster": "^1.5.3",
"lodash": "^4.17.11",
"ngeohash": "^0.6.3",
"p-debounce": "^4.0.0",
"phoenix": "^1.6",
"postcss": "^8",
"register-service-worker": "^1.7.2",
"sanitize-html": "^2.5.3",
"tailwindcss": "^3",
"tippy.js": "^6.2.3",
"unfetch": "^5.0.0",
"vue": "^3.2.37",
"vue-i18n": "9",
"vue-material-design-icons": "^5.1.2",
"vue-matomo": "^4.1.0",
"vue-plausible": "^1.3.1",
"vue-router": "4",
"vue-scrollto": "^2.17.1",
"vue-use-route-query": "^1.1.0",
"zhyswan-vuedraggable": "^4.1.3"
},
"devDependencies": {
"@histoire/plugin-vue": "^0.16.1",
"@playwright/test": "^1.25.1",
"@rushstack/eslint-patch": "^1.1.4",
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.4",
"@types/hammerjs": "^2.0.41",
"@types/leaflet": "^1.5.2",
"@types/leaflet.locatecontrol": "^0.74",
"@types/leaflet.markercluster": "^1.5.1",
"@types/lodash": "^4.14.141",
"@types/ngeohash": "^0.6.2",
"@types/phoenix": "^1.5.2",
"@types/sanitize-html": "^2.5.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vitest/coverage-c8": "^0.32.2",
"@vitest/ui": "^0.32.2",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/test-utils": "^2.0.2",
"eslint": "^8.21.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^9.3.0",
"flush-promises": "^1.0.2",
"histoire": "^0.16.1",
"husky": "^8.0.3",
"jsdom": "^22.0.0",
"lint-staged": "^13.2.2",
"mock-apollo-client": "^1.1.0",
"prettier": "^2.2.1",
"prettier-eslint": "^15.0.1",
"rollup-plugin-visualizer": "^5.7.1",
"sass": "^1.34.1",
"typescript": "~5.1.3",
"vite": "^4.0.4",
"vite-plugin-pwa": "^0.16.4",
"vitest": "^0.32.2",
"vue-i18n-extract": "^2.0.4"
}
}

View File

@ -6001,6 +6001,10 @@ with pkgs;
moar = callPackage ../tools/misc/moar { };
mobilizon = callPackage ../servers/mobilizon {
mobilizon-frontend = callPackage ../servers/mobilizon/frontend.nix { };
};
molly-brown = callPackage ../servers/gemini/molly-brown { };
monetdb = callPackage ../servers/sql/monetdb { };