To avoid distracting the kids from studying, our TV at home only gets turned on during holidays. When my wife wanted to binge a show, she’d have to sneak onto the computer – but sitting at a desk is no match for lounging on the couch. During the New Year break, I got the itch to set up Netflix on the TV. After half a day of research, I realized our LeTV wasn’t going to cut it. Time for a TV box.

The N1 Box

After browsing Taobao, I found the N1 box runs Android and can be flashed with custom firmware. I picked one up for about 150 RMB (with remote). These boxes used to be given away for free, but after the company went under, scarcity drove the price up from a few dozen to 150.

Connecting to Wi-Fi

The package arrived quickly – the seller even included a dual-male USB flashing cable and an HDMI cable. I plugged it into power, connected it to the TV, and found a customized Launcher showing only device info and network info. After connecting a mouse and configuring Wi-Fi, the device’s IP address appeared:

N1 Screenshot

Enabling adb

Tap Firmware Version four times. Normally, a Toast message “adb enabled” should appear. From there, you can use adb on your computer to connect to the box remotely:

1
adb connect 192.168.1.78

On success, adb outputs:

1
connected to 192.168.1.78:5555

Once connected, you can install all sorts of apps via adb. For a TV, Kodi is definitely worth trying.

Kodi

For anyone who’s developed Android TV apps, not knowing Kodi is practically embarrassing. Kodi used to be called XBMC. It’s a cross-platform home media center supporting Windows, Linux, macOS, Android, and iOS – extremely powerful. Its cross-platform support is entirely built on a C++ GUI system with a custom XML layout specification. On Android, there’s minimal Java code – even the MainActivity extends NativeActivity. It also supports Python, Samba, UPnP, and more.

Bubble Cloud

Around 2010, cloud computing was all the hype and cloud storage services were popping up everywhere. One day, our boss demonstrated DropBox on his iPhone at an all-hands meeting. We were marveling at how great it was when he suddenly pivoted: “We’re going to build a private cloud!” Everyone was stunned.

Unlike public cloud, a private cloud stores data on devices at home. All end-to-end data access goes through an HTTP Relay Server, so the device doesn’t need a domain name to be accessible from outside the local network – it can even punch through firewalls.

I don’t know who came up with the name “Bubble Cloud,” but it was catchy enough to land on Baidu Baike. The box was built on AMLogic‘s platform. To reduce power consumption, the entire Android Framework was stripped out, leaving only the Kernel and root filesystem. We even modified init.rc – I remember getting stuck because a service wouldn’t start, and after reading through the source line by line, I discovered that service names couldn’t exceed 16 characters.

To better support SATA hard drive mounting, we also modified the Volume Daemon. For the private cloud server, we needed an app server. After evaluating options, we went with Python, porting Python 2.7 to the Android platform.

For LAN file sharing, we ported Samba – which was a nightmare. Android‘s C library is Bionic C, while Samba uses GNU C. Many APIs were missing from the NDK, so we had to implement them ourselves. That’s when I first learned about netlink. For someone three years out of college, that experience was a huge growth opportunity.

Switching to root

Normally, you switch to root via the su (switch user) command, but on the N1 it prompts:

1
Please enter password!

Nothing worked – the seller didn’t know, and the internet was no help. Eventually, I found the answer in an online flashing script. Turns out the N1 controls root access via system properties:

1
2
adb shell setprop service.phiadb.root 1
adb shell setprop service.adb.root 1

Then restart the adb service and reconnect:

1
2
adb kill-server
adb connect 192.168.1.78

Now running adb shell drops you into:

1
p230:/ #

The # prompt means you’re root (regular users see $). To confirm:

1
2
3
p230:/ # whoami
root
p230:/ #

With root access, the real fun begins.

Flashing Armbian

