Compare commits

...

10 commits

Author SHA1 Message Date
ashie
db959e9fd4 feat: Replace SearXNG with Websurfx
- Add new Websurfx module (Rust-based meta search engine)
- Websurfx is faster and lighter than SearXNG
- Uses Redis for caching via Podman containers
- Configurable themes and color schemes (defaults to catppuccin-mocha)
- Disable SearXNG, enable Websurfx on search.ashisgreat.xyz
2026-03-19 23:37:32 +00:00
aa670604b5 fix(openclaw): recursively fix permissions on data directory
- Use 'chown -R 1000:1000' and 'chmod -R u+rwX' in the preStart script.

- This ensures all files in /var/lib/openclaw are accessible by the container's node user.
2026-03-19 23:13:41 +01:00
49ffe471a3 fix(openclaw): remove invalid self-improvement hook
- The 'hooks' section with 'self-improvement' was causing a validation error and a crash loop on startup.
2026-03-19 23:10:31 +01:00
b505d2a327 fix(openclaw): fix ebusy on config file write
- Replace read-only bind mount for openclaw.json with a full directory bind mount.

- Use systemd preStart to copy the Nix declarative config file before startup.

- This prevents the EBUSY crash loop when OpenClaw attempts to modify its own config file on launch.
2026-03-19 23:04:50 +01:00
43bc670bf4 feat(openclaw): enable self-improvement hook
- Declaratively enable the `self-improvement` hook in the OpenClaw configuration file since it is mounted read-only into the Podman container.
2026-03-19 23:02:28 +01:00
adb8ddb611 feat(security): expose internal services and DoH to public
- Remove `internalOnly = true` flag from Vaultwarden, Forgejo, and AdGuard Home to make them publicly accessible again.

- This also re-exposes the DNS-over-HTTPS (DoH) endpoint on the AdGuard Home domain.
2026-03-19 22:48:14 +01:00
5d177a0d5c feat(network): rewrite internal dns and expose adguard ui
- Add DNS rewrites in AdGuard Home for `*.ashisgreat.xyz` to route to the Tailscale IP (`100.64.0.3`).

- Securely expose the AdGuard Home Web UI to the Tailscale network instead of blocking it completely.
2026-03-19 22:43:45 +01:00
f31ec2ce65 feat(security): restrict internal services to tailscale
- Add `internalOnly` option to nginx module to block public access.

- Apply `internalOnly` flag to Forgejo and Vaultwarden to ensure they are only accessible over the VPN or localhost.
2026-03-19 22:35:33 +01:00
b0b0989d36 fix(openclaw): revert invalid github tools/mcp config 2026-03-19 22:26:07 +01:00
6155b36279 . 2026-03-19 22:17:54 +01:00
7 changed files with 186 additions and 19 deletions

View file

@ -90,12 +90,14 @@
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# === SearXNG ===
myModules.searxng = {
# === Websurfx (replaces SearXNG) ===
myModules.websurfx = {
enable = true;
port = 8888;
domain = "search.ashisgreat.xyz"; # Change to your domain
instanceName = "Ashie Search";
domain = "search.ashisgreat.xyz";
threads = 8;
theme = "simple";
colorscheme = "catppuccin-mocha";
};
# === Nginx Reverse Proxy ===

View file

@ -96,6 +96,16 @@ in
querylog_enabled = true;
querylog_file_enabled = true;
statistics_enabled = true;
rewrites = [
{
domain = "*.ashisgreat.xyz";
answer = "100.64.0.3";
}
{
domain = "ashisgreat.xyz";
answer = "100.64.0.3";
}
];
};
tls = {
@ -175,15 +185,10 @@ in
iptables -t nat -D PREROUTING -i tailscale0 -p tcp --dport 53 -j REDIRECT --to-ports 5353 || true
'';
# Nginx configuration (kept to satisfy ACME challenges for DoT certificates)
services.nginx.virtualHosts."${cfg.domain}" = {
enableACME = true;
forceSSL = true;
# Block all paths (no DoH or UI exposed via Nginx)
locations."/" = {
return = "404";
};
# Nginx reverse proxy for AdGuard Home Web UI and DoH
myModules.nginx.domains."${cfg.domain}" = {
port = cfg.port;
contentSecurityPolicy = null; # AdGuard Home handles its own CSP
};
# Ensure nginx user can access ACME certs

View file

@ -5,6 +5,7 @@
./podman.nix
./nginx.nix
./searxng.nix
./websurfx.nix
./openclaw-podman.nix
./vaultwarden.nix
./crowdsec.nix

View file

@ -74,6 +74,12 @@ in
description = "Content-Security-Policy header value. Set to null to omit.";
};
internalOnly = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Restrict access to Tailscale network and localhost only";
};
rateLimit = {
enable = lib.mkOption {
type = lib.types.nullOr lib.types.bool;
@ -156,6 +162,12 @@ in
# Security headers applied per-vhost
extraConfig = ''
${lib.optionalString opts.internalOnly ''
# Restrict access to Tailscale network
allow 100.64.0.0/10;
allow 127.0.0.0/8;
deny all;
''}
# Strict Transport Security — 6 months, include subdomains, preload-ready
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;

View file

@ -28,10 +28,6 @@
}
},
"tools": {
"github": {
"enabled": true,
"token": "${GITHUB_TOKEN}"
},
"elevated": { "enabled": true },
"exec": { "security": "full", "ask": "off" },
"web": {

View file

@ -39,6 +39,11 @@ in
# Enable podman
myModules.podman.enable = true;
# Create directory for OpenClaw data
systemd.tmpfiles.rules = [
"d /var/lib/openclaw 0755 1000 1000 -" # Assuming node user is uid 1000
];
# OpenClaw container (bridge network — isolated from host services)
virtualisation.oci-containers.containers."openclaw" = {
image = "ghcr.io/openclaw/openclaw:latest";
@ -47,9 +52,17 @@ in
config.sops.templates."openclaw.env".path
];
volumes = [
"${./openclaw-config.json}:/home/node/.openclaw/openclaw.json:ro"
"openclaw-data:/home/node/.openclaw"
"/var/lib/openclaw:/home/node/.openclaw"
];
};
# Copy the declarative config before starting the container
# This allows OpenClaw to safely write/rename the file at runtime without EBUSY errors
systemd.services."podman-openclaw".preStart = lib.mkBefore ''
mkdir -p /var/lib/openclaw
cp -f ${./openclaw-config.json} /var/lib/openclaw/openclaw.json
chown -R 1000:1000 /var/lib/openclaw
chmod -R u+rwX /var/lib/openclaw
'';
};
}

