1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.virtualisation.anbox;
8
9 addrOpts = v: addr: pref: name: {
10 address = mkOption {
11 default = addr;
12 type = types.str;
13 description = ''
14 IPv${toString v} ${name} address.
15 '';
16 };
17
18 prefixLength = mkOption {
19 default = pref;
20 type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
21 description = ''
22 Subnet mask of the ${name} address, specified as the number of
23 bits in the prefix (`${if v == 4 then "24" else "64"}`).
24 '';
25 };
26 };
27
28 finalImage = if cfg.imageModifications == "" then cfg.image else ( pkgs.callPackage (
29 { runCommandNoCC, squashfsTools }:
30
31 runCommandNoCC "${cfg.image.name}-modified.img" {
32 nativeBuildInputs = [
33 squashfsTools
34 ];
35 } ''
36 echo "-> Extracting Anbox root image..."
37 unsquashfs -dest rootfs ${cfg.image}
38
39 echo "-> Modifying Anbox root image..."
40 (
41 cd rootfs
42 ${cfg.imageModifications}
43 )
44
45 echo "-> Packing modified Anbox root image..."
46 mksquashfs rootfs $out -comp xz -no-xattrs -all-root
47 ''
48 ) { });
49
50in
51
52{
53
54 options.virtualisation.anbox = {
55
56 enable = mkEnableOption "Anbox";
57
58 image = mkOption {
59 default = pkgs.anbox.image;
60 defaultText = literalExpression "pkgs.anbox.image";
61 type = types.package;
62 description = ''
63 Base android image for Anbox.
64 '';
65 };
66
67 imageModifications = mkOption {
68 default = "";
69 type = types.lines;
70 description = ''
71 Commands to edit the image filesystem.
72
73 This can be used to e.g. bundle a privileged F-Droid.
74
75 Commands are ran with PWD being at the root of the filesystem.
76 '';
77 };
78
79 extraInit = mkOption {
80 type = types.lines;
81 default = "";
82 description = ''
83 Extra shell commands to be run inside the container image during init.
84 '';
85 };
86
87 ipv4 = {
88 container = addrOpts 4 "192.168.250.2" 24 "Container";
89 gateway = addrOpts 4 "192.168.250.1" 24 "Host";
90
91 dns = mkOption {
92 default = "1.1.1.1";
93 type = types.str;
94 description = ''
95 Container DNS server.
96 '';
97 };
98 };
99 };
100
101 config = mkIf cfg.enable {
102
103 assertions = singleton {
104 assertion = with config.boot.kernelPackages; kernelAtLeast "5.5" && kernelOlder "5.18";
105 message = "Anbox needs a kernel with binder and ashmem support";
106 };
107
108 environment.systemPackages = with pkgs; [ anbox ];
109
110 systemd.mounts = singleton {
111 requiredBy = [ "anbox-container-manager.service" ];
112 description = "Anbox Binder File System";
113 what = "binder";
114 where = "/dev/binderfs";
115 type = "binder";
116 };
117
118 virtualisation.lxc.enable = true;
119 networking.bridges.anbox0.interfaces = [];
120 networking.interfaces.anbox0.ipv4.addresses = [ cfg.ipv4.gateway ];
121
122 networking.nat = {
123 enable = true;
124 internalInterfaces = [ "anbox0" ];
125 };
126
127 # Ensures NetworkManager doesn't touch anbox0
128 networking.networkmanager.unmanaged = [ "anbox0" ];
129
130 systemd.services.anbox-container-manager = let
131 anboxloc = "/var/lib/anbox";
132 in {
133 description = "Anbox Container Management Daemon";
134
135 environment.XDG_RUNTIME_DIR="${anboxloc}";
136
137 wantedBy = [ "multi-user.target" ];
138 preStart = let
139 initsh = pkgs.writeText "nixos-init" (''
140 #!/system/bin/sh
141 setprop nixos.version ${config.system.nixos.version}
142
143 # we don't have radio
144 setprop ro.radio.noril yes
145 stop ril-daemon
146
147 # speed up boot
148 setprop debug.sf.nobootanimation 1
149 '' + cfg.extraInit);
150 initshloc = "${anboxloc}/rootfs-overlay/system/etc/init.goldfish.sh";
151 in ''
152 mkdir -p ${anboxloc}
153 mkdir -p $(dirname ${initshloc})
154 [ -f ${initshloc} ] && rm ${initshloc}
155 cp ${initsh} ${initshloc}
156 chown 100000:100000 ${initshloc}
157 chmod +x ${initshloc}
158 '';
159
160 serviceConfig = {
161 ExecStart = ''
162 ${pkgs.anbox}/bin/anbox container-manager \
163 --data-path=${anboxloc} \
164 --android-image=${finalImage} \
165 --container-network-address=${cfg.ipv4.container.address} \
166 --container-network-gateway=${cfg.ipv4.gateway.address} \
167 --container-network-dns-servers=${cfg.ipv4.dns} \
168 --use-rootfs-overlay \
169 --privileged \
170 --daemon
171 '';
172 };
173 };
174 };
175
176}