Back then, a Bubble Cloud box retailed for around 500 RMB with a hardware cost of about 200 RMB. Compared to the N1, the N1 is a steal. One box for the TV wasn’t enough to play with, so I grabbed another. To run Docker, I decided to flash Armbian (Debian Linux for ARM). Before flashing, you’ll need:

  1. A computer, preferably Windows
    Typically you’d use AMLogic‘s USB Burning Tools for a wired flash, but it only runs on Windows and Linux. On macOS, you’ll need to boot from a USB drive instead
  2. A dual-male USB cable (only for wired flashing) – have a few spares
  3. An Armbian image file – easily found online
  4. At least one 8GB USB drive – some drives have compatibility issues; I went through a few before finding one that worked
  5. A bootable USB creator – balena Etcher (skip for wired flashing)
  6. A USB keyboard
  7. An HDMI cable
  8. A display/TV with an HDMI port
  9. An ethernet cable

Flashing on macOS is straightforward:

  1. Confirm the stock Android firmware has been downgraded (plenty of guides online)
  2. Create a bootable USB drive with balena Etcher
  3. Power off the N1
  4. Plug in the ethernet cable
  5. Connect the HDMI display
  6. Insert the USB drive
  7. Connect the USB keyboard
  8. Power on the N1
  9. Boot into the stock Android system, tap firmware version four times to enable ADB
  10. On your computer, connect to the N1 via adb:
    1
    adb connect 192.168.1.101
  11. Boot from USB:
    1
    adb shell reboot update

If you see Linux boot logs, the USB boot succeeded. Otherwise, try a different USB drive. After boot, you’ll be prompted to log in:

  • Username: root
  • Password: 1234

After logging in, you’ll see the AML S9XXX logo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    _    __  __ _       ____  ___
/ \ | \/ | | / ___|/ _ \__ ____ ____ __
/ _ \ | |\/| | | \___ \ (_) \ \/ /\ \/ /\ \/ /
/ ___ \| | | | |___ ___) \__, |> < > < > <
/_/ \_\_| |_|_____| |____/ /_//_/\_\/_/\_\/_/\_\

Welcome to Debian GNU/Linux 10 (buster) with Linux 5.9.16-flippy-51+

No end-user support: built from trunk

System load: 2% Up time: 13 days 12:11
Memory usage: 17% of 1.70G Zram usage: 7% of 0.85G IP: 172.30.0.1 192.168.1.81
CPU temp: 42 C Usage of /: 33% of 7.0G storage/: 53% of 192M

root@amlogic:~#

At this point, you haven’t actually flashed yet – you’ve only booted from USB. To run Armbian without the USB drive, you need to write the USB contents to the eMMC storage. There are built-in scripts in the root directory:

1
2
3
4
root@amlogic:~# ls -l
-rwxr-xr-x 1 root root 82 Dec 30 22:46 install-docker.sh
-rwxr-xr-x 1 root root 8626 Dec 30 22:46 install-to-emmc.sh
-rwxr-xr-x 1 root root 221 Dec 30 22:46 install-zerotier.sh

Just run install-to-emmc.sh. If you want docker and zerotier too, install them first:

1
./install-docker.sh && ./install-zerotier.sh && ./install-to-emmc.sh

If you’re not comfortable with the Docker command line, install portainer, a web-based Docker client:

1
2
3
4
5
6
7
docker volume create portainer_data && docker run -d \
-p 9000:9000 \
--name=portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer

Watching Netflix in Peace

When using the N1 as a TV box for Netflix, I initially used Igniter, but it was unreliable – the network would drop mid-movie. To actually finish watching something, I set up a proxy server. I chose trojan as a SOCKS5 proxy, but many devices only support HTTP(S). To bridge this gap, I used privoxy to wrap HTTP(S) requests into the SOCKS5 protocol and forward them to trojan. See: https://github.com/johnsonlee/proxify – fully Dockerized. On the N1, just start it with docker-compose:

1
2
3
4
5
git clone https://github.com/johnsonlee/proxify
cd proxify && docker-compose up -d \
-e TROJAN_SERVER_HOST=xxx \
-e TROJAN_SERVER_PORT=xxx \
-e TROJAN_PASSWORD=xxx

Default SOCKS5 port is 1080, HTTP(S) port is 1081.

Software Router

You could also flash openwrt for fun, but proxify covers most daily needs, so I didn’t bother.