diff --git a/modules/darwin/apps.nix b/modules/darwin/apps.nix
index 5f8c561b..b217c8f2 100644
--- a/modules/darwin/apps.nix
+++ b/modules/darwin/apps.nix
@@ -158,6 +158,8 @@ in {
"telegram"
"discord"
"microsoft-remote-desktop"
+ "moonlight" # remote desktop client
+ "rustdesk"
# "anki"
"shadowsocksx-ng" # proxy tool
diff --git a/modules/nixos/desktop/game/steam.nix b/modules/nixos/desktop/game/steam.nix
index d16274ca..cf58a24e 100644
--- a/modules/nixos/desktop/game/steam.nix
+++ b/modules/nixos/desktop/game/steam.nix
@@ -1,6 +1,7 @@
# https://github.com/fufexan/dotfiles/blob/483680e/system/programs/steam.nix
{pkgs, ...}: {
# https://wiki.archlinux.org/title/steam
+ # Games installed by Steam works fine on NixOS, no other configuration needed.
programs.steam = {
# Some location that should be persistent:
# ~/.local/share/Steam - The default Steam install location
diff --git a/modules/nixos/desktop/remote-desktop/README.md b/modules/nixos/desktop/remote-desktop/README.md
new file mode 100644
index 00000000..2eef9997
--- /dev/null
+++ b/modules/nixos/desktop/remote-desktop/README.md
@@ -0,0 +1,10 @@
+# Remote Desktop
+
+1. **X11**: We have `xrdp` & `ssh -x` for remote desktop access, which works well for most use cases.
+2. **Wayland**: (not tested)
+ 1. `waypipe`: similar to `ssh -X`, transfer wayland data over a ssh connection.
+ 2. [rustdesk](https://github.com/rustdesk/rustdesk): a remote desktop client/server written in rust.
+ 1. confirmed broken currently:
+ 3. [sunshine server](https://github.com/LizardByte/Sunshine) + [moonlight client](https://github.com/moonlight-stream): It's designed for game streaming, but it can be used for remote desktop as well.
+ 1. broken currently:
+
diff --git a/modules/nixos/desktop/remote-desktop/default.nix b/modules/nixos/desktop/remote-desktop/default.nix
new file mode 100644
index 00000000..3ab750cd
--- /dev/null
+++ b/modules/nixos/desktop/remote-desktop/default.nix
@@ -0,0 +1,13 @@
+{
+ pkgs,
+ mylib,
+ ...
+}: {
+ imports = mylib.scanPaths ./.;
+
+ environment.systemPackages = with pkgs; [
+ waypipe
+ moonlight-qt # moonlight client, for streaming games/desktop from a PC
+ rustdesk # p2p remote desktop
+ ];
+}
diff --git a/modules/nixos/desktop/remote-desktop/sunshine.nix b/modules/nixos/desktop/remote-desktop/sunshine.nix
new file mode 100644
index 00000000..b8e9bdcc
--- /dev/null
+++ b/modules/nixos/desktop/remote-desktop/sunshine.nix
@@ -0,0 +1,70 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+# ===============================================================================
+#
+# Sunshine: A self-hosted game stream server for Moonlight(Client).
+# It's designed for game streaming, but it can be used for remote desktop as well.
+#
+# TODO: currently broken, fixed but not released yet: https://github.com/LizardByte/Sunshine/pull/1977
+#
+# How to use(Web Console: ):
+# https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/usage.html
+#
+# Check Service Status
+# systemctl --user status sunshine
+# Check logs
+# journalctl --user -u sunshine --since "2 minutes ago"
+#
+# References:
+# https://github.com/LongerHV/nixos-configuration/blob/c7a06a2125673c472946cda68b918f68c635c41f/modules/nixos/sunshine.nix
+# https://github.com/RandomNinjaAtk/nixos/blob/fc7d6e8734e6de175e0a18a43460c48003108540/services.sunshine.nix
+#
+# ===============================================================================
+{
+ security.wrappers.sunshine = {
+ owner = "root";
+ group = "root";
+ capabilities = "cap_sys_admin+p";
+ source = "${pkgs.sunshine}/bin/sunshine";
+ };
+
+ # Requires to simulate input
+ boot.kernelModules = ["uinput"];
+ services.udev.extraRules = ''
+ KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", TAG+="uaccess"
+ '';
+
+ # systemd.user.services.sunshine = {
+ # description = "A self-hosted game stream server for Moonlight(Client)";
+ # after = ["graphical-session-pre.target"];
+ # wants = ["graphical-session-pre.target"];
+ # wantedBy = ["graphical-session.target"];
+ # startLimitIntervalSec = 500;
+ # startLimitBurst = 5;
+ #
+ # serviceConfig = {
+ # ExecStart = "${config.security.wrapperDir}/sunshine";
+ # Restart = "on-failure";
+ # RestartSec = "5s";
+ # };
+ # };
+
+ networking.firewall = {
+ allowedTCPPortRanges = [
+ {
+ from = 47984;
+ to = 48010;
+ }
+ ];
+ allowedUDPPortRanges = [
+ {
+ from = 47998;
+ to = 48010;
+ }
+ ];
+ };
+}
diff --git a/modules/nixos/desktop/remote-desktop/tailscale.nix b/modules/nixos/desktop/remote-desktop/tailscale.nix
new file mode 100644
index 00000000..f493c3d2
--- /dev/null
+++ b/modules/nixos/desktop/remote-desktop/tailscale.nix
@@ -0,0 +1,76 @@
+{
+ config,
+ pkgs,
+ ...
+}:
+# =============================================================
+#
+# Tailscale - your own private network(VPN) that uses WireGuard
+#
+# It's open souce and free for personal use,
+# and it's really easy to setup and use.
+# Tailscale has great client coverage for Linux, windows, Mac, android, and iOS.
+# Tailscale is more mature and stable compared to other alternatives such as netbird/netmaker.
+# Maybe I'll give netbird/netmaker a try when they are more mature, but for now, I'm sticking with Tailscale.
+#
+# How to use:
+# 1. Create a Tailscale account at https://login.tailscale.com
+# 2. Login via `tailscale login`
+# 3. join into your Tailscale network via `tailscale up`
+# 4. If you prefer automatic connection to Tailscale, then generate a authkey, and uncomment the systemd service below.
+#
+# Status Data:
+# `journalctl -u tailscaled` shows tailscaled's logs
+# logs indicate that tailscale store its data in /var/lib/tailscale
+# which is already persistent across reboots(via impermanence.nix)
+#
+# References:
+# https://tailscale.com/blog/nixos-minecraft
+#
+# =============================================================
+{
+ # make the tailscale command usable to users
+ environment.systemPackages = [pkgs.tailscale];
+
+ # enable the tailscale service
+ services.tailscale.enable = true;
+
+ # create a oneshot job to authenticate to Tailscale
+ # systemd.services.tailscale-autoconnect = {
+ # description = "Automatic connection to Tailscale";
+ #
+ # # make sure tailscale is running before trying to connect to tailscale
+ # after = ["network-pre.target" "tailscale.service"];
+ # wants = ["network-pre.target" "tailscale.service"];
+ # wantedBy = ["multi-user.target"];
+ #
+ # # set this service as a oneshot job
+ # serviceConfig.Type = "oneshot";
+ #
+ # # have the job run this shell script
+ # script = with pkgs; ''
+ # # wait for tailscaled to settle
+ # sleep 2
+ #
+ # # check if we are already authenticated to tailscale
+ # status="$(${tailscale}/bin/tailscale status -json | ${jq}/bin/jq -r .BackendState)"
+ # if [ $status = "Running" ]; then # if so, then do nothing
+ # exit 0
+ # fi
+ #
+ # # otherwise authenticate with tailscale
+ # ${tailscale}/bin/tailscale up -authkey file:${config.age.secrets.tailscale-authkey.path}
+ # '';
+ # };
+
+ networking.firewall = {
+ # always allow traffic from your Tailscale network
+ trustedInterfaces = ["tailscale0"];
+
+ # allow the Tailscale UDP port through the firewall
+ allowedUDPPorts = [config.services.tailscale.port];
+
+ # allow you to SSH in over the public internet
+ allowedTCPPorts = [22];
+ };
+}