Random Thoughts on Investment for Retirement (Dec 2025)

In general I don’t want to give investment advices because everyone is different (wealth, objectives, pain tolerance, etc). However, some advices are so safe and sound that I feel it is worthwhile to share, better than watching people around me falling into some simple traps (impulse or intuition trading, coffee shop stock whispering, headline chasing, etc).

If You Are “Young”

“Young” means you are 5 years away from retirement. You are generally in the asset growth mode. Divide your investment into 3 buckets, and assign certain weight (percentages) to each bucket. If you are not sure, just assign 33.3% each.

  • Broad market ETF: VOO (Vanguard S&P 500), VTI (Vanguard total market index)
  • Growth ETF: QQQM (Nasdaq 100), VUG (Vanguard growth)
  • Value/dividend ETF: SCHD (Charles-Schwab Dividend), VYM (Vanguard high dividend), DGRO (iShares Core Dividend)

Feel free to pick’n’choose, and even add more of your favorites to each bucket list, but the key is to stick with the bucket-based asset allocation strategy and the pre-defined percentages.

On-going maintenance is easy:

  • Re-balance the portfolio to the preset percentages once a year or twice a year.
  • If you need money, sell from the bucket that exceeds the preset limit.
  • If you have money to invest, buy into the bucket that is under the preset limit.

If you do this consistently for over 10 years, something amazing happens:

  • You have very few things to do, no research, not many trading. Very suitable for lazy people.
  • Your portfolio will almost certainly perform better than general market (e.g., against VOO or VTI), which also means it performs better than 85% of all mutual funds.
  • As a result, you sleep better in the night and you smile better.

If you want to dig deeper, see this video for some insight on the so-called 3-fund portfolio (or 3-bucket strategy in my case). It also have suggestions on assigning the percentages for each bucket.

If You Are “Old”

“Old” means you are retiring in 2 years or have already retired. In this case a 4th bucket becomes very important, the income generation ETF.

Why? Income ETF does not give you better total return over the time. But it gives better night sleeps by avoiding the really bad years, while also losing some spectacular years at the same time. For retirees, that is a good trade-off.

I recommend you allocate this bucket large enough to cover your 50% to 80% of your living expenses. The remaining money can follow some variations of the above 3-bucket strategy.

I have researched and rated some income generation ETF funds. The ones I like right now include:

  • JEPI, JEPQ, GPIX, GPIQ, SPYI, QQQI, XYLD, O, SPYD

In general you should be able to generate ~8% income while preserving or slightly growing underlying capital.

If you want to dig deeper, the following youtubers have influenced me or share some similar ideas or offer more insights in this topic:

Some Misc Notes

  • Don’t fall into Roth IRA conversion trap. It is usually worse than it sounds.
  • Also don’t believe that it is always better to delay your social security benefits. I did an analysis, which shows maybe you should claim your benefits as early as 62.

Android Cuttlefish Container for Local Development

Android Cuttlefish has made a lot of progress since last time I have messed with them (about 5, 6 years ago!) For example, arm64 host is now supported. WebRTC is finally working. However, certain things are still pretty hideous. Docs are scattered around. Info are hard to find. Simply running a CVD instance locally for dev purpose is very troublesome.

So I took some time and cooked a docker container that satisfy my heart. It basically wrap around cvd host tools and create a single CVD instance inside a container. All the plumbing is in place to make it easy and quick to run. I call it cuttlefish-host-container.

Below is a straight copy of the README.md file from the github project page.


cuttlefish-host-container

Docker container that runs Android cuttlefish emulators for x86_64, arm64, riscv64 guests

Background

I simply wanted to run RISC-V AOSP via cuttlefish on my laptop, and the journey wasn’t smooth.

Goal

Simple container that runs one cuttlefish instance on an x86_64 Linux host. It can run the following guests:

  1. x86 guest via crosvm
  2. x86 guest via qemu
  3. arm64 guest via qemu
  4. riscv64 guest via qemu

Usage