138
modules/websurfx.nix Normal file
View file

@ -0,0 +1,138 @@
# Websurfx Module (Podman)
# Provides: Fast, privacy-focused meta-search engine written in Rust
#
# Usage:
# myModules.websurfx = {
# enable = true;
# port = 8080;
# domain = "search.example.com";
# };
{
config,
lib,
pkgs,
...
}:
let
cfg = config.myModules.websurfx;
in
{
options.myModules.websurfx = {
enable = lib.mkEnableOption "Websurfx meta-search engine";
port = lib.mkOption {
type = lib.types.port;
default = 8080;
description = "Port to expose Websurfx on localhost";
};
domain = lib.mkOption {
type = lib.types.str;
example = "search.example.com";
description = "Public domain name for Websurfx";
};
threads = lib.mkOption {
type = lib.types.int;
default = 8;
description = "Number of threads for the app to use";
};
theme = lib.mkOption {
type = lib.types.str;
default = "simple";
description = "Default theme (simple, dark, etc.)";
};
colorscheme = lib.mkOption {
type = lib.types.str;
default = "catppuccin-mocha";
description = "Color scheme (catppuccin-mocha, dracula, monokai, nord, etc.)";
};
};
config = lib.mkIf cfg.enable {
# Ensure Podman is enabled
myModules.podman.enable = true;
# Create bridge network
systemd.services.create-websurfx-network = {
description = "Create Websurfx podman network";
after = [ "network-online.target" ];
requires = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.podman ];
script = ''
if ! podman network exists websurfx-net 2>/dev/null; then
podman network create websurfx-net --subnet 10.89.3.0/24
fi
'';
};
# Config file for Websurfx
environment.etc."xdg/websurfx/config.lua".text = ''
-- ### General ###
logging = true
debug = false
threads = ${toString cfg.threads}
-- ### Server ###
port = "8080"
binding_ip = "0.0.0.0"
production_use = true
-- ### Search ###
rate_limiter = {
number_of_requests = 20,
time_limit = 60,
}
-- ### Cache ###
redis_url = "redis://redis:6379"
-- ### UI ###
style = {
theme = "${cfg.theme}",
colorscheme = "${cfg.colorscheme}",
}
'';
# Allow list and block list files (empty by default)
environment.etc."xdg/websurfx/allowlist.txt".text = "";
environment.etc."xdg/websurfx/blocklist.txt".text = "";
# Redis Container (Cache)
virtualisation.oci-containers.containers."websurfx-redis" = {
image = "docker.io/redis:alpine";
cmd = [ "redis-server" "--save" "" "--appendonly" "no" ];
extraOptions = [
"--network=websurfx-net"
"--network-alias=redis"
];
};
# Websurfx Container
virtualisation.oci-containers.containers."websurfx" = {
image = "docker.io/neonmmd/websurfx:latest";
ports = [ "127.0.0.1:${toString cfg.port}:8080" ];
extraOptions = [
"--network=websurfx-net"
"--network-alias=websurfx"
"--cap-drop=ALL"
"--dns=9.9.9.9"
"--dns=1.1.1.1"
];
volumes = [
"/etc/xdg/websurfx/config.lua:/etc/xdg/websurfx/config.lua:ro"
"/etc/xdg/websurfx/allowlist.txt:/etc/xdg/websurfx/allowlist.txt:ro"
"/etc/xdg/websurfx/blocklist.txt:/etc/xdg/websurfx/blocklist.txt:ro"
];
dependsOn = [ "websurfx-redis" ];
};
};
}