├── .gitignore ├── LICENSE ├── Makefile ├── Readme.md ├── container ├── .tmp ├── flake.lock └── flake.nix ├── docs └── dev-upgrading.md ├── fetchFunkwhaleCode.sh ├── flake.lock ├── flake.nix ├── funkwhale.patch ├── mkFunkwhaleFront.sh ├── module.nix └── vendor.yml /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | .tmp 3 | funkwhale_upstream 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Henri Bourcereau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | update: 2 | nix flake lock --update-input nixpkgs 3 | local: 4 | cd container && nix flake lock --update-input funkwhale --update-input typesense && cd - 5 | sudo nixos-container destroy funkwhale 6 | sudo nixos-container create funkwhale --flake ./container/ 7 | sudo nixos-container start funkwhale 8 | root: 9 | sudo nixos-container root-login funkwhale 10 | superuser: 11 | sudo nixos-container run funkwhale -- sudo --user=funkwhale sh -c 'cd /srv/funkwhale && ./createSuperUser.sh' 12 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Funkwhale flake 2 | 3 | Funkwhale 1.3.3 for NixOS 23.05 4 | 5 | Below is an example of a nixos configuration using this flake : 6 | 7 | ```nix 8 | { 9 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; 10 | inputs.funkwhale.url = "github:mmai/funkwhale-flake"; 11 | 12 | outputs = { self, nixpkgs, funkwhale }: 13 | let 14 | system = "x86_64-linux"; 15 | djangoSecretFile = pkgs.writeText "djangoSecret" "test123"; # DON'T DO THIS IN PRODUCTION - the password file will be world-readable in the Nix Store! 16 | in { 17 | nixosConfigurations = { 18 | 19 | server-hostname = nixpkgs.lib.nixosSystem { 20 | system = system; 21 | modules = [ 22 | nixpkgs.nixosModules.notDetected 23 | funkwhale.nixosModule 24 | ( { config, pkgs, ... }: 25 | { imports = [ ./hardware-configuration.nix ]; 26 | 27 | nix = { 28 | package = pkgs.nixUnstable; 29 | extraOptions = '' 30 | experimental-features = nix-command flakes 31 | ''; 32 | }; 33 | 34 | nixpkgs.overlays = [ funkwhale.overlay ]; 35 | 36 | services.funkwhale = { 37 | enable = true; 38 | hostname = "funkwhale.rhumbs.fr"; 39 | defaultFromEmail = "noreply@funkwhale.rhumbs.fr"; 40 | protocol = "https"; 41 | # forceSSL = false; # uncomment when LetsEncrypt needs to access "http:" in order to check domain 42 | api = { 43 | djangoSecretKeyFile = "${djangoSecretFile}"; 44 | }; 45 | }; 46 | 47 | security.acme = { 48 | email = "your@email.com"; 49 | acceptTerms = true; 50 | }; 51 | 52 | # Overrides default 30M 53 | services.nginx.clientMaxBodySize = "100m"; 54 | 55 | services.fail2ban.enable = true; 56 | networking.firewall.allowedTCPPorts = [ 80 443 ]; 57 | }) 58 | ]; 59 | }; 60 | 61 | }; 62 | }; 63 | } 64 | ``` 65 | 66 | ## Imports 67 | 68 | * Copy audio files to the server on the _/srv/funkwhale/music/import/_ directory 69 | * execute the import command as _funkwhale_ user, with the library id as a parameter 70 | ``` 71 | su -l funkwhale -s /bin/sh -c "funkwhale-manage import_files '/srv/funkwhale/music/imports' --recursive --noinput --in-place" 72 | ``` 73 | 74 | ## Test on a local container 75 | 76 | - start the funkwhale services in a container on the local machine : `make local` 77 | - wait 30s for the bootstraping of funkwhale services, then create the super user : `make superuser` 78 | - connect to the local service: 79 | Get the ip address of the container : `machinectl`, which output something like this : 80 | ``` 81 | MACHINE CLASS SERVICE OS VERSION ADDRESSES 82 | funkwhale container systemd-nspawn nixos 23.05 10.233.2.2… 83 | ``` 84 | 85 | Then browse to the ip `firefox http://10.233.2.2` and login with the super user credentials you created. 86 | -------------------------------------------------------------------------------- /container/.tmp: -------------------------------------------------------------------------------- 1 | /nix/store/mdbhsri3x4wgfbjly02akha1ihyjxs2v-nixos-system-funkwhale-20.09.20210306.37b47da -------------------------------------------------------------------------------- /container/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "funkwhale": { 4 | "inputs": { 5 | "nixpkgs": "nixpkgs" 6 | }, 7 | "locked": { 8 | "lastModified": 1695558379, 9 | "narHash": "sha256-cfCg+laLNNZjnvhmVnsfS6wqS0gRs6wVRoFFmmDuHhw=", 10 | "owner": "mmai", 11 | "repo": "funkwhale-flake", 12 | "rev": "178b7dbe1e01213546aa17def9f7970dc92c1166", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "mmai", 17 | "repo": "funkwhale-flake", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1695416179, 24 | "narHash": "sha256-610o1+pwbSu+QuF3GE0NU5xQdTHM3t9wyYhB9l94Cd8=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "715d72e967ec1dd5ecc71290ee072bcaf5181ed6", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-23.05", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs_2": { 38 | "locked": { 39 | "lastModified": 1688939073, 40 | "narHash": "sha256-jYhYjeK5s6k8QS3i+ovq9VZqBJaWbxm7awTKNhHL9d0=", 41 | "owner": "NixOS", 42 | "repo": "nixpkgs", 43 | "rev": "8df7a67abaf8aefc8a2839e0b48f92fdcf69a38b", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "NixOS", 48 | "ref": "nixos-23.05", 49 | "repo": "nixpkgs", 50 | "type": "github" 51 | } 52 | }, 53 | "root": { 54 | "inputs": { 55 | "funkwhale": "funkwhale", 56 | "nixpkgs": "nixpkgs_2" 57 | } 58 | } 59 | }, 60 | "root": "root", 61 | "version": 7 62 | } 63 | -------------------------------------------------------------------------------- /container/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; 3 | inputs.funkwhale.url = "github:mmai/funkwhale-flake"; 4 | # inputs.funkwhale.url = "/home/henri/travaux/nix_flakes/funkwhale"; 5 | # 6 | # inputs.funkwhale.url = "/home/henri/travaux/perso/funkwhale-flake"; 7 | # inputs.typesense.url = "/home/henri/travaux/nix_flakes/typesense-flake"; 8 | 9 | # outputs = { self, nixpkgs, funkwhale, typesense }: 10 | outputs = { self, nixpkgs, funkwhale }: 11 | { 12 | nixosConfigurations = { 13 | 14 | container = nixpkgs.lib.nixosSystem { 15 | system = "x86_64-linux"; 16 | 17 | modules = [ 18 | funkwhale.nixosModules.default 19 | # typesense.nixosModules.default 20 | ( { pkgs, ... }: 21 | let 22 | hostname = "funkwhale"; 23 | secretFile = pkgs.writeText "djangoSecret" "test123"; # DON'T DO THIS IN PRODUCTION - the password file will be world-readable in the Nix Store! 24 | in { 25 | boot.isContainer = true; 26 | 27 | # Let 'nixos-version --json' know about the Git revision 28 | # of this flake. 29 | system.configurationRevision = nixpkgs.lib.mkIf (self ? rev) self.rev; 30 | system.stateVersion = "23.05"; 31 | 32 | # Network configuration. 33 | networking.useDHCP = false; 34 | networking.firewall.allowedTCPPorts = [ 80 ]; 35 | networking.hostName = hostname; 36 | 37 | nixpkgs.overlays = [ funkwhale.overlays.default ]; 38 | 39 | # services.typesense = { 40 | # enable = true; 41 | # apiKey = "my typesense key"; 42 | # }; 43 | services.funkwhale = { 44 | enable = true; 45 | hostname = hostname; 46 | # typesenseKey = "my typesense key"; 47 | defaultFromEmail = "noreply@funkwhale.rhumbs.fr"; 48 | protocol = "http"; # no ssl for virtualbox 49 | forceSSL = false; # uncomment when LetsEncrypt needs to access "http:" in order to check domain 50 | api = { 51 | djangoSecretKeyFile = "${secretFile}"; 52 | }; 53 | }; 54 | 55 | # Overrides default 30M 56 | services.nginx.clientMaxBodySize = "100m"; 57 | 58 | environment.systemPackages = with pkgs; [ neovim ]; 59 | }) 60 | ]; 61 | }; 62 | 63 | }; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /docs/dev-upgrading.md: -------------------------------------------------------------------------------- 1 | 2 | # Dev notes : how to upgrade 3 | 4 | ## Update funkwhale nixos pkgs 5 | 6 | Get last Funkwhale release source code in order to compare with previous release. 7 | 8 | ```sh 9 | ./fetchFunkwhaleCode.sh 1.3.1 10 | ``` 11 | 12 | ### Module 13 | 14 | See changes in https://docs.funkwhale.audio/changelog.html (look for manual actions...) 15 | 16 | Compare releases `meld funkwhale_upstream_1.2.9/deploy/ funkwhale_upstream_1.3.1/deploy/` 17 | 18 | Look at theses files and make changes in _module.nix_ : 19 | - deploy/*.service 20 | - deploy/nginx.template 21 | 22 | Edit module in `nixos/modules/services/web-apps/funkwhale/` 23 | 24 | ### Package 25 | 26 | Look for requirements changes 27 | * system packages in api/pyproject.toml 28 | * python packages : checkout the _develop_ branch and look at the api/poetry.lock file at the commit just after the merge of release tag 29 | 30 | ```sh 31 | ./fetchFunkwhaleCode.sh 1.3.1 32 | 33 | grep "name\|^version =" funkwhale_upstream_1.3.1/api/poetry.lock > docs/pythonDeps_1.3.1.txt 34 | diff docs/pythonDeps_1.2.9.txt docs/pythonDeps_1.3.1.txt 35 | ``` 36 | 37 | 38 | Edit flake.nix 39 | 40 | * update release version 41 | * update sha256 checksums 42 | 43 | ### Test 44 | 45 | ```sh 46 | make local 47 | make root 48 | ``` 49 | 50 | In the container : 51 | 52 | ```sh 53 | systemctl status 54 | 55 | ``` 56 | -------------------------------------------------------------------------------- /fetchFunkwhaleCode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Récupère une version spécifique des sources Funkwhale 4 | # permet de comparer les fichiers de dépendances et déploiement 5 | # ex pour dépendances python : 6 | # ./fetchFunkwhaleCode.sh 1.3.1 7 | # grep "name\|^version =" funkwhale_upstream_1.3.1/api/poetry.lock > docs/pythonDeps_1.3.1.txt 8 | # diff docs/pythonDeps_1.2.9.txt docs/pythonDeps_1.3.1.txt 9 | # pour évolutions déploiement : 10 | # meld funkwhale_upstream_1.2.9/deploy/ funkwhale_upstream_1.3.1/deploy/ 11 | 12 | VERSION=$1 # ex: 1.2.7 13 | REPO_PATH="./funkwhale_upstream_$VERSION/" 14 | 15 | [ ! -d $REPO_PATH ] && git clone https://dev.funkwhale.audio/funkwhale/funkwhale.git $REPO_PATH 16 | cd $REPO_PATH 17 | 18 | echo "Fetching develop commit for $VERSION" 19 | git checkout develop && git pull 20 | COMMIT=$(git log --reverse --ancestry-path $VERSION..develop --oneline | head -1 | awk '{print $1}') 21 | echo $COMMIT 22 | git checkout $COMMIT 23 | 24 | # affiche les versions des packages python 25 | grep "name\|^version =" api/poetry.lock 26 | 27 | cd - 28 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1688392541, 6 | "narHash": "sha256-lHrKvEkCPTUO+7tPfjIcb7Trk6k31rz18vkyqmkeJfY=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-22.11", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Funkwhale"; 3 | 4 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; 5 | 6 | outputs = { self, nixpkgs }: 7 | let 8 | version = "1.3.3"; 9 | systems = [ "x86_64-linux" "i686-linux" "aarch64-linux" ]; 10 | forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); 11 | # Memoize nixpkgs for different platforms for efficiency. 12 | nixpkgsFor = forAllSystems (system: 13 | import nixpkgs { 14 | inherit system; 15 | overlays = [ self.overlays.default ]; 16 | } 17 | ); 18 | in 19 | { 20 | overlays.default = final: prev: { 21 | 22 | funkwhale-front = with final; stdenv.mkDerivation { 23 | pname = "funkwhale-front"; 24 | inherit version; 25 | 26 | nativeBuildInputs = [ pkgs.unzip ]; 27 | unpackCmd = "unzip $curSrc"; 28 | 29 | src = fetchurl { 30 | url = 31 | "https://dev.funkwhale.audio/funkwhale/funkwhale/-/jobs/artifacts/${version}/download?job=build_front"; 32 | sha256 = "sha256-0DOcTkKKU+REoMTJQW11hrrND88eTQ2+tIuQRzxa0rw="; 33 | }; 34 | 35 | installPhase = '' 36 | mkdir $out 37 | cp -R ./dist/* $out 38 | ''; 39 | }; 40 | 41 | funkwhale = with final; (stdenv.mkDerivation { 42 | pname = "funkwhale"; 43 | inherit version; 44 | src = fetchurl { 45 | url = "https://dev.funkwhale.audio/funkwhale/funkwhale/-/archive/${version}/funkwhale-${version}.tar.bz2"; 46 | sha256 = "sha256-bcJMY0Ndnlq5hKJVYMO/qwckYTZtABhDJem2C9E/zB4="; 47 | }; 48 | 49 | patches = [ ./funkwhale.patch ]; 50 | 51 | installPhase = '' 52 | sed "s|env -S|env|g" -i front/scripts/*.sh 53 | mkdir $out 54 | cp -R ./* $out 55 | ''; 56 | 57 | meta = with lib; { 58 | description = "A modern, convivial and free music server"; 59 | homepage = https://funkwhale.audio/; 60 | license = licenses.agpl3; 61 | platforms = platforms.linux; 62 | maintainers = with maintainers; [ mmai ]; 63 | }; 64 | }); 65 | 66 | # ------------- new packages ------------------- 67 | 68 | requests-http-message-signatures = with final; with pkgs.python3.pkgs; (buildPythonPackage rec { 69 | pname = "requests-http-message-signatures"; 70 | version = "0.3.1"; 71 | src = fetchPypi { 72 | inherit pname version; 73 | sha256 = "sha256-AjW7XNP0p9ZZZF4qyTfacnkTIUNOd1cPfiEELlEIINo="; 74 | }; 75 | propagatedBuildInputs = [ requests cryptography ]; 76 | doCheck = false; 77 | }); 78 | 79 | 80 | django-cache-memoize = with final; with pkgs.python3.pkgs; (buildPythonPackage rec { 81 | pname = "django-cache-memoize"; 82 | version = "0.1.10"; 83 | src = fetchPypi { 84 | inherit pname version; 85 | sha256 = "sha256-Y+j6okWkHA262EOAfp8hpuWeuo5uUN8xD99khaZ0mEM="; 86 | }; 87 | propagatedBuildInputs = [ ]; 88 | doCheck = false; 89 | }); 90 | 91 | django-versatileimagefield = with final; with pkgs.python3.pkgs; (buildPythonPackage rec { 92 | pname = "django-versatileimagefield"; 93 | version = "3.0"; 94 | 95 | src = fetchPypi { 96 | inherit pname version; 97 | hash = "sha256-FlHbLtNthDz7F4jyYBRyopPZuoZyk2m29uVZERI1esc="; 98 | }; 99 | propagatedBuildInputs = [ django pillow python-magic ]; 100 | 101 | nativeCheckInputs = [ django ]; 102 | 103 | # tests not included with pypi release 104 | doCheck = false; 105 | 106 | pythonImportsCheck = [ "versatileimagefield" ]; 107 | 108 | meta = with lib; { 109 | description = "Replaces django's ImageField with a more flexible interface"; 110 | homepage = "https://github.com/respondcreate/django-versatileimagefield/"; 111 | license = licenses.mit; 112 | maintainers = with maintainers; [ mmai ]; 113 | }; 114 | }); 115 | 116 | # ------------- will be in 23.11 117 | jsonschema-specifications = with final; with pkgs.python3.pkgs; (buildPythonPackage rec { 118 | pname = "jsonschema-specifications"; 119 | version = "2023.7.1"; 120 | format = "pyproject"; 121 | 122 | disabled = pythonOlder "3.8"; 123 | 124 | src = fetchPypi { 125 | pname = "jsonschema_specifications"; 126 | inherit version; 127 | hash = "sha256-yRpQQE6Iofa6QGNneOLuCPbiTFYT/kxTrCRXilp/crs="; 128 | }; 129 | 130 | nativeBuildInputs = [ 131 | hatch-vcs 132 | hatchling 133 | ]; 134 | 135 | propagatedBuildInputs = [ 136 | referencing 137 | ] ++ lib.optionals (pythonOlder "3.9") [ 138 | importlib-resources 139 | ]; 140 | 141 | nativeCheckInputs = [ 142 | pytestCheckHook 143 | ]; 144 | 145 | pythonImportsCheck = [ 146 | "jsonschema_specifications" 147 | ]; 148 | 149 | meta = with lib; { 150 | description = "Support files exposing JSON from the JSON Schema specifications"; 151 | homepage = "https://github.com/python-jsonschema/jsonschema-specifications"; 152 | license = licenses.mit; 153 | maintainers = with maintainers; [ SuperSandro2000 ]; 154 | }; 155 | }); 156 | 157 | referencing = with final; with pkgs.python3.pkgs; (buildPythonPackage rec { 158 | pname = "referencing"; 159 | version = "0.30.0"; 160 | format = "pyproject"; 161 | 162 | disabled = pythonOlder "3.7"; 163 | 164 | src = fetchFromGitHub { 165 | owner = "python-jsonschema"; 166 | repo = "referencing"; 167 | rev = "refs/tags/v${version}"; 168 | fetchSubmodules = true; 169 | hash = "sha256-nJSnZM3gg2+yfFAnOJzzXsmIEQdNf5ypt5R0O60NphA="; 170 | }; 171 | 172 | SETUPTOOLS_SCM_PRETEND_VERSION = version; 173 | 174 | nativeBuildInputs = [ 175 | hatch-vcs 176 | hatchling 177 | ]; 178 | 179 | propagatedBuildInputs = [ 180 | attrs 181 | rpds-py 182 | ]; 183 | 184 | nativeCheckInputs = [ 185 | jsonschema 186 | pytest-subtests 187 | pytestCheckHook 188 | ]; 189 | 190 | # avoid infinite recursion with jsonschema 191 | doCheck = false; 192 | 193 | passthru.tests.referencing = self.overridePythonAttrs { doCheck = true; }; 194 | 195 | pythonImportsCheck = [ 196 | "referencing" 197 | ]; 198 | 199 | meta = with lib; { 200 | description = "Cross-specification JSON referencing"; 201 | homepage = "https://github.com/python-jsonschema/referencing"; 202 | changelog = "https://github.com/python-jsonschema/referencing/blob/${version}/CHANGELOG.rst"; 203 | license = licenses.mit; 204 | maintainers = with maintainers; [ fab ]; 205 | }; 206 | }); 207 | 208 | rpds-py = with final; with pkgs.python3.pkgs; (buildPythonPackage rec { 209 | pname = "rpds-py"; 210 | version = "0.9.2"; 211 | format = "pyproject"; 212 | 213 | disabled = pythonOlder "3.8"; 214 | 215 | src = fetchPypi { 216 | pname = "rpds_py"; 217 | inherit version; 218 | hash = "sha256-jXDo8UkA8mV8JJ6k3vljvthqKbgfgfW3a1qSFWgN6UU="; 219 | }; 220 | 221 | cargoDeps = rustPlatform.fetchCargoTarball { 222 | inherit src; 223 | name = "${pname}-${version}"; 224 | hash = "sha256-2LiQ+beFj9+kykObPNtqcg+F+8wBDzvWcauwDLHa7Yo="; 225 | }; 226 | 227 | nativeBuildInputs = [ 228 | rustPlatform.cargoSetupHook 229 | rustPlatform.maturinBuildHook 230 | cargo 231 | rustc 232 | ]; 233 | 234 | buildInputs = lib.optionals stdenv.hostPlatform.isDarwin [ 235 | libiconv 236 | ]; 237 | 238 | nativeCheckInputs = [ 239 | pytestCheckHook 240 | ]; 241 | 242 | pythonImportsCheck = [ 243 | "rpds" 244 | ]; 245 | 246 | meta = with lib; { 247 | description = "Python bindings to Rust's persistent data structures (rpds"; 248 | homepage = "https://pypi.org/project/rpds-py/"; 249 | license = licenses.mit; 250 | maintainers = with maintainers; [ fab ]; 251 | }; 252 | }); 253 | 254 | }; 255 | 256 | 257 | packages = forAllSystems (system: { 258 | inherit (nixpkgsFor.${system}) funkwhale; 259 | inherit (nixpkgsFor.${system}) funkwhale-front; 260 | }); 261 | 262 | defaultPackage = forAllSystems (system: self.packages.${system}.funkwhale); 263 | 264 | 265 | # funkwhale service module 266 | nixosModules.default = (import ./module.nix); 267 | 268 | }; 269 | } 270 | -------------------------------------------------------------------------------- /funkwhale.patch: -------------------------------------------------------------------------------- 1 | diff --git a/api/manage.py b/api/manage.py 2 | index 2c50c4082..e12abdb7f 100755 3 | --- a/api/manage.py 4 | +++ b/api/manage.py 5 | @@ -1,15 +1,6 @@ 6 | #!/usr/bin/env python3 7 | 8 | -import warnings 9 | - 10 | from funkwhale_api.main import main 11 | 12 | -warnings.warn( 13 | - DeprecationWarning( 14 | - "the './manage.py' script has been deprecated, please use the 'funkwhale-manage' " 15 | - "entrypoint instead (e.g. 'funkwhale-manage migrate')" 16 | - ) 17 | -) 18 | - 19 | if __name__ == "__main__": 20 | raise SystemExit(main()) 21 | -------------------------------------------------------------------------------- /mkFunkwhaleFront.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Obsolete. We don't compile front assets ourselves, we use the pre-compiled package from the funkwhale ci server 4 | 5 | FUNKWHALE_VERSION="1.2.7" 6 | 7 | LOCAL='./funkwhale-front' 8 | TMP=$LOCAL'_tmp' 9 | 10 | rm -rf $TMP && mkdir $TMP 11 | cd $TMP 12 | 13 | DIST="https://dev.funkwhale.audio/funkwhale/funkwhale/-/archive/$FUNKWHALE_VERSION/funkwhale-$FUNKWHALE_VERSION.tar.bz2" 14 | curl $DIST | tar xj 15 | mv funkwhale-*/front/* . && rm -rf funkwhale-* 16 | sed -i 's/scripts\/fix-fomantic/python scripts\/fix-fomantic/' scripts/fix-fomantic-css.sh 17 | yarn 18 | yarn build 19 | 20 | cd .. 21 | mv $TMP/dist $LOCAL 22 | rm -rf $TMP 23 | git add $LOCAL 24 | -------------------------------------------------------------------------------- /module.nix: -------------------------------------------------------------------------------- 1 | {config, lib, pkgs, ...}: 2 | 3 | with lib; 4 | 5 | let 6 | 7 | pythonPackagesOverrides = self: super: { 8 | }; 9 | 10 | 11 | pythonEnv = (pkgs.python3.override { packageOverrides = pythonPackagesOverrides; }).withPackages (ps: [ # default is python3.10 on nixos 23.05 12 | # pythonEnv = pkgs.python3.withPackages (ps: [ 13 | # --- packages not in nixpkgs 14 | pkgs.requests-http-message-signatures 15 | pkgs.django-cache-memoize # --- packages from nixpkgs 16 | ps.PyLD 17 | ps.aiohttp 18 | ps.aioredis 19 | ps.arrow 20 | ps.astroid 21 | ps.autobahn 22 | ps.av 23 | # ps.backports-zoneinfo # fails with : backports-zoneinfo-0.2.1 not supported for interpreter python3.10 24 | pkgs.python311Packages.backports-zoneinfo 25 | ps.bleach 26 | ps.boto3 27 | ps.cached-property 28 | ps.celery 29 | ps.channels 30 | ps.channels-redis 31 | ps.click 32 | ps.daphne 33 | ps.dill 34 | ps.django 35 | ps.django-allauth 36 | ps.django-auth-ldap 37 | ps.django-cacheops 38 | ps.django-cleanup 39 | ps.django-cors-headers 40 | ps.django-dynamic-preferences 41 | ps.django-extensions 42 | ps.django-filter 43 | ps.django-oauth-toolkit 44 | ps.django-redis 45 | ps.django-storages 46 | # ps.django-versatileimagefield # fails with ERROR: Could not find a version that satisfies the requirement Django>=3.0 ( 47 | pkgs.django-versatileimagefield 48 | ps.django_environ 49 | ps.django_taggit 50 | ps.djangorestframework 51 | ps.dj-rest-auth 52 | ps.drf-spectacular 53 | ps.feedparser 54 | ps.gunicorn 55 | ps.isort 56 | ps.kombu 57 | ps.lazy-object-proxy 58 | ps.ldap 59 | ps.markdown 60 | ps.musicbrainzngs 61 | ps.mutagen 62 | ps.pendulum 63 | ps.persisting-theory 64 | ps.pillow 65 | ps.psycopg2 66 | ps.pyacoustid 67 | ps.pydub 68 | ps.pylint 69 | ps.pylint-django 70 | ps.pylint-plugin-utils 71 | ps.pyopenssl 72 | ps.pyrsistent 73 | ps.python_magic 74 | ps.pytz 75 | ps.redis 76 | ps.requests 77 | ps.service-identity 78 | ps.toml 79 | ps.tomlkit 80 | ps.unicode-slugify 81 | ps.unidecode 82 | ps.uvicorn 83 | ps.uvloop ps.httptools ps.websockets # additonal packages for uvicorn (to mimic `pip install uvicorn[standard]`) needed for websockets 84 | ps.watchdog 85 | ]); 86 | cfg = config.services.funkwhale; 87 | databasePassword = if (cfg.database.passwordFile != null) 88 | then builtins.readFile cfg.database.passwordFile 89 | else cfg.database.password; 90 | databaseUrl = if (cfg.database.createLocally && cfg.database.socket != null) 91 | then "postgresql:///${cfg.database.name}?host=${cfg.database.socket}" 92 | else "postgresql://${cfg.database.user}:${databasePassword}@${cfg.database.host}:${toString cfg.database.port}/${cfg.database.name}"; 93 | 94 | funkwhaleEnvironment = [ 95 | "FUNKWHALE_URL=${cfg.hostname}" 96 | "FUNKWHALE_HOSTNAME=${cfg.hostname}" 97 | "FUNKWHALE_PROTOCOL=${cfg.protocol}" 98 | "EMAIL_CONFIG=${cfg.emailConfig}" 99 | "DEFAULT_FROM_EMAIL=${cfg.defaultFromEmail}" 100 | "REVERSE_PROXY_TYPE=nginx" 101 | "DATABASE_URL=${databaseUrl}" 102 | "CACHE_URL=redis://localhost:${toString config.services.redis.servers."".port}/0" 103 | "MEDIA_ROOT=${cfg.api.mediaRoot}" 104 | "STATIC_ROOT=${cfg.api.staticRoot}" 105 | "DJANGO_SECRET_KEY=(cat ${cfg.api.djangoSecretKeyFile})" 106 | "MUSIC_DIRECTORY_PATH=${cfg.musicPath}" 107 | "MUSIC_DIRECTORY_SERVE_PATH=${cfg.musicPath}" 108 | "FUNKWHALE_FRONTEND_PATH=${cfg.dataDir}/front" 109 | "FUNKWHALE_PLUGINS=funkwhale_api.contrib.scrobbler" 110 | "TYPESENSE_API_KEY=${cfg.typesenseKey}" 111 | ]; 112 | funkwhaleEnvFileData = builtins.concatStringsSep "\n" funkwhaleEnvironment; 113 | funkwhaleEnvScriptData = builtins.concatStringsSep " " funkwhaleEnvironment; 114 | 115 | funkwhaleEnvFile = pkgs.writeText "funkwhale.env" funkwhaleEnvFileData; 116 | 117 | # used in systemd units 118 | funkwhaleEnv = { 119 | ENV_FILE = "${funkwhaleEnvFile}"; 120 | 121 | # XXX : copié de api/config/asgi.py ( sinon erreur lors du lancement gunicorn ) 122 | DJANGO_SETTINGS_MODULE = "config.settings.production"; 123 | ASGI_THREADS = "5"; 124 | }; 125 | funkwhaleManageScript = (pkgs.writeScriptBin "funkwhale-manage" '' 126 | ${funkwhaleEnvScriptData} ${pythonEnv.interpreter} ${pkgs.funkwhale}/api/manage.py "$@" 127 | ''); 128 | in 129 | { 130 | 131 | options = { 132 | services.funkwhale = { 133 | enable = mkEnableOption "funkwhale"; 134 | 135 | user = mkOption { 136 | type = types.str; 137 | default = "funkwhale"; 138 | description = "User under which Funkwhale is ran."; 139 | }; 140 | 141 | group = mkOption { 142 | type = types.str; 143 | default = "funkwhale"; 144 | description = "Group under which Funkwhale is ran."; 145 | }; 146 | 147 | database = { 148 | host = mkOption { 149 | type = types.str; 150 | default = "localhost"; 151 | description = "Database host address."; 152 | }; 153 | 154 | port = mkOption { 155 | type = types.int; 156 | default = 5432; 157 | description = "Database host port."; 158 | }; 159 | 160 | name = mkOption { 161 | type = types.str; 162 | default = "funkwhale"; 163 | description = "Database name."; 164 | }; 165 | 166 | user = mkOption { 167 | type = types.str; 168 | default = "funkwhale"; 169 | description = "Database user."; 170 | }; 171 | 172 | password = mkOption { 173 | type = types.str; 174 | default = ""; 175 | description = '' 176 | The password corresponding to . 177 | Warning: this is stored in cleartext in the Nix store! 178 | Use instead. 179 | ''; 180 | }; 181 | 182 | passwordFile = mkOption { 183 | type = types.nullOr types.path; 184 | default = null; 185 | example = "/run/keys/funkwhale-dbpassword"; 186 | description = '' 187 | A file containing the password corresponding to 188 | . 189 | ''; 190 | }; 191 | 192 | socket = mkOption { 193 | type = types.nullOr types.path; 194 | default = "/run/postgresql"; 195 | description = "Path to the unix socket file to use for authentication for local connections."; 196 | }; 197 | 198 | createLocally = mkOption { 199 | type = types.bool; 200 | default = true; 201 | description = "Create the database and database user locally."; 202 | }; 203 | }; 204 | 205 | dataDir = mkOption { 206 | type = types.str; 207 | default = "/srv/funkwhale"; 208 | description = '' 209 | Where to keep the funkwhale data. 210 | ''; 211 | }; 212 | 213 | typesenseKey = mkOption { 214 | type = types.str; 215 | default = "my-secret-typesense-key"; 216 | description = '' 217 | Typesense API key. 218 | ''; 219 | }; 220 | 221 | apiIp = mkOption { 222 | type = types.str; 223 | default = "127.0.0.1"; 224 | description = '' 225 | Funkwhale API IP. 226 | ''; 227 | }; 228 | 229 | webWorkers = mkOption { 230 | type = types.int; 231 | default = 1; 232 | description = '' 233 | Funkwhale number of web workers. 234 | ''; 235 | }; 236 | 237 | apiPort = mkOption { 238 | type = types.port; 239 | default = 5000; 240 | description = '' 241 | Funkwhale API Port. 242 | ''; 243 | }; 244 | 245 | frontIp = mkOption { 246 | type = types.str; 247 | default = "127.0.0.1"; 248 | description = '' 249 | Funkwhale Front IP. 250 | ''; 251 | }; 252 | 253 | frontPort = mkOption { 254 | type = types.port; 255 | default = 80; 256 | # default = 8080; 257 | description = '' 258 | Funkwhale Front Port. 259 | ''; 260 | }; 261 | 262 | hostname = mkOption { 263 | type = types.str; 264 | description = '' 265 | The definitive, public domain you will use for your instance. 266 | ''; 267 | example = "funkwhale.yourdomain.net"; 268 | }; 269 | 270 | protocol = mkOption { 271 | type = types.enum [ "http" "https" ]; 272 | default = "https"; 273 | description = '' 274 | Web server protocol. 275 | ''; 276 | }; 277 | 278 | forceSSL = mkOption { 279 | type = types.bool; 280 | default = true; 281 | description = '' 282 | Force SSL : put this to 'false' when Let's Encrypt has problems calling 'http:' to check the domain 283 | ''; 284 | }; 285 | 286 | emailConfig = mkOption { 287 | type = types.str; 288 | default = "consolemail://"; 289 | description = '' 290 | Configure email sending. By default, it outputs emails to console instead of sending them. 291 | See for details. 292 | ''; 293 | example = "smtp+ssl://user@:password@youremail.host:465"; 294 | }; 295 | 296 | defaultFromEmail = mkOption { 297 | type = types.str; 298 | description = '' 299 | The email address to use to send system emails. 300 | ''; 301 | example = "funkwhale@yourdomain.net"; 302 | }; 303 | 304 | api = { 305 | mediaRoot = mkOption { 306 | type = types.str; 307 | default = "/srv/funkwhale/media"; 308 | description = '' 309 | Where media files (such as album covers or audio tracks) should be stored on your system. 310 | ''; 311 | }; 312 | 313 | staticRoot = mkOption { 314 | type = types.str; 315 | default = "/srv/funkwhale/static"; 316 | description = '' 317 | Where static files (such as API css or icons) should be compiled on your system. 318 | ''; 319 | }; 320 | 321 | djangoSecretKeyFile = mkOption { 322 | type = types.str; 323 | default = "/run/secrets/funkwhale_django_secret"; 324 | description = '' 325 | File containing the django secret key. Generate one using openssl rand -base64 45 for example. 326 | ''; 327 | }; 328 | }; 329 | 330 | musicPath = mkOption { 331 | type = types.str; 332 | default = "/srv/funkwhale/music"; 333 | description = '' 334 | In-place import settings. 335 | ''; 336 | }; 337 | 338 | }; 339 | }; 340 | 341 | config = mkIf cfg.enable { 342 | assertions = [ 343 | { assertion = cfg.database.passwordFile != null || cfg.database.password != "" || cfg.database.socket != null; 344 | message = "one of services.funkwhale.database.socket, services.funkwhale.database.passwordFile, or services.funkwhale.database.password must be set"; 345 | } 346 | { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user; 347 | message = "services.funkwhale.database.user must be set to ${cfg.user} if services.funkwhale.database.createLocally is set true"; 348 | } 349 | { assertion = cfg.database.createLocally -> cfg.database.socket != null; 350 | message = "services.funkwhale.database.socket must be set if services.funkwhale.database.createLocally is set to true"; 351 | } 352 | { assertion = cfg.database.createLocally -> cfg.database.host == "localhost"; 353 | message = "services.funkwhale.database.host must be set to localhost if services.funkwhale.database.createLocally is set to true"; 354 | } 355 | ]; 356 | 357 | users.users.funkwhale = mkIf (cfg.user == "funkwhale") { 358 | group = cfg.group; 359 | isSystemUser = true; 360 | }; 361 | 362 | users.groups.funkwhale = mkIf (cfg.group == "funkwhale") {}; 363 | 364 | services.postgresql = mkIf cfg.database.createLocally { 365 | enable = true; 366 | ensureDatabases = [ cfg.database.name ]; 367 | ensureUsers = [ 368 | { name = cfg.database.user; 369 | ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; }; 370 | } 371 | ]; 372 | }; 373 | 374 | services.redis.servers."".enable = true; 375 | 376 | services.nginx = { 377 | enable = true; 378 | appendHttpConfig = '' 379 | upstream funkwhale-api { 380 | server ${cfg.apiIp}:${toString cfg.apiPort}; 381 | } 382 | upstream funkwhale-front { 383 | server ${cfg.frontIp}:${toString cfg.frontPort}; 384 | } 385 | ''; 386 | virtualHosts = 387 | let proxyConfig = '' 388 | # global proxy conf 389 | proxy_set_header Host $host; 390 | proxy_set_header X-Real-IP $remote_addr; 391 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 392 | proxy_set_header X-Forwarded-Proto $scheme; 393 | proxy_set_header X-Forwarded-Host $host:$server_port; 394 | proxy_set_header X-Forwarded-Port $server_port; 395 | proxy_redirect off; 396 | 397 | # websocket support 398 | proxy_http_version 1.1; 399 | proxy_set_header Upgrade $http_upgrade; 400 | proxy_set_header Connection $connection_upgrade; 401 | ''; 402 | withSSL = cfg.protocol == "https"; 403 | in { 404 | "${cfg.hostname}" = { 405 | enableACME = withSSL; 406 | forceSSL = cfg.forceSSL; 407 | root = "${cfg.dataDir}/front"; 408 | # gzip config is nixos nginx recommendedGzipSettings with gzip_types 409 | # from funkwhale doc (https://docs.funkwhale.audio/changelog.html#id5) 410 | extraConfig = '' 411 | add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; 412 | add_header Referrer-Policy "strict-origin-when-cross-origin"; 413 | 414 | gzip on; 415 | gzip_disable "msie6"; 416 | gzip_proxied any; 417 | gzip_comp_level 5; 418 | gzip_types 419 | application/javascript 420 | application/vnd.geo+json 421 | application/vnd.ms-fontobject 422 | application/x-font-ttf 423 | application/x-web-app-manifest+json 424 | font/opentype 425 | image/bmp 426 | image/svg+xml 427 | image/x-icon 428 | text/cache-manifest 429 | text/css 430 | text/plain 431 | text/vcard 432 | text/vnd.rim.location.xloc 433 | text/vtt 434 | text/x-component 435 | text/x-cross-domain-policy; 436 | gzip_vary on; 437 | ''; 438 | locations = { 439 | "/api/" = { 440 | extraConfig = proxyConfig; 441 | proxyPass = "http://funkwhale-api"; 442 | }; 443 | "/" = { 444 | # proxyPass = "http://funkwhale-front"; 445 | extraConfig = '' 446 | try_files $uri $uri/ /index.html; 447 | 448 | add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'"; 449 | add_header Referrer-Policy "strict-origin-when-cross-origin"; 450 | add_header Service-Worker-Allowed "/"; 451 | expires 30d; 452 | add_header Pragma public; 453 | add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 454 | ''; 455 | }; 456 | "/front/" = { 457 | # "/front/embed.html" = { 458 | # proxyPass = "http://funkwhale-front/embed.html"; 459 | alias = "${cfg.dataDir}/front/"; 460 | extraConfig = '' 461 | add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'"; 462 | add_header Referrer-Policy "strict-origin-when-cross-origin"; 463 | add_header X-Frame-Options "" always; 464 | expires 30d; 465 | add_header Pragma public; 466 | add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 467 | ''; 468 | }; 469 | "/embed.html" = { 470 | # proxyPass = "http://funkwhale-front/embed.html"; 471 | extraConfig = '' 472 | add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:; worker-src 'self'"; 473 | add_header Referrer-Policy "strict-origin-when-cross-origin"; 474 | add_header X-Frame-Options "" always; 475 | expires 30d; 476 | add_header Pragma public; 477 | add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 478 | ''; 479 | }; 480 | "/federation/" = { 481 | extraConfig = proxyConfig; 482 | proxyPass = "http://funkwhale-api"; 483 | }; 484 | "/rest/" = { 485 | extraConfig = proxyConfig; 486 | proxyPass = "http://funkwhale-api/api/subsonic/rest/"; 487 | }; 488 | "/.well-known/" = { 489 | extraConfig = proxyConfig; 490 | proxyPass = "http://funkwhale-api"; 491 | }; 492 | 493 | "/media/" = { 494 | alias = "${cfg.api.mediaRoot}/"; 495 | extraConfig = '' 496 | add_header Access-Control-Allow-Origin '*'; 497 | add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; object-src 'none'; media-src 'self' data:"; 498 | ''; 499 | }; 500 | 501 | "/_protected/media/" = { 502 | extraConfig = '' 503 | internal; 504 | ''; 505 | alias = "${cfg.api.mediaRoot}/"; 506 | }; 507 | "/_protected/music/" = { 508 | extraConfig = '' 509 | internal; 510 | ''; 511 | alias = "${cfg.musicPath}/"; 512 | }; 513 | "/staticfiles/".alias = "${cfg.api.staticRoot}/"; 514 | 515 | # "/manifest.json" = { 516 | # return 302 ${FUNKWHALE_PROTOCOL}://${FUNKWHALE_HOSTNAME}/api/v1/instance/spa-manifest.json; 517 | # } 518 | 519 | }; 520 | }; 521 | }; 522 | }; 523 | 524 | systemd.tmpfiles.rules = [ 525 | "d ${cfg.dataDir} 0755 ${cfg.user} ${cfg.group} - -" 526 | "d ${cfg.api.mediaRoot} 0755 ${cfg.user} ${cfg.group} - -" 527 | "d ${cfg.api.staticRoot} 0755 ${cfg.user} ${cfg.group} - -" 528 | "d ${cfg.musicPath} 0755 ${cfg.user} ${cfg.group} - -" 529 | ]; 530 | 531 | systemd.targets.funkwhale = { 532 | description = "Funkwhale"; 533 | wants = ["funkwhale-server.service" "funkwhale-worker.service" "funkwhale-beat.service"]; 534 | }; 535 | systemd.services = 536 | let serviceConfig = { 537 | User = "${cfg.user}"; 538 | WorkingDirectory = "${pkgs.funkwhale}/api"; 539 | }; 540 | in { 541 | funkwhale-psql-init = mkIf cfg.database.createLocally { 542 | description = "Funkwhale database preparation"; 543 | after = [ "redis.service" "postgresql.service" ]; 544 | wantedBy = [ "funkwhale-init.service" ]; 545 | before = [ "funkwhale-init.service" ]; 546 | serviceConfig = { 547 | User = "postgres"; 548 | ExecStart = '' ${config.services.postgresql.package}/bin/psql \ 549 | -d ${cfg.database.name} -c 'CREATE EXTENSION IF NOT EXISTS \ 550 | "unaccent";CREATE EXTENSION IF NOT EXISTS "citext";' ''; 551 | }; 552 | }; 553 | funkwhale-init = { 554 | description = "Funkwhale initialization"; 555 | wantedBy = [ "funkwhale-server.service" "funkwhale-worker.service" "funkwhale-beat.service" ]; 556 | before = [ "funkwhale-server.service" "funkwhale-worker.service" "funkwhale-beat.service" ]; 557 | environment = funkwhaleEnv; 558 | serviceConfig = { 559 | User = "${cfg.user}"; 560 | Group = "${cfg.group}"; 561 | }; 562 | script = '' 563 | ${funkwhaleManageScript}/bin/funkwhale-manage migrate 564 | ${funkwhaleManageScript}/bin/funkwhale-manage collectstatic --no-input 565 | echo "#!/bin/sh 566 | 567 | ${funkwhaleManageScript}/bin/funkwhale-manage \ 568 | createsuperuser" > ${cfg.dataDir}/createSuperUser.sh 569 | chmod u+x ${cfg.dataDir}/createSuperUser.sh 570 | chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir} 571 | echo "#!/bin/sh 572 | 573 | LIBRARY_ID=\$1 574 | ${funkwhaleManageScript}/bin/funkwhale-manage \ 575 | import_files \$LIBRARY_ID '/srv/funkwhale/music/imports' --recursive --noinput --in-place" > ${cfg.dataDir}/importMusic.sh 576 | chmod u+x ${cfg.dataDir}/importMusic.sh 577 | chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir} 578 | 579 | mkdir -p ${cfg.dataDir}/config 580 | rm -f ${cfg.dataDir}/config/.env 581 | ln -s ${funkwhaleEnvFile} ${cfg.dataDir}/config/.env 582 | rm -f ${cfg.dataDir}/config/.env 583 | ln -s ${funkwhaleEnvFile} ${cfg.dataDir}/.env 584 | 585 | rm -rf ${cfg.dataDir}/front 586 | cp -r ${pkgs.funkwhale-front} ${cfg.dataDir}/front 587 | chmod -R u+rwx ${cfg.dataDir}/front 588 | ''; 589 | }; 590 | 591 | funkwhale-server = { 592 | description = "Funkwhale application server"; 593 | partOf = [ "funkwhale.target" ]; 594 | 595 | serviceConfig = serviceConfig // { 596 | ExecStart = ''${pythonEnv}/bin/gunicorn config.asgi:application \ 597 | -w ${toString cfg.webWorkers} -k uvicorn.workers.UvicornWorker \ 598 | -b ${cfg.apiIp}:${toString cfg.apiPort}''; 599 | }; 600 | environment = funkwhaleEnv; 601 | 602 | wantedBy = [ "multi-user.target" ]; 603 | }; 604 | 605 | funkwhale-worker = { 606 | description = "Funkwhale celery worker"; 607 | partOf = [ "funkwhale.target" ]; 608 | 609 | serviceConfig = serviceConfig // { 610 | RuntimeDirectory = "funkwhaleworker"; 611 | ExecStart = "${pythonEnv}/bin/celery --app=funkwhale_api.taskapp worker --loglevel=INFO --concurrency=0"; 612 | }; 613 | environment = funkwhaleEnv; 614 | 615 | wantedBy = [ "multi-user.target" ]; 616 | }; 617 | 618 | funkwhale-beat = { 619 | description = "Funkwhale celery beat process"; 620 | partOf = [ "funkwhale.target" ]; 621 | 622 | serviceConfig = serviceConfig // { 623 | RuntimeDirectory = "funkwhalebeat"; 624 | ExecStart = '' ${pythonEnv}/bin/celery --app=funkwhale_api.taskapp beat --loglevel=INFO \ 625 | --schedule="/run/funkwhalebeat/celerybeat-schedule.db" \ 626 | --pidfile="/run/funkwhalebeat/celerybeat.pid" ''; 627 | }; 628 | environment = funkwhaleEnv; 629 | 630 | wantedBy = [ "multi-user.target" ]; 631 | }; 632 | 633 | }; 634 | 635 | environment.systemPackages = [ pkgs.ffmpeg funkwhaleManageScript ]; 636 | }; 637 | 638 | meta = { 639 | maintainers = with lib.maintainers; [ mmai ]; 640 | }; 641 | } 642 | -------------------------------------------------------------------------------- /vendor.yml: -------------------------------------------------------------------------------- 1 | - ^funkwhale-front/ 2 | --------------------------------------------------------------------------------