Three steps to use the container:

  1. Build the docker image via "docker build ..." (once)
  2. Create or update the cvd instance via "cf-init.sh ..." (infrequently)
  • populate/update instance with aosp img zip and/or cvd host package
  1. Run the cvd instance via "cf-run.sh" (frequently)

Below is a sample execution sequence:


docker build . -t cf-host

./cf-init.sh -P aosp_cf_x86_64_only_phone-img-14421689.zip -H cvd-host_package.tar.gz

# run with qemu
./cf-run.sh
gvncviewer localhost

# or run with crosvm, much faster
./cf-run.sh -- -e CF_VM_MANAGER=crosvm -e CF_START_WEBRTC=true
firefox https://localhost:8443

You can find the product img zip file and cvd host package at here:

Or build them from your own aosp tree:


source build/envsetup.sh
lunch aosp_cf_x86_64_only_phone-aosp_current-userdebug
m dist
# packages are in $(ANDROID_ROOT)/out/dist/

Additional notes:

  • run cf-init.sh -h and cf-run.sh -h for more info
  • create multiple containers/instances by creating multiple directories. Example: mkdir riscv; cd riscv; ../cf-init.sh ...; ../cf-run.sh
    • However, only one instance can run at a time due to port conflicts.
  • ADB & VNC ports are visible to the LAN. You can run on a headless server.

TODO

  • GPU acceleration does not work yet

Screenshot

Nuances about docker.io on Ubuntu 24.04

I just realized today that the default docker.io package from Ubuntu is seriously limited, compared with the one provided by Docker Inc with their docker-ce (community edition).

For example, with the following Dockerfile, the Ubuntu’s version of docker would build all intermediate stages, including test-stage, while with Docker Engine packages from docker-ce the test-stage will be skipped (which is more desirable).

# syntax=docker/dockerfile:1
FROM alpine:latest AS build-stage
RUN echo "Installing build tools..."
RUN mkdir /app
RUN touch /app/artifact.txt

FROM alpine:latest AS test-stage
RUN echo "Running tests..."
# This stage is only run if explicitly targeted

FROM alpine:latest AS production
COPY --from=build-stage /app/artifact.txt /app/artifact.txt
CMD ["cat", "/app/artifact.txt"]

To properly installed the docker.io and its companion packages, just follow this page. The output for running the above Dockerfile will look like below with docker-ce packages.

jsun@dev:~/temp$ docker build . -t mytest
[+] Building 1.5s (11/11) FINISHED                                                                                                                                                                                                             docker:default
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                     0.0s
 => => transferring dockerfile: 419B                                                                                                                                                                                                                     0.0s
 => resolve image config for docker-image://docker.io/docker/dockerfile:1                                                                                                                                                                                0.7s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6                                                                                                                          0.0s
 => => resolve docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6                                                                                                                                     0.0s
 => [internal] load metadata for docker.io/library/alpine:latest                                                                                                                                                                                         0.3s
 => [internal] load .dockerignore                                                                                                                                                                                                                        0.0s
 => => transferring context: 2B                                                                                                                                                                                                                          0.0s
 => [build-stage 1/4] FROM docker.io/library/alpine:latest@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375                                                                                                                       0.0s
 => => resolve docker.io/library/alpine:latest@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375                                                                                                                                   0.0s
 => CACHED [build-stage 2/4] RUN echo "Installing build tools..."                                                                                                                                                                                        0.0s
 => CACHED [build-stage 3/4] RUN mkdir /app                                                                                                                                                                                                              0.0s
 => CACHED [build-stage 4/4] RUN touch /app/artifact.txt                                                                                                                                                                                                 0.0s
 => CACHED [production 2/2] COPY --from=build-stage /app/artifact.txt /app/artifact.txt                                                                                                                                                                  0.0s
 => exporting to image                                                                                                                                                                                                                                   0.1s
 => => exporting layers                                                                                                                                                                                                                                  0.0s
 => => exporting manifest sha256:cfce1f88ad86e2d55be2ea639e9ccbee74e6feae06ff46d6a6f6efbe7b4e4f30                                                                                                                                                        0.0s
 => => exporting config sha256:f6db73b62eabd225c8916c02941c7389b510781cba6e8308c80036cde6a9d4f9                                                                                                                                                          0.0s
 => => exporting attestation manifest sha256:1b3ada3c9b1c771e50fbbb44962ee1baaaf659b635a66302d0c25b5fe484c86e                                                                                                                                            0.1s
 => => exporting manifest list sha256:44066fc8f168c1d0508e4ceca10171a3a1623deb7cbc813f97aeec5df4faa3c9                                                                                                                                                   0.0s
 => => naming to docker.io/library/mytest:latest                                                                                                                                                                                                         0.0s
 => => unpacking to docker.io/library/mytest:latest        

