1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7let
8 cfg = config.services.odoo;
9 format = pkgs.formats.ini { };
10in
11{
12 options = {
13 services.odoo = {
14 enable = lib.mkEnableOption "odoo, an open source ERP and CRM system";
15
16 package = lib.mkPackageOption pkgs "odoo" { };
17
18 addons = lib.mkOption {
19 type = with lib.types; listOf package;
20 default = [ ];
21 example = lib.literalExpression "[ pkgs.odoo_enterprise ]";
22 description = "Odoo addons.";
23 };
24
25 autoInit = lib.mkEnableOption "automatically initialize the DB";
26
27 autoInitExtraFlags = lib.mkOption {
28 type = with lib.types; listOf str;
29 default = [ ];
30 example =
31 lib.literalExpression # nix
32 ''
33 [ "--without-demo=all" ]
34 '';
35 description = "Extra flags passed to odoo when run for the first time by autoInit";
36 };
37
38 settings = lib.mkOption {
39 type = format.type;
40 default = { };
41 description = ''
42 Odoo configuration settings. For more details see <https://www.odoo.com/documentation/15.0/administration/install/deploy.html>
43 '';
44 example = lib.literalExpression ''
45 options = {
46 db_user = "odoo";
47 db_password="odoo";
48 };
49 '';
50 };
51
52 domain = lib.mkOption {
53 type = with lib.types; nullOr str;
54 description = "Domain to host Odoo with nginx";
55 default = null;
56 };
57 };
58 };
59
60 config = lib.mkIf (cfg.enable) (
61 let
62 cfgFile = format.generate "odoo.cfg" cfg.settings;
63 in
64 {
65 services.nginx = lib.mkIf (cfg.domain != null) {
66 upstreams = {
67 odoo.servers = {
68 "127.0.0.1:8069" = { };
69 };
70
71 odoochat.servers = {
72 "127.0.0.1:8072" = { };
73 };
74 };
75
76 virtualHosts."${cfg.domain}" = {
77 extraConfig = ''
78 proxy_read_timeout 720s;
79 proxy_connect_timeout 720s;
80 proxy_send_timeout 720s;
81
82 proxy_set_header X-Forwarded-Host $host;
83 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
84 proxy_set_header X-Forwarded-Proto $scheme;
85 proxy_set_header X-Real-IP $remote_addr;
86 '';
87
88 locations = {
89 "/longpolling" = {
90 proxyPass = "http://odoochat";
91 };
92
93 "/" = {
94 proxyPass = "http://odoo";
95 extraConfig = ''
96 proxy_redirect off;
97 '';
98 };
99 };
100 };
101 };
102
103 services.odoo.settings.options = {
104 data_dir = "/var/lib/private/odoo/data";
105 proxy_mode = cfg.domain != null;
106 }
107 // (lib.optionalAttrs (cfg.addons != [ ]) {
108 addons_path = lib.concatMapStringsSep "," lib.escapeShellArg cfg.addons;
109 });
110
111 users.users.odoo = {
112 isSystemUser = true;
113 group = "odoo";
114 };
115 users.groups.odoo = { };
116
117 systemd.services.odoo = {
118 wantedBy = [ "multi-user.target" ];
119 after = [
120 "network.target"
121 "postgresql.target"
122 ];
123
124 # pg_dump
125 path = [ config.services.postgresql.package ];
126
127 requires = [ "postgresql.target" ];
128
129 serviceConfig = {
130 ExecStart = "${cfg.package}/bin/odoo";
131 ExecStartPre = pkgs.writeShellScript "odoo-start-pre.sh" (
132 ''
133 set -euo pipefail
134
135 cd "$STATE_DIRECTORY"
136
137 # Auto-migrate old deployments
138 if [[ -d .local/share/Odoo ]]; then
139 echo "pre-start: migrating state directory from $STATE_DIRECTORY/.local/share/Odoo to $STATE_DIRECTORY/data"
140 mv .local/share/Odoo ./data
141 rmdir .local/share
142 rmdir .local
143 fi
144 ''
145 + (lib.optionalString cfg.autoInit ''
146 echo "pre-start: auto-init"
147 INITIALIZED="${cfg.settings.options.data_dir}/.odoo.initialized"
148 if [ ! -e "$INITIALIZED" ]; then
149 ${cfg.package}/bin/odoo --init=INIT --database=odoo --db_user=odoo --stop-after-init ${lib.concatStringsSep " " cfg.autoInitExtraFlags}
150 touch "$INITIALIZED"
151 fi
152 '')
153 + "echo pre-start: OK"
154 );
155 DynamicUser = true;
156 User = "odoo";
157 StateDirectory = "odoo";
158 Environment = [
159 "ODOO_RC=${cfgFile}"
160 ];
161 };
162 };
163
164 services.postgresql = {
165 enable = true;
166
167 ensureDatabases = [ "odoo" ];
168 ensureUsers = [
169 {
170 name = "odoo";
171 ensureDBOwnership = true;
172 }
173 ];
174 };
175 }
176 );
177}