Tutorial: turn an old Samsung phone into a private, always-on git server (Magisk + Termux + Tailscale), including every mistake I made so you don't
> I'm making this post in the hope it will help someone else turn a piece of e-waste in an actually useful piece of tech. I'm not really a "tutorial guy" so please feel free to correct me anytime.
> Keep in mind that this tutorial isn't exactly 'non-tech-savvy friendly', so don't be surprised if you turn to your go-to chatbot for a few quick questions to understand what you're doing and if it's correct.
I had two git repos that are too big for GitHub (about 6 GB and 400 MB), and an old phone. Here's how I turned a Galaxy A14 4G (SM-A145R, MediaTek MT6769) into a git-over-SSH server:
- It's reachable only through my Tailscale network.
- It can't reach my PCs.
- Every app on it except two has no network access.
- The battery is held at 60–70% even though the charger is always plugged in.
Below is what to do, and in "DON'T" boxes, what went wrong for me. I did all of it from Ubuntu. Most of it applies to any recent Samsung, and a lot of it to any rootable Android phone.
What you need
- A Samsung phone whose bootloader can be unlocked. Most international/EU models can. US models usually can't: if download mode shows no "OEM LOCK" line, give up.
- A Linux PC (Windows works too, with different tools), a USB cable, and a free Tailscale account.
- Patience: the phone will be wiped several times.
1. Unlock the bootloader
1. Settings → Developer options → OEM unlocking on.
2. Power off. Hold Vol Up + Vol Down while plugging the USB cable in (PC end connected). Long-press Vol Up to unlock, then confirm. This wipes the phone.
3. Do the setup connected to Wi-Fi. Afterwards, the OEM unlocking switch must be greyed out. In download mode, KG STATUS should read CHECKING, COMPLETED or BROKEN. PRENORMAL means Knox Guard won't accept unofficial images yet: wait for about 168 hours of uptime.
2. Download the exact firmware
Use samloader-rs by topjohnwu (the Magisk author): topjohnwu/samloader-rs. It downloads straight from Samsung's servers _and_ flashes.
- Samsung now only serves the latest firmware, and the request must include the phone's serial or IMEI, otherwise you get error S01.
- Verify the MD5 trailer of every .tar.md5.
3. Patch the boot image with Magisk
Patch the firmware's AP file with the Magisk app ("Select and Patch a File"), or patch only init_boot.img with Magisk's boot_patch.sh over adb. The result is the same: a patched init_boot, vbmeta with flags = 3 (verification off), and no userdata.img.
4. Flash and wipe
samloader flash -b BL_*.tar.md5 -a magisk_patched.tar -c CP_*.tar.md5 -s CSC_*.tar.md5
Use CSC, not HOME_CSC: the first Magisk install _needs_ a data wipe. If recovery then says "Can't load Android system", choose Factory data reset. That's expected.
> DON'T use Heimdall on newer Samsungs. On mine:
>
> - its timeouts were too short (userdata failed);
> - flashing misc made the bootloader fail the whole session, leaving the phone on "An error occurred while updating the device software";
> - every half-finished session blocked all later handshakes until I restarted into download mode with the buttons.
>
> samloader-rs worked first try.
>
> DON'T flash HOME_CSC for the first root ("to keep data"): I got a boot loop.
>
> DON'T flash the stock init_boot/vbmeta back to "undo" a boot loop. Magisk's docs warn about this, and they're right: it keeps looping until you wipe.
>
> DON'T rely on --erase: on MediaTek it erased "0 sectors".
>
> DON'T use -p partition_name in samloader-rs on MediaTek: it upper-cases the name and the PIT is lowercase. Use -f init_boot.img. Also, a failed command in the middle of a session means a button restart.
5. First boot
- Minimal setup: Wi-Fi, no accounts. Then developer options and USB debugging.
- Before installing anything over adb, disable the Play Store and the Galaxy Store:
pm disable-user --user 0 com.android.vending
pm disable-user --user 0 com.sec.android.app.samsungapps
Right after setup they restore and install apps (on mine even TikTok) and occupy the install queue, so adb install just hangs.
- Install the full Magisk app and accept its "additional setup" reboot. From then on, adb shell su -c id gives root.
6. Debloat: in batches, with a failsafe
pm uninstall --user 0 <package> doesn't touch the system partition and can be undone with cmd package install-existing <package>. But:
> DON'T remove 170 packages in one go. I did, and the phone stuck on "Phone is starting..." forever, with no USB and no adb. Factory reset.
>
> DON'T remove Google Play Services (com.google.android.gms), GSF and Chrome on One UI 7, even if you never use Google. Samsung reinstalls them at boot; if you remove them again, boot hangs exactly like above.
>
> Some packages (setup wizards, Smart Switch, AdServices and telemetry modules, a few Samsung services) are re-enabled by Samsung at every boot, whether you uninstall or disable them. Don't fight them: firewall them (step 9).
What worked for me:
- Batches, with one reboot test per batch.
- A Magisk service.d failsafe: before applying a batch, write its list to /data/adb/.../pending.txt. At boot, a script waits up to 7 minutes for a real home screen (not FallbackHome, the activity behind "Phone is starting..."). If it doesn't come, it reinstalls the batch and reboots.
- Test the failsafe on purpose with a fake failure. Mine was broken the first time:
> DON'T redirect cmd package ... output to a file under /data/adb in a Magisk script. system_server isn't allowed to write to that fd (SELinux), so the call fails with "Failed transaction". Capture the output through a pipe (r=$(cmd package ... 2>&1 | cat)).
Rescue without a factory reset. A hang on "Phone is starting..." can be fixed without wiping:
1. Add an overlay.d/*.rc plus a script to the Magisk-patched init_boot ramdisk. magiskboot from the Magisk APK (lib/x86_64) runs on Linux.
2. The script waits for the package service, runs cmd package install-existing on what you removed, writes a marker file, and reboots once.
3. Flash just init_boot from download mode.
That saved me a second full reinstall.
7. Termux + sshd
- Install Termux and Termux:Boot from their GitHub releases, or F-Droid.
- pkg install openssh git.
- In sshd_config: port 8022, PasswordAuthentication no, AuthenticationMethods publickey, all forwarding off.
- On the phone, put your repos in bare repositories: git init --bare ~/git/<name>.git.
Running Termux commands from a root shell:
su <termux-uid> -g <termux-uid> -G 3003 -G 9997 -c "export PREFIX=... HOME=... PATH=$PREFIX/bin LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so; ..."
> DON'T forget -g: otherwise the first -G becomes the primary group and dpkg fails with "unable to change ownership".
>
> DON'T forget -G 3003 (inet): without it, Android denies network sockets.
>
> DON'T trust Termux:Boot to keep sshd up. Android killed its process as "empty" before sshd started.
Instead, start sshd from a Magisk service.d script as the Termux user, check it every 30 seconds, and hold a kernel wake lock (echo myserver > /sys/power/wake_lock) so the CPU never sleeps.
8. Tailscale
- Install from F-Droid.
- In the admin console, create a single-use, pre-approved auth key with a tag (e.g. tag:gitserver). In the app: Settings → Accounts → ⋮ → "Use an auth key".
- ACLs: your own devices may reach tag:gitserver on tcp:8022. The tag is never a source, so a stolen phone can't connect to your other machines.
- Set Tailscale as Always-on VPN (settings put secure always_on_vpn_app com.tailscale.ipn), otherwise it doesn't come back after a reboot.
> DON'T use Private DNS in "strict" mode (e.g. dns.quad9.net) together with Tailscale on Android: _all_ DNS dies. Use "automatic", and turn "Use Tailscale DNS" off on the phone.
9. Firewall (iptables from a Magisk script)
- Inbound: port 8022 only on tun+ (Tailscale) and lo (adb forward); no new TCP connections from Wi-Fi.
- Outbound:
- no new connections to private ranges, except Tailscale's own UID (so it can use direct LAN paths), DHCP, and DNS (53) for system UIDs;
- then -m owner --uid-owner 10000-99999 -j REJECT for every app except Termux and Tailscale.
The app rule also takes care of the Google/Samsung packages you weren't allowed to remove.
> DON'T block DNS to your router: in "automatic" mode Android's resolver uses it, and everything breaks the moment you block the LAN. I found out the hard way.
>
> DON'T pkill -f or grep for a pattern that also appears in your own command line (e.g. the script path): you kill your own shell. It happened to me three times.
Re-check the chains every minute, because netd can rebuild them.
10. Battery on a phone that's always plugged in
Cheap Samsungs don't have "Pause USB Power Delivery", but the battery driver exposes:
- /sys/class/power_supply/battery/store_mode: 1 = retail demo mode. Charging stops and the phone runs from the charger, but the battery sits at 100%.
- /sys/class/power_supply/battery/input_suspend: 1 cuts the charger input, so the phone drains (about 160 mA on mine).
A 5-minute loop holds the battery between 60 and 70%: suspend the input above 70%, restore it at 60%, always restore below 55%. Note that Samsung's gauge shows 100% for a while after real discharge starts.
11. Push the repos
Do the first push over USB (adb forward tcp:18022 tcp:8022), it's much faster. After that, push over Tailscale. Set receive.denyNonFastForwards true on the bare repos so a bad force-push can't destroy history.
Result
The phone boots unattended (no Power press needed at the unlocked-bootloader warning), and SSH over Tailscale is up about 80 seconds after power-on.
This was done with the great help of Claude Opus 5.5.
6 points · 4 comments
Tutorial: turn an old Samsung phone into a private, always-on git server (Magisk + Termux + Tailscale), including every mistake I made so you don't
>I'm making this post in the hope it will help someone else turn a piece of e-waste in an actually useful piece of tech. I'm not really a "tutorial guy" so please feel free to correct me anytime.
>Keep in mind that this tutorial isn't exactly 'non-tech-savvy friendly', so don't be surprised if you turn to your go-to chatbot for a few quick questions to understand what you're doing and if it's correct.
I had two git repos that are too big for GitHub (about 6 GB and 400 MB), and an old phone. Here's how I turned a Galaxy A14 4G (SM-A145R, MediaTek MT6769) into a git-over-SSH server:
- It's reachable only through my Tailscale network.
- It can't reach my PCs.
- Every app on it except two has no network access.
- The battery is held at 60–70% even though the charger is always plugged in.
Below is what to do, and in "DON'T" boxes, what went wrong for me. I did all of it from Ubuntu. Most of it applies to any recent Samsung, and a lot of it to any rootable Android phone.
What you need
- A Samsung phone whose bootloader can be unlocked. Most international/EU models can. US models usually can't: if download mode shows no "OEM LOCK" line, you can theoretically still do the home server but without battery handling and many other optimizations, basically the last part of the tutorial.
- A Linux PC (Windows works too, with different tools), a USB cable, and a free Tailscale account.
- Patience: the phone will be wiped several times.
1. Unlock the bootloader
1. Settings → Developer options → OEM unlocking on.
2. Power off. Hold Vol Up + Vol Down while plugging the USB cable in (PC end connected). Long-press Vol Up to unlock, then confirm. This wipes the phone.
3. Do the setup connected to Wi-Fi. Afterwards, the OEM unlocking switch must be greyed out. In download mode, KG STATUS should read CHECKING, COMPLETED or BROKEN. PRENORMAL means Knox Guard won't accept unofficial images yet: wait for about 168 hours of uptime.
2. Download the exact firmware
Use samloader-rs by topjohnwu (the Magisk author): topjohnwu/samloader-rs. It downloads straight from Samsung's servers and flashes.
- Samsung now only serves the latest firmware, and the request must include the phone's serial or IMEI, otherwise you get error S01.
- Verify the MD5 trailer of every .tar.md5.
3. Patch the boot image with Magisk
Patch the firmware's AP file with the Magisk app ("Select and Patch a File"), or patch only init_boot.img with Magisk's boot_patch.sh over adb. The result is the same: a patched init_boot, vbmeta with flags = 3 (verification off), and no userdata.img.
4. Flash and wipe
samloader flash -b BL_*.tar.md5 -a magisk_patched.tar -c CP_*.tar.md5 -s CSC_*.tar.md5
Use CSC, not HOME_CSC: the first Magisk install needs a data wipe. If recovery then says "Can't load Android system", choose Factory data reset. That's expected.
>DON'T use Heimdall on newer Samsungs. On mine:
>
>samloader-rs worked first try.
>
>DON'T flash HOME_CSC for the first root ("to keep data"): I got a boot loop.
>
>DON'T flash the stock init_boot/vbmeta back to "undo" a boot loop. Magisk's docs warn about this, and they're right: it keeps looping until you wipe.
>
>DON'T rely on --erase: on MediaTek it erased "0 sectors".
>
>DON'T use -p partition_name in samloader-rs on MediaTek: it upper-cases the name and the PIT is lowercase. Use -f init_boot.img. Also, a failed command in the middle of a session means a button restart.
5. First boot
- Minimal setup: Wi-Fi, no accounts. Then developer options and USB debugging.
- Before installing anything over adb, disable the Play Store and the Galaxy Store:
- Right after setup they restore and install apps (on mine even TikTok) and occupy the install queue, so adb install just hangs.
- Install the full Magisk app and accept its "additional setup" reboot. From then on, adb shell su -c id gives root.
6. Debloat: in batches, with a failsafe
pm uninstall --user 0 <package> doesn't touch the system partition and can be undone with cmd package install-existing <package>. But:
>DON'T remove 170 packages in one go. I did, and the phone stuck on "Phone is starting..." forever, with no USB and no adb. Factory reset.
>
>DON'T remove Google Play Services (com.google.android.gms), GSF and Chrome on One UI 7, even if you never use Google. Samsung reinstalls them at boot; if you remove them again, boot hangs exactly like above.
>
>Some packages (setup wizards, Smart Switch, AdServices and telemetry modules, a few Samsung services) are re-enabled by Samsung at every boot, whether you uninstall or disable them. Don't fight them: firewall them (step 9).
What worked for me:
- Batches, with one reboot test per batch.
- A Magisk service.d failsafe: before applying a batch, write its list to /data/adb/.../pending.txt. At boot, a script waits up to 7 minutes for a real home screen (not FallbackHome, the activity behind "Phone is starting..."). If it doesn't come, it reinstalls the batch and reboots.
- Test the failsafe on purpose with a fake failure. Mine was broken the first time:
>DON'T redirect cmd package ... output to a file under /data/adb in a Magisk script. system_server isn't allowed to write to that fd (SELinux), so the call fails with "Failed transaction". Capture the output through a pipe (r=$(cmd package ... 2>&1 | cat)).
Rescue without a factory reset. A hang on "Phone is starting..." can be fixed without wiping:
1. Add an overlay.d/*.rc plus a script to the Magisk-patched init_boot ramdisk. magiskboot from the Magisk APK (lib/x86_64) runs on Linux.
2. The script waits for the package service, runs cmd package install-existing on what you removed, writes a marker file, and reboots once.
3. Flash just init_boot from download mode.
That saved me a second full reinstall.
7. Termux + sshd
- Install Termux and Termux:Boot from their GitHub releases, or F-Droid.
- pkg install openssh git.
- In sshd_config: port 8022, PasswordAuthentication no, AuthenticationMethods publickey, all forwarding off.
- On the phone, put your repos in bare repositories: git init --bare ~/git/<name>.git.
Running Termux commands from a root shell:
su <termux-uid> -g <termux-uid> -G 3003 -G 9997 -c "export PREFIX=... HOME=... PATH=$PREFIX/bin LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so; ..."
>DON'T forget -g: otherwise the first -G becomes the primary group and dpkg fails with "unable to change ownership".
>
>DON'T forget -G 3003 (inet): without it, Android denies network sockets.
>
>DON'T trust Termux:Boot to keep sshd up. Android killed its process as "empty" before sshd started.
Instead, start sshd from a Magisk service.d script as the Termux user, check it every 30 seconds, and hold a kernel wake lock (echo myserver > /sys/power/wake_lock) so the CPU never sleeps.
8. Tailscale
- Install from F-Droid.
- In the admin console, create a single-use, pre-approved auth key with a tag (e.g. tag:gitserver). In the app: Settings → Accounts → ⋮ → "Use an auth key".
- ACLs: your own devices may reach tag:gitserver on tcp:8022. The tag is never a source, so a stolen phone can't connect to your other machines.
- Set Tailscale as Always-on VPN (settings put secure always_on_vpn_app com.tailscale.ipn), otherwise it doesn't come back after a reboot.
>DON'T use Private DNS in "strict" mode (e.g. dns.quad9.net) together with Tailscale on Android: all DNS dies. Use "automatic", and turn "Use Tailscale DNS" off on the phone.
9. Firewall (iptables from a Magisk script)
- Inbound: port 8022 only on tun+ (Tailscale) and lo (adb forward); no new TCP connections from Wi-Fi.
- Outbound:
- no new connections to private ranges, except Tailscale's own UID (so it can use direct LAN paths), DHCP, and DNS (53) for system UIDs;
- then -m owner --uid-owner 10000-99999 -j REJECT for every app except Termux and Tailscale.
- The app rule also takes care of the Google/Samsung packages you weren't allowed to remove.
>DON'T block DNS to your router: in "automatic" mode Android's resolver uses it, and everything breaks the moment you block the LAN. I found out the hard way.
>
>DON'T pkill -f or grep for a pattern that also appears in your own command line (e.g. the script path): you kill your own shell. It happened to me three times.
Re-check the chains every minute, because netd can rebuild them.
10. Battery on a phone that's always plugged in
Cheap Samsungs don't have "Pause USB Power Delivery", but the battery driver exposes:
- /sys/class/power_supply/battery/store_mode: 1 = retail demo mode. Charging stops and the phone runs from the charger, but the battery sits at 100%.
- /sys/class/power_supply/battery/input_suspend: 1 cuts the charger input, so the phone drains (about 160 mA on mine).
A 5-minute loop holds the battery between 60 and 70%: suspend the input above 70%, restore it at 60%, always restore below 55%. Note that Samsung's gauge shows 100% for a while after real discharge starts.
11. Push the repos
Do the first push over USB (adb forward tcp:18022 tcp:8022), it's much faster. After that, push over Tailscale. Set receive.denyNonFastForwards true on the bare repos so a bad force-push can't destroy history.
Result
The phone boots unattended (no Power press needed at the unlocked-bootloader warning), and SSH over Tailscale is up about 80 seconds after power-on.
This was done with the great help of Claude Opus 5.5.
3 points · 1 comment
Tutorial: turn an old Samsung phone into a private, always-on git server (Magisk + Termux + Tailscale), including every mistake I made so you don't
> I'm making this post in the hope it will help someone else turn a piece of e-waste in an actually useful piece of tech. I'm not really a "tutorial guy" so please feel free to correct me anytime.
> Keep in mind that this tutorial isn't exactly 'non-tech-savvy friendly', so don't be surprised if you turn to your go-to chatbot for a few quick questions to understand what you're doing and if it's correct.
I had two git repos that are too big for GitHub (about 6 GB and 400 MB), and an old phone. Here's how I turned a Galaxy A14 4G (SM-A145R, MediaTek MT6769) into a git-over-SSH server:
- It's reachable only through my Tailscale network.
- It can't reach my PCs.
- Every app on it except two has no network access.
- The battery is held at 60–70% even though the charger is always plugged in.
Below is what to do, and in "DON'T" boxes, what went wrong for me. I did all of it from Ubuntu. Most of it applies to any recent Samsung, and a lot of it to any rootable Android phone.
What you need
- A Samsung phone whose bootloader can be unlocked. Most international/EU models can. US models usually can't: if download mode shows no "OEM LOCK" line, give up.
- A Linux PC (Windows works too, with different tools), a USB cable, and a free Tailscale account.
- Patience: the phone will be wiped several times.
1. Unlock the bootloader
1. Settings → Developer options → OEM unlocking on.
2. Power off. Hold Vol Up + Vol Down while plugging the USB cable in (PC end connected). Long-press Vol Up to unlock, then confirm. This wipes the phone.
3. Do the setup connected to Wi-Fi. Afterwards, the OEM unlocking switch must be greyed out. In download mode, KG STATUS should read CHECKING, COMPLETED or BROKEN. PRENORMAL means Knox Guard won't accept unofficial images yet: wait for about 168 hours of uptime.
2. Download the exact firmware
Use samloader-rs by topjohnwu (the Magisk author): topjohnwu/samloader-rs. It downloads straight from Samsung's servers _and_ flashes.
- Samsung now only serves the latest firmware, and the request must include the phone's serial or IMEI, otherwise you get error S01.
- Verify the MD5 trailer of every .tar.md5.
3. Patch the boot image with Magisk
Patch the firmware's AP file with the Magisk app ("Select and Patch a File"), or patch only init_boot.img with Magisk's boot_patch.sh over adb. The result is the same: a patched init_boot, vbmeta with flags = 3 (verification off), and no userdata.img.
4. Flash and wipe
samloader flash -b BL_*.tar.md5 -a magisk_patched.tar -c CP_*.tar.md5 -s CSC_*.tar.md5
Use CSC, not HOME_CSC: the first Magisk install _needs_ a data wipe. If recovery then says "Can't load Android system", choose Factory data reset. That's expected.
> DON'T use Heimdall on newer Samsungs. On mine:
>
> - its timeouts were too short (userdata failed);
> - flashing misc made the bootloader fail the whole session, leaving the phone on "An error occurred while updating the device software";
> - every half-finished session blocked all later handshakes until I restarted into download mode with the buttons.
>
> samloader-rs worked first try.
>
> DON'T flash HOME_CSC for the first root ("to keep data"): I got a boot loop.
>
> DON'T flash the stock init_boot/vbmeta back to "undo" a boot loop. Magisk's docs warn about this, and they're right: it keeps looping until you wipe.
>
> DON'T rely on --erase: on MediaTek it erased "0 sectors".
>
> DON'T use -p partition_name in samloader-rs on MediaTek: it upper-cases the name and the PIT is lowercase. Use -f init_boot.img. Also, a failed command in the middle of a session means a button restart.
5. First boot
- Minimal setup: Wi-Fi, no accounts. Then developer options and USB debugging.
- Before installing anything over adb, disable the Play Store and the Galaxy Store:
pm disable-user --user 0 com.android.vending
pm disable-user --user 0 com.sec.android.app.samsungapps
Right after setup they restore and install apps (on mine even TikTok) and occupy the install queue, so adb install just hangs.
- Install the full Magisk app and accept its "additional setup" reboot. From then on, adb shell su -c id gives root.
6. Debloat: in batches, with a failsafe
pm uninstall --user 0 <package> doesn't touch the system partition and can be undone with cmd package install-existing <package>. But:
> DON'T remove 170 packages in one go. I did, and the phone stuck on "Phone is starting..." forever, with no USB and no adb. Factory reset.
>
> DON'T remove Google Play Services (com.google.android.gms), GSF and Chrome on One UI 7, even if you never use Google. Samsung reinstalls them at boot; if you remove them again, boot hangs exactly like above.
>
> Some packages (setup wizards, Smart Switch, AdServices and telemetry modules, a few Samsung services) are re-enabled by Samsung at every boot, whether you uninstall or disable them. Don't fight them: firewall them (step 9).
What worked for me:
- Batches, with one reboot test per batch.
- A Magisk service.d failsafe: before applying a batch, write its list to /data/adb/.../pending.txt. At boot, a script waits up to 7 minutes for a real home screen (not FallbackHome, the activity behind "Phone is starting..."). If it doesn't come, it reinstalls the batch and reboots.
- Test the failsafe on purpose with a fake failure. Mine was broken the first time:
> DON'T redirect cmd package ... output to a file under /data/adb in a Magisk script. system_server isn't allowed to write to that fd (SELinux), so the call fails with "Failed transaction". Capture the output through a pipe (r=$(cmd package ... 2>&1 | cat)).
Rescue without a factory reset. A hang on "Phone is starting..." can be fixed without wiping:
1. Add an overlay.d/*.rc plus a script to the Magisk-patched init_boot ramdisk. magiskboot from the Magisk APK (lib/x86_64) runs on Linux.
2. The script waits for the package service, runs cmd package install-existing on what you removed, writes a marker file, and reboots once.
3. Flash just init_boot from download mode.
That saved me a second full reinstall.
7. Termux + sshd
- Install Termux and Termux:Boot from their GitHub releases, or F-Droid.
- pkg install openssh git.
- In sshd_config: port 8022, PasswordAuthentication no, AuthenticationMethods publickey, all forwarding off.
- On the phone, put your repos in bare repositories: git init --bare ~/git/<name>.git.
Running Termux commands from a root shell:
su <termux-uid> -g <termux-uid> -G 3003 -G 9997 -c "export PREFIX=... HOME=... PATH=$PREFIX/bin LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so; ..."
> DON'T forget -g: otherwise the first -G becomes the primary group and dpkg fails with "unable to change ownership".
>
> DON'T forget -G 3003 (inet): without it, Android denies network sockets.
>
> DON'T trust Termux:Boot to keep sshd up. Android killed its process as "empty" before sshd started.
Instead, start sshd from a Magisk service.d script as the Termux user, check it every 30 seconds, and hold a kernel wake lock (echo myserver > /sys/power/wake_lock) so the CPU never sleeps.
8. Tailscale
- Install from F-Droid.
- In the admin console, create a single-use, pre-approved auth key with a tag (e.g. tag:gitserver). In the app: Settings → Accounts → ⋮ → "Use an auth key".
- ACLs: your own devices may reach tag:gitserver on tcp:8022. The tag is never a source, so a stolen phone can't connect to your other machines.
- Set Tailscale as Always-on VPN (settings put secure always_on_vpn_app com.tailscale.ipn), otherwise it doesn't come back after a reboot.
> DON'T use Private DNS in "strict" mode (e.g. dns.quad9.net) together with Tailscale on Android: _all_ DNS dies. Use "automatic", and turn "Use Tailscale DNS" off on the phone.
9. Firewall (iptables from a Magisk script)
- Inbound: port 8022 only on tun+ (Tailscale) and lo (adb forward); no new TCP connections from Wi-Fi.
- Outbound:
- no new connections to private ranges, except Tailscale's own UID (so it can use direct LAN paths), DHCP, and DNS (53) for system UIDs;
- then -m owner --uid-owner 10000-99999 -j REJECT for every app except Termux and Tailscale.
The app rule also takes care of the Google/Samsung packages you weren't allowed to remove.
> DON'T block DNS to your router: in "automatic" mode Android's resolver uses it, and everything breaks the moment you block the LAN. I found out the hard way.
>
> DON'T pkill -f or grep for a pattern that also appears in your own command line (e.g. the script path): you kill your own shell. It happened to me three times.
Re-check the chains every minute, because netd can rebuild them.
10. Battery on a phone that's always plugged in
Cheap Samsungs don't have "Pause USB Power Delivery", but the battery driver exposes:
- /sys/class/power_supply/battery/store_mode: 1 = retail demo mode. Charging stops and the phone runs from the charger, but the battery sits at 100%.
- /sys/class/power_supply/battery/input_suspend: 1 cuts the charger input, so the phone drains (about 160 mA on mine).
A 5-minute loop holds the battery between 60 and 70%: suspend the input above 70%, restore it at 60%, always restore below 55%. Note that Samsung's gauge shows 100% for a while after real discharge starts.
11. Push the repos
Do the first push over USB (adb forward tcp:18022 tcp:8022), it's much faster. After that, push over Tailscale. Set receive.denyNonFastForwards true on the bare repos so a bad force-push can't destroy history.
Result
The phone boots unattended (no Power press needed at the unlocked-bootloader warning), and SSH over Tailscale is up about 80 seconds after power-on.
This was done with the great help of Claude Opus 5.5.
4 comments
Mentions
Tutorial: turn an old Samsung phone into a private, always-on git server (Magisk + Termux + Tailscale), including every mistake I made so you don't > I'm making this post in the hope it will help someone else turn a piece of e-waste in an actually useful piece of tech. I'm not really a "tutorial guy" so please feel free to correct me anytime. > Keep in mind that this tutorial isn't exactly 'non-tech-savvy friendly', so don't be surprised if you turn to your go-to chatbot for a few quick questions to understand what you're doing and if it's correct. I had two git repos that are too big for GitHub (about 6 GB and 400 MB), and an old phone. Here's how I turned a Galaxy A14 4G (SM-A145R, MediaTek MT6769) into a git-over-SSH server: - It's reachable only through my Tailscale network. - It can't reach my PCs. - Every app on it except two has no network access. - The battery is held at 60–70% even though the charger is always plugged in. Below is what to do, and in "DON'T" boxes, what went wrong for me. I did all of it from Ubuntu. Most of it applies to any recent Samsung, and a lot of it to any rootable Android phone. What you need - A Samsung phone whose bootloader can be unlocked. Most international/EU models can. US models usually can't: if download mode shows no "OEM LOCK" line, give up. - A Linux PC (Windows works too, with different tools), a USB cable, and a free Tailscale account. - Patience: the phone will be wiped several times. 1. Unlock the bootloader 1. Settings → Developer options → OEM unlocking on. 2. Power off. Hold Vol Up + Vol Down while plugging the USB cable in (PC end connected). Long-press Vol Up to unlock, then confirm. This wipes the phone. 3. Do the setup connected to Wi-Fi. Afterwards, the OEM unlocking switch must be greyed out. In download mode, KG STATUS should read CHECKING, COMPLETED or BROKEN. PRENORMAL means Knox Guard won't accept unofficial images yet: wait for about 168 hours of uptime. 2. Download the exact firmware Use samloader-rs by topjohnwu (the Magisk author): topjohnwu/samloader-rs. It downloads straight from Samsung's servers _and_ flashes. - Samsung now only serves the latest firmware, and the request must include the phone's serial or IMEI, otherwise you get error S01. - Verify the MD5 trailer of every .tar.md5. 3. Patch the boot image with Magisk Patch the firmware's AP file with the Magisk app ("Select and Patch a File"), or patch only init_boot.img with Magisk's boot_patch.sh over adb. The result is the same: a patched init_boot, vbmeta with flags = 3 (verification off), and no userdata.img. 4. Flash and wipe samloader flash -b BL_*.tar.md5 -a magisk_patched.tar -c CP_*.tar.md5 -s CSC_*.tar.md5 Use CSC, not HOME_CSC: the first Magisk install _needs_ a data wipe. If recovery then says "Can't load Android system", choose Factory data reset. That's expected. > DON'T use Heimdall on newer Samsungs. On mine: > > - its timeouts were too short (userdata failed); > - flashing misc made the bootloader fail the whole session, leaving the phone on "An error occurred while updating the device software"; > - every half-finished session blocked all later handshakes until I restarted into download mode with the buttons. > > samloader-rs worked first try. > > DON'T flash HOME_CSC for the first root ("to keep data"): I got a boot loop. > > DON'T flash the stock init_boot/vbmeta back to "undo" a boot loop. Magisk's docs warn about this, and they're right: it keeps looping until you wipe. > > DON'T rely on --erase: on MediaTek it erased "0 sectors". > > DON'T use -p partition_name in samloader-rs on MediaTek: it upper-cases the name and the PIT is lowercase. Use -f init_boot.img. Also, a failed command in the middle of a session means a button restart. 5. First boot - Minimal setup: Wi-Fi, no accounts. Then developer options and USB debugging. - Before installing anything over adb, disable the Play Store and the Galaxy Store: pm disable-user --user 0 com.android.vending pm disable-user --user 0 com.sec.android.app.samsungapps Right after setup they restore and install apps (on mine even TikTok) and occupy the install queue, so adb install just hangs. - Install the full Magisk app and accept its "additional setup" reboot. From then on, adb shell su -c id gives root. 6. Debloat: in batches, with a failsafe pm uninstall --user 0 <package> doesn't touch the system partition and can be undone with cmd package install-existing <package>. But: > DON'T remove 170 packages in one go. I did, and the phone stuck on "Phone is starting..." forever, with no USB and no adb. Factory reset. > > DON'T remove Google Play Services (com.google.android.gms), GSF and Chrome on One UI 7, even if you never use Google. Samsung reinstalls them at boot; if you remove them again, boot hangs exactly like above. > > Some packages (setup wizards, Smart Switch, AdServices and telemetry modules, a few Samsung services) are re-enabled by Samsung at every boot, whether you uninstall or disable them. Don't fight them: firewall them (step 9). What worked for me: - Batches, with one reboot test per batch. - A Magisk service.d failsafe: before applying a batch, write its list to /data/adb/.../pending.txt. At boot, a script waits up to 7 minutes for a real home screen (not FallbackHome, the activity behind "Phone is starting..."). If it doesn't come, it reinstalls the batch and reboots. - Test the failsafe on purpose with a fake failure. Mine was broken the first time: > DON'T redirect cmd package ... output to a file under /data/adb in a Magisk script. system_server isn't allowed to write to that fd (SELinux), so the call fails with "Failed transaction". Capture the output through a pipe (r=$(cmd package ... 2>&1 | cat)). Rescue without a factory reset. A hang on "Phone is starting..." can be fixed without wiping: 1. Add an overlay.d/*.rc plus a script to the Magisk-patched init_boot ramdisk. magiskboot from the Magisk APK (lib/x86_64) runs on Linux. 2. The script waits for the package service, runs cmd package install-existing on what you removed, writes a marker file, and reboots once. 3. Flash just init_boot from download mode. That saved me a second full reinstall. 7. Termux + sshd - Install Termux and Termux:Boot from their GitHub releases, or F-Droid. - pkg install openssh git. - In sshd_config: port 8022, PasswordAuthentication no, AuthenticationMethods publickey, all forwarding off. - On the phone, put your repos in bare repositories: git init --bare ~/git/<name>.git. Running Termux commands from a root shell: su <termux-uid> -g <termux-uid> -G 3003 -G 9997 -c "export PREFIX=... HOME=... PATH=$PREFIX/bin LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so; ..." > DON'T forget -g: otherwise the first -G becomes the primary group and dpkg fails with "unable to change ownership". > > DON'T forget -G 3003 (inet): without it, Android denies network sockets. > > DON'T trust Termux:Boot to keep sshd up. Android killed its process as "empty" before sshd started. Instead, start sshd from a Magisk service.d script as the Termux user, check it every 30 seconds, and hold a kernel wake lock (echo myserver > /sys/power/wake_lock) so the CPU never sleeps. 8. Tailscale - Install from F-Droid. - In the admin console, create a single-use, pre-approved auth key with a tag (e.g. tag:gitserver). In the app: Settings → Accounts → ⋮ → "Use an auth key". - ACLs: your own devices may reach tag:gitserver on tcp:8022. The tag is never a source, so a stolen phone can't connect to your other machines. - Set Tailscale as Always-on VPN (settings put secure always_on_vpn_app com.tailscale.ipn), otherwise it doesn't come back after a reboot. > DON'T use Private DNS in "strict" mode (e.g. dns.quad9.net) together with Tailscale on Android: _all_ DNS dies. Use "automatic", and turn "Use Tailscale DNS" off on the phone. 9. Firewall (iptables from a Magisk script) - Inbound: port 8022 only on tun+ (Tailscale) and lo (adb forward); no new TCP connections from Wi-Fi. - Outbound: - no new connections to private ranges, except Tailscale's own UID (so it can use direct LAN paths), DHCP, and DNS (53) for system UIDs; - then -m owner --uid-owner 10000-99999 -j REJECT for every app except Termux and Tailscale. The app rule also takes care of the Google/Samsung packages you weren't allowed to remove. > DON'T block DNS to your router: in "automatic" mode Android's resolver uses it, and everything breaks the moment you block the LAN. I found out the hard way. > > DON'T pkill -f or grep for a pattern that also appears in your own command line (e.g. the script path): you kill your own shell. It happened to me three times. Re-check the chains every minute, because netd can rebuild them. 10. Battery on a phone that's always plugged in Cheap Samsungs don't have "Pause USB Power Delivery", but the battery driver exposes: - /sys/class/power_supply/battery/store_mode: 1 = retail demo mode. Charging stops and the phone runs from the charger, but the battery sits at 100%. - /sys/class/power_supply/battery/input_suspend: 1 cuts the charger input, so the phone drains (about 160 mA on mine). A 5-minute loop holds the battery between 60 and 70%: suspend the input above 70%, restore it at 60%, always restore below 55%. Note that Samsung's gauge shows 100% for a while after real discharge starts. 11. Push the repos Do the first push over USB (adb forward tcp:18022 tcp:8022), it's much faster. After that, push over Tailscale. Set receive.denyNonFastForwards true on the bare repos so a bad force-push can't destroy history. Result The phone boots unattended (no Power press needed at the unlocked-bootloader warning), and SSH over Tailscale is up about 80 seconds after power-on. This was done with the great help of Claude Opus 5.5.
Tutorial: turn an old Samsung phone into a private, always-on git server (Magisk + Termux + Tailscale), including every mistake I made so you don't >I'm making this post in the hope it will help someone else turn a piece of e-waste in an actually useful piece of tech. I'm not really a "tutorial guy" so please feel free to correct me anytime. >Keep in mind that this tutorial isn't exactly 'non-tech-savvy friendly', so don't be surprised if you turn to your go-to chatbot for a few quick questions to understand what you're doing and if it's correct. I had two git repos that are too big for GitHub (about 6 GB and 400 MB), and an old phone. Here's how I turned a Galaxy A14 4G (SM-A145R, MediaTek MT6769) into a git-over-SSH server: - It's reachable only through my Tailscale network. - It can't reach my PCs. - Every app on it except two has no network access. - The battery is held at 60–70% even though the charger is always plugged in. Below is what to do, and in "DON'T" boxes, what went wrong for me. I did all of it from Ubuntu. Most of it applies to any recent Samsung, and a lot of it to any rootable Android phone. What you need - A Samsung phone whose bootloader can be unlocked. Most international/EU models can. US models usually can't: if download mode shows no "OEM LOCK" line, you can theoretically still do the home server but without battery handling and many other optimizations, basically the last part of the tutorial. - A Linux PC (Windows works too, with different tools), a USB cable, and a free Tailscale account. - Patience: the phone will be wiped several times. 1. Unlock the bootloader 1. Settings → Developer options → OEM unlocking on. 2. Power off. Hold Vol Up + Vol Down while plugging the USB cable in (PC end connected). Long-press Vol Up to unlock, then confirm. This wipes the phone. 3. Do the setup connected to Wi-Fi. Afterwards, the OEM unlocking switch must be greyed out. In download mode, KG STATUS should read CHECKING, COMPLETED or BROKEN. PRENORMAL means Knox Guard won't accept unofficial images yet: wait for about 168 hours of uptime. 2. Download the exact firmware Use samloader-rs by topjohnwu (the Magisk author): topjohnwu/samloader-rs. It downloads straight from Samsung's servers and flashes. - Samsung now only serves the latest firmware, and the request must include the phone's serial or IMEI, otherwise you get error S01. - Verify the MD5 trailer of every .tar.md5. 3. Patch the boot image with Magisk Patch the firmware's AP file with the Magisk app ("Select and Patch a File"), or patch only init_boot.img with Magisk's boot_patch.sh over adb. The result is the same: a patched init_boot, vbmeta with flags = 3 (verification off), and no userdata.img. 4. Flash and wipe samloader flash -b BL_*.tar.md5 -a magisk_patched.tar -c CP_*.tar.md5 -s CSC_*.tar.md5 Use CSC, not HOME_CSC: the first Magisk install needs a data wipe. If recovery then says "Can't load Android system", choose Factory data reset. That's expected. >DON'T use Heimdall on newer Samsungs. On mine: > >samloader-rs worked first try. > >DON'T flash HOME_CSC for the first root ("to keep data"): I got a boot loop. > >DON'T flash the stock init_boot/vbmeta back to "undo" a boot loop. Magisk's docs warn about this, and they're right: it keeps looping until you wipe. > >DON'T rely on --erase: on MediaTek it erased "0 sectors". > >DON'T use -p partition_name in samloader-rs on MediaTek: it upper-cases the name and the PIT is lowercase. Use -f init_boot.img. Also, a failed command in the middle of a session means a button restart. 5. First boot - Minimal setup: Wi-Fi, no accounts. Then developer options and USB debugging. - Before installing anything over adb, disable the Play Store and the Galaxy Store: - Right after setup they restore and install apps (on mine even TikTok) and occupy the install queue, so adb install just hangs. - Install the full Magisk app and accept its "additional setup" reboot. From then on, adb shell su -c id gives root. 6. Debloat: in batches, with a failsafe pm uninstall --user 0 <package> doesn't touch the system partition and can be undone with cmd package install-existing <package>. But: >DON'T remove 170 packages in one go. I did, and the phone stuck on "Phone is starting..." forever, with no USB and no adb. Factory reset. > >DON'T remove Google Play Services (com.google.android.gms), GSF and Chrome on One UI 7, even if you never use Google. Samsung reinstalls them at boot; if you remove them again, boot hangs exactly like above. > >Some packages (setup wizards, Smart Switch, AdServices and telemetry modules, a few Samsung services) are re-enabled by Samsung at every boot, whether you uninstall or disable them. Don't fight them: firewall them (step 9). What worked for me: - Batches, with one reboot test per batch. - A Magisk service.d failsafe: before applying a batch, write its list to /data/adb/.../pending.txt. At boot, a script waits up to 7 minutes for a real home screen (not FallbackHome, the activity behind "Phone is starting..."). If it doesn't come, it reinstalls the batch and reboots. - Test the failsafe on purpose with a fake failure. Mine was broken the first time: >DON'T redirect cmd package ... output to a file under /data/adb in a Magisk script. system_server isn't allowed to write to that fd (SELinux), so the call fails with "Failed transaction". Capture the output through a pipe (r=$(cmd package ... 2>&1 | cat)). Rescue without a factory reset. A hang on "Phone is starting..." can be fixed without wiping: 1. Add an overlay.d/*.rc plus a script to the Magisk-patched init_boot ramdisk. magiskboot from the Magisk APK (lib/x86_64) runs on Linux. 2. The script waits for the package service, runs cmd package install-existing on what you removed, writes a marker file, and reboots once. 3. Flash just init_boot from download mode. That saved me a second full reinstall. 7. Termux + sshd - Install Termux and Termux:Boot from their GitHub releases, or F-Droid. - pkg install openssh git. - In sshd_config: port 8022, PasswordAuthentication no, AuthenticationMethods publickey, all forwarding off. - On the phone, put your repos in bare repositories: git init --bare ~/git/<name>.git. Running Termux commands from a root shell: su <termux-uid> -g <termux-uid> -G 3003 -G 9997 -c "export PREFIX=... HOME=... PATH=$PREFIX/bin LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so; ..." >DON'T forget -g: otherwise the first -G becomes the primary group and dpkg fails with "unable to change ownership". > >DON'T forget -G 3003 (inet): without it, Android denies network sockets. > >DON'T trust Termux:Boot to keep sshd up. Android killed its process as "empty" before sshd started. Instead, start sshd from a Magisk service.d script as the Termux user, check it every 30 seconds, and hold a kernel wake lock (echo myserver > /sys/power/wake_lock) so the CPU never sleeps. 8. Tailscale - Install from F-Droid. - In the admin console, create a single-use, pre-approved auth key with a tag (e.g. tag:gitserver). In the app: Settings → Accounts → ⋮ → "Use an auth key". - ACLs: your own devices may reach tag:gitserver on tcp:8022. The tag is never a source, so a stolen phone can't connect to your other machines. - Set Tailscale as Always-on VPN (settings put secure always_on_vpn_app com.tailscale.ipn), otherwise it doesn't come back after a reboot. >DON'T use Private DNS in "strict" mode (e.g. dns.quad9.net) together with Tailscale on Android: all DNS dies. Use "automatic", and turn "Use Tailscale DNS" off on the phone. 9. Firewall (iptables from a Magisk script) - Inbound: port 8022 only on tun+ (Tailscale) and lo (adb forward); no new TCP connections from Wi-Fi. - Outbound: - no new connections to private ranges, except Tailscale's own UID (so it can use direct LAN paths), DHCP, and DNS (53) for system UIDs; - then -m owner --uid-owner 10000-99999 -j REJECT for every app except Termux and Tailscale. - The app rule also takes care of the Google/Samsung packages you weren't allowed to remove. >DON'T block DNS to your router: in "automatic" mode Android's resolver uses it, and everything breaks the moment you block the LAN. I found out the hard way. > >DON'T pkill -f or grep for a pattern that also appears in your own command line (e.g. the script path): you kill your own shell. It happened to me three times. Re-check the chains every minute, because netd can rebuild them. 10. Battery on a phone that's always plugged in Cheap Samsungs don't have "Pause USB Power Delivery", but the battery driver exposes: - /sys/class/power_supply/battery/store_mode: 1 = retail demo mode. Charging stops and the phone runs from the charger, but the battery sits at 100%. - /sys/class/power_supply/battery/input_suspend: 1 cuts the charger input, so the phone drains (about 160 mA on mine). A 5-minute loop holds the battery between 60 and 70%: suspend the input above 70%, restore it at 60%, always restore below 55%. Note that Samsung's gauge shows 100% for a while after real discharge starts. 11. Push the repos Do the first push over USB (adb forward tcp:18022 tcp:8022), it's much faster. After that, push over Tailscale. Set receive.denyNonFastForwards true on the bare repos so a bad force-push can't destroy history. Result The phone boots unattended (no Power press needed at the unlocked-bootloader warning), and SSH over Tailscale is up about 80 seconds after power-on. This was done with the great help of Claude Opus 5.5.
Tutorial: turn an old Samsung phone into a private, always-on git server (Magisk + Termux + Tailscale), including every mistake I made so you don't > I'm making this post in the hope it will help someone else turn a piece of e-waste in an actually useful piece of tech. I'm not really a "tutorial guy" so please feel free to correct me anytime. > Keep in mind that this tutorial isn't exactly 'non-tech-savvy friendly', so don't be surprised if you turn to your go-to chatbot for a few quick questions to understand what you're doing and if it's correct. I had two git repos that are too big for GitHub (about 6 GB and 400 MB), and an old phone. Here's how I turned a Galaxy A14 4G (SM-A145R, MediaTek MT6769) into a git-over-SSH server: - It's reachable only through my Tailscale network. - It can't reach my PCs. - Every app on it except two has no network access. - The battery is held at 60–70% even though the charger is always plugged in. Below is what to do, and in "DON'T" boxes, what went wrong for me. I did all of it from Ubuntu. Most of it applies to any recent Samsung, and a lot of it to any rootable Android phone. What you need - A Samsung phone whose bootloader can be unlocked. Most international/EU models can. US models usually can't: if download mode shows no "OEM LOCK" line, give up. - A Linux PC (Windows works too, with different tools), a USB cable, and a free Tailscale account. - Patience: the phone will be wiped several times. 1. Unlock the bootloader 1. Settings → Developer options → OEM unlocking on. 2. Power off. Hold Vol Up + Vol Down while plugging the USB cable in (PC end connected). Long-press Vol Up to unlock, then confirm. This wipes the phone. 3. Do the setup connected to Wi-Fi. Afterwards, the OEM unlocking switch must be greyed out. In download mode, KG STATUS should read CHECKING, COMPLETED or BROKEN. PRENORMAL means Knox Guard won't accept unofficial images yet: wait for about 168 hours of uptime. 2. Download the exact firmware Use samloader-rs by topjohnwu (the Magisk author): topjohnwu/samloader-rs. It downloads straight from Samsung's servers _and_ flashes. - Samsung now only serves the latest firmware, and the request must include the phone's serial or IMEI, otherwise you get error S01. - Verify the MD5 trailer of every .tar.md5. 3. Patch the boot image with Magisk Patch the firmware's AP file with the Magisk app ("Select and Patch a File"), or patch only init_boot.img with Magisk's boot_patch.sh over adb. The result is the same: a patched init_boot, vbmeta with flags = 3 (verification off), and no userdata.img. 4. Flash and wipe samloader flash -b BL_*.tar.md5 -a magisk_patched.tar -c CP_*.tar.md5 -s CSC_*.tar.md5 Use CSC, not HOME_CSC: the first Magisk install _needs_ a data wipe. If recovery then says "Can't load Android system", choose Factory data reset. That's expected. > DON'T use Heimdall on newer Samsungs. On mine: > > - its timeouts were too short (userdata failed); > - flashing misc made the bootloader fail the whole session, leaving the phone on "An error occurred while updating the device software"; > - every half-finished session blocked all later handshakes until I restarted into download mode with the buttons. > > samloader-rs worked first try. > > DON'T flash HOME_CSC for the first root ("to keep data"): I got a boot loop. > > DON'T flash the stock init_boot/vbmeta back to "undo" a boot loop. Magisk's docs warn about this, and they're right: it keeps looping until you wipe. > > DON'T rely on --erase: on MediaTek it erased "0 sectors". > > DON'T use -p partition_name in samloader-rs on MediaTek: it upper-cases the name and the PIT is lowercase. Use -f init_boot.img. Also, a failed command in the middle of a session means a button restart. 5. First boot - Minimal setup: Wi-Fi, no accounts. Then developer options and USB debugging. - Before installing anything over adb, disable the Play Store and the Galaxy Store: pm disable-user --user 0 com.android.vending pm disable-user --user 0 com.sec.android.app.samsungapps Right after setup they restore and install apps (on mine even TikTok) and occupy the install queue, so adb install just hangs. - Install the full Magisk app and accept its "additional setup" reboot. From then on, adb shell su -c id gives root. 6. Debloat: in batches, with a failsafe pm uninstall --user 0 <package> doesn't touch the system partition and can be undone with cmd package install-existing <package>. But: > DON'T remove 170 packages in one go. I did, and the phone stuck on "Phone is starting..." forever, with no USB and no adb. Factory reset. > > DON'T remove Google Play Services (com.google.android.gms), GSF and Chrome on One UI 7, even if you never use Google. Samsung reinstalls them at boot; if you remove them again, boot hangs exactly like above. > > Some packages (setup wizards, Smart Switch, AdServices and telemetry modules, a few Samsung services) are re-enabled by Samsung at every boot, whether you uninstall or disable them. Don't fight them: firewall them (step 9). What worked for me: - Batches, with one reboot test per batch. - A Magisk service.d failsafe: before applying a batch, write its list to /data/adb/.../pending.txt. At boot, a script waits up to 7 minutes for a real home screen (not FallbackHome, the activity behind "Phone is starting..."). If it doesn't come, it reinstalls the batch and reboots. - Test the failsafe on purpose with a fake failure. Mine was broken the first time: > DON'T redirect cmd package ... output to a file under /data/adb in a Magisk script. system_server isn't allowed to write to that fd (SELinux), so the call fails with "Failed transaction". Capture the output through a pipe (r=$(cmd package ... 2>&1 | cat)). Rescue without a factory reset. A hang on "Phone is starting..." can be fixed without wiping: 1. Add an overlay.d/*.rc plus a script to the Magisk-patched init_boot ramdisk. magiskboot from the Magisk APK (lib/x86_64) runs on Linux. 2. The script waits for the package service, runs cmd package install-existing on what you removed, writes a marker file, and reboots once. 3. Flash just init_boot from download mode. That saved me a second full reinstall. 7. Termux + sshd - Install Termux and Termux:Boot from their GitHub releases, or F-Droid. - pkg install openssh git. - In sshd_config: port 8022, PasswordAuthentication no, AuthenticationMethods publickey, all forwarding off. - On the phone, put your repos in bare repositories: git init --bare ~/git/<name>.git. Running Termux commands from a root shell: su <termux-uid> -g <termux-uid> -G 3003 -G 9997 -c "export PREFIX=... HOME=... PATH=$PREFIX/bin LD_PRELOAD=$PREFIX/lib/libtermux-exec-ld-preload.so; ..." > DON'T forget -g: otherwise the first -G becomes the primary group and dpkg fails with "unable to change ownership". > > DON'T forget -G 3003 (inet): without it, Android denies network sockets. > > DON'T trust Termux:Boot to keep sshd up. Android killed its process as "empty" before sshd started. Instead, start sshd from a Magisk service.d script as the Termux user, check it every 30 seconds, and hold a kernel wake lock (echo myserver > /sys/power/wake_lock) so the CPU never sleeps. 8. Tailscale - Install from F-Droid. - In the admin console, create a single-use, pre-approved auth key with a tag (e.g. tag:gitserver). In the app: Settings → Accounts → ⋮ → "Use an auth key". - ACLs: your own devices may reach tag:gitserver on tcp:8022. The tag is never a source, so a stolen phone can't connect to your other machines. - Set Tailscale as Always-on VPN (settings put secure always_on_vpn_app com.tailscale.ipn), otherwise it doesn't come back after a reboot. > DON'T use Private DNS in "strict" mode (e.g. dns.quad9.net) together with Tailscale on Android: _all_ DNS dies. Use "automatic", and turn "Use Tailscale DNS" off on the phone. 9. Firewall (iptables from a Magisk script) - Inbound: port 8022 only on tun+ (Tailscale) and lo (adb forward); no new TCP connections from Wi-Fi. - Outbound: - no new connections to private ranges, except Tailscale's own UID (so it can use direct LAN paths), DHCP, and DNS (53) for system UIDs; - then -m owner --uid-owner 10000-99999 -j REJECT for every app except Termux and Tailscale. The app rule also takes care of the Google/Samsung packages you weren't allowed to remove. > DON'T block DNS to your router: in "automatic" mode Android's resolver uses it, and everything breaks the moment you block the LAN. I found out the hard way. > > DON'T pkill -f or grep for a pattern that also appears in your own command line (e.g. the script path): you kill your own shell. It happened to me three times. Re-check the chains every minute, because netd can rebuild them. 10. Battery on a phone that's always plugged in Cheap Samsungs don't have "Pause USB Power Delivery", but the battery driver exposes: - /sys/class/power_supply/battery/store_mode: 1 = retail demo mode. Charging stops and the phone runs from the charger, but the battery sits at 100%. - /sys/class/power_supply/battery/input_suspend: 1 cuts the charger input, so the phone drains (about 160 mA on mine). A 5-minute loop holds the battery between 60 and 70%: suspend the input above 70%, restore it at 60%, always restore below 55%. Note that Samsung's gauge shows 100% for a while after real discharge starts. 11. Push the repos Do the first push over USB (adb forward tcp:18022 tcp:8022), it's much faster. After that, push over Tailscale. Set receive.denyNonFastForwards true on the bare repos so a bad force-push can't destroy history. Result The phone boots unattended (no Power press needed at the unlocked-bootloader warning), and SSH over Tailscale is up about 80 seconds after power-on. This was done with the great help of Claude Opus 5.5.