Delay Social Security Benefit until Full Retirement Age? Not really!

I keep hearing from so-called professional urging to delay claiming social security benefits until full retirement age, which is 67 right now, or later. The argument is that doing so would maximize the total benefit one receives over the life time. However, no one seems to care about the income gap this strategy creates during the delaying years. If someone needs to use those money, will the person go on borrowing a loan, or selling stock or withdrawing from IRA/Roth IRA to make up the difference? If so, is delaying benefits still a good strategy given lost opportunities or penalties in filling the gap?

So I set out to do a study, with the help of ChatGPT:

  • Assume all social security benefits are used to invest, with certain total return rate.
  • Then we calculate the total net worth at death assuming different starting ages to claim the benefit with respect to different death ages.
  • Re-examine the above with different total return rate in investment

The Base Case: 8% return, death age 85

The monthly benefit is assumed to $700, although it does not really matter since the total net worth scale proportionally and this amount will not affect the findings about optimal starting. In below calculation, social security benefit is deposited monthly, and investment value is compounded monthly as well.

Below the table to calculate the total net worth at death w.r.t. different starting ages.

We can see that from age 62 to 70, the total amount collected from Social Security is increasing, which is the basis for some to argue the delayed claim. However, in this case we see the compounding investment return is more pronounced for earlier claims and yield more total net worth at the age 85.

Extending the Death Age, 70-100

We now extend our consideration about the death age from 70 to 100, with 5 year jump in-between. Below table lists the optimal starting with respect to various death ages.

Death ageBest start ageNet worth at death
7062$93,708
7562$191,044
8062$336,060
8562$552,112
9062$873,995
9562$1,353,552
10062$2,068,017

Again, the optimal starting is 62 consistent across the board for all death ages.

Extending with Different Return Rates

We now consider 4 possible return rates, 4%, 6%, 8%, and 10%. The optimal starting ages and total net worth at death are listed in below 2 tables

optimal starting age for social security benefits
total net worth at death

Conclusion

Unless you live long and your investment return is low, start claiming SS benefits early!

Geekbench 6 Scores for Intel i9-13900H

The system is Minisforum MS-01 mini PC. I have installed 96GB Crucial DDR5 5600 RAM and Samsung 4TB 990 Pro SSD. OS is Ubuntu 24.04.

I9-13900H has 6 performance cores with up to 5.4GHz freqency and 8 efficient cores with up to 4.1GHz frequency. Additionally the performance cores can be configured in BIOS to enable hyperthreading (SMT), which essentially doubles the CPU core count for P-cores. When SMT is enabled, the total number of CPU cores is 20 from OS perspective.

Ubuntu supports 3 power mode: performance, balanced and power saver.

power modeperformancebalancedpower saver
observed cpu freq.P-core: 5400MHz
E-core: 4100MHz
3800MHz2500MHz
SMT enabledsingle: 2713
multi: 13571
single: 1968
multi: 11484
single: 1147
multi: 3436
SMT disabledsingle: 2657
multi: 13393
single: 1970
multi: 10647
single: 1141
multi: 3131

