---
title: "Flutter Integration Test Server in Debian 11 Nspawn Container"
date: 2021-09-24
draft: false
tags: ["debian", "nspawn", "android", "flutter"]
authors: ["trent"]
post: 22
---
date: 2021-09-24

## Introduction
### Performance
Your Debian Server is way more powerful than your laptop or desktop
and flutter integration_tests suck.
### Ergonomics
You have an Android Emulator (or a real device) connected to the machine
that you are sitting in front of for reference, and now you can run
integration_tests on a different device without having to juggle adb
connections on the same machine.
### Nspawn Tho?
Because containers unlike virtual machines access the full power of the
host, but nspawn containers are peristent like virtual machines, sparing
you the cognitive overhead of dealing with the ephemerality of docker
containers and/or of herding cats.

And you already have nspawn, it's build into systemd. Even including the
(virtual) network interfaces.
### Documentation
Let's face it: setting up an Android Development Environment is a nightmare.

So don't just follow this guide; follow this guide a repetition of three
times, building your own step-by-step guide for yourself as you go. Your brain
will thank you.

## Host Preparation (Debian 11)
1. install `systemd-container` and `debootstrap`
2. enable unprivileged user namespaces
    * `echo 'kernel.unprivileged_userns_clone=1' >/etc/sysctl.d/nspawn.conf`
    * `systemctl restart systemd-sysctl.service`
3. you might as well allow debootstrap to user your apt-cacher-ng proxy
    * `export http_proxy=http://<ip address>:3142`
### br0 bridge
describe br0 bridge in `/etc/systemd/nspawn/ftest.nspawn` (optional).
```cfg
# /etc/systemd/nspawn/ftest.nspawn
[Network]
VirtualEthernet=yes
Bridge=br0
```

## ZFS mountpoint
This is optional, obviously; you might not even use zfs.

* `zfs create vm_pool/nspawn/ftest`
* `zfs set mountpoint=/var/lib/machines/ftest vm_pool/nspawn/ftest`
* sanity check `zfs list -r vm_pool/nspawn`

## bootstrap container
```shell
# for apt-cacher-ng proxy
export http_proxy=http://<ip address>:3142

debootstrap --include=systemd-container stable /var/list/machines/ftest
```

## preboot config
1. delete container's package cache
2. copy `/etc/apt/apt.conf` to container
2. copy `/root/.bashrc` to container
2. copy `/root/.inputrc` to container
2. edit `/etc/hostname` in container
2. [write nspawn file on host](#br0-bridge)
2. copy `/etc/locale.gen` to `/etc/locale.gen.bak` on container

## first interactive boot
1. `systemd-nspawn -D /var/lib/machines/ftest -U --machine ftest`
2. set passwd: `passwd`
2. stop container: `logout`

## run as service
1. `systemctl start systemd-nspawn@ftest`
2. login: `machinectl login ftest`
2. start/enable network `systemctl enable --now systemd-networkd`
2. add regular user `useradd <username>`

## install applications
### locale
* install locales
* edit `/etc/locale.gen` to taste and then run the command `locale-gen`
### essential apps
```shell
apt-get install openssh-server git unzip wget sudo curl file rsync
```
### add regular user to sudo group
`usermod -a -G sudo <user>`
### other apps
```shell
apt-get install mosh htop haveged byobu needrestart tree bash-completion
```
### install openjdk-8 from stretch repo
1. add following to `/etc/apt/sources.list`
```cfg
deb http://security.debian.org/debian-security stretch/updates main
```
2. `apt-get update && apt-get install openjdk-8-jdk-headless`

## user environment
You can now ssh into your container.

scp your favorite environment files over to the container

* ~/.byobu/
* ~/.bashrc
* ~/.bash_aliases
* ~/.inputrc

## install flutter
Pick a location to taste; I prefer `~/.local/`
```shell
cd ; cd .local
git clone https://github.com/flutter/flutter.git
```
### downgrade flutter
if needed:
```shell
cd ~/.local/flutter
git checkout 2.2.3
```
## install command-line-tools
The schuck and jive here is absurd, but here goes.

Now is the time to decide where ANDROID_HOME and ANDROID_SDK_ROOT
are going to be; I prefer `~/.local/share/Android/Sdk/`
```shell
mkdir -p ~/.local/share/Android/Sdk
```
### temporary installation of cmdline-tools
[Command line tools only Scroll half way down](https://developer.android.com/studio){target="_blank"}
```shell
cd ~/.local/share/Android/Sdk
wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip
unzip commandlinetools-linux-7583922_latest.zip
mkdir 5.0
mv cmdline-tools/* 5.0/
mv 5.0 cmdline-tools/
```

## flutter and sdk environment
add the following to `~/.bashrc`
```cfg
function addToPATH {
  case ":$PATH:" in
    *":$1:"*) :;; # already there
    *) PATH="$PATH:$1";; # or PATH="$PATH:$1"
  esac
}

addToPATH ~/.local/flutter/bin
addToPATH ~/.local/share/Android/Sdk/cmdline-tools/latest/bin
addToPATH ~/.local/share/Android/Sdk/platform-tools

# temporary path to temporary version of cmdline-tools
addToPATH ~/.local/share/Android/Sdk/cmdline-tools/5.0/bin
```

add the following to `~/.bash_aliases`
```cfg
alias sdkmanager='sdkmanager --sdk_root=~/.local/share/Android/Sdk'
```


Confirm by logging out and then back in and:
```shell
which flutter ; which sdkmanager ; alias
```

### now install cmdline-tools for real
`sdkmanager --install "cmdline-tools;latest"`

and then logout and log back in
#### cleanup
At this point I think you can remove or comment the temporary PATH
statement from `~/.bashrc` for the temporary location of cmdline-tools

## install Android SDK
review your options
```shell
sdkmanager --list
```
and then install them
(platform-tools: adb and fastboot will be pulled in automatically)
```shell
sdkmanager --install "platforms;android-30" \
  "build-tools;31.0.0" "build-tools;30.0.3"
```

## confirm flutter installation
`flutter doctor`

## run tests
At this point you shoud be able to rsync a flutter app over to the container, connect to a device using network adb,
and run something like:
```shell
flutter drive --driver integration_test/driver.dart \
  --target integration_test/app_test.dart --profile
```
