Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 14d151c

Browse files
committedMar 19, 2025··
android/avf: init
Android Virtualization Framework is a new virtualization environment for Android Among others, it is used to provide the Terminal App starting from Android 15 QPR2 This PR allows building images for AVF The system changes have been taken from https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/build/debian/
1 parent daaae13 commit 14d151c

12 files changed

+5652
-0
lines changed
 

‎android/avf/debug.nix

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
# Print all logs to console for debug
3+
boot.kernelParams = [
4+
"systemd.journald.forward_to_console"
5+
];
6+
7+
# Enable AVF debug log
8+
avf.vmConfig.debugLevel = 1;
9+
10+
# Allow the user to log in as root without a password.
11+
users.users.root.initialHashedPassword = "";
12+
13+
# Automatically log in at the virtual consoles.
14+
services.getty.autologinUser = "droid";
15+
}

‎android/avf/default.nix

+288
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
{
2+
config,
3+
lib,
4+
modulesPath,
5+
pkgs,
6+
...
7+
}:
8+
9+
let
10+
base = pkgs.fetchgit {
11+
url = "https://android.googlesource.com/platform/packages/modules/Virtualization/";
12+
rev = "f3ae17a45df25d1c0913b5cca68fcea6e5a5ce05";
13+
hash = "sha256-BcfGSMOKc3Ku3GhpFgdOVM6VT3tj0ujbDIxR2MfZAxE=";
14+
};
15+
extraPkgs = pkgs.callPackage ./pkgs.nix { inherit base; };
16+
17+
serialDevice = "ttyS0";
18+
19+
mkService = name: {
20+
serviceConfig = {
21+
ExecStart = "${
22+
lib.getExe extraPkgs.android_virt.${name}
23+
} --grpc-port-file /mnt/internal/debian_service_port";
24+
Type = "simple";
25+
Restart = "on-failure";
26+
RestartSec = 1;
27+
User = "root";
28+
Group = "root";
29+
StandardOutput = "journal";
30+
StandardError = "journal";
31+
};
32+
wantedBy = [ "multi-user.target" ];
33+
after = [
34+
"network-online.target"
35+
"network.target"
36+
"mnt-internal.mount"
37+
];
38+
};
39+
40+
vmConfig = pkgs.formats.json { };
41+
42+
cfg = config.avf;
43+
in
44+
45+
with lib;
46+
{
47+
imports = [
48+
"${modulesPath}/profiles/qemu-guest.nix"
49+
];
50+
51+
options = {
52+
avf.vmConfig = mkOption {
53+
description = "VM config for AVF";
54+
default = { };
55+
type = vmConfig.type;
56+
};
57+
};
58+
59+
config = {
60+
avf.vmConfig = {
61+
name = "nixos";
62+
disks = [
63+
{
64+
partitions = [
65+
{
66+
label = "nixos";
67+
path = "$PAYLOAD_DIR/root_part";
68+
writable = true;
69+
guid = "{root_part_guid}";
70+
}
71+
{
72+
label = "ESP";
73+
path = "$PAYLOAD_DIR/efi_part";
74+
writable = false;
75+
guid = "{efi_part_guid}";
76+
}
77+
];
78+
writable = true;
79+
}
80+
];
81+
sharedPath = [
82+
{
83+
sharedPath = "/storage/emulated";
84+
}
85+
{
86+
sharedPath = "$APP_DATA_DIR/files";
87+
}
88+
];
89+
protected = false;
90+
cpu_topology = "match_host";
91+
platform_version = "~1.0";
92+
memory_mib = 4096;
93+
debuggable = true;
94+
console_out = true;
95+
console_input_device = "ttyS0";
96+
network = true;
97+
auto_memory_balloon = true;
98+
gpu = {
99+
backend = "2d";
100+
};
101+
};
102+
103+
/*
104+
services.ttyd = {
105+
enable = true;
106+
enableSSL = true;
107+
caFile = = "/mnt/internal/ca.crt";
108+
keyFile = "/etc/ttyd/server.key";
109+
clientOptions = [ "disableLeaveAlert=true" ];
110+
certFile = "/etc/ttyd/server.ct";
111+
entrypoint = [ "${pkgs.shadow}/bin/login" "-f" "droid" ];
112+
writeable = true;
113+
};
114+
*/
115+
116+
systemd.services.ttyd = {
117+
serviceConfig = {
118+
ExecStart = "${extraPkgs.ttyd}/bin/ttyd --ssl --ssl-cert /etc/ttyd/server.crt --ssl-key /etc/ttyd/server.key --ssl-ca /mnt/internal/ca.crt -t disableLeaveAlert=true -W ${config.services.ttyd.entrypoint} -f droid";
119+
Type = "simple";
120+
Restart = "always";
121+
User = "root";
122+
Group = "root";
123+
};
124+
125+
wantedBy = [ "multi-user.target" ];
126+
after = [
127+
"network-online.target"
128+
"network.target"
129+
"mnt-internal.mount"
130+
];
131+
};
132+
133+
systemd.services.avahi_ttyd = {
134+
description = "avahi_TTYD";
135+
136+
after = [
137+
"ttyd.service"
138+
"avahi-daemon.socket"
139+
];
140+
wantedBy = [ "multi-user.target" ];
141+
142+
serviceConfig = {
143+
ExecStart = "${pkgs.avahi}/bin/avahi-publish-service ttyd _http._tcp 7681";
144+
Type = "simple";
145+
Restart = "always";
146+
User = "root";
147+
Group = "root";
148+
};
149+
};
150+
151+
services.avahi = {
152+
enable = true;
153+
publish = {
154+
enable = true;
155+
userServices = true;
156+
};
157+
};
158+
159+
system.build.avfImage = pkgs.callPackage ./finish.nix {
160+
raw_disk_image = import "${pkgs.path}/nixos/lib/make-disk-image.nix" {
161+
inherit pkgs lib config;
162+
163+
partitionTableType = "efi";
164+
copyChannel = true;
165+
memSize = "2048";
166+
};
167+
168+
vm_config = vmConfig.generate "vm_config.json" cfg.vmConfig;
169+
};
170+
171+
boot.growPartition = true;
172+
boot.loader.systemd-boot.enable = true;
173+
boot.initrd.systemd.enable = true;
174+
175+
# image building needs to know what device to install bootloader on
176+
boot.loader.grub.device = "/dev/vda";
177+
# Faster boot. User can't access bootloader currently anyways (?)
178+
boot.loader.timeout = 0;
179+
180+
# avf patches only available for 6.1 right now
181+
boot.kernelPackages = pkgs.linuxPackages_6_1;
182+
183+
boot.kernelPatches = [
184+
{
185+
name = "avf";
186+
patch = "${base}/build/debian/kernel/patches/avf/arm64-balloon.patch";
187+
extraStructuredConfig = with lib.kernel; {
188+
# DRM = module;
189+
SND_VIRTIO = module;
190+
SND = yes;
191+
SOUND = yes;
192+
};
193+
}
194+
];
195+
196+
boot.kernelParams = [
197+
"console=tty1"
198+
"console=${serialDevice}"
199+
];
200+
201+
boot.kernelModules = [ "vhost_vsock" ];
202+
203+
fileSystems = {
204+
"/" = {
205+
device = "/dev/disk/by-label/nixos";
206+
autoResize = true;
207+
fsType = "ext4";
208+
};
209+
"/boot" = {
210+
device = "/dev/disk/by-label/ESP";
211+
fsType = "vfat";
212+
};
213+
214+
"/mnt/internal" = {
215+
device = "internal";
216+
fsType = "virtiofs";
217+
};
218+
"/mnt/shared" = {
219+
device = "android";
220+
fsType = "virtiofs";
221+
};
222+
/*
223+
"/mnt/backup" = {
224+
device = "/dev/vdb";
225+
fsType = "virtiofs";
226+
};
227+
*/
228+
};
229+
230+
# from Virtualization/guest/storage_balloon_agent/debian/service
231+
232+
systemd.services.storage_balloon_agent = mkService "storage_balloon_agent";
233+
234+
# from Virtualization/guest/forwarder_guest_launcher/debian/service
235+
236+
systemd.services.forwarder_guest_launcher = mkService "forwarder_guest_launcher" // {
237+
path = [
238+
extraPkgs.android_virt.forwarder_guest
239+
pkgs.bcc
240+
"/run/current-system/sw"
241+
];
242+
};
243+
244+
# from Virtualization/guest/shutdown_runner/debian/service
245+
246+
systemd.services.shutdown_runner = mkService "shutdown_runner";
247+
248+
system.activationScripts.setup_files = {
249+
text = ''
250+
if [ ! -e /_setup ]; then
251+
cp -rv ${./etc}/* /etc/
252+
mkdir -vp /mnt/{shared,internal,backup}
253+
chown -v 1000:100 /mnt/{shared,internal,backup}
254+
touch /_setup
255+
fi
256+
'';
257+
};
258+
259+
users.users.droid = {
260+
isNormalUser = true;
261+
extraGroups = [
262+
"droid"
263+
"wheel"
264+
"video"
265+
"render"
266+
];
267+
initialHashedPassword = "";
268+
};
269+
users.groups.droid = { };
270+
security.sudo.wheelNeedsPassword = false;
271+
272+
programs.bcc.enable = true;
273+
274+
programs.bash.promptInit = ''
275+
# Show title of current running command
276+
trap 'echo -ne "\e]0;\$BASH_COMMAND\007"' DEBUG
277+
'';
278+
279+
systemd.network.enable = true;
280+
networking.useNetworkd = true;
281+
networking.dhcpcd.enable = false;
282+
services.resolved.dnssec = "false";
283+
networking.useDHCP = true;
284+
networking.firewall.enable = true; # default
285+
networking.nftables.enable = true;
286+
networking.firewall.allowedTCPPorts = [ 7681 ];
287+
};
288+
}

‎android/avf/etc/ttyd/server.crt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDiTCCAnGgAwIBAgIUasfD1K/4tJHwNRXL2kdSD9VbeSwwDQYJKoZIhvcNAQEL
3+
BQAwUzELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAkdEMQswCQYDVQQHDAJTWjETMBEG
4+
A1UECgwKQWNtZSwgSW5jLjEVMBMGA1UEAwwMQWNtZSBSb290IENBMB4XDTI0MTAx
5+
NDAxMjgzOFoXDTI1MTAxNDAxMjgzOFowUDELMAkGA1UEBhMCQ04xCzAJBgNVBAgM
6+
AkdEMQswCQYDVQQHDAJTWjETMBEGA1UECgwKQWNtZSwgSW5jLjESMBAGA1UEAwwJ
7+
bG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+uVF4TP
8+
jUjfL8vJlECAN1rLFK8lDuOUv52VCrW7MXMfGYlA4nk1OKDjygnZIpET6I9cTfCG
9+
Xiwad6bU6Oqy4MZ2i338F+eERrGpkitSQ7QRqZannjBIDFxXZvJpMTJDIWNCmz+P
10+
K2VcvCh8im2tJA66wJogUcVmJBugNqleqxFcxPvXOdBdWBK7JYOcb4J643eLX6+D
11+
X6v2QTlKXfihouVC8wAzbw9HHmOVb7ono1rV7xpcFrOyBiDGVSgEteiB8l26iXA9
12+
fExkb0rUzHjlgvb/l8/nGAaQHd0eE+/SGd4tXvs9KHX6XJh/PI0ExTsDIBDcuVOt
13+
2YzXeuM6zzrKLQIDAQABo1gwVjAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0O
14+
BBYEFHpFYqFC/AEOfWfdZmpy5YBZfgR2MB8GA1UdIwQYMBaAFGThxe/2q5/Dg6hI
15+
9Von5HRAOUxZMA0GCSqGSIb3DQEBCwUAA4IBAQBQspP3wo3yzcPWuFk4lRyo7zpF
16+
JfBBX0UU1Z0MQfIGxLC2YtRvxobRqwLcKUKQjBqUuRdukleOaVVFeXb/HI9vY3ji
17+
9PfUb2UJ4O3z3pdSK0EwXbkCidtUflRLvPG6dgBrXyLOqxBqA5lWR2ds5HRAMRAi
18+
eXfDkJTmNOAQAnPgM+35FBgmhh6axG+bUudvvVoA8ca+zW9i1R6/vblxYJ6bhmw0
19+
8s+uoAX6FXcZ0YFOGdhcpJmnbiRd3D0VVacjc3b9pjFOI8d3bh9pR47p0kVOaRsh
20+
aAG3gZhyMPOgbYceCjfzND5YhycDI+MzPo/JOYdhHGGJawoh1nP94QNPan6J
21+
-----END CERTIFICATE-----

‎android/avf/etc/ttyd/server.key

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDf65UXhM+NSN8v
3+
y8mUQIA3WssUryUO45S/nZUKtbsxcx8ZiUDieTU4oOPKCdkikRPoj1xN8IZeLBp3
4+
ptTo6rLgxnaLffwX54RGsamSK1JDtBGplqeeMEgMXFdm8mkxMkMhY0KbP48rZVy8
5+
KHyKba0kDrrAmiBRxWYkG6A2qV6rEVzE+9c50F1YErslg5xvgnrjd4tfr4Nfq/ZB
6+
OUpd+KGi5ULzADNvD0ceY5VvuiejWtXvGlwWs7IGIMZVKAS16IHyXbqJcD18TGRv
7+
StTMeOWC9v+Xz+cYBpAd3R4T79IZ3i1e+z0odfpcmH88jQTFOwMgENy5U63ZjNd6
8+
4zrPOsotAgMBAAECggEAARJYlD12ch5WM2aDrPOGOAtREOfP7CCwWcMiOfBP72iR
9+
Y9Vipxmuz16nwTJ22F7HvPsdPOUo1cFtWhim2Aqr/ZxuT4Ce9oVrk6iDwRdeuYdY
10+
cIhtChvJi+p0ggMcuyzp90+3AYXxynsOlCufMjSNGaqvYUsNEXnJFSgiKr7mgbIO
11+
J0VU1Wrquw7N58RKL+T3xEvE7uO3QpLOim2MbfRSVq/JGNxqAGw0/uxtjFs7Vtf9
12+
z44e/ULeYDS7zMj6cMggxQp5nfzcboGoNVUEDgYjOzqXCe4cG0n3XfN7GJhaS1ZF
13+
tPd8l4Ch0IrT4hs5uVFaMdFbj+er7mvmqfTVytrRmQKBgQD2kVB53EKhqxgvz2N7
14+
bAJglOLd6FWKsWlLMSdER0/4dRVRMIBxnYWgQ0gaRc4TM7oyKOl3MDF9jdDne5KJ
15+
cnfzFoH2GD6VBQRr0mFmV1UV6oHEjDJBasMo/1Vw3TJ4oZgZpYpJjrDmPWZqHUs4
16+
I79TdvJrNFSmk3MGVFjatLIq5QKBgQDofHpHfBeRCn2Z3OOkiAN5V53n5deZl6Jt
17+
lGTsrXKpEzRTre4LWZojoB9hiGjptZkXHA2HW90RiV9OHhTa8W9ZntLnOnWc5RUn
18+
Tzh14KupjsBQm/gE8SuqHSDx1mxTnIUo0W28d/Beecri5KfaoEY+wxZXOeQy5JFR
19+
ec/AhU4FqQKBgGhVzUwDnF502+M/SsVrSwY7elSUf74UnI2o2wjVdE2anc6hS3jI
20+
Q0cxsU0MxMrzVJLtJP2+cvLCE+ggLj3jJkbC+3N7ht/gI6LMf1KjGeoQNaFKAeoU
21+
l0i94xXDRBwvpQEVP5MowkprKO82PiIfXlKfPq2Gk1t5gW7oOkExvULRAoGBAK7R
22+
051nec0uJ06I5IE3ae1X7jyP//TWKmTeHpo+vybWcxWth3/va9H4OUC9M67ySGEx
23+
ThcIBA+IzirOwf31aTbqEEuiEQje1m5NyvYQ8OS6nHDBJ9qHg78S0lAoXiLtYtBT
24+
04HSauSQDvlY2cOzm77cMjN7K9b9Oy0aPRfW5dmpAoGAGesq4Ojky4crpi0H1O7n
25+
cMuIAzaPozsMx7iSrhUe69fwVFiMkEKR6ems01DmjYwPb6DtxCieaRlGbd9E8oIZ
26+
y6n+Uh9Qbc5sDhPMsys6NyKOv/A6rkn49/etr40f0Z5g9g/d2+qtwoAXjo3sSPuW
27+
7iqbruRjbKUaJKzdpIqOKD0=
28+
-----END PRIVATE KEY-----

‎android/avf/finish.nix

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
stdenv,
3+
raw_disk_image,
4+
vm_config,
5+
utillinux,
6+
pigz,
7+
e2fsprogs,
8+
}:
9+
10+
stdenv.mkDerivation {
11+
name = "avf_image.tar.gz";
12+
13+
nativeBuildInputs = [
14+
utillinux
15+
pigz
16+
e2fsprogs
17+
];
18+
19+
dontUnpack = true;
20+
dontBuild = true;
21+
installPhase = ''
22+
diskImage=$(echo ${raw_disk_image}/*.img)
23+
24+
OFFSETS=($(sfdisk -l $diskImage -o Start,Sectors | tail -n 2 | grep -o "[0-9]*"))
25+
26+
echo $out > build_id
27+
28+
# bs=512 -> sector size is 512, skip=start sector, count=size in sectors
29+
dd if=$diskImage of=efi_part bs=512 skip="''${OFFSETS[0]}" count="''${OFFSETS[1]}"
30+
dd if=$diskImage of=root_part bs=512 skip="''${OFFSETS[2]}" count="''${OFFSETS[3]}"
31+
32+
# can be removed once android e2fsck supports this feature
33+
tune2fs -O ^orphan_file root_part
34+
35+
cp ${vm_config} vm_config.json
36+
37+
sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid $diskImage 1)/g" vm_config.json
38+
sed -i "s/{root_part_guid}/$(sfdisk --part-uuid $diskImage 2)/g" vm_config.json
39+
40+
contents=(
41+
build_id
42+
root_part
43+
efi_part
44+
vm_config.json
45+
)
46+
47+
# --sparse option isn't supported in apache-commons-compress
48+
tar cv -I pigz -f $out -C . "''${contents[@]}"
49+
'';
50+
}

‎android/avf/forwarder_guest_Cargo.lock

+392
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎android/avf/forwarder_guest_launcher_Cargo.lock

+1,734
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎android/avf/guest-tcpstates.patch

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
diff --git a/src/main.rs b/src/main.rs
2+
index 3cb557aed..b42e54eb9 100644
3+
--- a/src/main.rs
4+
+++ b/src/main.rs
5+
@@ -112,9 +112,7 @@ async fn report_active_ports(
6+
mut client: DebianServiceClient<Channel>,
7+
) -> Result<(), Box<dyn std::error::Error>> {
8+
// TODO: we can remove python3 -u when https://github.com/iovisor/bcc/pull/5142 is deployed
9+
- let mut cmd = Command::new("python3")
10+
- .arg("-u")
11+
- .arg("/usr/sbin/tcpstates-bpfcc")
12+
+ let mut cmd = Command::new("tcpstates")
13+
.arg("-s")
14+
.stdout(Stdio::piped())
15+
.spawn()?;

‎android/avf/pkgs.nix

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
base,
3+
lib,
4+
ttyd,
5+
rustPlatform,
6+
protobuf_28,
7+
libwebsockets,
8+
}:
9+
let
10+
11+
mkRustPkg =
12+
name: lock: extra:
13+
rustPlatform.buildRustPackage (
14+
{
15+
inherit name;
16+
17+
RUSTFLAGS = "-C linker=gcc";
18+
19+
# see https://github.com/NixOS/nixpkgs/issues/145726
20+
# TODO: disable when cross-compling
21+
prePatch = ''
22+
rm .cargo/config.toml
23+
'';
24+
25+
src = base;
26+
setSourceRoot = "sourceRoot=$(echo */guest/${name})";
27+
28+
nativeBuildInputs = [
29+
protobuf_28
30+
];
31+
32+
postPatch = ''
33+
ln -s ${lock} Cargo.lock
34+
'';
35+
36+
cargoLock = {
37+
lockFile = lock;
38+
};
39+
40+
meta = {
41+
mainProgram = name;
42+
};
43+
}
44+
// extra
45+
);
46+
in
47+
{
48+
ttyd =
49+
(ttyd.override ({
50+
libwebsockets = libwebsockets.overrideAttrs (_: {
51+
patches = [
52+
"${base}/build/debian/ttyd/client_cert.patch"
53+
];
54+
});
55+
})).overrideAttrs
56+
(a: {
57+
patches = [
58+
"${base}/build/debian/ttyd/xtermjs_a11y.patch"
59+
];
60+
});
61+
62+
android_virt = lib.recurseIntoAttrs {
63+
forwarder_guest = mkRustPkg "forwarder_guest" ./forwarder_guest_Cargo.lock { };
64+
forwarder_guest_launcher =
65+
mkRustPkg "forwarder_guest_launcher" ./forwarder_guest_launcher_Cargo.lock
66+
{
67+
patches = [
68+
./guest-tcpstates.patch
69+
];
70+
};
71+
shutdown_runner = mkRustPkg "shutdown_runner" ./shutdown_runner_Cargo.lock { };
72+
storage_balloon_agent = mkRustPkg "storage_balloon_agent" ./storage_balloon_agent_Cargo.lock { };
73+
};
74+
}

‎android/avf/shutdown_runner_Cargo.lock

+1,505
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎android/avf/storage_balloon_agent_Cargo.lock

+1,516
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test.nix

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
(import <nixpkgs/nixos/lib/eval-config.nix> {
2+
system = "aarch64-linux";
3+
modules = [
4+
(
5+
{ modulesPath, ... }:
6+
{
7+
imports = [
8+
./android/avf
9+
./android/avf/debug.nix
10+
];
11+
}
12+
)
13+
];
14+
}).config.system.build.avfImage

0 commit comments

Comments
 (0)
Please sign in to comment.