A few observations:

  • SMT does not change single core performance (expected) and add a little (2%-10%) to multi-core performance.
  • Multi-processor scaling is not very efficient, because multi-core score is usually about 3x-5x of single core score, while there are 6 performance core and 8 efficient cores in the system.
  • Ubuntu achieve various power modes by capping maximum CPU frequencies.

Install GCC-14 on ARM64 Debian 11 Bullseye

I need to install the latest gcc on an old ARM64 board, which runs Debian 11 Bullseye. Straightforward of running “sudo apt install g++-14” won’t find the package. And Internet search does not lead to a simple answer either. There deserves a post.

sudo update-alternatives --remove-all gcc 
sudo update-alternatives --remove-all g++
sudo update-alternatives --remove-all cpp

sudo add-apt-repository "deb http://deb.debian.org/debian sid main"
sudo apt update
sudo apt install g++-14

vers=14; sudo update-alternatives  \
    --install /usr/bin/gcc gcc /usr/bin/gcc-"${vers}" "${vers}"0  \
    --slave /usr/bin/aarch64-linux-gnu-gcc aarch64-linux-gnu-gcc /usr/bin/aarch64-linux-gnu-gcc-"${vers}"  \
    --slave /usr/bin/g++ g++ /usr/bin/g++-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-g++ aarch64-linux-gnu-g++ /usr/bin/aarch64-linux-gnu-g++-"${vers}"  \
    --slave /usr/bin/cpp cpp /usr/bin/cpp-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-cpp aarch64-linux-gnu-cpp /usr/bin/aarch64-linux-gnu-cpp-"${vers}"  \
    --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-gcc-ar aarch64-linux-gnu-gcc-ar /usr/bin/aarch64-linux-gnu-gcc-ar-"${vers}"  \
    --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-gcc-nm aarch64-linux-gnu-gcc-nm /usr/bin/aarch64-linux-gnu-gcc-nm-"${vers}"  \
    --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-gcc-ranlib aarch64-linux-gnu-gcc-ranlib /usr/bin/aarch64-linux-gnu-gcc-ranlib-"${vers}"  \
    --slave /usr/bin/gcov gcov /usr/bin/gcov-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-gcov aarch64-linux-gnu-gcov /usr/bin/aarch64-linux-gnu-gcov-"${vers}"  \
    --slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-gcov-dump aarch64-linux-gnu-gcov-dump /usr/bin/aarch64-linux-gnu-gcov-dump-"${vers}"  \
    --slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-gcov-tool aarch64-linux-gnu-gcov-tool /usr/bin/aarch64-linux-gnu-gcov-tool-"${vers}"  \
    --slave /usr/bin/lto-dump lto-dump /usr/bin/lto-dump-"${vers}"  \
    --slave /usr/bin/aarch64-linux-gnu-lto-dump aarch64-linux-gnu-lto-dump /usr/bin/aarch64-linux-gnu-lto-dump-"${vers}"

My Complaints/Wish List for Tesla FSD 12.5.2 (Model Y)

Don’t get me wrong. I love FSD. It is one of the most exciting consumer experiences in the recent years. Last such excitement for me probably goes back more than 10 years when I first put my hand on Apple iPhone 2. For me, FSD changes the perception of distance and mobility. It makes San Francisco symphony concert much more enjoyable than ever.

That said, I still have a lot of complaints of today’s FSD.

  1. Too many unnecessary accelerations, many of which are followed by consequential breakings, which means more discomfort. Note I chose “Chill” profile. Otherwise it is probably even worse.
  2. Acceleration is often too aggressive, especially for local driving. Why so? saving time? There are many other wasted opportunities for saving time. No need to focus on the acceleration aspect.
  3. Phantom breaking, i.e., breaking for no obvious reasons. (discomfort and wasting time)
  4. Non-human breaking pattern, which often breaks too hard at beginning and then, realizing it, relax the breaking. Human often breaks soft, and then, realizing not enough, start to break harder (discomfort)
  5. Don’t remember routine route road conditions. Every road has its own quirks. It is OK for Tesla to be cautious when seeing those quirk if it sees for the first time. However, it does not remember those quirks and repeating driving always feels like first-timer driving on those roads. Human tend to be more relaxed (and thus more smooth) on later repeated driving.
  6. Map sucks and don’t remember my corrections. This is especially annoying for routine routes.
  7. Can’t back to my garage, which is the only way I can charge my Tesla today.

Elon Musk, I hope you can see my wish list.

Build leveldb v1.22 for latest Alpine Linux (3.20)

Alpine Linux is a popular choice for building docker. Since v3.17, leveldb version is bumped from v1.22 to v1.23, where the new version disabled rtti, which caused many packages to break, including popular python module, plyvel. I encountered this problem when building electrumX docker image. Googling around I realize this is a popular problem and there are no easy and obvious solutions.

I decided to simply build apk files for leveldb v1.22 against the latest alpine linux, which is 3.20 today.

Steps:

  1. Mostly follow an excellent tutorial at fudex.org on setting up the docker build environment.
  2. Check out aports at alpine version v3.16, (host) git clone –depth=1 https://gitlab.alpinelinux.org/alpine/aports -b 3.16-stable aports-3.16
  3. Copy over leveldb APKBUILD file over to the latest 3.20 aports, (host) cp -a aports-3.16/main/leveldb aports/main/
  4. go back to alpine-dev container and build the package files
    1. (alpine-dev) cd ~/aports/main/leveldb
    2. (alpine-dev) abuild -r

The file APK files are located under ~/packages/main/x86_64. For convenience, I have include those two files below. Of course, you should strip the “.bin” suffix before using them. I had to add the suffix to work around wordpress restrictions. Please refer to this docker file to see how these files are used in a real case.

Migrate/Re-use UmbrelOS SSD from Raspberry Pi to X86

I was running UmbrelOS on Raspberry Pi, with an external 2TB SSD disk. The machine is obviously under powered and started to show weakness. So I decided to migrate to a Lenovo mini-PC, M710q.

I need to re-use the same the SSD disk because M710q doesn’t have enough storage by itself (500GB). Ideally I can keep all the blockchain data and setup, so that I don’t have re-sync everything and re-setup everything.

While this may sound like a common question, I did not find many answers on the internet. The closest one is this one. However, UmbrelOS is currently at v1.x. A lot of don’t apply anymore.

Below is how I did it.

  • Prepare SSD disk
    • Upgrade Raspberry Pi to the latest UmbrelOS v1.1.
    • Shut it down via Umbrel Settings and unplug SSD disk.
  • Prepare PC
  • Migrate SSD disk over
    • Plug SSD disk into PC. Permanently mount it at /mnt/umbrel-ssd
      • /etc/fstab: UUID=e9ae3217-6a06-4721-b725-78e6524b4272 /mnt/umbrel-ssd ext4 defaults 0 0
    • sudo systemctl stop umbrel
    • cd /home/umbrel; mv umbrel umbrel.bak; ln -s /mnt/umbrel-ssd/umbrel umbrel
    • sudo systemctl start umbrel

It will take a long to re-start umbrel since it will fetch various containers etc. But it will use the same blockchain and other app-specific settings you had before on SSD disk. After a couple of coffee time, you will be all set!

How I Rooted OnePlus 12 with Magisk

There are many conflicting sources on the Internet. Specifically I tried this one and did not work. Below is a short recap what has worked for me.

Short Recap

  • Follow Magisk official installation guide
    • OnePlus 12 has ramdisk and uses init_boot.img
    • get oneplus 12 image zip file from this site
    • use payload dumper to extract init_boot.img from here
    • patch init_boot.img and flash the patched version according to the guide
  • By now, Magisk should be installed and you should have root access
  • Install Magisk Module Manager to install modules
    • for unknown reasons, I could install modules with Magisk app itself, nor through the manual method
  • (Bonus) I like to create your own Magisk module. I used the template
    • Specifically if you like to modify a file under /system_ext, please use the path /system/system_ext.
    • For example, if you like to add a file /system_ext/foo, use /system/system_ext/foo instead.

That is it!