Kieran's opinionated (and probably slightly dumb) nix config

Compare changes

Choose any two refs to compare.

+46
.github/workflows/deploy.yaml
···
+
name: Deploy NixOS Configurations
+
+
on:
+
push:
+
branches:
+
- main
+
workflow_dispatch:
+
+
jobs:
+
deploy:
+
runs-on: ubuntu-latest
+
steps:
+
- uses: actions/checkout@v4
+
+
- name: Install Nix
+
uses: DeterminateSystems/determinate-nix-action@main
+
with:
+
extra-conf: |
+
extra-platforms = aarch64-linux
+
+
- name: Set up QEMU
+
uses: docker/setup-qemu-action@v3
+
with:
+
platforms: arm64
+
+
- name: Setup Tailscale
+
uses: tailscale/github-action@v3
+
with:
+
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
+
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
+
tags: tag:deploy
+
use-cache: "true"
+
+
- name: Configure SSH
+
run: |
+
mkdir -p ~/.ssh
+
echo "StrictHostKeyChecking accept-new" >> ~/.ssh/config
+
+
- name: Deploy all configurations
+
run: |
+
nix run github:serokell/deploy-rs -- \
+
--skip-checks \
+
--remote-build \
+
--ssh-user kierank \
+
--ssh-opts="-o StrictHostKeyChecking=accept-new" \
+
.
+1
.gitignore
···
.crush
+
.DS_Store
-57
INSTALL_GUIDE.md
···
-
# So, you want to use my dots?
-
-
Hey there! This guide supplements the README with additional details about using the installation scripts. I've tried to make installation as painless as possible. (mainly because i hate typing in commands manually with no autocomplete; yeah ik im not a "true" linux nerd but whatever lol)
-
-
## The Automated Way (Recommended)
-
-
### Step 1: Get connected
-
-
First, make sure you've got internet! (this is already covered in [`README.md`](/README.md) so not duplicating here) Also don't forget to double check with `ping 1.1.1.1`
-
-
### Step 2: Run the install script
-
-
```bash
-
curl -L https://raw.githubusercontent.com/taciturnaxolotl/dots/main/nixos/install.sh -o install.sh
-
chmod +x install.sh
-
./install.sh
-
```
-
-
This magic script will:
-
- Make sure you're online
-
- Enable git (required for the rest of this charade)
-
- Partition your disks with disko
-
- Clone my dots to the right place
-
- Let you add your SSH key if you've got one
-
- Install the flake
-
- Send you off to reboot land
-
-
### Step 3: First login after reboot
-
-
After the system reboots, login with user `kierank` and password `lolzthisaintsecure!`
-
-
(Please change this password immediately!)
-
-
### Step 4: Run the post-install script
-
-
```bash
-
curl -L https://raw.githubusercontent.com/taciturnaxolotl/dots/main/nixos/post-install.sh -o post-install.sh
-
chmod +x post-install.sh
-
./post-install.sh
-
```
-
-
This script will walk you through:
-
- Changing your password (if you haven't already)
-
- Moving config files to your home directory
-
- Setting up the fingerprint reader (optional)
-
- Configuring git (optional)
-
- Rebuilding the system with your hostname
-
-
## Available System Configurations
-
-
Currently, this repo has the following system configurations:
-
-
- `moonlark` - My Framework laptop setup (default config)
-
-
You'll be asked which one you want during the installation.
-
-
Good luck, and may the nix gods be with you! 🙏
+37 -5
README.md
···
├── machines
│ ├── atalanta # my macOS M4 machine
│ ├── ember # my dell r210 server (in my basement)
-
│ ├── moonlark # my framework 13
-
│ │ └── home
+
│ ├── moonlark # my framework 13 <dead>
│ ├── nest # shared tilde server through hc
-
│ └── tacyon # rpi 5
+
│ ├── prattle # oracle cloud x86_64 server
+
│ ├── tacyon # rpi 5
+
│ └── terebithia # oracle cloud aarch64 server
├── modules
│ ├── home # home-manager modules
│ │ ├── aesthetics # theming and wallpapers
···
│ └── system # pam and my fancy wifi module for now
└── secrets # keep your grubby hands (or paws) off my data
-
19 directories
+
16 directories
```
## Installation
···
For macOS machines, you can use nix-darwin:
1. Install Nix using the determinate systems installer:
+
```bash
curl -fsSL https://install.determinate.systems/nix | sh -s -- install
```
2. Clone the repository:
+
```bash
git clone git@github.com:taciturnaxolotl/dots.git
cd dots
```
3. Apply the configuration:
+
```bash
darwin-rebuild switch --flake .#atalanta
```
···
> These instructions have been validated by installing on my friend's machine ([`Nat2-Dev/dots`](https://github.com/Nat2-Dev/dots))
-
You have two options for installation: either the full guide as follows or the install script below and instructions in [INSTALL_GUIDE.md](/INSTALL_GUIDE.md)
+
#### Using nixos-anywhere (Recommended for remote installations)
+
+
> [!INFO]
+
> This only currently works with `prattle` and `terebithia` as they have the proper disko configs setup.
+
+
For remote installations (like Oracle Cloud), use [nixos-anywhere](https://github.com/nix-community/nixos-anywhere):
+
+
```bash
+
nix run github:nix-community/nixos-anywhere -- \
+
--flake .#prattle \
+
--generate-hardware-config nixos-facter ./machines/prattle/facter.json \
+
--build-on-remote \
+
root@<ip-address>
+
```
+
+
Replace `prattle` with your machine configuration and `<ip-address>` with your target machine's IP.
+
+
> **Note**: Make sure your SSH key is in the target machine's `authorized_keys` and the machine configuration has the correct network settings. The `--generate-hardware-config nixos-facter` flag will generate a comprehensive hardware report using [nixos-facter](https://github.com/numtide/nixos-facter) instead of the traditional `nixos-generate-config`.
+
+
#### Using the install script
```bash
curl -L https://raw.githubusercontent.com/taciturnaxolotl/dots/main/install.sh -o install.sh
···
```bash
atuin login
atuin sync
+
```
+
+
## some odd things
+
+
for helix if you want the grammar to work you must run the following as per [this helix discussion](https://github.com/helix-editor/helix/discussions/10035#discussioncomment-13852637)
+
+
```bash
+
hx -g fetch
+
hx -g build
```
## Screenshots
-531
dots/anthropic.sh
···
-
#!/bin/sh
-
-
# Anthropic OAuth client ID
-
CLIENT_ID="9d1c250a-e61b-44d9-88ed-5944d1962f5e"
-
-
# Token cache file location
-
CACHE_DIR="${HOME}/.config/crush/anthropic"
-
CACHE_FILE="${CACHE_DIR}/bearer_token"
-
REFRESH_TOKEN_FILE="${CACHE_DIR}/refresh_token"
-
-
# Function to extract expiration from cached token file
-
extract_expiration() {
-
if [ -f "${CACHE_FILE}.expires" ]; then
-
cat "${CACHE_FILE}.expires"
-
fi
-
}
-
-
# Function to check if token is valid
-
is_token_valid() {
-
local expires="$1"
-
-
if [ -z "$expires" ]; then
-
return 1
-
fi
-
-
local current_time=$(date +%s)
-
# Add 60 second buffer before expiration
-
local buffer_time=$((expires - 60))
-
-
if [ "$current_time" -lt "$buffer_time" ]; then
-
return 0
-
else
-
return 1
-
fi
-
}
-
-
# Function to generate PKCE challenge (requires openssl)
-
generate_pkce() {
-
# Generate 32 random bytes, base64url encode
-
local verifier=$(openssl rand -base64 32 | tr -d "=" | tr "/" "_" | tr "+" "-" | tr -d "\n")
-
# Create SHA256 hash of verifier, base64url encode
-
local challenge=$(printf '%s' "$verifier" | openssl dgst -sha256 -binary | openssl base64 | tr -d "=" | tr "/" "_" | tr "+" "-" | tr -d "\n")
-
-
echo "$verifier|$challenge"
-
}
-
-
# Function to exchange refresh token for new access token
-
exchange_refresh_token() {
-
local refresh_token="$1"
-
-
local bearer_response=$(curl -s -X POST "https://console.anthropic.com/v1/oauth/token" \
-
-H "Content-Type: application/json" \
-
-H "User-Agent: CRUSH/1.0" \
-
-d "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"${refresh_token}\",\"client_id\":\"${CLIENT_ID}\"}")
-
-
# Parse JSON response - try jq first, fallback to sed
-
local access_token=""
-
local new_refresh_token=""
-
local expires_in=""
-
-
if command -v jq >/dev/null 2>&1; then
-
access_token=$(echo "$bearer_response" | jq -r '.access_token // empty')
-
new_refresh_token=$(echo "$bearer_response" | jq -r '.refresh_token // empty')
-
expires_in=$(echo "$bearer_response" | jq -r '.expires_in // empty')
-
else
-
# Fallback to sed parsing
-
access_token=$(echo "$bearer_response" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
-
new_refresh_token=$(echo "$bearer_response" | sed -n 's/.*"refresh_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
-
expires_in=$(echo "$bearer_response" | sed -n 's/.*"expires_in"[[:space:]]*:[[:space:]]*\([0-9]*\).*/\1/p')
-
fi
-
-
if [ -n "$access_token" ] && [ -n "$expires_in" ]; then
-
# Calculate expiration timestamp
-
local current_time=$(date +%s)
-
local expires_timestamp=$((current_time + expires_in))
-
-
# Cache the new tokens
-
mkdir -p "$CACHE_DIR"
-
echo "$access_token" > "$CACHE_FILE"
-
chmod 600 "$CACHE_FILE"
-
-
if [ -n "$new_refresh_token" ]; then
-
echo "$new_refresh_token" > "$REFRESH_TOKEN_FILE"
-
chmod 600 "$REFRESH_TOKEN_FILE"
-
fi
-
-
# Store expiration for future reference
-
echo "$expires_timestamp" > "${CACHE_FILE}.expires"
-
chmod 600 "${CACHE_FILE}.expires"
-
-
echo "$access_token"
-
return 0
-
fi
-
-
return 1
-
}
-
-
# Function to exchange authorization code for tokens
-
exchange_authorization_code() {
-
local auth_code="$1"
-
local verifier="$2"
-
-
# Split code if it contains state (format: code#state)
-
local code=$(echo "$auth_code" | cut -d'#' -f1)
-
local state=""
-
if echo "$auth_code" | grep -q '#'; then
-
state=$(echo "$auth_code" | cut -d'#' -f2)
-
fi
-
-
# Use the working endpoint
-
local bearer_response=$(curl -s -X POST "https://console.anthropic.com/v1/oauth/token" \
-
-H "Content-Type: application/json" \
-
-H "User-Agent: CRUSH/1.0" \
-
-d "{\"code\":\"${code}\",\"state\":\"${state}\",\"grant_type\":\"authorization_code\",\"client_id\":\"${CLIENT_ID}\",\"redirect_uri\":\"https://console.anthropic.com/oauth/code/callback\",\"code_verifier\":\"${verifier}\"}")
-
-
# Parse JSON response - try jq first, fallback to sed
-
local access_token=""
-
local refresh_token=""
-
local expires_in=""
-
-
if command -v jq >/dev/null 2>&1; then
-
access_token=$(echo "$bearer_response" | jq -r '.access_token // empty')
-
refresh_token=$(echo "$bearer_response" | jq -r '.refresh_token // empty')
-
expires_in=$(echo "$bearer_response" | jq -r '.expires_in // empty')
-
else
-
# Fallback to sed parsing
-
access_token=$(echo "$bearer_response" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
-
refresh_token=$(echo "$bearer_response" | sed -n 's/.*"refresh_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
-
expires_in=$(echo "$bearer_response" | sed -n 's/.*"expires_in"[[:space:]]*:[[:space:]]*\([0-9]*\).*/\1/p')
-
fi
-
-
if [ -n "$access_token" ] && [ -n "$refresh_token" ] && [ -n "$expires_in" ]; then
-
# Calculate expiration timestamp
-
local current_time=$(date +%s)
-
local expires_timestamp=$((current_time + expires_in))
-
-
# Cache the tokens
-
mkdir -p "$CACHE_DIR"
-
echo "$access_token" > "$CACHE_FILE"
-
echo "$refresh_token" > "$REFRESH_TOKEN_FILE"
-
echo "$expires_timestamp" > "${CACHE_FILE}.expires"
-
chmod 600 "$CACHE_FILE" "$REFRESH_TOKEN_FILE" "${CACHE_FILE}.expires"
-
-
echo "$access_token"
-
return 0
-
else
-
return 1
-
fi
-
}
-
-
# Check for cached bearer token
-
if [ -f "$CACHE_FILE" ] && [ -f "${CACHE_FILE}.expires" ]; then
-
CACHED_TOKEN=$(cat "$CACHE_FILE")
-
CACHED_EXPIRES=$(cat "${CACHE_FILE}.expires")
-
if is_token_valid "$CACHED_EXPIRES"; then
-
# Token is still valid, output and exit
-
echo "$CACHED_TOKEN"
-
exit 0
-
fi
-
fi
-
-
# Bearer token is expired/missing, try to use cached refresh token
-
if [ -f "$REFRESH_TOKEN_FILE" ]; then
-
REFRESH_TOKEN=$(cat "$REFRESH_TOKEN_FILE")
-
if [ -n "$REFRESH_TOKEN" ]; then
-
# Try to exchange refresh token for new bearer token
-
BEARER_TOKEN=$(exchange_refresh_token "$REFRESH_TOKEN")
-
if [ -n "$BEARER_TOKEN" ]; then
-
# Successfully got new bearer token, output and exit
-
echo "$BEARER_TOKEN"
-
exit 0
-
fi
-
fi
-
fi
-
-
# No valid tokens found, start OAuth flow
-
# Check if openssl is available for PKCE
-
if ! command -v openssl >/dev/null 2>&1; then
-
exit 1
-
fi
-
-
# Generate PKCE challenge
-
PKCE_DATA=$(generate_pkce)
-
VERIFIER=$(echo "$PKCE_DATA" | cut -d'|' -f1)
-
CHALLENGE=$(echo "$PKCE_DATA" | cut -d'|' -f2)
-
-
# Build OAuth URL
-
AUTH_URL="https://claude.ai/oauth/authorize"
-
AUTH_URL="${AUTH_URL}?response_type=code"
-
AUTH_URL="${AUTH_URL}&client_id=${CLIENT_ID}"
-
AUTH_URL="${AUTH_URL}&redirect_uri=https://console.anthropic.com/oauth/code/callback"
-
AUTH_URL="${AUTH_URL}&scope=org:create_api_key%20user:profile%20user:inference"
-
AUTH_URL="${AUTH_URL}&code_challenge=${CHALLENGE}"
-
AUTH_URL="${AUTH_URL}&code_challenge_method=S256"
-
AUTH_URL="${AUTH_URL}&state=${VERIFIER}"
-
-
# Create a temporary HTML file with the authentication form
-
TEMP_HTML="/tmp/anthropic_auth_$$.html"
-
cat > "$TEMP_HTML" << EOF
-
<!DOCTYPE html>
-
<html>
-
<head>
-
<title>Anthropic Authentication</title>
-
<style>
-
* {
-
box-sizing: border-box;
-
margin: 0;
-
padding: 0;
-
}
-
-
body {
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
-
background: linear-gradient(135deg, #1a1a1a 0%, #2d1810 100%);
-
color: #ffffff;
-
min-height: 100vh;
-
display: flex;
-
align-items: center;
-
justify-content: center;
-
padding: 20px;
-
}
-
-
.container {
-
background: rgba(40, 40, 40, 0.95);
-
border: 1px solid #4a4a4a;
-
border-radius: 16px;
-
padding: 48px;
-
max-width: 480px;
-
width: 100%;
-
text-align: center;
-
backdrop-filter: blur(10px);
-
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
-
}
-
-
.logo {
-
width: 48px;
-
height: 48px;
-
margin: 0 auto 24px;
-
background: linear-gradient(135deg, #ff6b35 0%, #ff8e53 100%);
-
border-radius: 12px;
-
display: flex;
-
align-items: center;
-
justify-content: center;
-
font-weight: bold;
-
font-size: 24px;
-
color: white;
-
}
-
-
h1 {
-
font-size: 28px;
-
font-weight: 600;
-
margin-bottom: 12px;
-
color: #ffffff;
-
}
-
-
.subtitle {
-
color: #a0a0a0;
-
margin-bottom: 32px;
-
font-size: 16px;
-
line-height: 1.5;
-
}
-
-
.step {
-
margin-bottom: 32px;
-
text-align: left;
-
}
-
-
.step-number {
-
display: inline-flex;
-
align-items: center;
-
justify-content: center;
-
width: 24px;
-
height: 24px;
-
background: #ff6b35;
-
color: white;
-
border-radius: 50%;
-
font-size: 14px;
-
font-weight: 600;
-
margin-right: 12px;
-
}
-
-
.step-title {
-
font-weight: 600;
-
margin-bottom: 8px;
-
color: #ffffff;
-
}
-
-
.step-description {
-
color: #a0a0a0;
-
font-size: 14px;
-
margin-left: 36px;
-
}
-
-
.button {
-
display: inline-block;
-
background: linear-gradient(135deg, #ff6b35 0%, #ff8e53 100%);
-
color: white;
-
padding: 16px 32px;
-
text-decoration: none;
-
border-radius: 12px;
-
font-weight: 600;
-
font-size: 16px;
-
margin-bottom: 24px;
-
transition: all 0.2s ease;
-
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
-
}
-
-
.button:hover {
-
transform: translateY(-2px);
-
box-shadow: 0 8px 20px rgba(255, 107, 53, 0.4);
-
}
-
-
.input-group {
-
margin-bottom: 24px;
-
text-align: left;
-
}
-
-
label {
-
display: block;
-
margin-bottom: 8px;
-
font-weight: 500;
-
color: #ffffff;
-
}
-
-
textarea {
-
width: 100%;
-
background: #2a2a2a;
-
border: 2px solid #4a4a4a;
-
border-radius: 8px;
-
padding: 16px;
-
color: #ffffff;
-
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
-
font-size: 14px;
-
line-height: 1.4;
-
resize: vertical;
-
min-height: 120px;
-
transition: border-color 0.2s ease;
-
}
-
-
textarea:focus {
-
outline: none;
-
border-color: #ff6b35;
-
box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.1);
-
}
-
-
textarea::placeholder {
-
color: #666;
-
}
-
-
.submit-btn {
-
background: linear-gradient(135deg, #ff6b35 0%, #ff8e53 100%);
-
color: white;
-
border: none;
-
padding: 16px 32px;
-
border-radius: 12px;
-
font-weight: 600;
-
font-size: 16px;
-
cursor: pointer;
-
transition: all 0.2s ease;
-
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
-
width: 100%;
-
}
-
-
.submit-btn:hover {
-
transform: translateY(-2px);
-
box-shadow: 0 8px 20px rgba(255, 107, 53, 0.4);
-
}
-
-
.submit-btn:disabled {
-
opacity: 0.6;
-
cursor: not-allowed;
-
transform: none;
-
}
-
-
.status {
-
margin-top: 16px;
-
padding: 12px;
-
border-radius: 8px;
-
font-size: 14px;
-
display: none;
-
}
-
-
.status.success {
-
background: rgba(52, 168, 83, 0.1);
-
border: 1px solid rgba(52, 168, 83, 0.3);
-
color: #34a853;
-
}
-
-
.status.error {
-
background: rgba(234, 67, 53, 0.1);
-
border: 1px solid rgba(234, 67, 53, 0.3);
-
color: #ea4335;
-
}
-
</style>
-
</head>
-
<body>
-
<div class="container">
-
<div class="logo">A</div>
-
<h1>Anthropic Authentication</h1>
-
<p class="subtitle">Connect your Anthropic account to continue</p>
-
-
<div class="step">
-
<div class="step-title">
-
<span class="step-number">1</span>
-
Authorize with Anthropic
-
</div>
-
<div class="step-description">
-
Click the button below to open the Anthropic authorization page
-
</div>
-
</div>
-
-
<a href="$AUTH_URL" class="button" target="_blank">
-
Open Anthropic Authorization
-
</a>
-
-
<div class="step">
-
<div class="step-title">
-
<span class="step-number">2</span>
-
Paste your authorization token
-
</div>
-
<div class="step-description">
-
After authorizing, copy the token and paste it below
-
</div>
-
</div>
-
-
<form id="tokenForm">
-
<div class="input-group">
-
<label for="token">Authorization Token:</label>
-
<textarea
-
id="token"
-
name="token"
-
placeholder="Paste your token here..."
-
required
-
></textarea>
-
</div>
-
<button type="submit" class="submit-btn" id="submitBtn">
-
Complete Authentication
-
</button>
-
</form>
-
-
<div id="status" class="status"></div>
-
</div>
-
-
<script>
-
document.getElementById('tokenForm').addEventListener('submit', function(e) {
-
e.preventDefault();
-
-
const token = document.getElementById('token').value.trim();
-
if (!token) {
-
showStatus('Please paste your authorization token', 'error');
-
return;
-
}
-
-
// Ensure token has content before creating file
-
if (token.length > 0) {
-
// Save the token as a downloadable file
-
const blob = new Blob([token], { type: 'text/plain' });
-
const a = document.createElement('a');
-
a.href = URL.createObjectURL(blob);
-
a.download = "anthropic_token.txt";
-
document.body.appendChild(a); // Append to body to ensure it works in all browsers
-
a.click();
-
document.body.removeChild(a); // Clean up
-
-
// Verify file creation
-
console.log("Token file created with content length: " + token.length);
-
} else {
-
showStatus('Empty token detected, please provide a valid token', 'error');
-
return;
-
}
-
-
document.getElementById('submitBtn').disabled = true;
-
document.getElementById('submitBtn').textContent = "Token saved, you may close this tab.";
-
showStatus('Token file downloaded! You can close this window.', 'success');
-
-
// setTimeout(() => {
-
// window.close();
-
// }, 2000);
-
});
-
-
function showStatus(message, type) {
-
const status = document.getElementById('status');
-
status.textContent = message;
-
status.className = 'status ' + type;
-
status.style.display = 'block';
-
}
-
-
// Auto-close after 10 minutes
-
setTimeout(() => {
-
window.close();
-
}, 600000);
-
</script>
-
</body>
-
</html>
-
EOF
-
-
# Open the HTML file
-
if command -v xdg-open >/dev/null 2>&1; then
-
xdg-open "$TEMP_HTML" >/dev/null 2>&1 &
-
elif command -v open >/dev/null 2>&1; then
-
open "$TEMP_HTML" >/dev/null 2>&1 &
-
elif command -v start >/dev/null 2>&1; then
-
start "$TEMP_HTML" >/dev/null 2>&1 &
-
fi
-
-
# Wait for user to download the token file
-
TOKEN_FILE="$HOME/Downloads/anthropic_token.txt"
-
-
for i in $(seq 1 60); do
-
if [ -f "$TOKEN_FILE" ]; then
-
AUTH_CODE=$(cat "$TOKEN_FILE" | tr -d '\r\n')
-
rm -f "$TOKEN_FILE"
-
break
-
fi
-
sleep 2
-
done
-
-
# Clean up the temporary HTML file
-
rm -f "$TEMP_HTML"
-
-
if [ -z "$AUTH_CODE" ]; then
-
exit 1
-
fi
-
-
# Exchange code for tokens
-
ACCESS_TOKEN=$(exchange_authorization_code "$AUTH_CODE" "$VERIFIER")
-
if [ -n "$ACCESS_TOKEN" ]; then
-
echo "$ACCESS_TOKEN"
-
exit 0
-
else
-
exit 1
-
fi
+36
dots/dump-keybinds-mac.sh
···
+
#!/usr/bin/env bash
+
+
defaults export com.apple.symbolichotkeys - > /tmp/symbolichotkeys.plist
+
plutil -convert json -o /tmp/symbolichotkeys.json /tmp/symbolichotkeys.plist
+
cat /tmp/symbolichotkeys.json | \
+
jq -r '
+
.AppleSymbolicHotKeys
+
| to_entries
+
| map(
+
" \"" + .key + "\" = {\n" +
+
" enabled = " + (
+
if (.value.enabled == 1 or .value.enabled == true) then
+
"true"
+
else
+
"false"
+
end
+
) + ";\n" +
+
(
+
if (.value.value? and .value.value.parameters? and .value.value.type?) then
+
" value = {\n" +
+
" parameters = [ " + (
+
.value.value.parameters
+
| map(tostring)
+
| join(" ")
+
) + " ];\n" +
+
" type = \"" + .value.value.type + "\";\n" +
+
" };\n"
+
else
+
""
+
end
+
) +
+
" };\n"
+
)
+
| " AppleSymbolicHotKeys = {\n" + ( join("") ) + " };\n"
+
'
+
+144
dots/update-crush-models.sh
···
+
#!/usr/bin/env bash
+
+
set -euo pipefail
+
+
# Configuration
+
COPILOT_TOKEN=$(bash ~/.config/crush/copilot.sh)
+
ANTHROPIC_TOKEN=$(bunx anthropic-api-key)
+
+
# Colors for output
+
RED='\033[0;31m'
+
GREEN='\033[0;32m'
+
YELLOW='\033[1;33m'
+
BLUE='\033[0;34m'
+
NC='\033[0m' # No Color
+
+
echo -e "${GREEN}Fetching latest models from APIs...${NC}"
+
+
# Fetch Copilot models
+
echo " → Fetching Copilot models..."
+
COPILOT_MODELS=$(curl -s https://api.githubcopilot.com/models \
+
-H "Authorization: Bearer $COPILOT_TOKEN" \
+
-H "Editor-Version: CRUSH/1.0" \
+
-H "Editor-Plugin-Version: CRUSH/1.0" \
+
-H "Copilot-Integration-Id: vscode-chat")
+
+
# Fetch Anthropic models
+
echo " → Fetching Anthropic models..."
+
ANTHROPIC_MODELS=$(curl -s https://api.anthropic.com/v1/models \
+
-H "Authorization: Bearer $ANTHROPIC_TOKEN" \
+
-H "anthropic-version: 2023-06-01" \
+
-H "anthropic-beta: oauth-2025-04-20")
+
+
# Extract model-picker enabled Copilot models
+
echo -e "\n${GREEN}Copilot models (model_picker_enabled):${NC}"
+
echo "$COPILOT_MODELS" | jq -r '.data[] | select(.model_picker_enabled == true) | " ✓ \(.id) - \(.name)"'
+
+
# Extract all Anthropic models
+
echo -e "\n${GREEN}Anthropic models:${NC}"
+
echo "$ANTHROPIC_MODELS" | jq -r '.data[] | " ✓ \(.id) - \(.display_name)"'
+
+
# Generate Copilot models Nix config
+
generate_copilot_models() {
+
echo "$COPILOT_MODELS" | jq -r '.data[] | select(.model_picker_enabled == true) |
+
(if .capabilities.supports.tool_calls then true else false end) as $can_reason |
+
(if .capabilities.supports.vision then true else false end) as $supports_attachments |
+
" {\n" +
+
" id = \"\(.id)\";\n" +
+
" name = \"Copilot: \(.name)\";\n" +
+
" cost_per_1m_in = 0;\n" +
+
" cost_per_1m_out = 0;\n" +
+
" cost_per_1m_in_cached = 0;\n" +
+
" cost_per_1m_out_cached = 0;\n" +
+
" context_window = \(.capabilities.limits.max_context_window_tokens);\n" +
+
" default_max_tokens = \(.capabilities.limits.max_output_tokens);\n" +
+
" can_reason = \($can_reason);\n" +
+
" has_reasoning_efforts = false;\n" +
+
" supports_attachments = \($supports_attachments);\n" +
+
" }"'
+
}
+
+
# Generate Anthropic models Nix config with pricing
+
generate_anthropic_models() {
+
echo "$ANTHROPIC_MODELS" | jq -r '.data[] |
+
# Determine pricing based on model family
+
(if (.id | contains("opus-4")) then
+
{in: 15.0, out: 75.0, in_cached: 1.5, out_cached: 75.0}
+
elif (.id | contains("sonnet-4")) then
+
{in: 3.0, out: 15.0, in_cached: 0.225, out_cached: 15.0}
+
elif (.id | contains("3-7-sonnet")) then
+
{in: 2.5, out: 12.0, in_cached: 0.187, out_cached: 12.0}
+
elif (.id | contains("3-5-sonnet")) then
+
{in: 3.0, out: 15.0, in_cached: 0.225, out_cached: 15.0}
+
elif (.id | contains("3-5-haiku")) then
+
{in: 0.8, out: 4.0, in_cached: 0.06, out_cached: 4.0}
+
elif (.id | contains("3-haiku")) then
+
{in: 0.25, out: 1.25, in_cached: 0.03, out_cached: 1.25}
+
elif (.id | contains("3-opus")) then
+
{in: 15.0, out: 75.0, in_cached: 1.5, out_cached: 75.0}
+
else
+
{in: 3.0, out: 15.0, in_cached: 0.225, out_cached: 15.0}
+
end) as $pricing |
+
+
# Determine context window and max tokens
+
(if (.id | test("claude-sonnet-4-5"))
+
then {context: 200000, max_tokens: 64000}
+
elif (.id | test("claude-sonnet-4-"))
+
then {context: 200000, max_tokens: 64000}
+
elif (.id | test("claude-3-7-sonnet"))
+
then {context: 200000, max_tokens: 64000}
+
elif (.id | test("claude-opus-4-1"))
+
then {context: 200000, max_tokens: 32000}
+
elif (.id | test("claude-opus-4-"))
+
then {context: 200000, max_tokens: 32000}
+
elif (.id | test("claude-3-5-haiku"))
+
then {context: 200000, max_tokens: 8192}
+
elif (.id | test("claude-3-haiku"))
+
then {context: 200000, max_tokens: 4096}
+
else
+
{context: 200000, max_tokens: 8192}
+
end) as $limits |
+
+
# Determine has_reasoning_efforts
+
(if (.id | contains("opus-4")) then true else false end) as $has_reasoning |
+
+
" {\n" +
+
" id = \"\(.id)\";\n" +
+
" name = \"\(.display_name)\";\n" +
+
" cost_per_1m_in = \($pricing.in);\n" +
+
" cost_per_1m_out = \($pricing.out);\n" +
+
" cost_per_1m_in_cached = \($pricing.in_cached);\n" +
+
" cost_per_1m_out_cached = \($pricing.out_cached);\n" +
+
" context_window = \($limits.context);\n" +
+
" default_max_tokens = \($limits.max_tokens);\n" +
+
" can_reason = true;\n" +
+
" has_reasoning_efforts = \($has_reasoning);\n" +
+
" supports_attachments = true;\n" +
+
" }"'
+
}
+
+
# Generate formatted model arrays
+
COPILOT_MODELS_NIX=$(generate_copilot_models)
+
ANTHROPIC_MODELS_NIX=$(generate_anthropic_models)
+
+
# Output results
+
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
+
echo -e "${YELLOW}COPILOT MODELS${NC}"
+
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
+
echo -e "\nPaste this into your copilot.models array:\n"
+
echo " models = ["
+
echo "$COPILOT_MODELS_NIX"
+
echo " ];"
+
+
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
+
echo -e "${YELLOW}ANTHROPIC MODELS${NC}"
+
echo -e "${BLUE}═══════════════════════════════════════════════════════════${NC}"
+
echo -e "\nPaste this into your claude-pro.models array:\n"
+
echo " models = ["
+
echo "$ANTHROPIC_MODELS_NIX"
+
echo " ];"
+
+
echo -e "\n${BLUE}═══════════════════════════════════════════════════════════${NC}"
+
echo -e "\n${GREEN}Summary:${NC}"
+
echo " • Copilot models: $(echo "$COPILOT_MODELS" | jq '[.data[] | select(.model_picker_enabled == true)] | length')"
+
echo " • Anthropic models: $(echo "$ANTHROPIC_MODELS" | jq '.data | length')"
+526 -71
flake.lock
···
{
"nodes": {
+
"actor-typeahead-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1762835797,
+
"narHash": "sha256-heizoWUKDdar6ymfZTnj3ytcEv/L4d4fzSmtr0HlXsQ=",
+
"ref": "refs/heads/main",
+
"rev": "677fe7f743050a4e7f09d4a6f87bbf1325a06f6b",
+
"revCount": 6,
+
"type": "git",
+
"url": "https://tangled.org/@jakelazaroff.com/actor-typeahead"
+
},
+
"original": {
+
"type": "git",
+
"url": "https://tangled.org/@jakelazaroff.com/actor-typeahead"
+
}
+
},
"agenix": {
"inputs": {
"darwin": "darwin",
···
"systems": "systems"
},
"locked": {
-
"lastModified": 1754433428,
-
"narHash": "sha256-NA/FT2hVhKDftbHSwVnoRTFhes62+7dxZbxj5Gxvghs=",
+
"lastModified": 1762618334,
+
"narHash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ=",
"owner": "ryantm",
"repo": "agenix",
-
"rev": "9edb1787864c4f59ae5074ad498b6272b3ec308d",
+
"rev": "fcdea223397448d35d9b31f798479227e80183f6",
"type": "github"
},
"original": {
···
"type": "github"
}
},
+
"battleship-arena": {
+
"inputs": {
+
"nixpkgs": [
+
"nixpkgs"
+
]
+
},
+
"locked": {
+
"lastModified": 1765226744,
+
"narHash": "sha256-3uCn0DQPlyYufnTeugp4qZbdddv4q2a+y9hXdgH/iOU=",
+
"owner": "taciturnaxolotl",
+
"repo": "battleship-arena",
+
"rev": "20d9604ef8fdd7e30244ef6e7d4cde541c4a0863",
+
"type": "github"
+
},
+
"original": {
+
"owner": "taciturnaxolotl",
+
"repo": "battleship-arena",
+
"type": "github"
+
}
+
},
"catppuccin": {
"inputs": {
"nixpkgs": [
···
"nixpkgs": "nixpkgs_2"
},
"locked": {
-
"lastModified": 1756337867,
-
"narHash": "sha256-2b5pZYASPn8nCs+1qeRtZ2lbdo+nOJz5qGe6Fby07GI=",
+
"lastModified": 1763921090,
+
"narHash": "sha256-2WprxeG8rOxqnWr4nIA59arkUqH1Up6ZsbruMyis7QE=",
"owner": "catppuccin",
"repo": "vscode",
-
"rev": "44894f62eb04bbc6321342c78ae493b5982bf843",
+
"rev": "bbe095f1c147152e45a8dc2399212fd8301af904",
"type": "github"
},
"original": {
···
]
},
"locked": {
-
"lastModified": 1755213882,
-
"narHash": "sha256-tsaCBluUpNnnlbkEXzEIkVnERq6yuO/ARealqRqrVJE=",
+
"lastModified": 1759865868,
+
"narHash": "sha256-Z3nWsQnDZswsIYP2nXwkB0fKWgsmHVmBLLkWMC8GZpI=",
"owner": "taciturnaxolotl",
"repo": "CedarLogic",
-
"rev": "ec5798687bf65c352d5d53aa8853e25bc2284c2b",
+
"rev": "122eac61a1e7273fadf524bd2b7b1364598df824",
"type": "github"
},
"original": {
···
]
},
"locked": {
-
"lastModified": 1756259950,
-
"narHash": "sha256-EUIcK1rNiza9JzRhDdlEGb8VFn3HLc4BMTLCR0b8Wwc=",
+
"lastModified": 1764098187,
+
"narHash": "sha256-H6JjWXhKqxZ8QLMoqndZx9e5x0Sv5AiipSmqvIxIbgo=",
"owner": "k3d3",
"repo": "claude-desktop-linux-flake",
-
"rev": "382d4ed3ea2b9f75149df97c7180c2543410690f",
+
"rev": "b2b040cb68231d2118906507d9cc8fd181ca6308",
"type": "github"
},
"original": {
···
]
},
"locked": {
-
"lastModified": 1752712874,
-
"narHash": "sha256-ZzeCft8RRcBbG7bt3LPJz5nrDjrNntipJdz/12E+NQ8=",
+
"lastModified": 1758312668,
+
"narHash": "sha256-ChN8Upf1TCMSJ0a7keuVCZr3DJIm3YPOFLZauVlQc+Y=",
"owner": "taciturnaxolotl",
"repo": "ctfd-alerts",
-
"rev": "140b7f0c8a2f78add3aa3fb0b9c061b1cf2a73a6",
+
"rev": "db05e69b7ae683d83aac019cc632346b25e941e2",
"type": "github"
},
"original": {
···
"type": "github"
}
},
+
"deploy-rs": {
+
"inputs": {
+
"flake-compat": "flake-compat",
+
"nixpkgs": [
+
"nixpkgs"
+
],
+
"utils": "utils"
+
},
+
"locked": {
+
"lastModified": 1762286984,
+
"narHash": "sha256-9I2H9x5We6Pl+DBYHjR1s3UT8wgwcpAH03kn9CqtdQc=",
+
"owner": "serokell",
+
"repo": "deploy-rs",
+
"rev": "9c870f63e28ec1e83305f7f6cb73c941e699f74f",
+
"type": "github"
+
},
+
"original": {
+
"owner": "serokell",
+
"repo": "deploy-rs",
+
"type": "github"
+
}
+
},
"disko": {
"inputs": {
"nixpkgs": [
···
]
},
"locked": {
-
"lastModified": 1756115622,
-
"narHash": "sha256-iv8xVtmLMNLWFcDM/HcAPLRGONyTRpzL9NS09RnryRM=",
+
"lastModified": 1765326679,
+
"narHash": "sha256-fTLX9kDwLr9Y0rH/nG+h1XG5UU+jBcy0PFYn5eneRX8=",
"owner": "nix-community",
"repo": "disko",
-
"rev": "bafad29f89e83b2d861b493aa23034ea16595560",
+
"rev": "d64e5cdca35b5fad7c504f615357a7afe6d9c49e",
"type": "github"
},
"original": {
···
"flake-compat": {
"flake": false,
"locked": {
+
"lastModified": 1733328505,
+
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
+
"owner": "edolstra",
+
"repo": "flake-compat",
+
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
+
"type": "github"
+
},
+
"original": {
+
"owner": "edolstra",
+
"repo": "flake-compat",
+
"type": "github"
+
}
+
},
+
"flake-compat_2": {
+
"flake": false,
+
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
···
"type": "github"
}
},
+
"flake-compat_3": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1751685974,
+
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
+
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
+
"type": "tarball",
+
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
+
}
+
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
···
},
"flake-utils_3": {
"inputs": {
-
"systems": "systems_4"
+
"systems": "systems_5"
},
"locked": {
"lastModified": 1731533236,
···
},
"flake-utils_4": {
"inputs": {
-
"systems": "systems_5"
+
"systems": "systems_6"
},
"locked": {
"lastModified": 1731533236,
···
},
"flake-utils_5": {
"inputs": {
-
"systems": "systems_6"
+
"systems": "systems_7"
},
"locked": {
"lastModified": 1731533236,
···
},
"flake-utils_6": {
"inputs": {
-
"systems": "systems_7"
+
"systems": "systems_9"
+
},
+
"locked": {
+
"lastModified": 1694529238,
+
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+
"type": "github"
+
},
+
"original": {
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"type": "github"
+
}
+
},
+
"flake-utils_7": {
+
"inputs": {
+
"systems": "systems_10"
},
"locked": {
"lastModified": 1731533236,
···
]
},
"locked": {
-
"lastModified": 1755631949,
-
"narHash": "sha256-1JBrZP5KXYI6pSX3DJCjxhlxP83GsKLuFqYLByFw2Fg=",
+
"lastModified": 1764827700,
+
"narHash": "sha256-9xpbmlCXEYU3rWv23OT4qcW0lyeelud0ZrNRicqoTkQ=",
"owner": "frc4451",
"repo": "frc-nix",
-
"rev": "a1ec2ebdff4d1b6207469c98271b3128ddcf42ca",
+
"rev": "2a52c5c29093d1cd9494ac0e5b7c70ef970ef7ff",
"type": "github"
},
"original": {
···
"type": "github"
}
},
+
"gitignore_2": {
+
"inputs": {
+
"nixpkgs": [
+
"wakatime-ls",
+
"nixpkgs"
+
]
+
},
+
"locked": {
+
"lastModified": 1762808025,
+
"narHash": "sha256-XmjITeZNMTQXGhhww6ed/Wacy2KzD6svioyCX7pkUu4=",
+
"owner": "hercules-ci",
+
"repo": "gitignore.nix",
+
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
+
"type": "github"
+
},
+
"original": {
+
"owner": "hercules-ci",
+
"repo": "gitignore.nix",
+
"type": "github"
+
}
+
},
+
"gomod2nix": {
+
"inputs": {
+
"flake-utils": "flake-utils_6",
+
"nixpkgs": [
+
"tangled",
+
"nixpkgs"
+
]
+
},
+
"locked": {
+
"lastModified": 1754078208,
+
"narHash": "sha256-YVoIFDCDpYuU3riaDEJ3xiGdPOtsx4sR5eTzHTytPV8=",
+
"owner": "nix-community",
+
"repo": "gomod2nix",
+
"rev": "7f963246a71626c7fc70b431a315c4388a0c95cf",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nix-community",
+
"repo": "gomod2nix",
+
"type": "github"
+
}
+
},
"hardware": {
"locked": {
-
"lastModified": 1756245047,
-
"narHash": "sha256-9bHzrVbjAudbO8q4vYFBWlEkDam31fsz0J7GB8k4AsI=",
+
"lastModified": 1764440730,
+
"narHash": "sha256-ZlJTNLUKQRANlLDomuRWLBCH5792x+6XUJ4YdFRjtO4=",
"owner": "NixOS",
"repo": "nixos-hardware",
-
"rev": "a65b650d6981e23edd1afa1f01eb942f19cdcbb7",
+
"rev": "9154f4569b6cdfd3c595851a6ba51bfaa472d9f3",
"type": "github"
},
"original": {
···
]
},
"locked": {
-
"lastModified": 1756245065,
-
"narHash": "sha256-aAZNbGcWrVRZgWgkQbkabSGcDVRDMgON4BipMy69gvI=",
+
"lastModified": 1765384171,
+
"narHash": "sha256-FuFtkJrW1Z7u+3lhzPRau69E0CNjADku1mLQQflUORo=",
"owner": "nix-community",
"repo": "home-manager",
-
"rev": "54b2879ce622d44415e727905925e21b8f833a98",
+
"rev": "44777152652bc9eacf8876976fa72cc77ca8b9d8",
"type": "github"
},
"original": {
"owner": "nix-community",
-
"ref": "release-25.05",
+
"ref": "release-25.11",
"repo": "home-manager",
"type": "github"
}
},
+
"htmx-src": {
+
"flake": false,
+
"locked": {
+
"narHash": "sha256-nm6avZuEBg67SSyyZUhjpXVNstHHgUxrtBHqJgowU08=",
+
"type": "file",
+
"url": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"
+
},
+
"original": {
+
"type": "file",
+
"url": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"
+
}
+
},
+
"htmx-ws-src": {
+
"flake": false,
+
"locked": {
+
"narHash": "sha256-2fg6KyEJoO24q0fQqbz9RMaYNPQrMwpZh29tkSqdqGY=",
+
"type": "file",
+
"url": "https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2"
+
},
+
"original": {
+
"type": "file",
+
"url": "https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2"
+
}
+
},
"hyprland-contrib": {
"inputs": {
"nixpkgs": [
···
]
},
"locked": {
-
"lastModified": 1755680610,
-
"narHash": "sha256-g7/g5o0spemkZCzPa8I21RgCmN0Kv41B5z9Z5HQWraY=",
+
"lastModified": 1765113580,
+
"narHash": "sha256-b8YOwGDFprkQJjXsKGuSNS1pWe8w4cUW36YxlUelNpU=",
"owner": "hyprwm",
"repo": "contrib",
-
"rev": "04721247f417256ca96acf28cdfe946cf1006263",
+
"rev": "db18f83bebbc2cf43a21dbb26cd99aabe672d923",
"type": "github"
},
"original": {
···
"type": "github"
}
},
+
"ibm-plex-mono-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1731402384,
+
"narHash": "sha256-OwUmrPfEehLDz0fl2ChYLK8FQM2p0G1+EMrGsYEq+6g=",
+
"type": "tarball",
+
"url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip"
+
}
+
},
"import-tree": {
"locked": {
-
"lastModified": 1752730890,
-
"narHash": "sha256-GES8fapSLGz36MMPRVNkSUWXUTtqvGQNXHjRmRLfJUY=",
+
"lastModified": 1763762820,
+
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
"owner": "vic",
"repo": "import-tree",
-
"rev": "6ebb8cb87987b20264c09296166543fd3761d274",
+
"rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
"type": "github"
},
"original": {
···
"type": "github"
}
},
+
"indigo": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1753693716,
+
"narHash": "sha256-DMIKnCJRODQXEHUxA+7mLzRALmnZhkkbHlFT2rCQYrE=",
+
"owner": "oppiliappan",
+
"repo": "indigo",
+
"rev": "5f170569da9360f57add450a278d73538092d8ca",
+
"type": "github"
+
},
+
"original": {
+
"owner": "oppiliappan",
+
"repo": "indigo",
+
"type": "github"
+
}
+
},
+
"inter-fonts-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1731687360,
+
"narHash": "sha256-5vdKKvHAeZi6igrfpbOdhZlDX2/5+UvzlnCQV6DdqoQ=",
+
"type": "tarball",
+
"url": "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip"
+
}
+
},
"ixx": {
"inputs": {
"flake-utils": [
···
"type": "github"
}
},
+
"lucide-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1754044466,
+
"narHash": "sha256-+exBR2OToB1iv7ZQI2S4B0lXA/QRvC9n6U99UxGpJGs=",
+
"type": "tarball",
+
"url": "https://github.com/lucide-icons/lucide/releases/download/0.536.0/lucide-icons-0.536.0.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://github.com/lucide-icons/lucide/releases/download/0.536.0/lucide-icons-0.536.0.zip"
+
}
+
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
···
]
},
"locked": {
-
"lastModified": 1749744770,
-
"narHash": "sha256-MEM9XXHgBF/Cyv1RES1t6gqAX7/tvayBC1r/KPyK1ls=",
+
"lastModified": 1765066094,
+
"narHash": "sha256-0YSU35gfRFJzx/lTGgOt6ubP8K6LeW0vaywzNNqxkl4=",
"owner": "nix-darwin",
"repo": "nix-darwin",
-
"rev": "536f951efb1ccda9b968e3c9dee39fbeb6d3fdeb",
+
"rev": "688427b1aab9afb478ca07989dc754fa543e03d5",
"type": "github"
},
"original": {
"owner": "nix-darwin",
-
"ref": "nix-darwin-25.05",
+
"ref": "nix-darwin-25.11",
"repo": "nix-darwin",
"type": "github"
}
···
},
"nix-vscode-extensions": {
"inputs": {
-
"flake-utils": "flake-utils_5",
"nixpkgs": "nixpkgs_3"
},
"locked": {
-
"lastModified": 1756346196,
-
"narHash": "sha256-esNUIJNbG/RZcGRamMqJBIiidNB93xCmEN/hswWDHk0=",
+
"lastModified": 1765418662,
+
"narHash": "sha256-8SSYagIUn+m9CKUYddq3DN1xkh04KCO0itB/LMgEgpc=",
"owner": "nix-community",
"repo": "nix-vscode-extensions",
-
"rev": "730c4353591b3c507a6973b75b52e367cde90d52",
+
"rev": "0f6679daa3f5bc2b09827b67f49caf0ac8e3a4c8",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-vscode-extensions",
+
"type": "github"
+
}
+
},
+
"nixos-facter-modules": {
+
"locked": {
+
"lastModified": 1765442039,
+
"narHash": "sha256-k3lYQ+A1F7aTz8HnlU++bd9t/x/NP2A4v9+x6opcVg0=",
+
"owner": "numtide",
+
"repo": "nixos-facter-modules",
+
"rev": "9dd775ee92de63f14edd021d59416e18ac2c00f1",
+
"type": "github"
+
},
+
"original": {
+
"owner": "numtide",
+
"repo": "nixos-facter-modules",
"type": "github"
}
},
···
},
"nixpkgs-unstable": {
"locked": {
-
"lastModified": 1756266583,
-
"narHash": "sha256-cr748nSmpfvnhqSXPiCfUPxRz2FJnvf/RjJGvFfaCsM=",
+
"lastModified": 1765186076,
+
"narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "nixos",
"repo": "nixpkgs",
-
"rev": "8a6d5427d99ec71c64f0b93d45778c889005d9c2",
+
"rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github"
},
"original": {
···
},
"nixpkgs_3": {
"locked": {
-
"lastModified": 1744868846,
-
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
-
"owner": "NixOS",
+
"lastModified": 1759770925,
+
"narHash": "sha256-CZwkCtzTNclqlhuwDsVtGoRumTpqCUK0xSnFIMgd8ls=",
+
"owner": "nixos",
"repo": "nixpkgs",
-
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
+
"rev": "674c2b09c59a220204350ced584cadaacee30038",
"type": "github"
},
"original": {
-
"owner": "NixOS",
+
"owner": "nixos",
"repo": "nixpkgs",
-
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
+
"rev": "674c2b09c59a220204350ced584cadaacee30038",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
-
"lastModified": 1756217674,
-
"narHash": "sha256-TH1SfSP523QI7kcPiNtMAEuwZR3Jdz0MCDXPs7TS8uo=",
+
"lastModified": 1765311797,
+
"narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=",
"owner": "nixos",
"repo": "nixpkgs",
-
"rev": "4e7667a90c167f7a81d906e5a75cba4ad8bee620",
+
"rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b",
"type": "github"
},
"original": {
"owner": "nixos",
-
"ref": "nixos-25.05",
+
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
···
"type": "github"
}
},
+
"nixpkgs_8": {
+
"locked": {
+
"lastModified": 1764635402,
+
"narHash": "sha256-6rYcajRLe2C5ZYnV1HYskJl+QAkhvseWTzbdQiTN9OI=",
+
"owner": "nixos",
+
"repo": "nixpkgs",
+
"rev": "5f53b0d46d320352684242d000b36dcfbbf7b0bc",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nixos",
+
"repo": "nixpkgs",
+
"type": "github"
+
}
+
},
"nixvim": {
"inputs": {
"flake-parts": "flake-parts",
···
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
-
"lastModified": 1747155592,
-
"narHash": "sha256-j2WR2dvOV3ArPNtTP6EY7P30xNNXdQgHBQX0AXdqsPg=",
+
"lastModified": 1758568990,
+
"narHash": "sha256-5Nn3M61UBidkxIoLS10th0FQhpfWpcF1xPTikPTTgeA=",
"owner": "taciturnaxolotl",
"repo": "nixvim",
-
"rev": "1c691577db459d71663bcb0f5647050af2221db6",
+
"rev": "2b8ef114f234a0596eb7601248a4efed38280093",
"type": "github"
},
"original": {
···
]
},
"locked": {
-
"lastModified": 1756416356,
-
"narHash": "sha256-xcqREhXocGZPbDsvjD7ncDz8++jfX5ZwpGc0maiD9CY=",
+
"lastModified": 1765470296,
+
"narHash": "sha256-bURojPUn8jloR046JNZf6qrYNmEPfFEoDaLTKoP9pg4=",
"owner": "nix-community",
"repo": "NUR",
-
"rev": "e4de97a152b367ba92ee590ff67fee4f33c46d92",
+
"rev": "441a70568483c0c48b338cca2030e3d9c7aef3ba",
"type": "github"
},
"original": {
···
},
"nuschtosSearch": {
"inputs": {
-
"flake-utils": "flake-utils_6",
+
"flake-utils": "flake-utils_5",
"ixx": "ixx",
"nixpkgs": [
"nixvim",
···
},
"pre-commit-hooks": {
"inputs": {
-
"flake-compat": "flake-compat",
+
"flake-compat": "flake-compat_2",
"gitignore": "gitignore",
"nixpkgs": "nixpkgs_7"
},
···
"root": {
"inputs": {
"agenix": "agenix",
+
"battleship-arena": "battleship-arena",
"catppuccin": "catppuccin",
"catppuccin-vsc": "catppuccin-vsc",
"cedarlogic": "cedarlogic",
"claude-desktop": "claude-desktop",
"ctfd-alerts": "ctfd-alerts",
+
"deploy-rs": "deploy-rs",
"disko": "disko",
"flare": "flare",
"frc-nix": "frc-nix",
···
"import-tree": "import-tree",
"nix-darwin": "nix-darwin",
"nix-vscode-extensions": "nix-vscode-extensions",
+
"nixos-facter-modules": "nixos-facter-modules",
"nixpkgs": "nixpkgs_4",
"nixpkgs-unstable": "nixpkgs-unstable",
"nixvim": "nixvim",
"nur": "nur",
+
"soapdump": "soapdump",
"spicetify-nix": "spicetify-nix",
-
"terminal-wakatime": "terminal-wakatime"
+
"tangled": "tangled",
+
"terminal-wakatime": "terminal-wakatime",
+
"wakatime-ls": "wakatime-ls",
+
"zmx": "zmx"
+
}
+
},
+
"rust-overlay": {
+
"inputs": {
+
"nixpkgs": [
+
"wakatime-ls",
+
"nixpkgs"
+
]
+
},
+
"locked": {
+
"lastModified": 1764557621,
+
"narHash": "sha256-kX5PoY8hQZ80+amMQgOO9t8Tc1JZ70gYRnzaVD4AA+o=",
+
"owner": "oxalica",
+
"repo": "rust-overlay",
+
"rev": "93316876c2229460a5d6f5f052766cc4cef538ce",
+
"type": "github"
+
},
+
"original": {
+
"owner": "oxalica",
+
"repo": "rust-overlay",
+
"type": "github"
+
}
+
},
+
"soapdump": {
+
"inputs": {
+
"nixpkgs": [
+
"nixpkgs"
+
]
+
},
+
"locked": {
+
"lastModified": 1757803066,
+
"narHash": "sha256-Bu+mICGedn+V0mo8ysdV9y1Qc+D5ENphfxUTjNz4Q4c=",
+
"owner": "taciturnaxolotl",
+
"repo": "soapdump",
+
"rev": "b2ff538becb73f41cf7dad636e9d79d7fa82f3a4",
+
"type": "github"
+
},
+
"original": {
+
"owner": "taciturnaxolotl",
+
"repo": "soapdump",
+
"type": "github"
}
},
"spicetify-nix": {
···
"systems": "systems_8"
},
"locked": {
-
"lastModified": 1756009939,
-
"narHash": "sha256-lD4Zn37DWEx0X1DqM3npH68b7oh81H8BaaO3c6Ol/DQ=",
+
"lastModified": 1765082296,
+
"narHash": "sha256-EcefoixU9ht+P6QB/TfjLY9E3MdJVfeSec6G8Ges0pA=",
"owner": "Gerg-L",
"repo": "spicetify-nix",
-
"rev": "2bedaf52261ef2adbe71af70820aeb41dfe9a5ef",
+
"rev": "ac4927ea1ec7e7ea3635a1d8b933106a596c4356",
"type": "github"
},
"original": {
···
"type": "github"
}
},
+
"sqlite-lib-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1706631843,
+
"narHash": "sha256-bJoMjirsBjm2Qk9KPiy3yV3+8b/POlYe76/FQbciHro=",
+
"type": "tarball",
+
"url": "https://sqlite.org/2024/sqlite-amalgamation-3450100.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://sqlite.org/2024/sqlite-amalgamation-3450100.zip"
+
}
+
},
"systems": {
+
"locked": {
+
"lastModified": 1681028828,
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
"owner": "nix-systems",
+
"repo": "default",
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nix-systems",
+
"repo": "default",
+
"type": "github"
+
}
+
},
+
"systems_10": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
···
"type": "github"
},
+
"systems_9": {
+
"locked": {
+
"lastModified": 1681028828,
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
"owner": "nix-systems",
+
"repo": "default",
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nix-systems",
+
"repo": "default",
+
"type": "github"
+
}
+
},
+
"tangled": {
+
"inputs": {
+
"actor-typeahead-src": "actor-typeahead-src",
+
"flake-compat": "flake-compat_3",
+
"gomod2nix": "gomod2nix",
+
"htmx-src": "htmx-src",
+
"htmx-ws-src": "htmx-ws-src",
+
"ibm-plex-mono-src": "ibm-plex-mono-src",
+
"indigo": "indigo",
+
"inter-fonts-src": "inter-fonts-src",
+
"lucide-src": "lucide-src",
+
"nixpkgs": [
+
"nixpkgs"
+
],
+
"sqlite-lib-src": "sqlite-lib-src"
+
},
+
"locked": {
+
"lastModified": 1765368304,
+
"narHash": "sha256-Q3JC5+FYtsKJU70WIhGhsAYWzu0CvUmmbdYhcFe46Pg=",
+
"ref": "refs/heads/master",
+
"rev": "a53d124ea4746109c1933f7adc72f0bde1309890",
+
"revCount": 1731,
+
"type": "git",
+
"url": "https://tangled.org/tangled.org/core"
+
},
+
"original": {
+
"type": "git",
+
"url": "https://tangled.org/tangled.org/core"
+
}
+
},
"terminal-wakatime": {
"inputs": {
"nixpkgs": [
···
"original": {
"owner": "taciturnaxolotl",
"repo": "terminal-wakatime",
+
"type": "github"
+
}
+
},
+
"utils": {
+
"inputs": {
+
"systems": "systems_4"
+
},
+
"locked": {
+
"lastModified": 1731533236,
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+
"type": "github"
+
},
+
"original": {
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"type": "github"
+
}
+
},
+
"wakatime-ls": {
+
"inputs": {
+
"gitignore": "gitignore_2",
+
"nixpkgs": [
+
"nixpkgs"
+
],
+
"rust-overlay": "rust-overlay"
+
},
+
"locked": {
+
"lastModified": 1764772399,
+
"narHash": "sha256-iZmN5d42tOsmssuFzOf3saUUeXbrwraQmDgh5czuMis=",
+
"owner": "mrnossiom",
+
"repo": "wakatime-ls",
+
"rev": "b8b9c1e612f198d767a64142f34c33ffbd347fae",
+
"type": "github"
+
},
+
"original": {
+
"owner": "mrnossiom",
+
"repo": "wakatime-ls",
+
"type": "github"
+
}
+
},
+
"zig2nix": {
+
"inputs": {
+
"flake-utils": "flake-utils_7",
+
"nixpkgs": "nixpkgs_8"
+
},
+
"locked": {
+
"lastModified": 1764678235,
+
"narHash": "sha256-NNQWR3DAufaH7fs6ZplfAv1xPHEc0Ne3Z0v4MNHCqSw=",
+
"owner": "Cloudef",
+
"repo": "zig2nix",
+
"rev": "8b6ec85bccdf6b91ded19e9ef671205937e271e6",
+
"type": "github"
+
},
+
"original": {
+
"owner": "Cloudef",
+
"repo": "zig2nix",
+
"type": "github"
+
}
+
},
+
"zmx": {
+
"inputs": {
+
"zig2nix": "zig2nix"
+
},
+
"locked": {
+
"lastModified": 1765397837,
+
"narHash": "sha256-nMlS9SA8MLJHJ0X/zEg3eG18mLw5vvZpZBbTbVcGFTI=",
+
"owner": "neurosnap",
+
"repo": "zmx",
+
"rev": "a22dba538a31480ed450b389f397e15880a1c53a",
+
"type": "github"
+
},
+
"original": {
+
"owner": "neurosnap",
+
"repo": "zmx",
"type": "github"
+99 -8
flake.nix
···
inputs = {
# Nixpkgs
-
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
+
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
# NixOS hardware configuration
hardware.url = "github:NixOS/nixos-hardware/master";
# Home manager
-
home-manager.url = "github:nix-community/home-manager/release-25.05";
+
home-manager.url = "github:nix-community/home-manager/release-25.11";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
# Nix-Darwin
-
nix-darwin.url = "github:nix-darwin/nix-darwin/nix-darwin-25.05";
+
nix-darwin.url = "github:nix-darwin/nix-darwin/nix-darwin-25.11";
nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
disko.url = "github:nix-community/disko";
disko.inputs.nixpkgs.follows = "nixpkgs";
+
nixos-facter-modules.url = "github:numtide/nixos-facter-modules";
+
# agenix
agenix.url = "github:ryantm/agenix";
···
nixvim.url = "github:taciturnaxolotl/nixvim";
-
-
terminal-wakatime = {
url = "github:taciturnaxolotl/terminal-wakatime";
inputs.nixpkgs.follows = "nixpkgs";
···
url = "github:taciturnaxolotl/CedarLogic";
inputs.nixpkgs.follows = "nixpkgs";
};
+
+
soapdump = {
+
url = "github:taciturnaxolotl/soapdump";
+
inputs.nixpkgs.follows = "nixpkgs";
+
};
+
+
wakatime-ls = {
+
url = "github:mrnossiom/wakatime-ls";
+
inputs.nixpkgs.follows = "nixpkgs";
+
};
+
+
deploy-rs = {
+
url = "github:serokell/deploy-rs";
+
inputs.nixpkgs.follows = "nixpkgs";
+
};
+
+
tangled = {
+
url = "git+https://tangled.org/tangled.org/core";
+
inputs.nixpkgs.follows = "nixpkgs";
+
};
+
+
battleship-arena = {
+
url = "github:taciturnaxolotl/battleship-arena";
+
inputs.nixpkgs.follows = "nixpkgs";
+
};
+
+
zmx = {
+
url = "github:neurosnap/zmx";
+
};
};
outputs =
···
home-manager,
nur,
nix-darwin,
+
deploy-rs,
+
tangled,
...
}@inputs:
let
···
nixpkgs.overlays = [
(final: prev: {
unstable = import nixpkgs-unstable {
-
system = final.system;
+
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
···
hash = "sha256-7mkrPl2CQSfc1lRjl1ilwxdYcK5iRU//QGKmdCicK30=";
};
});
+
+
zmx-binary = prev.callPackage ./packages/zmx.nix { };
})
];
};
···
nur.modules.nixos.default
];
};
+
+
prattle = nixpkgs.lib.nixosSystem {
+
specialArgs = { inherit inputs outputs; };
+
modules = [
+
inputs.disko.nixosModules.disko
+
agenix.nixosModules.default
+
inputs.nixos-facter-modules.nixosModules.facter
+
{ config.facter.reportPath = ./machines/prattle/facter.json; }
+
unstable-overlays
+
./machines/prattle
+
nur.modules.nixos.default
+
];
+
};
+
+
terebithia = nixpkgs.lib.nixosSystem {
+
specialArgs = { inherit inputs outputs; };
+
modules = [
+
inputs.disko.nixosModules.disko
+
agenix.nixosModules.default
+
inputs.nixos-facter-modules.nixosModules.facter
+
{ config.facter.reportPath = ./machines/terebithia/facter.json; }
+
unstable-overlays
+
./machines/terebithia
+
nur.modules.nixos.default
+
];
+
};
};
# Standalone home-manager configurations
···
extraSpecialArgs = {
inherit inputs outputs;
nixpkgs-unstable = nixpkgs-unstable;
+
system = "x86_64-linux";
};
modules = [
./machines/nest
···
./machines/ember
unstable-overlays
];
+
+
};
+
"john" = home-manager.lib.homeManagerConfiguration {
+
pkgs = nixpkgs.legacyPackages.x86_64-linux;
+
extraSpecialArgs = {
+
inherit inputs outputs;
+
nixpkgs-unstable = nixpkgs-unstable;
+
system = "x86_64-linux";
+
};
+
modules = [
+
./machines/john
+
unstable-overlays
+
];
};
};
-
-
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-tree;
# Darwin configurations
# Available through 'darwin-rebuild switch --flake .#hostname'
···
home-manager.darwinModules.home-manager
agenix.darwinModules.default
unstable-overlays
+
nur.modules.darwin.default
./machines/atalanta
];
};
};
+
+
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-tree;
+
formatter.aarch64-darwin = nixpkgs.legacyPackages.aarch64-darwin.nixfmt-tree;
+
+
# Deploy-rs configurations
+
deploy.nodes = {
+
# NixOS servers
+
terebithia = {
+
hostname = "terebithia";
+
profiles.system = {
+
sshUser = "kierank";
+
user = "root";
+
path = deploy-rs.lib.aarch64-linux.activate.nixos self.nixosConfigurations.terebithia;
+
};
+
};
+
};
+
+
# Validation checks
+
checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib;
};
}
+148 -4
machines/atalanta/default.nix
···
inputs,
pkgs,
...
-
}: {
+
}:
+
{
imports = [
./home-manager.nix
];
···
};
# Enable nix-darwin
-
nix.settings.experimental-features = [ "nix-command" "flakes" ];
+
nix.settings.experimental-features = [
+
"nix-command"
+
"flakes"
+
];
+
+
# switch to lix
+
nix.package = pkgs.lixPackageSets.stable.lix;
# Set hostname
networking.hostName = "atalanta";
···
pkgs.nil
pkgs.nixfmt-rfc-style
inputs.agenix.packages.aarch64-darwin.default
-
pkgs.lix
# dev_langs
pkgs.nodejs_22
pkgs.unstable.bun
···
pkgs.gcc
pkgs.rustc
pkgs.cargo
-
pkgs.jdk23
+
pkgs.jdk
pkgs.ruby
pkgs.cmake
pkgs.unstable.biome
···
pkgs.clang
pkgs.clang-tools
pkgs.ninja
+
# tools
+
pkgs.calc
+
pkgs.nh
+
pkgs.rustscan
+
pkgs.vhs
+
inputs.soapdump.packages.${pkgs.stdenv.hostPlatform.system}.default
];
+
+
programs.direnv.enable = true;
# import the secret
age.identityPaths = [
···
bluesky = {
file = ../../secrets/bluesky.age;
owner = "kierank";
+
};
+
crush = {
+
file = ../../secrets/crush.age;
+
owner = "kierank";
+
};
+
context7 = {
+
file = ../../secrets/context7.age;
+
owner = "kierank";
+
};
+
frp-auth-token = {
+
file = ../../secrets/frp-auth-token.age;
+
owner = "kierank";
+
};
+
};
+
+
environment.variables = {
+
EDITOR = "nvim";
+
SYSTEMD_EDITOR = "nvim";
+
VISUAL = "nvim";
+
};
+
+
# nothing but finder in the doc
+
system.defaults.dock = {
+
persistent-apps = [ ];
+
+
tilesize = 47;
+
show-recents = false;
+
};
+
+
# allow using apple watch or touch id for sudo
+
security.pam.services.sudo_local.touchIdAuth = true;
+
security.pam.services.sudo_local.watchIdAuth = true;
+
+
system.defaults = {
+
finder.FXPreferredViewStyle = "Nlsv";
+
finder.AppleShowAllExtensions = true;
+
# expand the save dialogs
+
NSGlobalDomain.NSNavPanelExpandedStateForSaveMode = true;
+
NSGlobalDomain.NSNavPanelExpandedStateForSaveMode2 = true;
+
LaunchServices.LSQuarantine = false; # disables "Are you sure?" for new apps
+
loginwindow.GuestEnabled = false;
+
+
NSGlobalDomain."com.apple.trackpad.scaling" = 0.875;
+
+
CustomSystemPreferences = {
+
"com.apple.DiskArbitration.diskarbitrationd" = {
+
DADisableEjectNotification = true;
+
};
+
};
+
+
CustomUserPreferences = {
+
"com.apple.driver.AppleBluetoothMultitouch.mouse" = {
+
MouseButtonMode = "TwoButton";
+
};
+
"com.apple.WindowManager" = {
+
EnableTiledWindowMargins = false;
+
};
+
"com.apple.desktopservices" = {
+
# Avoid creating .DS_Store files on network or USB volumes
+
DSDontWriteNetworkStores = true;
+
DSDontWriteUSBStores = true;
+
};
+
"com.apple.AdLib" = {
+
allowApplePersonalizedAdvertising = false;
+
};
+
"com.apple.SoftwareUpdate" = {
+
AutomaticCheckEnabled = true;
+
# Check for software updates daily, not just once per week
+
ScheduleFrequency = 1;
+
# Download newly available updates in background
+
AutomaticDownload = 1;
+
# Install System data files & security updates
+
CriticalUpdateInstall = 1;
+
};
+
# keybindings
+
# Script to export symbolic hotkey configs from MacOS
+
# https://gist.github.com/sawadashota/8e7ce32234e0f07a03e955f22ec4c0f9
+
# Screenshot selected area to file with Cmd+Option+Shift+4
+
"com.apple.symbolichotkeys" = {
+
AppleSymbolicHotKeys = {
+
# Screenshot selected area with Option+Cmd+Shift+4
+
"30" = {
+
enabled = true;
+
value = {
+
parameters = [
+
52
+
21
+
1703936
+
];
+
type = "standard";
+
};
+
};
+
# Screenshot selected area to clipboard with Cmd+Shift+4
+
"31" = {
+
enabled = true;
+
value = {
+
parameters = [
+
52
+
21
+
1179648
+
];
+
type = "standard";
+
};
+
};
+
# Fullscreen screenshot Option+Cmd+Shift+3
+
"28" = {
+
enabled = true;
+
value = {
+
parameters = [
+
51
+
20
+
1703936
+
];
+
type = "standard";
+
};
+
};
+
# Fullscreen screenshot to clipboard Cmd+Shift+3
+
"29" = {
+
enabled = true;
+
value = {
+
parameters = [
+
51
+
20
+
1179648
+
];
+
type = "standard";
+
};
+
};
+
};
+
};
};
};
+144 -6
machines/atalanta/home/default.nix
···
{
inputs,
pkgs,
+
osConfig,
...
}:
{
imports = [
(inputs.import-tree ../../../modules/home)
];
-
-
nixpkgs.enable = true;
home = {
username = "kierank";
homeDirectory = "/Users/kierank";
packages = with pkgs; [
-
neofetch
-
inputs.nixvim.packages.${system}.default
+
inputs.nixvim.packages.${pkgs.stdenv.hostPlatform.system}.default
vesktop
];
};
···
};
};
apps = {
-
irssi.enable = true;
-
spotify.enable = true;
+
halloy.enable = true;
crush.enable = true;
+
helix = {
+
enable = true;
+
swift = true;
+
};
+
};
+
bore = {
+
enable = true;
+
authTokenFile = osConfig.age.secrets.frp-auth-token.path;
+
};
+
ssh = {
+
enable = true;
+
+
zmx = {
+
enable = true;
+
hosts = [ "t.*" "p.*" "e.*" "j.*" ];
+
};
+
+
hosts = {
+
# Dynamic zmx sessions per server
+
"t.*" = {
+
hostname = "150.136.15.177"; # terebithia
+
};
+
+
"p.*" = {
+
hostname = "150.136.63.103"; # prattle
+
};
+
+
"e.*" = {
+
hostname = "192.168.0.94"; # ember
+
};
+
+
"j.*" = {
+
hostname = "john.cedarville.edu";
+
user = "klukas";
+
};
+
+
# Regular hosts
+
john = {
+
hostname = "john.cedarville.edu";
+
user = "klukas";
+
zmx = true;
+
};
+
+
bandit = {
+
hostname = "bandit.labs.overthewire.org";
+
port = 2220;
+
};
+
+
kali = {
+
user = "kali";
+
};
+
+
terebithia = {
+
hostname = "150.136.15.177";
+
zmx = true;
+
};
+
+
prattle = {
+
hostname = "150.136.63.103";
+
zmx = true;
+
};
+
+
ember = {
+
hostname = "192.168.0.94";
+
zmx = true;
+
};
+
+
remarkable = {
+
hostname = "10.11.99.01";
+
user = "root";
+
};
+
};
+
+
extraConfig = ''
+
IdentityFile ~/.ssh/id_rsa
+
'';
};
};
+
+
programs.zsh.initContent = ''
+
eval "$(/usr/libexec/path_helper)"
+
export PATH="$HOME/.cargo/bin:$PATH"
+
+
# MITM proxy management functions
+
MITM_SERVICE="Wi-Fi" # Change to "Ethernet" if needed
+
MITM_CERT="$HOME/.mitmproxy/mitmproxy-ca-cert.pem"
+
+
mitmup() {
+
# Generate mitmproxy CA certificate if it doesn't exist
+
if [ ! -f "$MITM_CERT" ]; then
+
echo "Generating mitmproxy CA certificate..."
+
(timeout 0.1 mitmproxy --set confdir="$HOME/.mitmproxy" 2>/dev/null; true)
+
fi
+
+
networksetup -setwebproxy "$MITM_SERVICE" localhost 8080 &&
+
networksetup -setsecurewebproxy "$MITM_SERVICE" localhost 8080 &&
+
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$MITM_CERT" &&
+
echo "mitmproxy enabled and cert added"
+
}
+
+
mitmdown() {
+
networksetup -setwebproxystate "$MITM_SERVICE" off &&
+
networksetup -setsecurewebproxystate "$MITM_SERVICE" off &&
+
sudo security delete-certificate -c mitmproxy /Library/Keychains/System.keychain &&
+
echo "mitmproxy disabled and cert removed"
+
}
+
+
mitmstatus() {
+
GREEN='\033[0;32m'
+
RED='\033[0;31m'
+
NC='\033[0m' # No Color
+
+
echo "========== Proxy Status =========="
+
for proto in webproxy securewebproxy; do
+
proxy_status=$(networksetup -get''${proto} "$MITM_SERVICE")
+
enabled=$(echo "$proxy_status" | grep "Enabled: Yes")
+
PROTO_UPPER=$(echo "$proto" | tr '[:lower:]' '[:upper:]')
+
if [ -n "$enabled" ]; then
+
echo -e "''${PROTO_UPPER} : ''${GREEN}ENABLED''${NC}"
+
else
+
echo -e "''${PROTO_UPPER} : ''${RED}DISABLED''${NC}"
+
fi
+
echo "$proxy_status" | grep -E "Server:|Port:"
+
done
+
+
echo "========== mitmproxy Certificate =========="
+
if security find-certificate -c mitmproxy /Library/Keychains/System.keychain > /dev/null 2>&1; then
+
echo -e "mitmproxy certificate: ''${GREEN}PRESENT''${NC}"
+
else
+
echo -e "mitmproxy certificate: ''${RED}NOT PRESENT''${NC}"
+
fi
+
+
echo "========== mitmproxy Process =========="
+
if pgrep -f mitmproxy > /dev/null; then
+
echo -e "mitmproxy process: ''${GREEN}RUNNING''${NC}"
+
else
+
echo -e "mitmproxy process: ''${RED}NOT RUNNING''${NC}"
+
fi
+
echo "==========================================="
+
}
+
+
+
'';
# Let Home Manager install and manage itself
programs.home-manager.enable = true;
+4 -2
machines/atalanta/home-manager.nix
···
inputs,
outputs,
...
-
}: {
+
}:
+
{
imports = [
# Import home-manager's Darwin module
inputs.home-manager.darwinModules.home-manager
];
home-manager = {
+
useGlobalPkgs = true;
extraSpecialArgs = {
inherit inputs outputs;
};
···
kierank = import ./home;
};
};
-
}
+
}
+8
machines/ember/default.nix
···
{
imports = [
(inputs.import-tree ../../modules/home)
+
../../modules/home/system/nixpkgs.nix.disabled
];
nixpkgs.enable = true;
···
atelier = {
shell.enable = true;
+
apps = {
+
helix.enable = true;
+
};
+
ssh = {
+
enable = true;
+
zmx.enable = true;
+
};
};
# Enable home-manager
+10 -21
machines/moonlark/default.nix
···
pkgs.alacritty
pkgs.unstable.ghostty
# cli_utils
-
pkgs.bat
-
pkgs.fd
-
pkgs.eza
-
pkgs.xh
-
pkgs.dust
-
pkgs.ripgrep
-
pkgs.ripgrep-all
pkgs.glow
-
pkgs.gitui
-
pkgs.lazygit
pkgs.clipse
pkgs.direnv
pkgs.nix-output-monitor
···
pkgs.nix-prefetch
pkgs.arduino-cli
pkgs.zsh
-
pkgs.starship
pkgs.gum
-
pkgs.unstable.wakatime-cli
-
inputs.terminal-wakatime.packages.x86_64-linux.default
# networking
+
pkgs.xh
pkgs.curl
pkgs.wget
pkgs.dogdns
···
pkgs.mako
pkgs.unstable.hyprpicker
pkgs.wl-screenrec
-
inputs.hyprland-contrib.packages.${pkgs.system}.grimblast
+
inputs.hyprland-contrib.packages.${pkgs.stdenv.hostPlatform.system}.grimblast
pkgs.playerctl
pkgs.libnotify
pkgs.notify-desktop
···
pkgs.gcc
pkgs.rustc
pkgs.cargo
-
pkgs.jdk23
+
pkgs.jdk
pkgs.ruby
pkgs.cmake
pkgs.unstable.biome
···
pkgs.gobang
pkgs.love
#frc
-
inputs.frc-nix.packages.${pkgs.system}.elastic-dashboard
-
inputs.frc-nix.packages.${pkgs.system}.pathplanner
-
inputs.frc-nix.packages.${pkgs.system}.roborioteamnumbersetter
-
inputs.frc-nix.packages.${pkgs.system}.sysid
-
inputs.frc-nix.packages.${pkgs.system}.wpilib-utility
-
inputs.frc-nix.packages.${pkgs.system}.advantagescope
+
inputs.frc-nix.packages.${pkgs.stdenv.hostPlatform.system}.elastic-dashboard
+
inputs.frc-nix.packages.${pkgs.stdenv.hostPlatform.system}.pathplanner
+
inputs.frc-nix.packages.${pkgs.stdenv.hostPlatform.system}.roborioteamnumbersetter
+
inputs.frc-nix.packages.${pkgs.stdenv.hostPlatform.system}.sysid
+
inputs.frc-nix.packages.${pkgs.stdenv.hostPlatform.system}.wpilib-utility
+
inputs.frc-nix.packages.${pkgs.stdenv.hostPlatform.system}.advantagescope
# misc
pkgs.invoice
pkgs.pop
···
pkgs.unstable.kicad-testing
pkgs.zenity
pkgs.atproto-goat
-
inputs.cedarlogic.packages.${pkgs.system}.cedarlogic
+
inputs.cedarlogic.packages.${pkgs.stdenv.hostPlatform.system}.cedarlogic
pkgs.unstable.betaflight-configurator
];
-3
machines/moonlark/home/default.nix
···
(inputs.import-tree ../../../modules/home)
];
-
nixpkgs.enable = true;
-
home = {
username = "kierank";
homeDirectory = "/home/kierank";
···
irssi.enable = true;
qutebrowser.enable = true;
spotify.enable = true;
-
vscode.enable = true;
crush.enable = true;
};
theming.enable = true;
+1
machines/moonlark/home-manager.nix
···
];
home-manager = {
+
useGlobalPkgs = true;
extraSpecialArgs = {
inherit inputs outputs;
};
+8
machines/nest/default.nix
···
{
imports = [
(inputs.import-tree ../../modules/home)
+
../../modules/home/system/nixpkgs.nix.disabled
];
nixpkgs.enable = true;
···
atelier = {
shell.enable = true;
+
apps = {
+
helix.enable = true;
+
};
+
ssh = {
+
enable = true;
+
zmx.enable = true;
+
};
};
# Enable home-manager
+207
machines/prattle/default.nix
···
+
{
+
inputs,
+
lib,
+
config,
+
pkgs,
+
...
+
}:
+
{
+
imports = [
+
./disk-config.nix
+
./home-manager.nix
+
+
(inputs.import-tree ../../modules/nixos)
+
];
+
+
nixpkgs = {
+
hostPlatform = "x86_64-linux";
+
config = {
+
allowUnfree = true;
+
};
+
};
+
+
nix =
+
let
+
flakeInputs = lib.filterAttrs (_: lib.isType "flake") inputs;
+
in
+
{
+
settings = {
+
experimental-features = "nix-command flakes";
+
flake-registry = "";
+
nix-path = config.nix.nixPath;
+
trusted-users = [
+
"kierank"
+
];
+
# Limit resource usage
+
max-jobs = 1;
+
cores = 1;
+
};
+
channel.enable = false;
+
# Disable automatic optimization to save CPU/memory
+
optimise.automatic = false;
+
registry = lib.mapAttrs (_: flake: { inherit flake; }) flakeInputs;
+
nixPath = lib.mapAttrsToList (n: _: "${n}=flake:${n}") flakeInputs;
+
# Enable automatic GC to free up disk space
+
gc = {
+
automatic = true;
+
dates = "weekly";
+
options = "--delete-older-than 7d";
+
};
+
};
+
+
time.timeZone = "America/New_York";
+
+
environment.systemPackages = with pkgs; [
+
# core essentials only
+
vim
+
curl
+
inputs.agenix.packages.x86_64-linux.default
+
];
+
+
age.identityPaths = [
+
"/home/kierank/.ssh/id_rsa"
+
"/etc/ssh/id_rsa"
+
];
+
age.secrets = {
+
wakatime = {
+
file = ../../secrets/wakatime.age;
+
path = "/home/kierank/.wakatime.cfg";
+
owner = "kierank";
+
};
+
cloudflare = {
+
file = ../../secrets/cloudflare.age;
+
owner = "caddy";
+
};
+
};
+
+
environment.sessionVariables = {
+
XDG_CACHE_HOME = "$HOME/.cache";
+
XDG_CONFIG_HOME = "$HOME/.config";
+
XDG_DATA_HOME = "$HOME/.local/share";
+
XDG_STATE_HOME = "$HOME/.local/state";
+
EDITOR = "vim";
+
SYSTEMD_EDITOR = "vim";
+
VISUAL = "vim";
+
};
+
+
atelier = {
+
authentication.enable = true;
+
};
+
+
networking = {
+
hostName = "prattle";
+
# Use systemd-networkd instead of NetworkManager (lighter)
+
useDHCP = true;
+
networkmanager.enable = false;
+
};
+
+
# Use bash instead of zsh to save memory
+
programs.bash.enableCompletion = true;
+
+
users.users = {
+
kierank = {
+
initialPassword = "changeme";
+
isNormalUser = true;
+
shell = pkgs.bashInteractive;
+
openssh.authorizedKeys.keys = [
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzEEjvbL/ttqmYoDjxYQmDIq36BabROJoXgQKeh9liBxApwp+2PmgxROzTg42UrRc9pyrkq5kVfxG5hvkqCinhL1fMiowCSEs2L2/Cwi40g5ZU+QwdcwI8a4969kkI46PyB19RHkxg54OUORiIiso/WHGmqQsP+5wbV0+4riSnxwn/JXN4pmnE//stnyAyoiEZkPvBtwJjKb3Ni9n3eNLNs6gnaXrCtaygEZdebikr9kS2g9mM696HvIFgM6cdR/wZ7DcLbG3IdTXuHN7PC3xxL+Y4ek5iMreQIPmuvs4qslbthPGYoYbYLUQiRa9XO5s/ksIj5Z14f7anHE6cuTQVpvNWdGDOigyIVS5qU+4ZF7j+rifzOXVL48gmcAvw/uV68m5Wl/p0qsC/d8vI3GYwEsWG/EzpAlc07l8BU2LxWgN+d7uwBFaJV9VtmUDs5dcslsh8IbzmtC9gq3OLGjklxTfIl6qPiL8U33oc/UwqzvZUrI2BlbagvIZYy6rP+q0= kierank@mockingjay"
+
];
+
extraGroups = [
+
"wheel"
+
];
+
};
+
root.openssh.authorizedKeys.keys = [
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzEEjvbL/ttqmYoDjxYQmDIq36BabROJoXgQKeh9liBxApwp+2PmgxROzTg42UrRc9pyrkq5kVfxG5hvkqCinhL1fMiowCSEs2L2/Cwi40g5ZU+QwdcwI8a4969kkI46PyB19RHkxg54OUORiIiso/WHGmqQsP+5wbV0+4riSnxwn/JXN4pmnE//stnyAyoiEZkPvBtwJjKb3Ni9n3eNLNs6gnaXrCtaygEZdebikr9kS2g9mM696HvIFgM6cdR/wZ7DcLbG3IdTXuHN7PC3xxL+Y4ek5iMreQIPmuvs4qslbthPGYoYbYLUQiRa9XO5s/ksIj5Z14f7anHE6cuTQVpvNWdGDOigyIVS5qU+4ZF7j+rifzOXVL48gmcAvw/uV68m5Wl/p0qsC/d8vI3GYwEsWG/EzpAlc07l8BU2LxWgN+d7uwBFaJV9VtmUDs5dcslsh8IbzmtC9gq3OLGjklxTfIl6qPiL8U33oc/UwqzvZUrI2BlbagvIZYy6rP+q0= kierank@mockingjay"
+
];
+
};
+
+
# Allow passwordless sudo for wheel group (needed for deploy-rs)
+
security.sudo.wheelNeedsPassword = false;
+
+
services.openssh = {
+
enable = true;
+
openFirewall = true;
+
settings = {
+
PermitRootLogin = "no";
+
PasswordAuthentication = false;
+
};
+
};
+
+
networking.firewall = {
+
enable = true;
+
allowedTCPPorts = [ 22 80 443 ];
+
logRefusedConnections = false;
+
rejectPackets = true;
+
# Disable firewall logging to reduce overhead
+
logReversePathDrops = false;
+
};
+
+
services.tailscale = {
+
enable = true;
+
useRoutingFeatures = "client";
+
};
+
+
services.caddy = {
+
enable = true;
+
package = pkgs.caddy.withPlugins {
+
plugins = [ "github.com/caddy-dns/cloudflare@v0.2.2" ];
+
hash = "sha256-ea8PC/+SlPRdEVVF/I3c1CBprlVp1nrumKM5cMwJJ3U=";
+
};
+
email = "me@dunkirk.sh";
+
globalConfig = ''
+
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
'';
+
virtualHosts."status.dunkirk.sh" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
reverse_proxy localhost:3001 {
+
header_up X-Forwarded-Proto {scheme}
+
header_up X-Forwarded-For {remote}
+
}
+
'';
+
};
+
extraConfig = ''
+
# Default response for unhandled domains
+
:80 {
+
respond "404 - Looks like this pin is unobtainable" 404
+
}
+
:443 {
+
respond "404 - Looks like this pin is unobtainable" 404
+
}
+
'';
+
};
+
+
systemd.services.caddy.serviceConfig = {
+
EnvironmentFile = config.age.secrets.cloudflare.path;
+
};
+
+
services.uptime-kuma = {
+
enable = true;
+
settings = {
+
PORT = "3001";
+
};
+
};
+
+
boot.loader.systemd-boot.enable = true;
+
boot.loader.efi.canTouchEfiVariables = true;
+
boot.kernelParams = [ "console=ttyS0" ];
+
+
# Memory and resource optimizations
+
zramSwap = {
+
enable = true;
+
memoryPercent = 50; # Use 50% of RAM for compressed swap
+
};
+
+
# Reduce system logging overhead
+
services.journald.extraConfig = ''
+
SystemMaxUse=100M
+
MaxRetentionSec=7day
+
'';
+
+
system.stateVersion = "23.05";
+
}
+32
machines/prattle/disk-config.nix
···
+
{
+
disko.devices = {
+
disk = {
+
main = {
+
type = "disk";
+
device = "/dev/sda";
+
content = {
+
type = "gpt";
+
partitions = {
+
boot = {
+
size = "512M";
+
type = "EF00";
+
content = {
+
type = "filesystem";
+
format = "vfat";
+
mountpoint = "/boot";
+
};
+
};
+
root = {
+
size = "100%";
+
content = {
+
type = "filesystem";
+
format = "ext4";
+
mountpoint = "/";
+
};
+
};
+
};
+
};
+
};
+
};
+
};
+
}
+1696
machines/prattle/facter.json
···
+
{
+
"version": 1,
+
"system": "x86_64-linux",
+
"virtualisation": "kvm",
+
"hardware": {
+
"bios": {
+
"apm_info": {
+
"supported": false,
+
"enabled": false,
+
"version": 0,
+
"sub_version": 0,
+
"bios_flags": 0
+
},
+
"vbe_info": {
+
"version": 0,
+
"video_memory": 0
+
},
+
"pnp": false,
+
"pnp_id": 0,
+
"lba_support": false,
+
"low_memory_size": 0,
+
"smbios_version": 520
+
},
+
"bridge": [
+
{
+
"index": 7,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 1
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0001",
+
"name": "ISA bridge",
+
"value": 1
+
},
+
"vendor": {
+
"hex": "8086",
+
"name": "Intel Corporation",
+
"value": 32902
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "7000",
+
"value": 28672
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"model": "Intel ISA bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.0",
+
"sysfs_bus_id": "0000:00:01.0",
+
"detail": {
+
"function": 0,
+
"command": 3,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 0,
+
"prog_if": 0
+
},
+
"module_alias": "pci:v00008086d00007000sv00001AF4sd00001100bc06sc01i00"
+
},
+
{
+
"index": 9,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Host bridge",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "8086",
+
"name": "Intel Corporation",
+
"value": 32902
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "1237",
+
"value": 4663
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0002",
+
"value": 2
+
},
+
"model": "Intel Host bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:00.0",
+
"sysfs_bus_id": "0000:00:00.0",
+
"detail": {
+
"function": 0,
+
"command": 3,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 0,
+
"prog_if": 0
+
},
+
"module_alias": "pci:v00008086d00001237sv00001AF4sd00001100bc06sc00i00"
+
},
+
{
+
"index": 10,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 1
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0080",
+
"name": "Bridge",
+
"value": 128
+
},
+
"vendor": {
+
"hex": "8086",
+
"name": "Intel Corporation",
+
"value": 32902
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "7113",
+
"value": 28947
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0003",
+
"value": 3
+
},
+
"model": "Intel Bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.3",
+
"sysfs_bus_id": "0000:00:01.3",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 9,
+
"triggered": 0,
+
"enabled": true
+
}
+
],
+
"detail": {
+
"function": 3,
+
"command": 3,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 9,
+
"prog_if": 0
+
},
+
"driver": "piix4_smbus",
+
"driver_module": "i2c_piix4",
+
"drivers": [
+
"piix4_smbus"
+
],
+
"driver_modules": [
+
"i2c_piix4"
+
],
+
"module_alias": "pci:v00008086d00007113sv00001AF4sd00001100bc06sc80i00"
+
}
+
],
+
"cpu": [
+
{
+
"architecture": "x86_64",
+
"vendor_name": "AuthenticAMD",
+
"family": 23,
+
"model": 1,
+
"stepping": 2,
+
"features": [
+
"fpu",
+
"vme",
+
"de",
+
"pse",
+
"tsc",
+
"msr",
+
"pae",
+
"mce",
+
"cx8",
+
"apic",
+
"sep",
+
"mtrr",
+
"pge",
+
"mca",
+
"cmov",
+
"pat",
+
"pse36",
+
"clflush",
+
"mmx",
+
"fxsr",
+
"sse",
+
"sse2",
+
"ht",
+
"syscall",
+
"nx",
+
"mmxext",
+
"fxsr_opt",
+
"pdpe1gb",
+
"rdtscp",
+
"lm",
+
"rep_good",
+
"nopl",
+
"xtopology",
+
"cpuid",
+
"extd_apicid",
+
"tsc_known_freq",
+
"pni",
+
"pclmulqdq",
+
"ssse3",
+
"fma",
+
"cx16",
+
"sse4_1",
+
"sse4_2",
+
"x2apic",
+
"movbe",
+
"popcnt",
+
"tsc_deadline_timer",
+
"aes",
+
"xsave",
+
"avx",
+
"f16c",
+
"rdrand",
+
"hypervisor",
+
"lahf_lm",
+
"cmp_legacy",
+
"svm",
+
"cr8_legacy",
+
"abm",
+
"sse4a",
+
"misalignsse",
+
"3dnowprefetch",
+
"osvw",
+
"topoext",
+
"perfctr_core",
+
"ssbd",
+
"ibpb",
+
"vmmcall",
+
"fsgsbase",
+
"tsc_adjust",
+
"bmi1",
+
"avx2",
+
"smep",
+
"bmi2",
+
"rdseed",
+
"adx",
+
"smap",
+
"clflushopt",
+
"sha_ni",
+
"xsaveopt",
+
"xsavec",
+
"xgetbv1",
+
"clzero",
+
"xsaveerptr",
+
"virt_ssbd",
+
"arat",
+
"npt",
+
"nrip_save",
+
"vgif",
+
"overflow_recov",
+
"succor",
+
"arch_capabilities"
+
],
+
"bugs": [
+
"sysret_ss_attrs",
+
"null_seg",
+
"spectre_v1",
+
"spectre_v2",
+
"spec_store_bypass",
+
"retbleed",
+
"smt_rsb",
+
"srso",
+
"div0",
+
"ibpb_no_ret"
+
],
+
"bogo": 3992.5,
+
"cache": 512,
+
"units": 2,
+
"physical_id": 0,
+
"siblings": 2,
+
"cores": 1,
+
"fpu": true,
+
"fpu_exception": true,
+
"cpuid_level": 13,
+
"write_protect": false,
+
"tlb_size": 1024,
+
"clflush_size": 64,
+
"cache_alignment": 64,
+
"address_sizes": {
+
"physical": "0x28",
+
"virtual": "0x30"
+
}
+
}
+
],
+
"disk": [
+
{
+
"index": 18,
+
"attached_to": 14,
+
"class_list": [
+
"disk",
+
"scsi",
+
"block_device"
+
],
+
"bus_type": {
+
"hex": "0084",
+
"name": "SCSI",
+
"value": 132
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0106",
+
"name": "Mass Storage Device",
+
"value": 262
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Disk",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "0000",
+
"name": "ORACLE",
+
"value": 0
+
},
+
"device": {
+
"hex": "0000",
+
"name": "BlockVolume",
+
"value": 0
+
},
+
"revision": {
+
"hex": "0000",
+
"name": "1.0",
+
"value": 0
+
},
+
"model": "ORACLE BlockVolume",
+
"sysfs_id": "/class/block/sda",
+
"sysfs_bus_id": "0:0:0:1",
+
"sysfs_device_link": "/devices/pci0000:00/0000:00:04.0/virtio1/host0/target0:0:0/0:0:0:1",
+
"unix_device_name": "/dev/sda",
+
"unix_device_number": {
+
"type": 98,
+
"major": 8,
+
"minor": 0,
+
"range": 16
+
},
+
"unix_device_names": [
+
"/dev/disk/by-id/scsi-36031c628421d4d309553ce9700b81f0d",
+
"/dev/disk/by-id/wwn-0x6031c628421d4d309553ce9700b81f0d",
+
"/dev/disk/by-path/pci-0000:00:04.0-scsi-0:0:0:1",
+
"/dev/sda"
+
],
+
"unix_device_name2": "/dev/sg0",
+
"unix_device_number2": {
+
"type": 99,
+
"major": 21,
+
"minor": 0,
+
"range": 1
+
},
+
"rom_id": "0x80",
+
"resources": [
+
{
+
"type": "disk_geo",
+
"cylinders": 47694,
+
"heads": 64,
+
"sectors": 32,
+
"size": "0x0",
+
"geo_type": "logical"
+
},
+
{
+
"type": "size",
+
"unit": "sectors",
+
"value_1": 97677312,
+
"value_2": 512
+
}
+
],
+
"driver": "virtio_scsi",
+
"driver_module": "virtio_scsi",
+
"drivers": [
+
"sd",
+
"virtio_scsi"
+
],
+
"driver_modules": [
+
"sd_mod",
+
"virtio_scsi"
+
]
+
}
+
],
+
"graphics_card": [
+
{
+
"index": 13,
+
"attached_to": 0,
+
"class_list": [
+
"graphics_card",
+
"pci"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 2
+
},
+
"base_class": {
+
"hex": "0003",
+
"name": "Display controller",
+
"value": 3
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "VGA compatible controller",
+
"value": 0
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "VGA",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1234",
+
"value": 4660
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "1111",
+
"value": 4369
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0002",
+
"value": 2
+
},
+
"model": "VGA compatible controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:02.0",
+
"sysfs_bus_id": "0000:00:02.0",
+
"resources": [
+
{
+
"type": "mem",
+
"base": 2147483648,
+
"range": 16777216,
+
"enabled": true,
+
"access": "read_only",
+
"prefetch": "no"
+
},
+
{
+
"type": "mem",
+
"base": 2164334592,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
},
+
{
+
"type": "mem",
+
"base": 786432,
+
"range": 131072,
+
"enabled": false,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 3,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 0,
+
"prog_if": 0
+
},
+
"driver": "bochs-drm",
+
"driver_module": "bochs",
+
"drivers": [
+
"bochs-drm"
+
],
+
"driver_modules": [
+
"bochs"
+
],
+
"module_alias": "pci:v00001234d00001111sv00001AF4sd00001100bc03sc00i00"
+
}
+
],
+
"hub": [
+
{
+
"index": 19,
+
"attached_to": 6,
+
"class_list": [
+
"usb",
+
"hub"
+
],
+
"bus_type": {
+
"hex": "0086",
+
"name": "USB",
+
"value": 134
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "010a",
+
"name": "Hub",
+
"value": 266
+
},
+
"vendor": {
+
"hex": "1d6b",
+
"name": "Linux 6.14.10 uhci_hcd",
+
"value": 7531
+
},
+
"device": {
+
"hex": "0001",
+
"name": "UHCI Host Controller",
+
"value": 1
+
},
+
"revision": {
+
"hex": "0000",
+
"name": "6.14",
+
"value": 0
+
},
+
"serial": "0000:00:01.2",
+
"model": "Linux 6.14.10 uhci_hcd UHCI Host Controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.2/usb1/1-0:1.0",
+
"sysfs_bus_id": "1-0:1.0",
+
"resources": [
+
{
+
"type": "baud",
+
"speed": 12000000,
+
"bits": 0,
+
"stop_bits": 0,
+
"parity": 0,
+
"handshake": 0
+
}
+
],
+
"detail": {
+
"device_class": {
+
"hex": "0009",
+
"name": "hub",
+
"value": 9
+
},
+
"device_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_protocol": 0,
+
"interface_class": {
+
"hex": "0009",
+
"name": "hub",
+
"value": 9
+
},
+
"interface_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"interface_protocol": 0,
+
"interface_number": 0,
+
"interface_alternate_setting": 0
+
},
+
"hotplug": "usb",
+
"driver": "hub",
+
"driver_module": "usbcore",
+
"drivers": [
+
"hub"
+
],
+
"driver_modules": [
+
"usbcore"
+
],
+
"module_alias": "usb:v1D6Bp0001d0614dc09dsc00dp00ic09isc00ip00in00"
+
}
+
],
+
"memory": [
+
{
+
"index": 5,
+
"attached_to": 0,
+
"class_list": [
+
"memory"
+
],
+
"base_class": {
+
"hex": "0101",
+
"name": "Internally Used Class",
+
"value": 257
+
},
+
"sub_class": {
+
"hex": "0002",
+
"name": "Main Memory",
+
"value": 2
+
},
+
"model": "Main Memory",
+
"resources": [
+
{
+
"type": "mem",
+
"base": 0,
+
"range": 1004576768,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "unknown"
+
},
+
{
+
"type": "phys_mem",
+
"range": 1006632960
+
}
+
]
+
}
+
],
+
"monitor": [
+
{
+
"index": 16,
+
"attached_to": 13,
+
"class_list": [
+
"monitor"
+
],
+
"base_class": {
+
"hex": "0100",
+
"name": "Monitor",
+
"value": 256
+
},
+
"sub_class": {
+
"hex": "0002",
+
"name": "LCD Monitor",
+
"value": 2
+
},
+
"vendor": {
+
"hex": "4914",
+
"value": 18708
+
},
+
"device": {
+
"hex": "1234",
+
"name": "QEMU Monitor",
+
"value": 4660
+
},
+
"serial": "0",
+
"model": "QEMU Monitor",
+
"resources": [
+
{
+
"type": "monitor",
+
"width": 1024,
+
"height": 768,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 1280,
+
"height": 800,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 1600,
+
"height": 1200,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 1920,
+
"height": 1080,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 2048,
+
"height": 1152,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 640,
+
"height": 480,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 800,
+
"height": 600,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "size",
+
"unit": "mm",
+
"value_1": 325,
+
"value_2": 203
+
}
+
],
+
"detail": {
+
"manufacture_year": 2014,
+
"manufacture_week": 42,
+
"vertical_sync": {
+
"min": 50,
+
"max": 125
+
},
+
"horizontal_sync": {
+
"min": 30,
+
"max": 160
+
},
+
"horizontal_sync_timings": {
+
"disp": 1280,
+
"sync_start": 1600,
+
"sync_end": 1638,
+
"total": 1728
+
},
+
"vertical_sync_timings": {
+
"disp": 800,
+
"sync_start": 804,
+
"sync_end": 808,
+
"total": 828
+
},
+
"clock": 107300,
+
"width": 1280,
+
"height": 800,
+
"width_millimetres": 325,
+
"height_millimetres": 203,
+
"horizontal_flag": 45,
+
"vertical_flag": 45,
+
"vendor": "",
+
"name": "QEMU Monitor"
+
},
+
"driver_info": {
+
"type": "display",
+
"width": 2048,
+
"height": 1152,
+
"vertical_sync": {
+
"min": 50,
+
"max": 125
+
},
+
"horizontal_sync": {
+
"min": 30,
+
"max": 160
+
},
+
"bandwidth": 0,
+
"horizontal_sync_timings": {
+
"disp": 1280,
+
"sync_start": 1600,
+
"sync_end": 1638,
+
"total": 1728
+
},
+
"vertical_sync_timings": {
+
"disp": 800,
+
"sync_start": 804,
+
"sync_end": 808,
+
"total": 828
+
},
+
"horizontal_flag": 45,
+
"vertical_flag": 45
+
}
+
}
+
],
+
"mouse": [
+
{
+
"index": 20,
+
"attached_to": 19,
+
"class_list": [
+
"mouse",
+
"usb"
+
],
+
"bus_type": {
+
"hex": "0086",
+
"name": "USB",
+
"value": 134
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0105",
+
"name": "Mouse",
+
"value": 261
+
},
+
"sub_class": {
+
"hex": "0003",
+
"name": "USB Mouse",
+
"value": 3
+
},
+
"vendor": {
+
"hex": "0627",
+
"name": "QEMU",
+
"value": 1575
+
},
+
"device": {
+
"hex": "0001",
+
"name": "QEMU USB Tablet",
+
"value": 1
+
},
+
"serial": "28754-0000:00:01.2-1",
+
"compat_vendor": "Unknown",
+
"compat_device": "Generic USB Mouse",
+
"model": "QEMU USB Tablet",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.2/usb1/1-1/1-1:1.0",
+
"sysfs_bus_id": "1-1:1.0",
+
"unix_device_name": "/dev/input/mice",
+
"unix_device_number": {
+
"type": 99,
+
"major": 13,
+
"minor": 63,
+
"range": 1
+
},
+
"unix_device_names": [
+
"/dev/input/mice"
+
],
+
"unix_device_name2": "/dev/input/mouse0",
+
"unix_device_number2": {
+
"type": 99,
+
"major": 13,
+
"minor": 32,
+
"range": 1
+
},
+
"resources": [
+
{
+
"type": "baud",
+
"speed": 12000000,
+
"bits": 0,
+
"stop_bits": 0,
+
"parity": 0,
+
"handshake": 0
+
}
+
],
+
"detail": {
+
"device_class": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_protocol": 0,
+
"interface_class": {
+
"hex": "0003",
+
"name": "hid",
+
"value": 3
+
},
+
"interface_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"interface_protocol": 0,
+
"interface_number": 0,
+
"interface_alternate_setting": 0
+
},
+
"hotplug": "usb",
+
"driver": "usbhid",
+
"driver_module": "usbhid",
+
"drivers": [
+
"usbhid"
+
],
+
"driver_modules": [
+
"usbhid"
+
],
+
"driver_info": {
+
"type": "mouse",
+
"db_entry_0": [
+
"explorerps/2",
+
"exps2"
+
],
+
"xf86": "explorerps/2",
+
"gpm": "exps2",
+
"buttons": -1,
+
"wheels": -1
+
},
+
"module_alias": "usb:v0627p0001d0000dc00dsc00dp00ic03isc00ip00in00"
+
}
+
],
+
"network_controller": [
+
{
+
"index": 15,
+
"attached_to": 11,
+
"class_list": [
+
"network_controller"
+
],
+
"bus_type": {
+
"hex": "008f",
+
"name": "Virtio",
+
"value": 143
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0002",
+
"name": "Network controller",
+
"value": 2
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Ethernet controller",
+
"value": 0
+
},
+
"vendor": "Virtio",
+
"device": "Ethernet Card 0",
+
"model": "Virtio Ethernet Card 0",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.0/virtio0",
+
"sysfs_bus_id": "virtio0",
+
"unix_device_name": "ens3",
+
"unix_device_names": [
+
"ens3"
+
],
+
"resources": [
+
{
+
"type": "hwaddr",
+
"address": 48
+
},
+
{
+
"type": "phwaddr",
+
"address": 48
+
}
+
],
+
"driver": "virtio_net",
+
"driver_module": "virtio_net",
+
"drivers": [
+
"virtio_net"
+
],
+
"driver_modules": [
+
"virtio_net"
+
],
+
"module_alias": "virtio:d00000001v0000108E"
+
}
+
],
+
"network_interface": [
+
{
+
"index": 21,
+
"attached_to": 0,
+
"class_list": [
+
"network_interface"
+
],
+
"base_class": {
+
"hex": "0107",
+
"name": "Network Interface",
+
"value": 263
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Loopback",
+
"value": 0
+
},
+
"model": "Loopback network interface",
+
"sysfs_id": "/class/net/lo",
+
"unix_device_name": "lo",
+
"unix_device_names": [
+
"lo"
+
]
+
},
+
{
+
"index": 22,
+
"attached_to": 15,
+
"class_list": [
+
"network_interface"
+
],
+
"base_class": {
+
"hex": "0107",
+
"name": "Network Interface",
+
"value": 263
+
},
+
"sub_class": {
+
"hex": "0001",
+
"name": "Ethernet",
+
"value": 1
+
},
+
"model": "Ethernet network interface",
+
"sysfs_id": "/class/net/ens3",
+
"sysfs_device_link": "/devices/pci0000:00/0000:00:03.0/virtio0",
+
"unix_device_name": "ens3",
+
"unix_device_names": [
+
"ens3"
+
],
+
"resources": [
+
{
+
"type": "hwaddr",
+
"address": 48
+
},
+
{
+
"type": "phwaddr",
+
"address": 48
+
}
+
],
+
"driver": "virtio_net",
+
"driver_module": "virtio_net",
+
"drivers": [
+
"virtio_net"
+
],
+
"driver_modules": [
+
"virtio_net"
+
]
+
}
+
],
+
"pci": [
+
{
+
"index": 11,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"unknown"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0002",
+
"name": "Network controller",
+
"value": 2
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Ethernet controller",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"sub_vendor": {
+
"hex": "108e",
+
"value": 4238
+
},
+
"device": {
+
"hex": "1000",
+
"value": 4096
+
},
+
"sub_device": {
+
"hex": "0001",
+
"value": 1
+
},
+
"model": "Ethernet controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.0",
+
"sysfs_bus_id": "0000:00:03.0",
+
"resources": [
+
{
+
"type": "io",
+
"base": 49216,
+
"range": 32,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "irq",
+
"base": 10,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 2164330496,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
},
+
{
+
"type": "mem",
+
"base": 34359738368,
+
"range": 16384,
+
"enabled": true,
+
"access": "read_only",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1031,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 10,
+
"prog_if": 0
+
},
+
"driver": "virtio-pci",
+
"driver_module": "virtio_pci",
+
"drivers": [
+
"virtio-pci"
+
],
+
"driver_modules": [
+
"virtio_pci"
+
],
+
"module_alias": "pci:v00001AF4d00001000sv0000108Esd00000001bc02sc00i00"
+
}
+
],
+
"storage_controller": [
+
{
+
"index": 8,
+
"attached_to": 0,
+
"class_list": [
+
"storage_controller",
+
"pci"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0001",
+
"name": "Mass storage controller",
+
"value": 1
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "SCSI storage controller",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"sub_vendor": {
+
"hex": "108e",
+
"value": 4238
+
},
+
"device": {
+
"hex": "1004",
+
"value": 4100
+
},
+
"sub_device": {
+
"hex": "0008",
+
"value": 8
+
},
+
"model": "SCSI storage controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.0",
+
"sysfs_bus_id": "0000:00:04.0",
+
"resources": [
+
{
+
"type": "io",
+
"base": 49152,
+
"range": 64,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "irq",
+
"base": 11,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 2164326400,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
},
+
{
+
"type": "mem",
+
"base": 34359754752,
+
"range": 16384,
+
"enabled": true,
+
"access": "read_only",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1031,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 11,
+
"prog_if": 0
+
},
+
"driver": "virtio-pci",
+
"driver_module": "virtio_pci",
+
"drivers": [
+
"virtio-pci"
+
],
+
"driver_modules": [
+
"virtio_pci"
+
],
+
"module_alias": "pci:v00001AF4d00001004sv0000108Esd00000008bc01sc00i00"
+
},
+
{
+
"index": 12,
+
"attached_to": 0,
+
"class_list": [
+
"storage_controller",
+
"pci"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 1
+
},
+
"base_class": {
+
"hex": "0001",
+
"name": "Mass storage controller",
+
"value": 1
+
},
+
"sub_class": {
+
"hex": "0001",
+
"name": "IDE interface",
+
"value": 1
+
},
+
"pci_interface": {
+
"hex": "0080",
+
"value": 128
+
},
+
"vendor": {
+
"hex": "8086",
+
"name": "Intel Corporation",
+
"value": 32902
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "7010",
+
"value": 28688
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"model": "Intel IDE interface",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.1",
+
"sysfs_bus_id": "0000:00:01.1",
+
"resources": [
+
{
+
"type": "io",
+
"base": 1014,
+
"range": 1,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "io",
+
"base": 368,
+
"range": 8,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "io",
+
"base": 49280,
+
"range": 16,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "io",
+
"base": 496,
+
"range": 8,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "io",
+
"base": 886,
+
"range": 1,
+
"enabled": true,
+
"access": "read_write"
+
}
+
],
+
"detail": {
+
"function": 1,
+
"command": 7,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 0,
+
"prog_if": 128
+
},
+
"driver": "ata_piix",
+
"driver_module": "ata_piix",
+
"drivers": [
+
"ata_piix"
+
],
+
"driver_modules": [
+
"ata_piix"
+
],
+
"module_alias": "pci:v00008086d00007010sv00001AF4sd00001100bc01sc01i80"
+
}
+
],
+
"system": {
+
"form_factor": "desktop"
+
},
+
"unknown": [
+
{
+
"index": 14,
+
"attached_to": 8,
+
"class_list": [
+
"unknown"
+
],
+
"base_class": {
+
"hex": "0000",
+
"name": "Unclassified device",
+
"value": 0
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Unclassified device",
+
"value": 0
+
},
+
"vendor": "Virtio",
+
"device": "",
+
"model": "Virtio Unclassified device",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.0/virtio1",
+
"sysfs_bus_id": "virtio1",
+
"driver": "virtio_scsi",
+
"driver_module": "virtio_scsi",
+
"drivers": [
+
"virtio_scsi"
+
],
+
"driver_modules": [
+
"virtio_scsi"
+
],
+
"module_alias": "virtio:d00000008v0000108E"
+
},
+
{
+
"index": 17,
+
"attached_to": 0,
+
"class_list": [
+
"unknown"
+
],
+
"base_class": {
+
"hex": "0007",
+
"name": "Communication controller",
+
"value": 7
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Serial controller",
+
"value": 0
+
},
+
"pci_interface": {
+
"hex": "0002",
+
"name": "16550",
+
"value": 2
+
},
+
"device": {
+
"hex": "0000",
+
"name": "16550A",
+
"value": 0
+
},
+
"model": "16550A",
+
"unix_device_name": "/dev/ttyS0",
+
"unix_device_names": [
+
"/dev/ttyS0"
+
],
+
"resources": [
+
{
+
"type": "io",
+
"base": 1016,
+
"range": 0,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "irq",
+
"base": 4,
+
"triggered": 0,
+
"enabled": true
+
}
+
]
+
}
+
],
+
"usb_controller": [
+
{
+
"index": 6,
+
"attached_to": 0,
+
"class_list": [
+
"usb_controller",
+
"pci"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 1
+
},
+
"base_class": {
+
"hex": "000c",
+
"name": "Serial bus controller",
+
"value": 12
+
},
+
"sub_class": {
+
"hex": "0003",
+
"name": "USB Controller",
+
"value": 3
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "UHCI",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "8086",
+
"name": "Intel Corporation",
+
"value": 32902
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "7020",
+
"value": 28704
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0001",
+
"value": 1
+
},
+
"model": "Intel USB Controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.2",
+
"sysfs_bus_id": "0000:00:01.2",
+
"resources": [
+
{
+
"type": "io",
+
"base": 49248,
+
"range": 32,
+
"enabled": true,
+
"access": "read_write"
+
},
+
{
+
"type": "irq",
+
"base": 11,
+
"triggered": 0,
+
"enabled": true
+
}
+
],
+
"detail": {
+
"function": 2,
+
"command": 7,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 11,
+
"prog_if": 0
+
},
+
"driver": "uhci_hcd",
+
"driver_module": "uhci_hcd",
+
"drivers": [
+
"uhci_hcd"
+
],
+
"driver_modules": [
+
"uhci_hcd"
+
],
+
"driver_info": {
+
"type": "module",
+
"db_entry_0": [
+
"uhci-hcd"
+
],
+
"active": true,
+
"modprobe": true,
+
"names": [
+
"uhci-hcd"
+
],
+
"module_args": [
+
""
+
],
+
"conf": ""
+
},
+
"module_alias": "pci:v00008086d00007020sv00001AF4sd00001100bc0Csc03i00"
+
}
+
]
+
},
+
"smbios": {
+
"bios": {
+
"handle": 0,
+
"vendor": "EFI Development Kit II / OVMF",
+
"version": "1.6.4",
+
"date": "02/27/2023",
+
"features": null,
+
"start_address": "0xe8000",
+
"rom_size": 65536
+
},
+
"chassis": [
+
{
+
"handle": 768,
+
"manufacturer": "QEMU",
+
"version": "pc-i440fx-7.2",
+
"chassis_type": {
+
"hex": "0001",
+
"name": "Other",
+
"value": 1
+
},
+
"lock_present": false,
+
"bootup_state": {
+
"hex": "0003",
+
"name": "Safe",
+
"value": 3
+
},
+
"power_state": {
+
"hex": "0003",
+
"name": "Safe",
+
"value": 3
+
},
+
"thermal_state": {
+
"hex": "0003",
+
"name": "Safe",
+
"value": 3
+
},
+
"security_state": {
+
"hex": "0002",
+
"name": "Unknown",
+
"value": 2
+
},
+
"oem": "0x0"
+
}
+
],
+
"memory_array": [
+
{
+
"handle": 4096,
+
"location": {
+
"hex": "0001",
+
"name": "Other",
+
"value": 1
+
},
+
"usage": {
+
"hex": "0003",
+
"name": "System memory",
+
"value": 3
+
},
+
"ecc": {
+
"hex": "0006",
+
"name": "Multi-bit",
+
"value": 6
+
},
+
"max_size": "0x100000",
+
"error_handle": 65534,
+
"slots": 1
+
}
+
],
+
"memory_array_mapped_address": [
+
{
+
"handle": 4864,
+
"array_handle": 4096,
+
"start_address": "0x0",
+
"end_address": "0x40000000",
+
"part_width": 1
+
}
+
],
+
"memory_device": [
+
{
+
"handle": 4352,
+
"location": "DIMM 0",
+
"bank_location": "",
+
"manufacturer": "QEMU",
+
"part_number": "",
+
"array_handle": 4096,
+
"error_handle": 65534,
+
"width": 0,
+
"ecc_bits": 0,
+
"size": 1048576,
+
"form_factor": {
+
"hex": "0009",
+
"name": "DIMM",
+
"value": 9
+
},
+
"set": 0,
+
"memory_type": {
+
"hex": "0007",
+
"name": "RAM",
+
"value": 7
+
},
+
"memory_type_details": [
+
"Other"
+
],
+
"speed": 0
+
}
+
],
+
"processor": [
+
{
+
"handle": 1024,
+
"socket": "CPU 0",
+
"socket_type": {
+
"hex": "0001",
+
"name": "Other",
+
"value": 1
+
},
+
"socket_populated": true,
+
"manufacturer": "QEMU",
+
"version": "pc-i440fx-7.2",
+
"part": "",
+
"processor_type": {
+
"hex": "0003",
+
"name": "CPU",
+
"value": 3
+
},
+
"processor_family": {
+
"hex": "0001",
+
"name": "Other",
+
"value": 1
+
},
+
"processor_status": {
+
"hex": "0001",
+
"name": "Enabled",
+
"value": 1
+
},
+
"clock_ext": 0,
+
"clock_max": 2000,
+
"cache_handle_l1": 0,
+
"cache_handle_l2": 0,
+
"cache_handle_l3": 0
+
}
+
],
+
"system": {
+
"handle": 256,
+
"manufacturer": "QEMU",
+
"product": "Standard PC (i440FX + PIIX, 1996)",
+
"version": "pc-i440fx-7.2",
+
"wake_up": {
+
"hex": "0006",
+
"name": "Power Switch",
+
"value": 6
+
}
+
}
+
}
+
}
+24
machines/prattle/home/default.nix
···
+
{ inputs, ... }:
+
{
+
imports = [
+
(inputs.import-tree ../../../modules/home)
+
];
+
+
home = {
+
username = "kierank";
+
homeDirectory = "/home/kierank";
+
};
+
+
atelier = {
+
ssh = {
+
enable = true;
+
zmx.enable = true;
+
};
+
};
+
+
programs.home-manager.enable = true;
+
+
systemd.user.startServices = "sd-switch";
+
+
home.stateVersion = "23.05";
+
}
+16
machines/prattle/home-manager.nix
···
+
{ inputs, outputs, ... }:
+
{
+
imports = [
+
inputs.home-manager.nixosModules.home-manager
+
];
+
+
home-manager = {
+
useGlobalPkgs = true;
+
extraSpecialArgs = {
+
inherit inputs outputs;
+
};
+
users = {
+
kierank = import ./home;
+
};
+
};
+
}
+3 -3
machines/tacyon/default.nix
···
{
pkgs,
inputs,
-
system,
...
}:
{
imports = [
(inputs.import-tree ../../modules/home)
+
../../modules/home/system/nixpkgs.nix.disabled
];
nixpkgs.enable = true;
···
homeDirectory = "/home/pi";
packages = with pkgs; [
-
inputs.nixvim.packages.${system}.default
+
inputs.nixvim.packages.${pkgs.stdenv.hostPlatform.system}.default
# languages
go
···
go-tools
# my apps
-
inputs.ctfd-alerts.packages.${system}.default
+
inputs.ctfd-alerts.packages.${pkgs.stdenv.hostPlatform.system}.default
# Fonts
fira
+372
machines/terebithia/default.nix
···
+
{
+
inputs,
+
lib,
+
config,
+
pkgs,
+
...
+
}:
+
{
+
imports = [
+
./disk-config.nix
+
./home-manager.nix
+
+
(inputs.import-tree ../../modules/nixos)
+
inputs.tangled.nixosModules.knot
+
inputs.tangled.nixosModules.spindle
+
];
+
+
nixpkgs = {
+
hostPlatform = "aarch64-linux";
+
config = {
+
allowUnfree = true;
+
};
+
};
+
+
nix =
+
let
+
flakeInputs = lib.filterAttrs (_: lib.isType "flake") inputs;
+
in
+
{
+
settings = {
+
experimental-features = "nix-command flakes";
+
flake-registry = "";
+
nix-path = config.nix.nixPath;
+
trusted-users = [
+
"kierank"
+
];
+
};
+
channel.enable = false;
+
optimise.automatic = true;
+
registry = lib.mapAttrs (_: flake: { inherit flake; }) flakeInputs;
+
nixPath = lib.mapAttrsToList (n: _: "${n}=flake:${n}") flakeInputs;
+
};
+
+
time.timeZone = "America/New_York";
+
+
environment.systemPackages = with pkgs; [
+
# core
+
coreutils
+
screen
+
bc
+
jq
+
psmisc
+
# cli_utils
+
direnv
+
zsh
+
gum
+
vim
+
# networking
+
xh
+
curl
+
wget
+
dogdns
+
inetutils
+
mosh
+
# nix_tools
+
inputs.nixvim.packages.aarch64-linux.default
+
nixd
+
nil
+
nixfmt-rfc-style
+
inputs.agenix.packages.aarch64-linux.default
+
# security
+
openssl
+
gpgme
+
gnupg
+
# dev_langs
+
nodejs_22
+
unstable.bun
+
python3
+
go
+
gopls
+
gotools
+
go-tools
+
gcc
+
# misc
+
neofetch
+
git
+
];
+
+
programs.nh = {
+
enable = true;
+
clean.enable = true;
+
clean.extraArgs = "--keep-since 4d --keep 3";
+
flake = "/home/kierank/dots";
+
};
+
+
age.identityPaths = [
+
"/home/kierank/.ssh/id_rsa"
+
"/etc/ssh/id_rsa"
+
];
+
age.secrets = {
+
wakatime = {
+
file = ../../secrets/wakatime.age;
+
path = "/home/kierank/.wakatime.cfg";
+
owner = "kierank";
+
};
+
cachet = {
+
file = ../../secrets/cachet.age;
+
owner = "cachet";
+
};
+
hn-alerts = {
+
file = ../../secrets/hn-alerts.age;
+
owner = "hn-alerts";
+
};
+
emojibot = {
+
file = ../../secrets/emojibot.age;
+
owner = "emojibot";
+
};
+
cloudflare = {
+
file = ../../secrets/cloudflare.age;
+
owner = "caddy";
+
};
+
github-knot-sync = {
+
file = ../../secrets/github-knot-sync.age;
+
owner = "git";
+
};
+
battleship-arena = {
+
file = ../../secrets/battleship-arena.age;
+
owner = "battleship-arena";
+
};
+
frp-auth-token = {
+
file = ../../secrets/frp-auth-token.age;
+
};
+
};
+
+
environment.sessionVariables = {
+
XDG_CACHE_HOME = "$HOME/.cache";
+
XDG_CONFIG_HOME = "$HOME/.config";
+
XDG_DATA_HOME = "$HOME/.local/share";
+
XDG_STATE_HOME = "$HOME/.local/state";
+
EDITOR = "nvim";
+
SYSTEMD_EDITOR = "nvim";
+
VISUAL = "nvim";
+
};
+
+
atelier = {
+
authentication.enable = true;
+
};
+
+
networking = {
+
hostName = "terebithia";
+
networkmanager.enable = true;
+
};
+
+
programs.zsh.enable = true;
+
programs.direnv.enable = true;
+
+
users.users = {
+
kierank = {
+
initialPassword = "changeme";
+
isNormalUser = true;
+
shell = pkgs.zsh;
+
openssh.authorizedKeys.keys = [
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzEEjvbL/ttqmYoDjxYQmDIq36BabROJoXgQKeh9liBxApwp+2PmgxROzTg42UrRc9pyrkq5kVfxG5hvkqCinhL1fMiowCSEs2L2/Cwi40g5ZU+QwdcwI8a4969kkI46PyB19RHkxg54OUORiIiso/WHGmqQsP+5wbV0+4riSnxwn/JXN4pmnE//stnyAyoiEZkPvBtwJjKb3Ni9n3eNLNs6gnaXrCtaygEZdebikr9kS2g9mM696HvIFgM6cdR/wZ7DcLbG3IdTXuHN7PC3xxL+Y4ek5iMreQIPmuvs4qslbthPGYoYbYLUQiRa9XO5s/ksIj5Z14f7anHE6cuTQVpvNWdGDOigyIVS5qU+4ZF7j+rifzOXVL48gmcAvw/uV68m5Wl/p0qsC/d8vI3GYwEsWG/EzpAlc07l8BU2LxWgN+d7uwBFaJV9VtmUDs5dcslsh8IbzmtC9gq3OLGjklxTfIl6qPiL8U33oc/UwqzvZUrI2BlbagvIZYy6rP+q0= kierank@mockingjay"
+
];
+
extraGroups = [
+
"wheel"
+
"networkmanager"
+
"services"
+
];
+
};
+
root.openssh.authorizedKeys.keys = [
+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzEEjvbL/ttqmYoDjxYQmDIq36BabROJoXgQKeh9liBxApwp+2PmgxROzTg42UrRc9pyrkq5kVfxG5hvkqCinhL1fMiowCSEs2L2/Cwi40g5ZU+QwdcwI8a4969kkI46PyB19RHkxg54OUORiIiso/WHGmqQsP+5wbV0+4riSnxwn/JXN4pmnE//stnyAyoiEZkPvBtwJjKb3Ni9n3eNLNs6gnaXrCtaygEZdebikr9kS2g9mM696HvIFgM6cdR/wZ7DcLbG3IdTXuHN7PC3xxL+Y4ek5iMreQIPmuvs4qslbthPGYoYbYLUQiRa9XO5s/ksIj5Z14f7anHE6cuTQVpvNWdGDOigyIVS5qU+4ZF7j+rifzOXVL48gmcAvw/uV68m5Wl/p0qsC/d8vI3GYwEsWG/EzpAlc07l8BU2LxWgN+d7uwBFaJV9VtmUDs5dcslsh8IbzmtC9gq3OLGjklxTfIl6qPiL8U33oc/UwqzvZUrI2BlbagvIZYy6rP+q0= kierank@mockingjay"
+
];
+
};
+
+
# Allow passwordless sudo for wheel group (needed for deploy-rs)
+
security.sudo.wheelNeedsPassword = false;
+
+
services.openssh = {
+
enable = true;
+
openFirewall = true;
+
settings = {
+
PermitRootLogin = "no";
+
PasswordAuthentication = false;
+
};
+
};
+
+
networking.firewall = {
+
enable = true;
+
allowedTCPPorts = [
+
22
+
80
+
443
+
];
+
logRefusedConnections = false;
+
rejectPackets = true;
+
};
+
+
services.tailscale = {
+
enable = true;
+
useRoutingFeatures = "client";
+
};
+
+
services.caddy = {
+
enable = true;
+
package = pkgs.caddy.withPlugins {
+
plugins = [ "github.com/caddy-dns/cloudflare@v0.2.2" ];
+
hash = "sha256-ea8PC/+SlPRdEVVF/I3c1CBprlVp1nrumKM5cMwJJ3U=";
+
};
+
email = "me@dunkirk.sh";
+
globalConfig = ''
+
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
'';
+
virtualHosts."knot.dunkirk.sh" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
reverse_proxy localhost:5555 {
+
header_up X-Forwarded-Proto {scheme}
+
header_up X-Forwarded-For {remote}
+
}
+
'';
+
};
+
virtualHosts."spindle.dunkirk.sh" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
reverse_proxy localhost:6555 {
+
header_up X-Forwarded-Proto {scheme}
+
header_up X-Forwarded-For {remote}
+
}
+
'';
+
};
+
virtualHosts."emojibot.dunkirk.sh" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
reverse_proxy localhost:3002 {
+
header_up X-Forwarded-Proto {scheme}
+
header_up X-Forwarded-For {remote}
+
}
+
'';
+
};
+
virtualHosts."battleship.dunkirk.sh" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
reverse_proxy localhost:8081 {
+
header_up X-Forwarded-Proto {scheme}
+
header_up X-Forwarded-For {remote}
+
}
+
'';
+
};
+
extraConfig = ''
+
# Default response for unhandled domains
+
:80 {
+
respond "404 - Looks like this bridge doesn't have an end" 404
+
}
+
:443 {
+
respond "404 - Looks like this bridge doesn't have an end" 404
+
}
+
'';
+
};
+
+
systemd.services.caddy.serviceConfig = {
+
EnvironmentFile = config.age.secrets.cloudflare.path;
+
};
+
+
atelier.services.cachet = {
+
enable = true;
+
domain = "cachet.dunkirk.sh";
+
secretsFile = config.age.secrets.cachet.path;
+
};
+
+
atelier.services.hn-alerts = {
+
enable = true;
+
domain = "hn.dunkirk.sh";
+
secretsFile = config.age.secrets.hn-alerts.path;
+
};
+
+
atelier.services.emojibot = {
+
enable = true;
+
domain = "emojibot.dunkirk.sh";
+
secretsFile = config.age.secrets.emojibot.path;
+
};
+
+
atelier.services.battleship-arena = {
+
enable = true;
+
domain = "battleship.dunkirk.sh";
+
sshPort = 2222;
+
package = inputs.battleship-arena.packages.aarch64-linux.default;
+
secretsFile = config.age.secrets.battleship-arena.path;
+
};
+
+
services.tangled.knot = {
+
enable = true;
+
package = inputs.tangled.packages.aarch64-linux.knot;
+
appviewEndpoint = "https://tangled.org";
+
server = {
+
owner = "did:plc:krxbvxvis5skq7jj6eot23ul";
+
hostname = "knot.dunkirk.sh";
+
listenAddr = "127.0.0.1:5555";
+
};
+
};
+
+
services.tangled.spindle = {
+
enable = true;
+
package = inputs.tangled.packages.aarch64-linux.spindle;
+
server = {
+
owner = "did:plc:krxbvxvis5skq7jj6eot23ul";
+
hostname = "spindle.dunkirk.sh";
+
listenAddr = "127.0.0.1:6555";
+
};
+
};
+
+
atelier.services.knot-sync = {
+
enable = true;
+
secretsFile = config.age.secrets.github-knot-sync.path;
+
};
+
+
atelier.services.frps = {
+
enable = true;
+
domain = "bore.dunkirk.sh";
+
authTokenFile = config.age.secrets.frp-auth-token.path;
+
};
+
+
services.n8n = {
+
enable = true;
+
environment = {
+
N8N_HOST = "n8n.dunkirk.sh";
+
N8N_PROTOCOL = "https";
+
WEBHOOK_URL = "https://n8n.dunkirk.sh";
+
};
+
};
+
+
services.caddy.virtualHosts."n8n.dunkirk.sh" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
reverse_proxy localhost:5678 {
+
header_up X-Forwarded-Proto {scheme}
+
header_up X-Forwarded-For {remote}
+
}
+
'';
+
};
+
+
boot.loader.systemd-boot.enable = true;
+
boot.loader.efi.canTouchEfiVariables = true;
+
boot.kernelParams = [ "console=ttyS0" ];
+
+
system.stateVersion = "23.05";
+
}
+32
machines/terebithia/disk-config.nix
···
+
{
+
disko.devices = {
+
disk = {
+
main = {
+
type = "disk";
+
device = "/dev/sda";
+
content = {
+
type = "gpt";
+
partitions = {
+
boot = {
+
size = "512M";
+
type = "EF00";
+
content = {
+
type = "filesystem";
+
format = "vfat";
+
mountpoint = "/boot";
+
};
+
};
+
root = {
+
size = "100%";
+
content = {
+
type = "filesystem";
+
format = "ext4";
+
mountpoint = "/";
+
};
+
};
+
};
+
};
+
};
+
};
+
};
+
}
+3449
machines/terebithia/facter.json
···
+
{
+
"version": 1,
+
"system": "aarch64-linux",
+
"virtualisation": "kvm",
+
"hardware": {
+
"bridge": [
+
{
+
"index": 7,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.1",
+
"sysfs_bus_id": "0000:00:03.1",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318775296,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 1,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 2,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 8,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.4",
+
"sysfs_bus_id": "0000:00:04.4",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318820352,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 4,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 13,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 9,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.7",
+
"sysfs_bus_id": "0000:00:05.7",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318865408,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 7,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 24,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 10,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.2",
+
"sysfs_bus_id": "0000:00:04.2",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318812160,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 2,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 11,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 12,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.5",
+
"sysfs_bus_id": "0000:00:05.5",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318857216,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 5,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 22,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 13,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.0",
+
"sysfs_bus_id": "0000:00:04.0",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318803968,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 9,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 14,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.6",
+
"sysfs_bus_id": "0000:00:03.6",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318795776,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 6,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 7,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 15,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.3",
+
"sysfs_bus_id": "0000:00:05.3",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318849024,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 3,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 20,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 16,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.4",
+
"sysfs_bus_id": "0000:00:03.4",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318787584,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 4,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 5,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 17,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.1",
+
"sysfs_bus_id": "0000:00:05.1",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318840832,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 1,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 18,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 18,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.7",
+
"sysfs_bus_id": "0000:00:04.7",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318832640,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 7,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 16,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 19,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.2",
+
"sysfs_bus_id": "0000:00:03.2",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318779392,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 2,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 3,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 20,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Host bridge",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "0008",
+
"value": 8
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"model": "Host bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:00.0",
+
"sysfs_bus_id": "0000:00:00.0",
+
"detail": {
+
"function": 0,
+
"command": 0,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 0,
+
"prog_if": 0
+
},
+
"module_alias": "pci:v00001B36d00000008sv00001AF4sd00001100bc06sc00i00"
+
},
+
{
+
"index": 22,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.5",
+
"sysfs_bus_id": "0000:00:04.5",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318824448,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 5,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 14,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 23,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.0",
+
"sysfs_bus_id": "0000:00:03.0",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318771200,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 1,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 24,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.3",
+
"sysfs_bus_id": "0000:00:04.3",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318816256,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 3,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 12,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 26,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.6",
+
"sysfs_bus_id": "0000:00:05.6",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318861312,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 6,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 23,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 27,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.1",
+
"sysfs_bus_id": "0000:00:04.1",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318808064,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 1,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 10,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 28,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.7",
+
"sysfs_bus_id": "0000:00:03.7",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318799872,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 7,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 8,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 29,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.4",
+
"sysfs_bus_id": "0000:00:05.4",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318853120,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 4,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 21,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 30,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.5",
+
"sysfs_bus_id": "0000:00:03.5",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318791680,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 5,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 6,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 31,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.2",
+
"sysfs_bus_id": "0000:00:05.2",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318844928,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 2,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 19,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 33,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 3
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:03.3",
+
"sysfs_bus_id": "0000:00:03.3",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 48,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318783488,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 3,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 4,
+
"irq": 48,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 34,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 5
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.0",
+
"sysfs_bus_id": "0000:00:05.0",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318836736,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 17,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
},
+
{
+
"index": 35,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"bridge"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 4
+
},
+
"base_class": {
+
"hex": "0006",
+
"name": "Bridge",
+
"value": 6
+
},
+
"sub_class": {
+
"hex": "0004",
+
"name": "PCI bridge",
+
"value": 4
+
},
+
"pci_interface": {
+
"hex": "0000",
+
"name": "Normal decode",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"device": {
+
"hex": "000c",
+
"value": 12
+
},
+
"sub_device": {
+
"hex": "0000",
+
"value": 0
+
},
+
"model": "PCI bridge",
+
"sysfs_id": "/devices/pci0000:00/0000:00:04.6",
+
"sysfs_bus_id": "0000:00:04.6",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 45,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318828544,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 6,
+
"command": 1030,
+
"header_type": 1,
+
"secondary_bus": 15,
+
"irq": 45,
+
"prog_if": 0
+
},
+
"driver": "pcieport",
+
"driver_module": "pcieportdrv",
+
"drivers": [
+
"pcieport"
+
],
+
"driver_modules": [
+
"pcieportdrv"
+
],
+
"module_alias": "pci:v00001B36d0000000Csv00001B36sd00000000bc06sc04i00"
+
}
+
],
+
"cpu": [
+
{
+
"architecture": "aarch64",
+
"vendor_name": "ARM Limited",
+
"family": 3,
+
"model": 1,
+
"stepping": 0,
+
"features": [
+
"fp",
+
"asimd",
+
"evtstrm",
+
"aes",
+
"pmull",
+
"sha1",
+
"sha2",
+
"crc32",
+
"atomics",
+
"fphp",
+
"asimdhp",
+
"cpuid",
+
"asimdrdm",
+
"lrcpc",
+
"dcpop",
+
"asimddp"
+
],
+
"bogo": 50,
+
"physical_id": 0,
+
"fpu": false,
+
"fpu_exception": false,
+
"write_protect": false,
+
"address_sizes": {
+
"physical": "0x0",
+
"virtual": "0x0"
+
}
+
}
+
],
+
"disk": [
+
{
+
"index": 40,
+
"attached_to": 37,
+
"class_list": [
+
"disk",
+
"scsi",
+
"block_device"
+
],
+
"bus_type": {
+
"hex": "0084",
+
"name": "SCSI",
+
"value": 132
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0106",
+
"name": "Mass Storage Device",
+
"value": 262
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Disk",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "0000",
+
"name": "ORACLE",
+
"value": 0
+
},
+
"device": {
+
"hex": "0000",
+
"name": "BlockVolume",
+
"value": 0
+
},
+
"revision": {
+
"hex": "0000",
+
"name": "1.0",
+
"value": 0
+
},
+
"model": "ORACLE BlockVolume",
+
"sysfs_id": "/class/block/sda",
+
"sysfs_bus_id": "0:0:0:1",
+
"sysfs_device_link": "/devices/pci0000:00/0000:00:05.7/0000:18:00.0/virtio2/host0/target0:0:0/0:0:0:1",
+
"unix_device_name": "/dev/sda",
+
"unix_device_number": {
+
"type": 98,
+
"major": 8,
+
"minor": 0,
+
"range": 16
+
},
+
"unix_device_names": [
+
"/dev/disk/by-id/scsi-3603b6fa6f58b438fb2a97299de94a79f",
+
"/dev/disk/by-id/wwn-0x603b6fa6f58b438fb2a97299de94a79f",
+
"/dev/disk/by-path/pci-0000:18:00.0-scsi-0:0:0:1",
+
"/dev/sda"
+
],
+
"unix_device_name2": "/dev/sg0",
+
"unix_device_number2": {
+
"type": 99,
+
"major": 21,
+
"minor": 0,
+
"range": 1
+
},
+
"resources": [
+
{
+
"type": "disk_geo",
+
"cylinders": 19581,
+
"heads": 255,
+
"sectors": 63,
+
"size": "0x0",
+
"geo_type": "logical"
+
},
+
{
+
"type": "size",
+
"unit": "sectors",
+
"value_1": 314572800,
+
"value_2": 512
+
}
+
],
+
"driver": "virtio_scsi",
+
"driver_module": "virtio_scsi",
+
"drivers": [
+
"sd",
+
"virtio_scsi"
+
],
+
"driver_modules": [
+
"virtio_scsi"
+
]
+
}
+
],
+
"graphics_card": [
+
{
+
"index": 11,
+
"attached_to": 0,
+
"class_list": [
+
"graphics_card",
+
"pci"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 1
+
},
+
"base_class": {
+
"hex": "0003",
+
"name": "Display controller",
+
"value": 3
+
},
+
"sub_class": {
+
"hex": "0080",
+
"name": "Display controller",
+
"value": 128
+
},
+
"vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"sub_vendor": {
+
"hex": "108e",
+
"value": 4238
+
},
+
"device": {
+
"hex": "1050",
+
"value": 4176
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0001",
+
"value": 1
+
},
+
"model": "Display controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.0",
+
"sysfs_bus_id": "0000:00:01.0",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318767104,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
},
+
{
+
"type": "mem",
+
"base": 549806145536,
+
"range": 16384,
+
"enabled": true,
+
"access": "read_only",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1030,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "virtio-pci",
+
"driver_module": "virtio_pci",
+
"drivers": [
+
"virtio-pci"
+
],
+
"driver_modules": [
+
"virtio_pci"
+
],
+
"module_alias": "pci:v00001AF4d00001050sv0000108Esd00001100bc03sc80i00"
+
}
+
],
+
"hub": [
+
{
+
"index": 43,
+
"attached_to": 32,
+
"class_list": [
+
"usb",
+
"hub"
+
],
+
"bus_type": {
+
"hex": "0086",
+
"name": "USB",
+
"value": 134
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "010a",
+
"name": "Hub",
+
"value": 266
+
},
+
"vendor": {
+
"hex": "1d6b",
+
"name": "Linux 6.14.10 xhci-hcd",
+
"value": 7531
+
},
+
"device": {
+
"hex": "0002",
+
"name": "xHCI Host Controller",
+
"value": 2
+
},
+
"revision": {
+
"hex": "0000",
+
"name": "6.14",
+
"value": 0
+
},
+
"serial": "0000:00:02.0",
+
"model": "Linux 6.14.10 xhci-hcd xHCI Host Controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:02.0/usb1/1-0:1.0",
+
"sysfs_bus_id": "1-0:1.0",
+
"resources": [
+
{
+
"type": "baud",
+
"speed": 480000000,
+
"bits": 0,
+
"stop_bits": 0,
+
"parity": 0,
+
"handshake": 0
+
}
+
],
+
"detail": {
+
"device_class": {
+
"hex": "0009",
+
"name": "hub",
+
"value": 9
+
},
+
"device_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_protocol": 1,
+
"interface_class": {
+
"hex": "0009",
+
"name": "hub",
+
"value": 9
+
},
+
"interface_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"interface_protocol": 0,
+
"interface_number": 0,
+
"interface_alternate_setting": 0
+
},
+
"hotplug": "usb",
+
"driver": "hub",
+
"driver_module": "usbcore",
+
"drivers": [
+
"hub"
+
],
+
"driver_modules": [
+
"usbcore"
+
],
+
"module_alias": "usb:v1D6Bp0002d0614dc09dsc00dp01ic09isc00ip00in00"
+
},
+
{
+
"index": 45,
+
"attached_to": 32,
+
"class_list": [
+
"usb",
+
"hub"
+
],
+
"bus_type": {
+
"hex": "0086",
+
"name": "USB",
+
"value": 134
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "010a",
+
"name": "Hub",
+
"value": 266
+
},
+
"vendor": {
+
"hex": "1d6b",
+
"name": "Linux 6.14.10 xhci-hcd",
+
"value": 7531
+
},
+
"device": {
+
"hex": "0003",
+
"name": "xHCI Host Controller",
+
"value": 3
+
},
+
"revision": {
+
"hex": "0000",
+
"name": "6.14",
+
"value": 0
+
},
+
"serial": "0000:00:02.0",
+
"model": "Linux 6.14.10 xhci-hcd xHCI Host Controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:02.0/usb2/2-0:1.0",
+
"sysfs_bus_id": "2-0:1.0",
+
"detail": {
+
"device_class": {
+
"hex": "0009",
+
"name": "hub",
+
"value": 9
+
},
+
"device_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_protocol": 3,
+
"interface_class": {
+
"hex": "0009",
+
"name": "hub",
+
"value": 9
+
},
+
"interface_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"interface_protocol": 0,
+
"interface_number": 0,
+
"interface_alternate_setting": 0
+
},
+
"hotplug": "usb",
+
"driver": "hub",
+
"driver_module": "usbcore",
+
"drivers": [
+
"hub"
+
],
+
"driver_modules": [
+
"usbcore"
+
],
+
"module_alias": "usb:v1D6Bp0003d0614dc09dsc00dp03ic09isc00ip00in00"
+
}
+
],
+
"keyboard": [
+
{
+
"index": 41,
+
"attached_to": 43,
+
"class_list": [
+
"keyboard",
+
"usb"
+
],
+
"bus_type": {
+
"hex": "0086",
+
"name": "USB",
+
"value": 134
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0108",
+
"name": "Keyboard",
+
"value": 264
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Keyboard",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "0627",
+
"name": "QEMU",
+
"value": 1575
+
},
+
"device": {
+
"hex": "0001",
+
"name": "QEMU USB Keyboard",
+
"value": 1
+
},
+
"serial": "68284-0000:00:02.0-2",
+
"model": "QEMU USB Keyboard",
+
"sysfs_id": "/devices/pci0000:00/0000:00:02.0/usb1/1-2/1-2:1.0",
+
"sysfs_bus_id": "1-2:1.0",
+
"unix_device_name": "/dev/input/event2",
+
"unix_device_number": {
+
"type": 99,
+
"major": 13,
+
"minor": 66,
+
"range": 1
+
},
+
"unix_device_names": [
+
"/dev/input/by-id/usb-QEMU_QEMU_USB_Keyboard_68284-0000:00:02.0-2-event-kbd",
+
"/dev/input/by-path/pci-0000:00:02.0-usb-0:2:1.0-event-kbd",
+
"/dev/input/by-path/pci-0000:00:02.0-usbv2-0:2:1.0-event-kbd",
+
"/dev/input/event2"
+
],
+
"resources": [
+
{
+
"type": "baud",
+
"speed": 480000000,
+
"bits": 0,
+
"stop_bits": 0,
+
"parity": 0,
+
"handshake": 0
+
}
+
],
+
"detail": {
+
"device_class": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_protocol": 0,
+
"interface_class": {
+
"hex": "0003",
+
"name": "hid",
+
"value": 3
+
},
+
"interface_subclass": {
+
"hex": "0001",
+
"name": "audio",
+
"value": 1
+
},
+
"interface_protocol": 1,
+
"interface_number": 0,
+
"interface_alternate_setting": 0
+
},
+
"hotplug": "usb",
+
"driver": "usbhid",
+
"driver_module": "usbhid",
+
"drivers": [
+
"usbhid"
+
],
+
"driver_modules": [
+
"usbhid"
+
],
+
"driver_info": {
+
"type": "keyboard",
+
"xkb_rules": "xfree86"
+
},
+
"module_alias": "usb:v0627p0001d0000dc00dsc00dp00ic03isc01ip01in00"
+
}
+
],
+
"memory": [
+
{
+
"index": 6,
+
"attached_to": 0,
+
"class_list": [
+
"memory"
+
],
+
"base_class": {
+
"hex": "0101",
+
"name": "Internally Used Class",
+
"value": 257
+
},
+
"sub_class": {
+
"hex": "0002",
+
"name": "Main Memory",
+
"value": 2
+
},
+
"model": "Main Memory",
+
"resources": [
+
{
+
"type": "mem",
+
"base": 0,
+
"range": 25128050688,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "unknown"
+
},
+
{
+
"type": "phys_mem",
+
"range": 25769803776
+
}
+
]
+
}
+
],
+
"monitor": [
+
{
+
"index": 39,
+
"attached_to": 11,
+
"class_list": [
+
"monitor"
+
],
+
"base_class": {
+
"hex": "0100",
+
"name": "Monitor",
+
"value": 256
+
},
+
"sub_class": {
+
"hex": "0002",
+
"name": "LCD Monitor",
+
"value": 2
+
},
+
"vendor": {
+
"hex": "4914",
+
"value": 18708
+
},
+
"device": {
+
"hex": "1234",
+
"name": "QEMU Monitor",
+
"value": 4660
+
},
+
"serial": "0",
+
"model": "QEMU Monitor",
+
"resources": [
+
{
+
"type": "monitor",
+
"width": 1024,
+
"height": 768,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 1280,
+
"height": 800,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 1600,
+
"height": 1200,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 1920,
+
"height": 1080,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 2048,
+
"height": 1152,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 640,
+
"height": 480,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "monitor",
+
"width": 800,
+
"height": 600,
+
"vertical_frequency": 60,
+
"interlaced": false
+
},
+
{
+
"type": "size",
+
"unit": "mm",
+
"value_1": 325,
+
"value_2": 203
+
}
+
],
+
"detail": {
+
"manufacture_year": 2014,
+
"manufacture_week": 42,
+
"vertical_sync": {
+
"min": 50,
+
"max": 125
+
},
+
"horizontal_sync": {
+
"min": 30,
+
"max": 160
+
},
+
"horizontal_sync_timings": {
+
"disp": 1280,
+
"sync_start": 1600,
+
"sync_end": 1638,
+
"total": 1728
+
},
+
"vertical_sync_timings": {
+
"disp": 800,
+
"sync_start": 804,
+
"sync_end": 808,
+
"total": 828
+
},
+
"clock": 107300,
+
"width": 1280,
+
"height": 800,
+
"width_millimetres": 325,
+
"height_millimetres": 203,
+
"horizontal_flag": 45,
+
"vertical_flag": 45,
+
"vendor": "",
+
"name": "QEMU Monitor"
+
},
+
"driver_info": {
+
"type": "display",
+
"width": 2048,
+
"height": 1152,
+
"vertical_sync": {
+
"min": 50,
+
"max": 125
+
},
+
"horizontal_sync": {
+
"min": 30,
+
"max": 160
+
},
+
"bandwidth": 0,
+
"horizontal_sync_timings": {
+
"disp": 1280,
+
"sync_start": 1600,
+
"sync_end": 1638,
+
"total": 1728
+
},
+
"vertical_sync_timings": {
+
"disp": 800,
+
"sync_start": 804,
+
"sync_end": 808,
+
"total": 828
+
},
+
"horizontal_flag": 45,
+
"vertical_flag": 45
+
}
+
}
+
],
+
"mouse": [
+
{
+
"index": 42,
+
"attached_to": 43,
+
"class_list": [
+
"mouse",
+
"usb"
+
],
+
"bus_type": {
+
"hex": "0086",
+
"name": "USB",
+
"value": 134
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0105",
+
"name": "Mouse",
+
"value": 261
+
},
+
"sub_class": {
+
"hex": "0003",
+
"name": "USB Mouse",
+
"value": 3
+
},
+
"vendor": {
+
"hex": "0627",
+
"name": "QEMU",
+
"value": 1575
+
},
+
"device": {
+
"hex": "0001",
+
"name": "QEMU USB Mouse",
+
"value": 1
+
},
+
"serial": "89126-0000:00:02.0-3",
+
"compat_vendor": "Unknown",
+
"compat_device": "Generic USB Mouse",
+
"model": "QEMU USB Mouse",
+
"sysfs_id": "/devices/pci0000:00/0000:00:02.0/usb1/1-3/1-3:1.0",
+
"sysfs_bus_id": "1-3:1.0",
+
"unix_device_name": "/dev/input/mice",
+
"unix_device_number": {
+
"type": 99,
+
"major": 13,
+
"minor": 63,
+
"range": 1
+
},
+
"unix_device_names": [
+
"/dev/input/mice"
+
],
+
"unix_device_name2": "/dev/input/mouse1",
+
"unix_device_number2": {
+
"type": 99,
+
"major": 13,
+
"minor": 33,
+
"range": 1
+
},
+
"resources": [
+
{
+
"type": "baud",
+
"speed": 480000000,
+
"bits": 0,
+
"stop_bits": 0,
+
"parity": 0,
+
"handshake": 0
+
}
+
],
+
"detail": {
+
"device_class": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_protocol": 0,
+
"interface_class": {
+
"hex": "0003",
+
"name": "hid",
+
"value": 3
+
},
+
"interface_subclass": {
+
"hex": "0001",
+
"name": "audio",
+
"value": 1
+
},
+
"interface_protocol": 2,
+
"interface_number": 0,
+
"interface_alternate_setting": 0
+
},
+
"hotplug": "usb",
+
"driver": "usbhid",
+
"driver_module": "usbhid",
+
"drivers": [
+
"usbhid"
+
],
+
"driver_modules": [
+
"usbhid"
+
],
+
"driver_info": {
+
"type": "mouse",
+
"db_entry_0": [
+
"explorerps/2",
+
"exps2"
+
],
+
"xf86": "explorerps/2",
+
"gpm": "exps2",
+
"buttons": -1,
+
"wheels": -1
+
},
+
"module_alias": "usb:v0627p0001d0000dc00dsc00dp00ic03isc01ip02in00"
+
},
+
{
+
"index": 44,
+
"attached_to": 43,
+
"class_list": [
+
"mouse",
+
"usb"
+
],
+
"bus_type": {
+
"hex": "0086",
+
"name": "USB",
+
"value": 134
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0105",
+
"name": "Mouse",
+
"value": 261
+
},
+
"sub_class": {
+
"hex": "0003",
+
"name": "USB Mouse",
+
"value": 3
+
},
+
"vendor": {
+
"hex": "0627",
+
"name": "QEMU",
+
"value": 1575
+
},
+
"device": {
+
"hex": "0001",
+
"name": "QEMU USB Tablet",
+
"value": 1
+
},
+
"serial": "28754-0000:00:02.0-1",
+
"compat_vendor": "Unknown",
+
"compat_device": "Generic USB Mouse",
+
"model": "QEMU USB Tablet",
+
"sysfs_id": "/devices/pci0000:00/0000:00:02.0/usb1/1-1/1-1:1.0",
+
"sysfs_bus_id": "1-1:1.0",
+
"unix_device_name": "/dev/input/mice",
+
"unix_device_number": {
+
"type": 99,
+
"major": 13,
+
"minor": 63,
+
"range": 1
+
},
+
"unix_device_names": [
+
"/dev/input/mice"
+
],
+
"unix_device_name2": "/dev/input/mouse0",
+
"unix_device_number2": {
+
"type": 99,
+
"major": 13,
+
"minor": 32,
+
"range": 1
+
},
+
"resources": [
+
{
+
"type": "baud",
+
"speed": 480000000,
+
"bits": 0,
+
"stop_bits": 0,
+
"parity": 0,
+
"handshake": 0
+
}
+
],
+
"detail": {
+
"device_class": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"device_protocol": 0,
+
"interface_class": {
+
"hex": "0003",
+
"name": "hid",
+
"value": 3
+
},
+
"interface_subclass": {
+
"hex": "0000",
+
"name": "per_interface",
+
"value": 0
+
},
+
"interface_protocol": 0,
+
"interface_number": 0,
+
"interface_alternate_setting": 0
+
},
+
"hotplug": "usb",
+
"driver": "usbhid",
+
"driver_module": "usbhid",
+
"drivers": [
+
"usbhid"
+
],
+
"driver_modules": [
+
"usbhid"
+
],
+
"driver_info": {
+
"type": "mouse",
+
"db_entry_0": [
+
"explorerps/2",
+
"exps2"
+
],
+
"xf86": "explorerps/2",
+
"gpm": "exps2",
+
"buttons": -1,
+
"wheels": -1
+
},
+
"module_alias": "usb:v0627p0001d0000dc00dsc00dp00ic03isc00ip00in00"
+
}
+
],
+
"network_controller": [
+
{
+
"index": 36,
+
"attached_to": 25,
+
"class_list": [
+
"network_controller"
+
],
+
"bus_type": {
+
"hex": "008f",
+
"name": "Virtio",
+
"value": 143
+
},
+
"slot": {
+
"bus": 0,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0002",
+
"name": "Network controller",
+
"value": 2
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Ethernet controller",
+
"value": 0
+
},
+
"vendor": "Virtio",
+
"device": "Ethernet Card 0",
+
"model": "Virtio Ethernet Card 0",
+
"sysfs_id": "/devices/pci0000:00/0000:00:06.0/virtio1",
+
"sysfs_bus_id": "virtio1",
+
"unix_device_name": "enp0s6",
+
"unix_device_names": [
+
"enp0s6"
+
],
+
"resources": [
+
{
+
"type": "hwaddr",
+
"address": 48
+
},
+
{
+
"type": "phwaddr",
+
"address": 48
+
}
+
],
+
"driver": "virtio_net",
+
"drivers": [
+
"virtio_net"
+
],
+
"module_alias": "virtio:d00000001v0000108E"
+
}
+
],
+
"network_interface": [
+
{
+
"index": 46,
+
"attached_to": 0,
+
"class_list": [
+
"network_interface"
+
],
+
"base_class": {
+
"hex": "0107",
+
"name": "Network Interface",
+
"value": 263
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Loopback",
+
"value": 0
+
},
+
"model": "Loopback network interface",
+
"sysfs_id": "/class/net/lo",
+
"unix_device_name": "lo",
+
"unix_device_names": [
+
"lo"
+
]
+
},
+
{
+
"index": 47,
+
"attached_to": 36,
+
"class_list": [
+
"network_interface"
+
],
+
"base_class": {
+
"hex": "0107",
+
"name": "Network Interface",
+
"value": 263
+
},
+
"sub_class": {
+
"hex": "0001",
+
"name": "Ethernet",
+
"value": 1
+
},
+
"model": "Ethernet network interface",
+
"sysfs_id": "/class/net/enp0s6",
+
"sysfs_device_link": "/devices/pci0000:00/0000:00:06.0/virtio1",
+
"unix_device_name": "enp0s6",
+
"unix_device_names": [
+
"enp0s6"
+
],
+
"resources": [
+
{
+
"type": "hwaddr",
+
"address": 48
+
},
+
{
+
"type": "phwaddr",
+
"address": 48
+
}
+
],
+
"driver": "virtio_net",
+
"drivers": [
+
"virtio_net"
+
]
+
}
+
],
+
"pci": [
+
{
+
"index": 25,
+
"attached_to": 0,
+
"class_list": [
+
"pci",
+
"unknown"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 6
+
},
+
"base_class": {
+
"hex": "0002",
+
"name": "Network controller",
+
"value": 2
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Ethernet controller",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"sub_vendor": {
+
"hex": "108e",
+
"value": 4238
+
},
+
"device": {
+
"hex": "1041",
+
"value": 4161
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0001",
+
"value": 1
+
},
+
"model": "Ethernet controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:06.0",
+
"sysfs_bus_id": "0000:00:06.0",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 47,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 318869504,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
},
+
{
+
"type": "mem",
+
"base": 549806178304,
+
"range": 16384,
+
"enabled": true,
+
"access": "read_only",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1030,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 47,
+
"prog_if": 0
+
},
+
"driver": "virtio-pci",
+
"driver_module": "virtio_pci",
+
"drivers": [
+
"virtio-pci"
+
],
+
"driver_modules": [
+
"virtio_pci"
+
],
+
"module_alias": "pci:v00001AF4d00001041sv0000108Esd00001100bc02sc00i00"
+
}
+
],
+
"storage_controller": [
+
{
+
"index": 21,
+
"attached_to": 9,
+
"class_list": [
+
"storage_controller",
+
"pci"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 24,
+
"number": 0
+
},
+
"base_class": {
+
"hex": "0001",
+
"name": "Mass storage controller",
+
"value": 1
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "SCSI storage controller",
+
"value": 0
+
},
+
"vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"sub_vendor": {
+
"hex": "108e",
+
"value": 4238
+
},
+
"device": {
+
"hex": "1048",
+
"value": 4168
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0001",
+
"value": 1
+
},
+
"model": "SCSI storage controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.7/0000:18:00.0",
+
"sysfs_bus_id": "0000:18:00.0",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 46,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 316669952,
+
"range": 4096,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
},
+
{
+
"type": "mem",
+
"base": 549804048384,
+
"range": 16384,
+
"enabled": true,
+
"access": "read_only",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1030,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 46,
+
"prog_if": 0
+
},
+
"driver": "virtio-pci",
+
"driver_module": "virtio_pci",
+
"drivers": [
+
"virtio-pci"
+
],
+
"driver_modules": [
+
"virtio_pci"
+
],
+
"module_alias": "pci:v00001AF4d00001048sv0000108Esd00001100bc01sc00i00"
+
}
+
],
+
"system": {},
+
"unknown": [
+
{
+
"index": 37,
+
"attached_to": 21,
+
"class_list": [
+
"unknown"
+
],
+
"base_class": {
+
"hex": "0000",
+
"name": "Unclassified device",
+
"value": 0
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Unclassified device",
+
"value": 0
+
},
+
"vendor": "Virtio",
+
"device": "",
+
"model": "Virtio Unclassified device",
+
"sysfs_id": "/devices/pci0000:00/0000:00:05.7/0000:18:00.0/virtio2",
+
"sysfs_bus_id": "virtio2",
+
"driver": "virtio_scsi",
+
"driver_module": "virtio_scsi",
+
"drivers": [
+
"virtio_scsi"
+
],
+
"driver_modules": [
+
"virtio_scsi"
+
],
+
"module_alias": "virtio:d00000008v0000108E"
+
},
+
{
+
"index": 38,
+
"attached_to": 11,
+
"class_list": [
+
"unknown"
+
],
+
"base_class": {
+
"hex": "0000",
+
"name": "Unclassified device",
+
"value": 0
+
},
+
"sub_class": {
+
"hex": "0000",
+
"name": "Unclassified device",
+
"value": 0
+
},
+
"vendor": "Virtio",
+
"device": "",
+
"model": "Virtio Unclassified device",
+
"sysfs_id": "/devices/pci0000:00/0000:00:01.0/virtio0",
+
"sysfs_bus_id": "virtio0",
+
"driver": "virtio_gpu",
+
"driver_module": "virtio_gpu",
+
"drivers": [
+
"virtio_gpu"
+
],
+
"driver_modules": [
+
"virtio_gpu"
+
],
+
"module_alias": "virtio:d00000010v0000108E"
+
}
+
],
+
"usb_controller": [
+
{
+
"index": 32,
+
"attached_to": 0,
+
"class_list": [
+
"usb_controller",
+
"pci"
+
],
+
"bus_type": {
+
"hex": "0004",
+
"name": "PCI",
+
"value": 4
+
},
+
"slot": {
+
"bus": 0,
+
"number": 2
+
},
+
"base_class": {
+
"hex": "000c",
+
"name": "Serial bus controller",
+
"value": 12
+
},
+
"sub_class": {
+
"hex": "0003",
+
"name": "USB Controller",
+
"value": 3
+
},
+
"pci_interface": {
+
"hex": "0030",
+
"value": 48
+
},
+
"vendor": {
+
"hex": "1b36",
+
"value": 6966
+
},
+
"sub_vendor": {
+
"hex": "1af4",
+
"value": 6900
+
},
+
"device": {
+
"hex": "000d",
+
"value": 13
+
},
+
"sub_device": {
+
"hex": "1100",
+
"value": 4352
+
},
+
"revision": {
+
"hex": "0001",
+
"value": 1
+
},
+
"model": "USB Controller",
+
"sysfs_id": "/devices/pci0000:00/0000:00:02.0",
+
"sysfs_bus_id": "0000:00:02.0",
+
"resources": [
+
{
+
"type": "irq",
+
"base": 47,
+
"triggered": 0,
+
"enabled": true
+
},
+
{
+
"type": "mem",
+
"base": 549806161920,
+
"range": 16384,
+
"enabled": true,
+
"access": "read_write",
+
"prefetch": "no"
+
}
+
],
+
"detail": {
+
"function": 0,
+
"command": 1030,
+
"header_type": 0,
+
"secondary_bus": 0,
+
"irq": 47,
+
"prog_if": 48
+
},
+
"driver": "xhci_hcd",
+
"driver_module": "xhci_pci",
+
"drivers": [
+
"xhci_hcd"
+
],
+
"driver_modules": [
+
"xhci_pci"
+
],
+
"module_alias": "pci:v00001B36d0000000Dsv00001AF4sd00001100bc0Csc03i30"
+
}
+
]
+
},
+
"smbios": {}
+
}
+31
machines/terebithia/home/default.nix
···
+
{ inputs, ... }:
+
{
+
imports = [
+
(inputs.import-tree ../../../modules/home)
+
];
+
+
home = {
+
username = "kierank";
+
homeDirectory = "/home/kierank";
+
};
+
+
atelier = {
+
shell = {
+
enable = true;
+
};
+
apps = {
+
helix.enable = true;
+
irssi.enable = true;
+
};
+
ssh = {
+
enable = true;
+
zmx.enable = true;
+
};
+
};
+
+
programs.home-manager.enable = true;
+
+
systemd.user.startServices = "sd-switch";
+
+
home.stateVersion = "23.05";
+
}
+16
machines/terebithia/home-manager.nix
···
+
{ inputs, outputs, ... }:
+
{
+
imports = [
+
inputs.home-manager.nixosModules.home-manager
+
];
+
+
home-manager = {
+
useGlobalPkgs = true;
+
extraSpecialArgs = {
+
inherit inputs outputs;
+
};
+
users = {
+
kierank = import ./home;
+
};
+
};
+
}
+131
modules/home/apps/bore/bore.1.md
···
+
% BORE(1) bore 1.0
+
% Kieran Klukas
+
% December 2024
+
+
# NAME
+
+
bore - secure tunneling service for exposing local services to the internet
+
+
# SYNOPSIS
+
+
**bore** [*SUBDOMAIN*] [*PORT*] [**--protocol** *PROTOCOL*] [**--label** *LABEL*] [**--save**]
+
+
**bore** **--list** | **-l**
+
+
**bore** **--saved** | **-s**
+
+
# DESCRIPTION
+
+
**bore** is a tunneling service that uses frp (fast reverse proxy) to expose local services to the internet via bore.dunkirk.sh. It provides a simple CLI for creating and managing HTTP, TCP, and UDP tunnels with optional labels and persistent configuration.
+
+
# OPTIONS
+
+
**-l**, **--list**
+
: List all active tunnels on the bore server.
+
+
**-s**, **--saved**
+
: List all saved tunnel configurations from bore.toml in the current directory.
+
+
**-p**, **--protocol** *PROTOCOL*
+
: Specify the protocol to use for the tunnel: **http** (default), **tcp**, or **udp**.
+
+
**--label** *LABEL*
+
: Assign a label/tag to the tunnel for organization and identification.
+
+
**--save**
+
: Save the tunnel configuration to bore.toml in the current directory for future use.
+
+
# ARGUMENTS
+
+
*SUBDOMAIN*
+
: The subdomain to use for the tunnel (e.g., "myapp" creates myapp.bore.dunkirk.sh). Must contain only lowercase letters, numbers, and hyphens.
+
+
*PORT*
+
: The local port to expose (e.g., 8000 for localhost:8000).
+
+
# CONFIGURATION
+
+
Tunnel configurations can be saved to a **bore.toml** file in the current directory. This file uses TOML format and can be committed to repositories.
+
+
## bore.toml Format
+
+
```toml
+
[myapp]
+
port = 8000
+
+
[api]
+
port = 3000
+
protocol = "http"
+
label = "dev"
+
+
[database]
+
port = 5432
+
protocol = "tcp"
+
label = "postgres"
+
+
[game-server]
+
port = 27015
+
protocol = "udp"
+
label = "game"
+
```
+
+
When running **bore** without arguments in a directory with bore.toml, you'll be prompted to choose between creating a new tunnel or using a saved configuration.
+
+
# EXAMPLES
+
+
Create a simple HTTP tunnel:
+
```
+
$ bore myapp 8000
+
```
+
+
Create an HTTP tunnel with a label:
+
```
+
$ bore api 3000 --label dev
+
```
+
+
Create a TCP tunnel for a database:
+
```
+
$ bore database 5432 --protocol tcp --label postgres
+
```
+
+
Create a UDP tunnel for a game server:
+
```
+
$ bore game-server 27015 --protocol udp --label game
+
```
+
+
Save a tunnel configuration:
+
```
+
$ bore frontend 5173 --label local --save
+
```
+
+
List active tunnels:
+
```
+
$ bore --list
+
```
+
+
List saved configurations:
+
```
+
$ bore --saved
+
```
+
+
Interactive mode (choose saved or new):
+
```
+
$ bore
+
```
+
+
# FILES
+
+
**bore.toml**
+
: Local tunnel configuration file (current directory)
+
+
# SEE ALSO
+
+
Dashboard: https://bore.dunkirk.sh
+
+
# BUGS
+
+
Report bugs at: https://github.com/yourusername/dots/issues
+
+
# AUTHORS
+
+
Kieran Klukas <crush@charm.land>
+38
modules/home/apps/bore/completions/bore.bash
···
+
# bash completion for bore
+
+
_bore_completion() {
+
local cur prev opts
+
COMPREPLY=()
+
cur="${COMP_WORDS[COMP_CWORD]}"
+
prev="${COMP_WORDS[COMP_CWORD-1]}"
+
opts="--list --saved --protocol --label --save -l -s -p"
+
+
# Complete flags
+
if [[ ${cur} == -* ]]; then
+
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+
return 0
+
fi
+
+
# Complete protocol values after --protocol or -p
+
if [[ ${prev} == "--protocol" ]] || [[ ${prev} == "-p" ]]; then
+
COMPREPLY=( $(compgen -W "http tcp udp" -- ${cur}) )
+
return 0
+
fi
+
+
# Complete label value after --label or -l
+
if [[ ${prev} == "--label" ]] || [[ ${prev} == "-l" ]]; then
+
# Could potentially read from bore.toml for label suggestions
+
return 0
+
fi
+
+
# Complete saved tunnel names as first argument
+
if [[ ${COMP_CWORD} -eq 1 ]] && [[ -f "bore.toml" ]]; then
+
local tunnels=$(grep '^\[' bore.toml | sed 's/^\[\(.*\)\]$/\1/')
+
COMPREPLY=( $(compgen -W "${tunnels}" -- ${cur}) )
+
return 0
+
fi
+
+
return 0
+
}
+
+
complete -F _bore_completion bore
+21
modules/home/apps/bore/completions/bore.fish
···
+
# fish completion for bore
+
+
# Helper function to get saved tunnel names
+
function __bore_saved_tunnels
+
if test -f bore.toml
+
grep '^\[' bore.toml | sed 's/^\[\(.*\)\]$/\1/'
+
end
+
end
+
+
# Complete flags
+
complete -c bore -s l -l list -d 'List active tunnels'
+
complete -c bore -s s -l saved -d 'List saved tunnels from bore.toml'
+
complete -c bore -s p -l protocol -d 'Specify protocol' -xa 'http tcp udp'
+
complete -c bore -l label -d 'Assign a label to the tunnel' -r
+
complete -c bore -l save -d 'Save tunnel configuration to bore.toml'
+
+
# Complete subdomain from saved tunnels (first argument)
+
complete -c bore -n '__fish_is_first_token' -a '(__bore_saved_tunnels)' -d 'Saved tunnel'
+
+
# Port is always a number (second argument)
+
complete -c bore -n 'test (count (commandline -opc)) -eq 2' -d 'Local port'
+42
modules/home/apps/bore/completions/bore.zsh
···
+
#compdef bore
+
+
_bore() {
+
local -a tunnels
+
local curcontext="$curcontext" state line
+
typeset -A opt_args
+
+
# Read saved tunnels from bore.toml if it exists
+
if [[ -f "bore.toml" ]]; then
+
tunnels=(${(f)"$(grep '^\[' bore.toml | sed 's/^\[\(.*\)\]$/\1/')"})
+
fi
+
+
_arguments -C \
+
'1: :->subdomain' \
+
'2: :->port' \
+
'--list[List active tunnels]' \
+
'-l[List active tunnels]' \
+
'--saved[List saved tunnels from bore.toml]' \
+
'-s[List saved tunnels from bore.toml]' \
+
'--protocol[Specify protocol]:protocol:(http tcp udp)' \
+
'-p[Specify protocol]:protocol:(http tcp udp)' \
+
'--label[Assign a label to the tunnel]:label:' \
+
'--save[Save tunnel configuration to bore.toml]' \
+
&& return 0
+
+
case $state in
+
subdomain)
+
if [[ ${#tunnels[@]} -gt 0 ]]; then
+
_describe 'saved tunnels' tunnels
+
else
+
_message 'subdomain (e.g., myapp)'
+
fi
+
;;
+
port)
+
_message 'local port (e.g., 8000)'
+
;;
+
esac
+
+
return 0
+
}
+
+
_bore "$@"
+490
modules/home/apps/bore/default.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
let
+
cfg = config.atelier.bore;
+
+
boreScript = pkgs.writeShellScript "bore" ''
+
CONFIG_FILE="bore.toml"
+
+
# Check for flags
+
if [ "$1" = "--list" ] || [ "$1" = "-l" ]; then
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Active tunnels"
+
echo
+
+
tunnels=$(${pkgs.curl}/bin/curl -s https://${cfg.domain}/api/proxy/http)
+
+
if ! echo "$tunnels" | ${pkgs.jq}/bin/jq -e '.proxies | length > 0' >/dev/null 2>&1; then
+
${pkgs.gum}/bin/gum style --foreground 117 "No active tunnels"
+
exit 0
+
fi
+
+
# Filter only online tunnels with valid conf
+
echo "$tunnels" | ${pkgs.jq}/bin/jq -r '.proxies[] | select(.status == "online" and .conf != null) | if .type == "http" then "\(.name) → https://\(.conf.subdomain).${cfg.domain} [http]" elif .type == "tcp" then "\(.name) → tcp://\(.conf.remotePort) → localhost:\(.conf.localPort) [tcp]" elif .type == "udp" then "\(.name) → udp://\(.conf.remotePort) → localhost:\(.conf.localPort) [udp]" else "\(.name) [\(.type)]" end' | while read -r line; do
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ $line"
+
done
+
exit 0
+
fi
+
+
if [ "$1" = "--saved" ] || [ "$1" = "-s" ]; then
+
if [ ! -f "$CONFIG_FILE" ]; then
+
${pkgs.gum}/bin/gum style --foreground 117 "No bore.toml found in current directory"
+
exit 0
+
fi
+
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Saved tunnels in bore.toml"
+
echo
+
+
# Parse TOML and show tunnels
+
while IFS= read -r line; do
+
if [[ "$line" =~ ^\[([^]]+)\] ]]; then
+
current_tunnel="''${BASH_REMATCH[1]}"
+
elif [[ "$line" =~ ^port[[:space:]]*=[[:space:]]*([0-9]+) ]]; then
+
port="''${BASH_REMATCH[1]}"
+
elif [[ "$line" =~ ^protocol[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then
+
protocol="''${BASH_REMATCH[1]}"
+
elif [[ "$line" =~ ^label[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then
+
label="''${BASH_REMATCH[1]}"
+
proto_display="''${protocol:-http}"
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display] [$label]"
+
label=""
+
protocol=""
+
elif [[ -z "$line" ]] && [[ -n "$current_tunnel" ]] && [[ -n "$port" ]]; then
+
proto_display="''${protocol:-http}"
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display]"
+
current_tunnel=""
+
port=""
+
protocol=""
+
fi
+
done < "$CONFIG_FILE"
+
+
# Handle last entry if file doesn't end with blank line
+
if [[ -n "$current_tunnel" ]] && [[ -n "$port" ]]; then
+
proto_display="''${protocol:-http}"
+
if [[ -n "$label" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display] [$label]"
+
else
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display]"
+
fi
+
fi
+
exit 0
+
fi
+
+
# Get tunnel name/subdomain
+
if [ -n "$1" ]; then
+
tunnel_name="$1"
+
else
+
# Check if we have a bore.toml in current directory
+
if [ -f "$CONFIG_FILE" ]; then
+
# Count tunnels in TOML
+
tunnel_count=$(${pkgs.gnugrep}/bin/grep -c '^\[' "$CONFIG_FILE" 2>/dev/null || echo "0")
+
+
if [ "$tunnel_count" -gt 0 ]; then
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel"
+
echo
+
+
# Show choice between new or saved
+
choice=$(${pkgs.gum}/bin/gum choose "New tunnel" "Use saved tunnel")
+
+
if [ "$choice" = "Use saved tunnel" ]; then
+
# Extract tunnel names from TOML
+
saved_names=$(${pkgs.gnugrep}/bin/grep '^\[' "$CONFIG_FILE" | ${pkgs.gnused}/bin/sed 's/^\[\(.*\)\]$/\1/')
+
tunnel_name=$(echo "$saved_names" | ${pkgs.gum}/bin/gum choose)
+
+
if [ -z "$tunnel_name" ]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No tunnel selected"
+
exit 1
+
fi
+
+
# Parse TOML for this tunnel's config
+
in_section=false
+
while IFS= read -r line; do
+
if [[ "$line" =~ ^\[([^]]+)\] ]]; then
+
if [[ "''${BASH_REMATCH[1]}" = "$tunnel_name" ]]; then
+
in_section=true
+
else
+
in_section=false
+
fi
+
elif [[ "$in_section" = true ]]; then
+
if [[ "$line" =~ ^port[[:space:]]*=[[:space:]]*([0-9]+) ]]; then
+
port="''${BASH_REMATCH[1]}"
+
elif [[ "$line" =~ ^protocol[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then
+
protocol="''${BASH_REMATCH[1]}"
+
elif [[ "$line" =~ ^label[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then
+
label="''${BASH_REMATCH[1]}"
+
fi
+
fi
+
done < "$CONFIG_FILE"
+
+
proto_display="''${protocol:-http}"
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Loaded from bore.toml: $tunnel_name → localhost:$port [$proto_display]''${label:+ [$label]}"
+
else
+
# New tunnel - prompt for protocol first to determine what to ask for
+
protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp")
+
if [ -z "$protocol" ]; then
+
protocol="http"
+
fi
+
+
if [ "$protocol" = "http" ]; then
+
tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "myapp" --prompt "Subdomain: ")
+
else
+
tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "my-tunnel" --prompt "Tunnel name: ")
+
fi
+
+
if [ -z "$tunnel_name" ]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No name provided"
+
exit 1
+
fi
+
fi
+
else
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel"
+
echo
+
# Prompt for protocol first
+
protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp")
+
if [ -z "$protocol" ]; then
+
protocol="http"
+
fi
+
+
if [ "$protocol" = "http" ]; then
+
tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "myapp" --prompt "Subdomain: ")
+
else
+
tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "my-tunnel" --prompt "Tunnel name: ")
+
fi
+
+
if [ -z "$tunnel_name" ]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No name provided"
+
exit 1
+
fi
+
fi
+
else
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel"
+
echo
+
# Prompt for protocol first
+
protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp")
+
if [ -z "$protocol" ]; then
+
protocol="http"
+
fi
+
+
if [ "$protocol" = "http" ]; then
+
tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "myapp" --prompt "Subdomain: ")
+
else
+
tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "my-tunnel" --prompt "Tunnel name: ")
+
fi
+
+
if [ -z "$tunnel_name" ]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No name provided"
+
exit 1
+
fi
+
fi
+
fi
+
+
# Validate tunnel name (only for http subdomains)
+
if [ "$protocol" = "http" ]; then
+
if ! echo "$tunnel_name" | ${pkgs.gnugrep}/bin/grep -qE '^[a-z0-9-]+$'; then
+
${pkgs.gum}/bin/gum style --foreground 196 "Invalid subdomain (use only lowercase letters, numbers, and hyphens)"
+
exit 1
+
fi
+
fi
+
+
# Get port (skip if loaded from saved config)
+
if [ -z "$port" ]; then
+
if [ -n "$2" ]; then
+
port="$2"
+
else
+
port=$(${pkgs.gum}/bin/gum input --placeholder "8000" --prompt "Local port: ")
+
if [ -z "$port" ]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No port provided"
+
exit 1
+
fi
+
fi
+
fi
+
+
# Validate port
+
if ! echo "$port" | ${pkgs.gnugrep}/bin/grep -qE '^[0-9]+$'; then
+
${pkgs.gum}/bin/gum style --foreground 196 "Invalid port (must be a number)"
+
exit 1
+
fi
+
+
# Get optional protocol, label and save flag (skip if loaded from saved config)
+
save_config=false
+
if [ -z "$label" ]; then
+
shift 2 2>/dev/null || true
+
while [[ $# -gt 0 ]]; do
+
case "$1" in
+
--protocol|-p)
+
protocol="$2"
+
shift 2
+
;;
+
--label|-l)
+
label="$2"
+
shift 2
+
;;
+
--save)
+
save_config=true
+
shift
+
;;
+
*)
+
shift
+
;;
+
esac
+
done
+
+
# Prompt for protocol if not provided via flag and not loaded from saved config and not already set
+
if [ -z "$protocol" ]; then
+
protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp")
+
if [ -z "$protocol" ]; then
+
protocol="http"
+
fi
+
fi
+
+
# Prompt for label if not provided via flag and not loaded from saved config
+
if [ -z "$label" ]; then
+
# Allow multiple labels selection
+
labels=$(${pkgs.gum}/bin/gum choose --no-limit --header "Labels (select multiple):" "dev" "prod" "custom")
+
+
if [ -n "$labels" ]; then
+
# Check if custom was selected
+
if echo "$labels" | ${pkgs.gnugrep}/bin/grep -q "custom"; then
+
custom_label=$(${pkgs.gum}/bin/gum input --placeholder "my-label" --prompt "Custom label: ")
+
if [ -z "$custom_label" ]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No custom label provided"
+
exit 1
+
fi
+
# Replace 'custom' with the actual custom label
+
labels=$(echo "$labels" | ${pkgs.gnused}/bin/sed "s/custom/$custom_label/")
+
fi
+
# Join labels with comma
+
label=$(echo "$labels" | ${pkgs.coreutils}/bin/tr '\n' ',' | ${pkgs.gnused}/bin/sed 's/,$//')
+
fi
+
fi
+
fi
+
+
# Default protocol to http if still not set
+
if [ -z "$protocol" ]; then
+
protocol="http"
+
fi
+
+
# Check if local port is accessible
+
if ! ${pkgs.netcat}/bin/nc -z 127.0.0.1 "$port" 2>/dev/null; then
+
${pkgs.gum}/bin/gum style --foreground 214 "! Warning: Nothing listening on localhost:$port"
+
fi
+
+
# Save configuration if requested
+
if [ "$save_config" = true ]; then
+
# Check if tunnel already exists in TOML
+
if [ -f "$CONFIG_FILE" ] && ${pkgs.gnugrep}/bin/grep -q "^\[$tunnel_name\]" "$CONFIG_FILE"; then
+
# Update existing entry
+
${pkgs.gnused}/bin/sed -i "/^\[$tunnel_name\]/,/^\[/{
+
s/^port[[:space:]]*=.*/port = $port/
+
s/^protocol[[:space:]]*=.*/protocol = \"$protocol\"/
+
''${label:+s/^label[[:space:]]*=.*/label = \"$label\"/}
+
}" "$CONFIG_FILE"
+
else
+
# Append new entry
+
{
+
echo ""
+
echo "[$tunnel_name]"
+
echo "port = $port"
+
if [ "$protocol" != "http" ]; then
+
echo "protocol = \"$protocol\""
+
fi
+
if [ -n "$label" ]; then
+
echo "label = \"$label\""
+
fi
+
} >> "$CONFIG_FILE"
+
fi
+
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Configuration saved to bore.toml"
+
echo
+
fi
+
+
# Create config file
+
config_file=$(${pkgs.coreutils}/bin/mktemp)
+
trap "${pkgs.coreutils}/bin/rm -f $config_file" EXIT
+
+
# Encode label into proxy name if provided (format: tunnel_name[label1,label2])
+
proxy_name="$tunnel_name"
+
if [ -n "$label" ]; then
+
proxy_name="''${tunnel_name}[''${label}]"
+
fi
+
+
# Build proxy configuration based on protocol
+
if [ "$protocol" = "http" ]; then
+
${pkgs.coreutils}/bin/cat > $config_file <<EOF
+
serverAddr = "${cfg.serverAddr}"
+
serverPort = ${toString cfg.serverPort}
+
+
auth.method = "token"
+
auth.tokenSource.type = "file"
+
auth.tokenSource.file.path = "${cfg.authTokenFile}"
+
+
[[proxies]]
+
name = "$proxy_name"
+
type = "http"
+
localIP = "127.0.0.1"
+
localPort = $port
+
subdomain = "$tunnel_name"
+
EOF
+
elif [ "$protocol" = "tcp" ] || [ "$protocol" = "udp" ]; then
+
# For TCP/UDP, enable admin API to query allocated port
+
# Use Python to find a free port (cross-platform and guaranteed to work)
+
admin_port=$(${pkgs.python3}/bin/python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
+
+
${pkgs.coreutils}/bin/cat > $config_file <<EOF
+
serverAddr = "${cfg.serverAddr}"
+
serverPort = ${toString cfg.serverPort}
+
+
auth.method = "token"
+
auth.tokenSource.type = "file"
+
auth.tokenSource.file.path = "${cfg.authTokenFile}"
+
+
webServer.addr = "127.0.0.1"
+
webServer.port = $admin_port
+
+
[[proxies]]
+
name = "$proxy_name"
+
type = "$protocol"
+
localIP = "127.0.0.1"
+
localPort = $port
+
remotePort = 0
+
EOF
+
else
+
${pkgs.gum}/bin/gum style --foreground 196 "Invalid protocol: $protocol (must be http, tcp, or udp)"
+
exit 1
+
fi
+
+
# Start tunnel
+
echo
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Tunnel configured"
+
${pkgs.gum}/bin/gum style --foreground 117 " Local: localhost:$port"
+
if [ "$protocol" = "http" ]; then
+
public_url="https://$tunnel_name.${cfg.domain}"
+
${pkgs.gum}/bin/gum style --foreground 117 " Public: $public_url"
+
else
+
${pkgs.gum}/bin/gum style --foreground 117 " Protocol: $protocol"
+
${pkgs.gum}/bin/gum style --foreground 214 " Waiting for server to allocate port..."
+
fi
+
echo
+
${pkgs.gum}/bin/gum style --foreground 214 "Connecting to ${cfg.serverAddr}:${toString cfg.serverPort}..."
+
echo
+
+
# For TCP/UDP, capture output to parse allocated port
+
if [ "$protocol" = "tcp" ] || [ "$protocol" = "udp" ]; then
+
# Start frpc in background and capture its PID
+
${pkgs.frp}/bin/frpc -c $config_file 2>&1 | while IFS= read -r line; do
+
echo "$line"
+
+
# Look for successful proxy start
+
if echo "$line" | ${pkgs.gnugrep}/bin/grep -q "start proxy success"; then
+
# Wait a moment for the proxy to fully initialize
+
sleep 1
+
+
# Query the frpc admin API for proxy status
+
proxy_status=$(${pkgs.curl}/bin/curl -s http://127.0.0.1:$admin_port/api/status 2>/dev/null || echo "{}")
+
+
# Try to extract remote port from JSON response
+
# Format: "remote_addr":"bore.dunkirk.sh:20097"
+
remote_addr=$(echo "$proxy_status" | ${pkgs.jq}/bin/jq -r ".tcp[]? | select(.name == \"$proxy_name\") | .remote_addr" 2>/dev/null)
+
if [ -z "$remote_addr" ] || [ "$remote_addr" = "null" ]; then
+
remote_addr=$(echo "$proxy_status" | ${pkgs.jq}/bin/jq -r ".udp[]? | select(.name == \"$proxy_name\") | .remote_addr" 2>/dev/null)
+
fi
+
+
# Extract just the port number
+
remote_port=$(echo "$remote_addr" | ${pkgs.gnugrep}/bin/grep -oP ':\K[0-9]+$')
+
+
if [ -n "$remote_port" ] && [ "$remote_port" != "null" ]; then
+
echo
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Tunnel established"
+
${pkgs.gum}/bin/gum style --foreground 117 " Local: localhost:$port"
+
${pkgs.gum}/bin/gum style --foreground 117 " Remote: ${cfg.serverAddr}:$remote_port"
+
${pkgs.gum}/bin/gum style --foreground 117 " Type: $protocol"
+
echo
+
fi
+
fi
+
done
+
else
+
exec ${pkgs.frp}/bin/frpc -c $config_file
+
fi
+
'';
+
+
bore = pkgs.stdenv.mkDerivation {
+
pname = "bore";
+
version = "1.0";
+
+
dontUnpack = true;
+
+
nativeBuildInputs = with pkgs; [ pandoc installShellFiles ];
+
+
manPageSrc = ./bore.1.md;
+
bashCompletionSrc = ./completions/bore.bash;
+
zshCompletionSrc = ./completions/bore.zsh;
+
fishCompletionSrc = ./completions/bore.fish;
+
+
buildPhase = ''
+
# Convert markdown man page to man format
+
${pkgs.pandoc}/bin/pandoc -s -t man $manPageSrc -o bore.1
+
'';
+
+
installPhase = ''
+
mkdir -p $out/bin
+
+
# Install binary
+
cp ${boreScript} $out/bin/bore
+
chmod +x $out/bin/bore
+
+
# Install man page
+
installManPage bore.1
+
+
# Install completions
+
installShellCompletion --bash --name bore $bashCompletionSrc
+
installShellCompletion --zsh --name _bore $zshCompletionSrc
+
installShellCompletion --fish --name bore.fish $fishCompletionSrc
+
'';
+
+
meta = with lib; {
+
description = "Secure tunneling service CLI";
+
homepage = "https://bore.dunkirk.sh";
+
license = licenses.mit;
+
maintainers = [ ];
+
};
+
};
+
in
+
{
+
options.atelier.bore = {
+
enable = lib.mkEnableOption "bore tunneling service";
+
+
serverAddr = lib.mkOption {
+
type = lib.types.str;
+
default = "bore.dunkirk.sh";
+
description = "bore server address";
+
};
+
+
serverPort = lib.mkOption {
+
type = lib.types.port;
+
default = 7000;
+
description = "bore server port";
+
};
+
+
domain = lib.mkOption {
+
type = lib.types.str;
+
default = "bore.dunkirk.sh";
+
description = "Domain for public tunnel URLs";
+
};
+
+
authTokenFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
default = null;
+
description = "Path to file containing authentication token";
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
home.packages = [
+
pkgs.frp
+
bore
+
];
+
};
+
}
+128 -73
modules/home/apps/crush.nix
···
settings = {
mcp = {
context7 = {
-
type = "sse";
-
url = "https://mcp.context7.com/sse";
+
type = "http";
+
url = "https://mcp.context7.com/mcp";
+
headers = {
+
CONTEXT7_API_KEY = "$(cat /run/agenix/context7)";
+
};
};
sequential-thinking = {
command = "bunx";
···
cost_per_1m_out_cached = 0;
context_window = 128000;
default_max_tokens = 16384;
-
can_reason = false;
+
can_reason = true;
has_reasoning_efforts = false;
-
supports_attachments = false;
+
supports_attachments = true;
}
{
id = "gpt-5-mini";
-
name = "Copilot: GPT-5 mini (Preview)";
+
name = "Copilot: GPT-5 mini";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 128000;
+
context_window = 264000;
default_max_tokens = 64000;
-
can_reason = false;
+
can_reason = true;
has_reasoning_efforts = false;
-
supports_attachments = false;
+
supports_attachments = true;
}
{
id = "gpt-5";
-
name = "Copilot: GPT-5 (Preview)";
+
name = "Copilot: GPT-5";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 128000;
-
default_max_tokens = 64000;
-
can_reason = false;
+
context_window = 400000;
+
default_max_tokens = 128000;
+
can_reason = true;
has_reasoning_efforts = false;
-
supports_attachments = false;
+
supports_attachments = true;
}
{
id = "gpt-4o";
···
cost_per_1m_out_cached = 0;
context_window = 128000;
default_max_tokens = 4096;
-
can_reason = false;
+
can_reason = true;
has_reasoning_efforts = false;
-
supports_attachments = false;
+
supports_attachments = true;
}
{
-
id = "o3-mini";
-
name = "Copilot: o3-mini";
+
id = "grok-code-fast-1";
+
name = "Copilot: Grok Code Fast 1";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 200000;
-
default_max_tokens = 100000;
-
can_reason = false;
+
context_window = 128000;
+
default_max_tokens = 64000;
+
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = false;
}
{
-
id = "claude-3.5-sonnet";
-
name = "Copilot: Claude Sonnet 3.5";
+
id = "gpt-5-codex";
+
name = "Copilot: GPT-5-Codex (Preview)";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 90000;
-
default_max_tokens = 8192;
+
context_window = 400000;
+
default_max_tokens = 128000;
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
{
-
id = "claude-3.7-sonnet";
-
name = "Copilot: Claude Sonnet 3.7";
+
id = "claude-3.5-sonnet";
+
name = "Copilot: Claude Sonnet 3.5";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 200000;
-
default_max_tokens = 16384;
+
context_window = 90000;
+
default_max_tokens = 8192;
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
{
-
id = "claude-3.7-sonnet-thought";
-
name = "Copilot: Claude Sonnet 3.7 Thinking";
+
id = "claude-sonnet-4";
+
name = "Copilot: Claude Sonnet 4";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 200000;
-
default_max_tokens = 16384;
+
context_window = 216000;
+
default_max_tokens = 16000;
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
{
-
id = "claude-sonnet-4";
-
name = "Copilot: Claude Sonnet 4";
+
id = "claude-sonnet-4.5";
+
name = "Copilot: Claude Sonnet 4.5";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 128000;
+
context_window = 144000;
default_max_tokens = 16000;
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
{
-
id = "gemini-2.0-flash-001";
-
name = "Copilot: Gemini 2.0 Flash";
+
id = "claude-haiku-4.5";
+
name = "Copilot: Claude Haiku 4.5";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
cost_per_1m_out_cached = 0;
-
context_window = 1000000;
-
default_max_tokens = 8192;
+
context_window = 144000;
+
default_max_tokens = 16000;
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
{
id = "gemini-2.5-pro";
-
name = "Copilot: Gemini 2.5 Pro (Preview)";
+
name = "Copilot: Gemini 2.5 Pro";
cost_per_1m_in = 0;
cost_per_1m_out = 0;
cost_per_1m_in_cached = 0;
···
has_reasoning_efforts = false;
supports_attachments = true;
}
+
];
+
};
+
hyper = {
+
name = "Charm Hyper";
+
base_url = "https://hyper.charm.sh/api/v1/openai/";
+
api_key = "$(cat /run/agenix/crush)";
+
type = "openai";
+
models = [
{
-
id = "o4-mini";
-
name = "Copilot: o4-mini (Preview)";
-
cost_per_1m_in = 0;
-
cost_per_1m_out = 0;
-
cost_per_1m_in_cached = 0;
-
cost_per_1m_out_cached = 0;
-
context_window = 128000;
-
default_max_tokens = 16384;
-
can_reason = false;
-
has_reasoning_efforts = false;
-
supports_attachments = false;
+
name = "Qwen 3 Coder";
+
id = "qwen3_coder";
+
context_window = 118000;
+
default_max_tokens = 20000;
}
];
};
···
};
models = [
{
+
id = "claude-haiku-4-5-20251001";
+
name = "Claude Haiku 4.5";
+
cost_per_1m_in = 3.0;
+
cost_per_1m_out = 15.0;
+
cost_per_1m_in_cached = 0.225;
+
cost_per_1m_out_cached = 15.0;
+
context_window = 200000;
+
default_max_tokens = 8192;
+
can_reason = true;
+
has_reasoning_efforts = false;
+
supports_attachments = true;
+
}
+
{
+
id = "claude-sonnet-4-5-20250929";
+
name = "Claude Sonnet 4.5";
+
cost_per_1m_in = 3.0;
+
cost_per_1m_out = 15.0;
+
cost_per_1m_in_cached = 0.225;
+
cost_per_1m_out_cached = 15.0;
+
context_window = 200000;
+
default_max_tokens = 64000;
+
can_reason = true;
+
has_reasoning_efforts = false;
+
supports_attachments = true;
+
}
+
{
+
id = "claude-opus-4-1-20250805";
+
name = "Claude Opus 4.1";
+
cost_per_1m_in = 15.0;
+
cost_per_1m_out = 75.0;
+
cost_per_1m_in_cached = 1.5;
+
cost_per_1m_out_cached = 75.0;
+
context_window = 200000;
+
default_max_tokens = 32000;
+
can_reason = true;
+
has_reasoning_efforts = true;
+
supports_attachments = true;
+
}
+
{
id = "claude-opus-4-20250514";
name = "Claude Opus 4";
cost_per_1m_in = 15.0;
···
cost_per_1m_in_cached = 1.5;
cost_per_1m_out_cached = 75.0;
context_window = 200000;
-
default_max_tokens = 50000;
+
default_max_tokens = 32000;
can_reason = true;
has_reasoning_efforts = true;
supports_attachments = true;
···
{
id = "claude-sonnet-4-20250514";
name = "Claude Sonnet 4";
-
cost_per_1m_in = 3000;
-
cost_per_1m_out = 15000;
-
cost_per_1m_in_cached = 225;
-
cost_per_1m_out_cached = 15000;
+
cost_per_1m_in = 3.0;
+
cost_per_1m_out = 15.0;
+
cost_per_1m_in_cached = 0.225;
+
cost_per_1m_out_cached = 15.0;
context_window = 200000;
-
default_max_tokens = 50000;
+
default_max_tokens = 64000;
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
{
id = "claude-3-7-sonnet-20250219";
-
name = "Claude 3.7 Sonnet";
+
name = "Claude Sonnet 3.7";
cost_per_1m_in = 2.5;
cost_per_1m_out = 12.0;
cost_per_1m_in_cached = 0.187;
···
supports_attachments = true;
}
{
-
id = "claude-3-5-sonnet-20241022";
-
name = "Claude 3.5 Sonnet (Latest)";
-
cost_per_1m_in = 3.0;
-
cost_per_1m_out = 15.0;
-
cost_per_1m_in_cached = 0.225;
-
cost_per_1m_out_cached = 15.0;
+
id = "claude-3-5-haiku-20241022";
+
name = "Claude Haiku 3.5";
+
cost_per_1m_in = 0.8;
+
cost_per_1m_out = 4.0;
+
cost_per_1m_in_cached = 0.06;
+
cost_per_1m_out_cached = 4.0;
context_window = 200000;
default_max_tokens = 8192;
-
can_reason = false;
+
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
{
-
id = "claude-3-5-haiku-20241022";
-
name = "Claude 3.5 Haiku";
-
cost_per_1m_in = 0.8;
-
cost_per_1m_out = 4.0;
-
cost_per_1m_in_cached = 0.06;
-
cost_per_1m_out_cached = 4.0;
+
id = "claude-3-haiku-20240307";
+
name = "Claude Haiku 3";
+
cost_per_1m_in = 0.25;
+
cost_per_1m_out = 1.25;
+
cost_per_1m_in_cached = 0.03;
+
cost_per_1m_out_cached = 1.25;
+
context_window = 200000;
+
default_max_tokens = 4096;
+
can_reason = true;
+
has_reasoning_efforts = false;
+
supports_attachments = true;
+
}
+
{
+
id = "claude-3-opus-20240229";
+
name = "Claude Opus 3";
+
cost_per_1m_in = 15.0;
+
cost_per_1m_out = 75.0;
+
cost_per_1m_in_cached = 1.5;
+
cost_per_1m_out_cached = 75.0;
context_window = 200000;
default_max_tokens = 8192;
-
can_reason = false;
+
can_reason = true;
has_reasoning_efforts = false;
supports_attachments = true;
}
···
};
xdg.configFile."crush/copilot.sh".source = ../../../dots/copilot.sh;
-
xdg.configFile."crush/anthropic.sh".source = ../../../dots/anthropic.sh;
};
}
+1 -1
modules/home/apps/ghostty.nix
···
foreground = "#a7b1d3"
mouse-hide-while-typing = true
resize-overlay = "never"
-
theme = "catppuccin-mocha"
+
theme = "Catppuccin Macchiato"
window-decoration = ${if config.atelier.terminal.ghostty.windowDecoration then "true" else "false"}
window-padding-x = 12
window-padding-y = 12
+18 -13
modules/home/apps/git.nix
···
config = lib.mkIf config.atelier.shell.git.enable {
programs.git = {
enable = true;
-
userName = "Kieran Klukas";
-
userEmail = "me@dunkirk.sh";
-
aliases = {
-
c = "commit";
-
p = "push";
-
ch = "checkout";
-
pushfwl = "push --force-with-lease --force-if-includes";
-
};
includes = [
{
path = pkgs.writeText "git-user-config" ''
···
email = kieranklukas@cedarville.edu
signingKey = ~/.ssh/id_ed25519_cedarville.pub
[core]
-
sshCommand "ssh -i ~/.ssh/id_ed25519_cedarville"
+
sshCommand = ssh -i ~/.ssh/id_ed25519_cedarville
'';
-
condition = "gitdir:~/code/school";
+
condition = "gitdir:~/code/school/";
}
];
-
extraConfig = {
+
settings = {
+
user = {
+
name = "Kieran Klukas";
+
email = "me@dunkirk.sh";
+
signingKey = "~/.ssh/id_rsa.pub";
+
};
+
alias = {
+
c = "commit";
+
p = "push";
+
ch = "checkout";
+
pushfwl = "push --force-with-lease --force-if-includes";
+
};
branch.sort = "-committerdate";
pager.branch = false;
column.ui = "auto";
commit.gpgsign = true;
gpg.format = "ssh";
gpg.ssh.allowedSignersFile = "~/.ssh/allowedSigners";
-
user.signingKey = "~/.ssh/id_rsa.pub";
pull.rebase = true;
push.autoSetupRemote = true;
init.defaultBranch = "main";
};
-
delta.enable = true;
+
};
+
programs.delta = {
+
enable = true;
+
enableGitIntegration = true;
};
programs.gh.enable = true;
programs.lazygit = {
+46
modules/home/apps/halloy.nix
···
+
{lib, config, ...}:
+
{
+
options.atelier.apps.halloy.enable = lib.mkEnableOption "Enable halloy config";
+
config = lib.mkIf config.atelier.apps.halloy.enable {
+
programs.halloy = {
+
enable = true;
+
settings = {
+
theme = "ferra";
+
buffer.channel.topic = {
+
enabled = true;
+
};
+
servers = {
+
liberachat = {
+
nickname = "taciturnaxolotl";
+
realname = "kieran klukas";
+
username = "kierank";
+
server = "irc.libera.chat";
+
channels = ["#tangled" "#halloy"];
+
};
+
hackclub = {
+
nickname = "krn";
+
nick_password = "Extrude1-Herbal-Map";
+
realname = "kieran klukas";
+
username = "taciturnaxolotl";
+
server = "irc.hackclub.com";
+
port = 6667;
+
use_tls = false;
+
chathistory = true;
+
channels = [
+
"#lounge"
+
"#hq"
+
"#krn-rambles"
+
"#neon"
+
"#neighborhood"
+
"#meta"
+
"#fraud-land"
+
];
+
channel-keys = {
+
fraud-land = "fraudpheus";
+
};
+
};
+
};
+
};
+
};
+
};
+
}
+310
modules/home/apps/helix.nix
···
+
{
+
lib,
+
config,
+
pkgs,
+
inputs,
+
...
+
}:
+
{
+
options.atelier.apps.helix = {
+
enable = lib.mkEnableOption "Enable helix config";
+
swift = lib.mkEnableOption "Enable Swift support";
+
};
+
+
config = lib.mkIf config.atelier.apps.helix.enable {
+
programs.helix = {
+
enable = true;
+
package = pkgs.evil-helix;
+
extraPackages =
+
with pkgs;
+
[
+
clang-tools # clangd
+
cmake-language-server # neocmakelsp
+
omnisharp-roslyn # OmniSharp
+
gopls
+
jdt-language-server # jdtls
+
typescript-language-server
+
unstable.biome
+
lua-language-server
+
nil # nix
+
nodePackages.intelephense
+
python313Packages.python-lsp-server # pylsp
+
ruby-lsp
+
rust-analyzer
+
nodePackages.bash-language-server
+
taplo
+
vscode-langservers-extracted
+
kotlin-language-server
+
harper
+
inputs.wakatime-ls.packages.${pkgs.stdenv.hostPlatform.system}.default
+
]
+
++ lib.optionals config.atelier.apps.helix.swift [
+
sourcekit-lsp
+
unstable.sourcekit-lsp
+
];
+
settings = {
+
theme = "catppuccin_macchiato";
+
editor = {
+
line-number = "relative";
+
mouse = true;
+
rulers = [ 120 ];
+
true-color = true;
+
completion-replace = true;
+
end-of-line-diagnostics = "hint";
+
color-modes = true;
+
inline-diagnostics.cursor-line = "warning";
+
file-picker.hidden = false;
+
indent-guides = {
+
render = true;
+
character = "╎";
+
skip-levels = 0;
+
};
+
soft-wrap.enable = false;
+
auto-save = {
+
idle-timeout = 300000;
+
};
+
cursor-shape = {
+
normal = "block";
+
insert = "bar";
+
select = "underline";
+
};
+
statusline = {
+
left = [
+
"mode"
+
"spinner"
+
"version-control"
+
"spacer"
+
"separator"
+
"file-name"
+
"read-only-indicator"
+
"file-modification-indicator"
+
];
+
center = [ ];
+
right = [
+
"diagnostics"
+
"workspace-diagnostics"
+
"position"
+
"total-line-numbers"
+
"position-percentage"
+
"file-encoding"
+
"file-line-ending"
+
"file-type"
+
"register"
+
"selections"
+
];
+
separator = "│";
+
};
+
};
+
};
+
languages = {
+
language-server = {
+
harper-ls = {
+
command = "${pkgs.harper}/bin/harper-ls";
+
args = [ "--stdio" ];
+
};
+
biome = {
+
command = "${pkgs.unstable.biome}/bin/biome";
+
args = [ "lsp-proxy" ];
+
};
+
wakatime = {
+
command = "wakatime-ls";
+
};
+
};
+
language = [
+
{
+
name = "c";
+
language-servers = [
+
"clangd"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "cmake";
+
language-servers = [
+
"neocmakelsp"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "cpp";
+
language-servers = [
+
"clangd"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "c-sharp";
+
language-servers = [
+
"OmniSharp"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "go";
+
language-servers = [
+
"gopls"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "java";
+
language-servers = [
+
"jdtls"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "javascript";
+
language-servers = [
+
{
+
name = "typescript-language-server";
+
except-features = [ "format" ];
+
}
+
"biome"
+
"harper-ls"
+
"wakatime"
+
];
+
auto-format = true;
+
}
+
{
+
name = "jsx";
+
language-servers = [
+
{
+
name = "typescript-language-server";
+
except-features = [ "format" ];
+
}
+
"biome"
+
"harper-ls"
+
"wakatime"
+
];
+
auto-format = true;
+
}
+
{
+
name = "lua";
+
language-servers = [
+
"lua-language-server"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "nix";
+
language-servers = [
+
"nil"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "php";
+
language-servers = [
+
"intelephense"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "python";
+
language-servers = [
+
"pylsp"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "ruby";
+
language-servers = [
+
"ruby-lsp"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "rust";
+
language-servers = [
+
"rust-analyzer"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "bash";
+
language-servers = [
+
"bash-language-server"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "toml";
+
language-servers = [
+
"taplo"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
{
+
name = "typescript";
+
language-servers = [
+
{
+
name = "typescript-language-server";
+
except-features = [ "format" ];
+
}
+
"biome"
+
"harper-ls"
+
"wakatime"
+
];
+
auto-format = true;
+
}
+
{
+
name = "tsx";
+
language-servers = [
+
{
+
name = "typescript-language-server";
+
except-features = [ "format" ];
+
}
+
"biome"
+
"harper-ls"
+
"wakatime"
+
];
+
auto-format = true;
+
}
+
{
+
name = "json";
+
language-servers = [
+
{
+
name = "vscode-json-language-server";
+
except-features = [ "format" ];
+
}
+
"biome"
+
"wakatime"
+
];
+
}
+
{
+
name = "kotlin";
+
language-servers = [
+
"kotlin-language-server"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
] ++ lib.optionals config.atelier.apps.helix.swift [
+
{
+
name = "swift";
+
language-servers = [
+
"sourcekit-lsp"
+
"harper-ls"
+
"wakatime"
+
];
+
}
+
];
+
};
+
};
+
};
+
}
+1 -1
modules/home/apps/spotify.nix
···
config = lib.mkIf config.atelier.apps.spotify.enable {
programs.spicetify =
let
-
spicePkgs = inputs.spicetify-nix.legacyPackages.${pkgs.system};
+
spicePkgs = inputs.spicetify-nix.legacyPackages.${pkgs.stdenv.hostPlatform.system};
in
{
enable = true;
+183
modules/home/apps/ssh.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
inputs,
+
...
+
}:
+
with lib;
+
let
+
cfg = config.atelier.ssh;
+
in
+
{
+
options.atelier.ssh = {
+
enable = mkEnableOption "SSH configuration";
+
+
zmx = {
+
enable = mkEnableOption "zmx integration for persistent sessions";
+
hosts = mkOption {
+
type = types.listOf types.str;
+
default = [ ];
+
description = "List of host patterns to enable zmx auto-attach (e.g., 'd.*')";
+
};
+
};
+
+
extraConfig = mkOption {
+
type = types.lines;
+
default = "";
+
description = "Extra SSH configuration";
+
};
+
+
hosts = mkOption {
+
type = types.attrsOf (
+
types.submodule {
+
options = {
+
hostname = mkOption {
+
type = types.nullOr types.str;
+
default = null;
+
description = "Hostname or IP address";
+
};
+
+
port = mkOption {
+
type = types.nullOr types.int;
+
default = null;
+
description = "SSH port";
+
};
+
+
user = mkOption {
+
type = types.nullOr types.str;
+
default = null;
+
description = "Username for SSH connection";
+
};
+
+
identityFile = mkOption {
+
type = types.nullOr types.str;
+
default = null;
+
description = "Path to SSH identity file";
+
};
+
+
forwardAgent = mkOption {
+
type = types.nullOr types.bool;
+
default = null;
+
description = "Enable SSH agent forwarding";
+
};
+
+
extraOptions = mkOption {
+
type = types.attrsOf types.str;
+
default = { };
+
description = "Additional SSH options for this host";
+
};
+
+
zmx = mkOption {
+
type = types.bool;
+
default = false;
+
description = "Enable zmx persistent sessions for this host";
+
};
+
};
+
}
+
);
+
default = { };
+
description = "SSH host configurations";
+
};
+
};
+
+
config = mkIf cfg.enable {
+
# zmx provides pre-built binaries that we download instead of building from source
+
# This avoids the zig2nix dependency which causes issues in CI
+
home.packages =
+
(optionals cfg.zmx.enable [
+
pkgs.zmx-binary
+
pkgs.autossh
+
]);
+
+
programs.ssh = {
+
enable = true;
+
enableDefaultConfig = false;
+
+
matchBlocks =
+
let
+
# Convert atelier.ssh.hosts to SSH matchBlocks
+
hostConfigs = mapAttrs (
+
name: hostCfg:
+
{
+
hostname = mkIf (hostCfg.hostname != null) hostCfg.hostname;
+
port = mkIf (hostCfg.port != null) hostCfg.port;
+
user = mkIf (hostCfg.user != null) hostCfg.user;
+
identityFile = mkIf (hostCfg.identityFile != null) hostCfg.identityFile;
+
forwardAgent = mkIf (hostCfg.forwardAgent != null) hostCfg.forwardAgent;
+
extraOptions = hostCfg.extraOptions // (
+
if hostCfg.zmx then
+
{
+
RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %n";
+
RequestTTY = "yes";
+
ControlPath = "~/.ssh/cm-%r@%h:%p";
+
ControlMaster = "auto";
+
ControlPersist = "10m";
+
}
+
else
+
{ }
+
);
+
}
+
) cfg.hosts;
+
+
# Create zmx pattern hosts if enabled
+
zmxPatternHosts = if cfg.zmx.enable then
+
listToAttrs (
+
map (pattern:
+
let
+
patternHost = cfg.hosts.${pattern} or {};
+
in {
+
name = pattern;
+
value = {
+
hostname = mkIf (patternHost.hostname or null != null) patternHost.hostname;
+
port = mkIf (patternHost.port or null != null) patternHost.port;
+
user = mkIf (patternHost.user or null != null) patternHost.user;
+
extraOptions = {
+
RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %k";
+
RequestTTY = "yes";
+
ControlPath = "~/.ssh/cm-%r@%h:%p";
+
ControlMaster = "auto";
+
ControlPersist = "10m";
+
};
+
};
+
}) cfg.zmx.hosts
+
)
+
else
+
{ };
+
+
# Default match block for extraConfig
+
defaultBlock = if cfg.extraConfig != "" then
+
{
+
"*" = { };
+
}
+
else
+
{ };
+
in
+
defaultBlock // hostConfigs // zmxPatternHosts;
+
+
extraConfig = cfg.extraConfig;
+
};
+
+
# Add shell aliases for easier zmx usage
+
programs.zsh.shellAliases = mkIf cfg.zmx.enable {
+
zmls = "zmx list";
+
zmk = "zmx kill";
+
zma = "zmx attach";
+
ash = "autossh -M 0 -q";
+
};
+
+
programs.bash.shellAliases = mkIf cfg.zmx.enable {
+
zmls = "zmx list";
+
zmk = "zmx kill";
+
zma = "zmx attach";
+
ash = "autossh -M 0 -q";
+
};
+
+
programs.fish.shellAliases = mkIf cfg.zmx.enable {
+
zmls = "zmx list";
+
zmk = "zmx kill";
+
zma = "zmx attach";
+
ash = "autossh -M 0 -q";
+
};
+
};
+
}
-85
modules/home/apps/vscode.nix
···
-
{
-
lib,
-
pkgs,
-
config,
-
inputs,
-
...
-
}:
-
{
-
options.atelier.apps.vscode.enable = lib.mkEnableOption "Enable VSCode config";
-
config = lib.mkIf config.atelier.apps.vscode.enable {
-
nixpkgs.overlays = [
-
inputs.nix-vscode-extensions.overlays.default
-
inputs.catppuccin-vsc.overlays.default
-
];
-
programs.vscode = {
-
enable = true;
-
package = pkgs.unstable.vscode;
-
profiles.default = {
-
extensions = with pkgs.vscode-marketplace; [
-
ms-vscode.live-server
-
formulahendry.auto-rename-tag
-
edwinkofler.vscode-assorted-languages
-
golang.go
-
eamodio.gitlens
-
yzhang.markdown-all-in-one
-
github.vscode-github-actions
-
yoavbls.pretty-ts-errors
-
esbenp.prettier-vscode
-
ms-vscode.vscode-serial-monitor
-
prisma.prisma
-
ms-azuretools.vscode-docker
-
astro-build.astro-vscode
-
github.copilot
-
github.copilot-chat
-
dotjoshjohnson.xml
-
mikestead.dotenv
-
bradlc.vscode-tailwindcss
-
mechatroner.rainbow-csv
-
wakatime.vscode-wakatime
-
paulober.pico-w-go
-
ms-python.python
-
karunamurti.tera
-
biomejs.biome
-
bschulte.love
-
yinfei.luahelper
-
tamasfe.even-better-toml
-
fill-labs.dependi
-
rust-lang.rust-analyzer
-
dustypomerleau.rust-syntax
-
catppuccin.catppuccin-vsc
-
inputs.frc-nix.packages.${pkgs.system}.vscode-wpilib
-
];
-
userSettings = {
-
"editor.semanticHighlighting.enabled" = true;
-
"terminal.integrated.minimumContrastRatio" = 1;
-
"window.titleBarStyle" = "custom";
-
"gopls" = {
-
"ui.semanticTokens" = true;
-
};
-
"workbench.colorTheme" = "Catppuccin Macchiato";
-
"workbench.iconTheme" = "catppuccin-macchiato";
-
"catppuccin.accentColor" = lib.mkForce "blue";
-
"editor.fontFamily" = "'FiraCode Nerd Font', 'monospace', monospace";
-
"git.autofetch" = true;
-
"git.confirmSync" = false;
-
"github.copilot.editor.enableAutoCompletions" = false;
-
"editor.formatOnSave" = true;
-
"editor.defaultFormatter" = "biomejs.biome";
-
"[go]" = {
-
"editor.defaultFormatter" = "golang.go";
-
};
-
"[yaml]" = {
-
"editor.defaultFormatter" = "esbenp.prettier-vscode";
-
};
-
"[lua]" = {
-
"editor.defaultFormatter" = "yinfei.luahelper";
-
};
-
"[html]" = {
-
"editor.defaultFormatter" = "esbenp.prettier-vscode";
-
};
-
};
-
};
-
};
-
};
-
}
-26
modules/home/system/nixpkgs.nix
···
-
{
-
lib,
-
pkgs,
-
config,
-
inputs,
-
...
-
}:
-
{
-
options.nixpkgs.enable = lib.mkEnableOption "Enable custom nixpkgs overlays/config";
-
config = lib.mkIf config.nixpkgs.enable {
-
nixpkgs = {
-
overlays = [
-
(final: prev: {
-
unstable = import inputs.nixpkgs-unstable {
-
system = pkgs.stdenv.hostPlatform.system;
-
config.allowUnfree = true;
-
};
-
})
-
];
-
config = {
-
allowUnfree = true;
-
allowUnfreePredicate = _: true;
-
};
-
};
-
};
-
}
+26
modules/home/system/nixpkgs.nix.disabled
···
+
{
+
lib,
+
pkgs,
+
config,
+
inputs,
+
...
+
}:
+
{
+
options.nixpkgs.enable = lib.mkEnableOption "Enable custom nixpkgs overlays/config";
+
config = lib.mkIf config.nixpkgs.enable {
+
nixpkgs = {
+
overlays = [
+
(final: prev: {
+
unstable = import inputs.nixpkgs-unstable {
+
inherit (pkgs.stdenv.hostPlatform) system;
+
config.allowUnfree = true;
+
};
+
})
+
];
+
config = {
+
allowUnfree = true;
+
allowUnfreePredicate = _: true;
+
};
+
};
+
};
+
}
+386 -236
modules/home/system/shell.nix
···
inputs,
...
}:
+
let
+
tangled-setup = pkgs.writeShellScriptBin "tangled-setup" ''
+
# Configuration
+
default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul"
+
default_github_username="taciturnaxolotl"
+
default_knot_host="knot.dunkirk.sh"
+
+
# Verify git repository
+
if ! ${pkgs.git}/bin/git rev-parse --is-inside-work-tree &>/dev/null; then
+
${pkgs.gum}/bin/gum style --foreground 196 "Not a git repository"
+
exit 1
+
fi
+
+
repo_name=$(basename "$(${pkgs.git}/bin/git rev-parse --show-toplevel)")
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Configuring tangled remotes for: $repo_name"
+
echo
+
+
# Check current remotes
+
origin_url=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null)
+
github_url=$(${pkgs.git}/bin/git remote get-url github 2>/dev/null)
+
origin_is_knot=false
+
github_username="$default_github_username"
+
+
# Extract GitHub username from existing origin if it's GitHub
+
if [[ "$origin_url" == *"github.com"* ]]; then
+
github_username=$(echo "$origin_url" | ${pkgs.gnused}/bin/sed -E 's/.*github\.com[:/]([^/]+)\/.*$/\1/')
+
fi
+
+
# Check if origin points to knot
+
if [[ "$origin_url" == *"$default_knot_host"* ]]; then
+
origin_is_knot=true
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Origin → knot ($origin_url)"
+
elif [[ -n "$origin_url" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 214 "! Origin → $origin_url (not knot)"
+
else
+
${pkgs.gum}/bin/gum style --foreground 214 "! Origin not configured"
+
fi
+
+
# Check github remote
+
if [[ -n "$github_url" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ GitHub → $github_url"
+
else
+
${pkgs.gum}/bin/gum style --foreground 214 "! GitHub remote not configured"
+
fi
+
+
echo
+
+
# Configure origin remote if needed
+
if [[ "$origin_is_knot" = false ]]; then
+
should_migrate=true
+
if [[ -n "$origin_url" ]]; then
+
${pkgs.gum}/bin/gum confirm "Migrate origin from $origin_url to knot?" || should_migrate=false
+
fi
+
+
if [[ "$should_migrate" = true ]]; then
+
plc_id=$(${pkgs.gum}/bin/gum input --placeholder "$default_plc_id" --prompt "PLC ID: " --value "$default_plc_id")
+
plc_id=''${plc_id:-$default_plc_id}
+
+
if ${pkgs.git}/bin/git remote get-url origin &>/dev/null; then
+
${pkgs.git}/bin/git remote remove origin
+
fi
+
${pkgs.git}/bin/git remote add origin "git@$default_knot_host:''${plc_id}/''${repo_name}"
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Configured origin → git@$default_knot_host:''${plc_id}/''${repo_name}"
+
fi
+
fi
+
+
# Configure github remote if needed
+
if [[ -z "$github_url" ]]; then
+
username=$(${pkgs.gum}/bin/gum input --placeholder "$github_username" --prompt "GitHub username: " --value "$github_username")
+
username=''${username:-$github_username}
+
+
${pkgs.git}/bin/git remote add github "git@github.com:''${username}/''${repo_name}.git"
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Configured github → git@github.com:''${username}/''${repo_name}.git"
+
fi
+
+
echo
+
+
# Configure default push remote
+
current_remote=$(${pkgs.git}/bin/git config --get branch.main.remote 2>/dev/null)
+
if [[ -z "$current_remote" ]]; then
+
if ${pkgs.gum}/bin/gum confirm "Set origin (knot) as default push remote?"; then
+
${pkgs.git}/bin/git config branch.main.remote origin
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Default push remote → origin"
+
fi
+
elif [[ "$current_remote" != "origin" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 117 "Current default: $current_remote"
+
if ${pkgs.gum}/bin/gum confirm "Change default push remote to origin (knot)?"; then
+
${pkgs.git}/bin/git config branch.main.remote origin
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Default push remote → origin"
+
fi
+
else
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Default push remote is origin"
+
fi
+
'';
+
+
assh = pkgs.writeShellScriptBin "assh" ''
+
# SSH auto-reconnect
+
host=$1
+
port=$2
+
+
if [[ -z "$host" || -z "$port" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "Usage: assh <host> <port>"
+
exit 1
+
fi
+
+
${pkgs.gum}/bin/gum style --foreground 212 "Connecting to $host:$port (auto-reconnect enabled)..."
+
+
while true; do
+
${pkgs.openssh}/bin/ssh -p "$port" -o "BatchMode yes" "$host" || {
+
${pkgs.gum}/bin/gum style --foreground 214 "Connection lost. Reconnecting in 1s..."
+
sleep 1
+
}
+
done
+
'';
+
+
hackatime-summary = pkgs.writeShellScriptBin "hackatime-summary" ''
+
# Hackatime summary
+
user_id=""
+
use_waka=false
+
+
# Parse arguments
+
while [[ $# -gt 0 ]]; do
+
case "$1" in
+
--waka)
+
use_waka=true
+
shift
+
;;
+
*)
+
user_id="$1"
+
shift
+
;;
+
esac
+
done
+
+
if [[ -z "$user_id" ]]; then
+
user_id=$(${pkgs.gum}/bin/gum input --placeholder "Enter user ID" --prompt "User ID: ")
+
if [[ -z "$user_id" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No user ID provided"
+
exit 1
+
fi
+
fi
+
+
if [[ "$use_waka" = true ]]; then
+
host="waka.hackclub.com"
+
else
+
host="hackatime.hackclub.com"
+
fi
+
+
${pkgs.gum}/bin/gum spin --spinner dot --title "Fetching summary from $host for $user_id..." -- \
+
${pkgs.curl}/bin/curl -s -X 'GET' \
+
"https://$host/api/summary?user=''${user_id}&interval=month" \
+
-H 'accept: application/json' \
+
-H 'Authorization: Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608' \
+
> /tmp/hackatime-$$.json
+
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Summary for $user_id"
+
echo
+
+
# Extract and display total time
+
total_seconds=$(${pkgs.jq}/bin/jq -r '
+
if (.categories | length) > 0 then
+
(.categories | map(.total) | add)
+
elif (.projects | length) > 0 then
+
(.projects | map(.total) | add)
+
else
+
0
+
end
+
' /tmp/hackatime-$$.json)
+
+
if [[ "$total_seconds" -gt 0 ]]; then
+
hours=$((total_seconds / 3600))
+
minutes=$(((total_seconds % 3600) / 60))
+
seconds=$((total_seconds % 60))
+
${pkgs.gum}/bin/gum style --foreground 35 "Total time: ''${hours}h ''${minutes}m ''${seconds}s"
+
else
+
${pkgs.gum}/bin/gum style --foreground 214 "No activity recorded"
+
fi
+
+
echo
+
+
# Top projects
+
${pkgs.gum}/bin/gum style --bold "Top Projects:"
+
${pkgs.jq}/bin/jq -r '
+
if (.projects | length) > 0 then
+
.projects | sort_by(-.total) | .[0:10] | .[] |
+
" \(.key): \((.total / 3600 | floor))h \(((.total % 3600) / 60) | floor)m"
+
else
+
" No projects"
+
end
+
' /tmp/hackatime-$$.json
+
+
echo
+
+
# Top languages
+
${pkgs.gum}/bin/gum style --bold "Top Languages:"
+
${pkgs.jq}/bin/jq -r '
+
if (.languages | length) > 0 then
+
.languages | sort_by(-.total) | .[0:10] | .[] |
+
" \(.key): \((.total / 3600 | floor))h \(((.total % 3600) / 60) | floor)m"
+
else
+
" No languages"
+
end
+
' /tmp/hackatime-$$.json
+
+
rm -f /tmp/hackatime-$$.json
+
'';
+
+
now = pkgs.writeShellScriptBin "now" ''
+
# Post AtProto status updates
+
message=""
+
prompt_message=true
+
+
# Parse arguments
+
while [[ $# -gt 0 ]]; do
+
case "$1" in
+
-m|--message)
+
message="$2"
+
prompt_message=false
+
shift 2
+
;;
+
*)
+
${pkgs.gum}/bin/gum style --foreground 196 "Usage: now [-m|--message \"your message\"]"
+
exit 1
+
;;
+
esac
+
done
+
+
# Load account information from agenix secrets
+
if [[ -f "/run/agenix/bluesky" ]]; then
+
source "/run/agenix/bluesky"
+
else
+
${pkgs.gum}/bin/gum style --foreground 196 "Error: Bluesky credentials file not found at /run/agenix/bluesky"
+
exit 1
+
fi
+
+
# Prompt for message if none provided
+
if [[ "$prompt_message" = true ]]; then
+
message=$(${pkgs.gum}/bin/gum input --placeholder "What's happening?" --prompt "$ACCOUNT1 is: ")
+
if [[ -z "$message" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 214 "No message provided. Aborting."
+
exit 1
+
fi
+
fi
+
+
${pkgs.gum}/bin/gum spin --spinner dot --title "Posting to Bluesky..." -- /bin/bash <<EOF
+
# Generate JWT for ACCOUNT1
+
account1_response=\$(${pkgs.curl}/bin/curl -s -X POST \
+
-H "Content-Type: application/json" \
+
-d '{
+
"identifier": "'$ACCOUNT1'",
+
"password": "'$ACCOUNT1_PASSWORD'"
+
}' \
+
"https://bsky.social/xrpc/com.atproto.server.createSession")
+
+
account1_jwt=\$(echo "\$account1_response" | ${pkgs.jq}/bin/jq -r '.accessJwt')
+
+
if [[ -z "\$account1_jwt" || "\$account1_jwt" == "null" ]]; then
+
echo "Failed to authenticate account $ACCOUNT1" >&2
+
echo "Response: \$account1_response" >&2
+
exit 1
+
fi
+
+
# Generate JWT for ACCOUNT2
+
account2_response=\$(${pkgs.curl}/bin/curl -s -X POST \
+
-H "Content-Type: application/json" \
+
-d '{
+
"identifier": "'$ACCOUNT2'",
+
"password": "'$ACCOUNT2_PASSWORD'"
+
}' \
+
"https://bsky.social/xrpc/com.atproto.server.createSession")
+
+
account2_jwt=\$(echo "\$account2_response" | ${pkgs.jq}/bin/jq -r '.accessJwt')
+
+
if [[ -z "\$account2_jwt" || "\$account2_jwt" == "null" ]]; then
+
echo "Failed to authenticate account $ACCOUNT2" >&2
+
echo "Response: \$account2_response" >&2
+
exit 1
+
fi
+
+
# Post to ACCOUNT1 as a.status.updates
+
account1_post_response=\$(${pkgs.curl}/bin/curl -s -X POST \
+
-H "Content-Type: application/json" \
+
-H "Authorization: Bearer \$account1_jwt" \
+
-d '{
+
"collection": "a.status.update",
+
"repo": "'$ACCOUNT1'",
+
"record": {
+
"\$type": "a.status.update",
+
"text": "'"$message"'",
+
"createdAt": "'\$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
+
}
+
}' \
+
"https://bsky.social/xrpc/com.atproto.repo.createRecord")
+
+
if [[ \$(echo "\$account1_post_response" | ${pkgs.jq}/bin/jq -r 'has("error")') == "true" ]]; then
+
echo "Error posting to $ACCOUNT1:" >&2
+
echo "\$account1_post_response" | ${pkgs.jq}/bin/jq >&2
+
exit 1
+
fi
+
+
# Post to ACCOUNT2 as normal post
+
account2_post_response=\$(${pkgs.curl}/bin/curl -s -X POST \
+
-H "Content-Type: application/json" \
+
-H "Authorization: Bearer \$account2_jwt" \
+
-d '{
+
"collection": "app.bsky.feed.post",
+
"repo": "'$ACCOUNT2'",
+
"record": {
+
"\$type": "app.bsky.feed.post",
+
"text": "'"$message"'",
+
"createdAt": "'\$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
+
}
+
}' \
+
"https://bsky.social/xrpc/com.atproto.repo.createRecord")
+
+
if [[ \$(echo "\$account2_post_response" | ${pkgs.jq}/bin/jq -r 'has("error")') == "true" ]]; then
+
echo "Error posting to $ACCOUNT2:" >&2
+
echo "\$account2_post_response" | ${pkgs.jq}/bin/jq >&2
+
exit 1
+
fi
+
EOF
+
+
if [[ $? -eq 0 ]]; then
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Posted successfully!"
+
else
+
${pkgs.gum}/bin/gum style --foreground 196 "✗ Failed to post"
+
exit 1
+
fi
+
'';
+
+
ghostty-setup = pkgs.writeShellScriptBin "ghostty-setup" ''
+
# Copy Ghostty terminfo to remote host
+
target="$1"
+
+
if [[ -z "$target" ]]; then
+
target=$(${pkgs.gum}/bin/gum input --placeholder "user@host" --prompt "Remote host: ")
+
if [[ -z "$target" ]]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "No target provided"
+
exit 1
+
fi
+
fi
+
+
${pkgs.gum}/bin/gum style --bold --foreground 212 "Setting up Ghostty on $target"
+
echo
+
+
${pkgs.gum}/bin/gum spin --spinner dot --title "Copying SSH key to $target..." -- \
+
${pkgs.openssh}/bin/ssh-copy-id "$target" 2>&1
+
+
if [[ $? -ne 0 ]]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "✗ SSH key copy failed"
+
exit 2
+
fi
+
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ SSH key copied"
+
+
${pkgs.gum}/bin/gum spin --spinner dot --title "Installing xterm-ghostty terminfo on $target..." -- \
+
bash -c "${pkgs.ncurses}/bin/infocmp -x xterm-ghostty | ${pkgs.openssh}/bin/ssh '$target' 'tic -x -'" 2>&1
+
+
if [[ $? -ne 0 ]]; then
+
${pkgs.gum}/bin/gum style --foreground 196 "✗ Terminfo transfer failed"
+
exit 3
+
fi
+
+
${pkgs.gum}/bin/gum style --foreground 35 "✓ Terminfo installed"
+
echo
+
${pkgs.gum}/bin/gum style --foreground 35 --bold "Done! Ghostty is ready on $target"
+
'';
+
in
{
options.atelier.shell.enable = lib.mkEnableOption "Custom shell config";
config = lib.mkIf config.atelier.shell.enable {
···
template = "{{ if .SSHSession }}{{.HostName}} {{ end }}";
}
{
+
type = "text";
+
style = "plain";
+
background = "transparent";
+
foreground = "green";
+
template = "{{ if .Env.ZMX_SESSION }}[{{ .Env.ZMX_SESSION }}] {{ end }}";
+
}
+
{
type = "path";
style = "plain";
background = "transparent";
···
style = "plain";
foreground_templates = [
"{{if gt .Code 0}}red{{end}}"
-
"{{if eq .Code 0}}magenta{{end}}"
+
"{{if eq .Code 0}}{{if .Env.SSH_CONNECTION}}cyan{{else}}magenta{{end}}{{end}}"
];
background = "transparent";
template = "❯";
···
transient_prompt = {
foreground_templates = [
"{{if gt .Code 0}}red{{end}}"
-
"{{if eq .Code 0}}magenta{{end}}"
+
"{{if eq .Code 0}}{{if .Env.SSH_CONNECTION}}cyan{{else}}magenta{{end}}{{end}}"
];
background = "transparent";
template = "❯ ";
···
vim = "nvim";
};
initContent = ''
-
#ssh auto reconnect
-
assh() {
-
local host=$1
-
local port=$2
-
while true; do
-
ssh -p $port -o "BatchMode yes" $host || sleep 1
-
done
-
}
-
# hackatime summary
-
summary() {
-
local user_id=$1
-
curl -X 'GET' \
-
"https://waka.hackclub.com/api/summary?user=''${user_id}&interval=month" \
-
-H 'accept: application/json' \
-
-H 'Authorization: Bearer 2ce9e698-8a16-46f0-b49a-ac121bcfd608' | jq '. + {
-
"total_categories_sum": (.categories | map(.total) | add),
-
"total_categories_human_readable": (
-
(.categories | map(.total) | add) as $total_seconds |
-
"\($total_seconds / 3600 | floor)h \(($total_seconds % 3600) / 60 | floor)m \($total_seconds % 60)s"
-
),
-
"projectsKeys": (
-
.projects | sort_by(-.total) | map(.key)
-
)
-
}'
-
}
-
-
tangled() {
-
# Configuration variables - set these to your defaults
-
local default_plc_id="did:plc:krxbvxvis5skq7jj6eot23ul"
-
local default_github_username="taciturnaxolotl"
-
local extracted_github_username=""
-
-
# Check if current directory is a git repository
-
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
-
echo "Not a git repository"
-
return 1
-
fi
-
-
# Get the repository name from the current directory
-
local repo_name=$(basename "$(git rev-parse --show-toplevel)")
-
-
# Check if origin remote exists and points to ember
-
local origin_url=$(git remote get-url origin 2>/dev/null)
-
local origin_ember=false
-
-
if [[ -n "$origin_url" ]]; then
-
# Try to extract GitHub username if origin is a GitHub URL
-
if [[ "$origin_url" == *"github.com"* ]]; then
-
extracted_github_username=$(echo "$origin_url" | sed -E 's/.*github\.com[:/]([^/]+)\/.*$/\1/')
-
# Override the default username with the extracted one
-
default_github_username=$extracted_github_username
-
fi
-
-
if [[ "$origin_url" == *"ember"* ]]; then
-
origin_ember=true
-
echo "✅ Origin remote exists and points to ember"
-
else
-
echo "⚠️ Origin remote exists but doesn't point to ember"
-
fi
-
else
-
echo "⚠️ Origin remote doesn't exist"
-
fi
-
-
# Check if github remote exists
-
local github_exists=false
-
if git remote get-url github &>/dev/null; then
-
github_exists=true
-
echo "✅ GitHub remote exists"
-
else
-
echo "⚠️ GitHub remote doesn't exist"
-
fi
-
-
# Fix remotes if needed
-
if [[ "$origin_ember" = false || "$github_exists" = false ]]; then
-
echo "Setting up remotes..."
-
-
# Prompt for PLC identifier if needed
-
local plc_id=""
-
if [[ "$origin_ember" = false ]]; then
-
echo -n "Enter your PLC identifier [default: $default_plc_id]: "
-
read plc_input
-
plc_id=''${plc_input:-$default_plc_id}
-
fi
-
-
# Prompt for GitHub username with default from origin if available
-
local github_username=""
-
if [[ "$github_exists" = false ]]; then
-
echo -n "Enter your GitHub username [default: $default_github_username]: "
-
read github_input
-
github_username=''${github_input:-$default_github_username}
-
fi
-
-
# Set up origin remote if needed
-
if [[ "$origin_ember" = false && -n "$plc_id" ]]; then
-
if git remote get-url origin &>/dev/null; then
-
git remote remove origin
-
fi
-
git remote add origin "git@ember:''${plc_id}/''${repo_name}"
-
echo "✅ Set up origin remote: git@ember:''${plc_id}/''${repo_name}"
-
fi
-
-
# Set up GitHub remote if needed
-
if [[ "$github_exists" = false && -n "$github_username" ]]; then
-
git remote add github "git@github.com:''${github_username}/''${repo_name}.git"
-
echo "✅ Set up GitHub remote: git@github.com:''${github_username}/''${repo_name}.git"
-
fi
-
else
-
echo "Remotes are correctly configured"
-
fi
-
}
-
-
# Post AtProto status updates
-
now() {
-
local message=""
-
local prompt_message=true
-
local account1_name=""
-
local account2_name=""
-
local account1_jwt=""
-
local account2_jwt=""
-
-
# Load account information from agenix secrets
-
if [[ -f "/run/agenix/bluesky" ]]; then
-
source "/run/agenix/bluesky"
-
else
-
echo "Error: Bluesky credentials file not found at /run/agenix/bluesky"
-
return 1
-
fi
-
-
# Parse arguments
-
while [[ $# -gt 0 ]]; do
-
case "$1" in
-
-m|--message)
-
message="$2"
-
prompt_message=false
-
shift 2
-
;;
-
*)
-
echo "Usage: now [-m|--message \"your message\"]"
-
return 1
-
;;
-
esac
-
done
-
-
# Prompt for message if none provided
-
if [[ "$prompt_message" = true ]]; then
-
echo -n "$ACCOUNT1 is: "
-
read message
-
-
if [[ -z "$message" ]]; then
-
echo "No message provided. Aborting."
-
return 1
-
fi
-
fi
-
-
# Generate JWT for ACCOUNT1
-
local account1_response=$(curl -s -X POST \
-
-H "Content-Type: application/json" \
-
-d '{
-
"identifier": "'$ACCOUNT1'",
-
"password": "'$ACCOUNT1_PASSWORD'"
-
}' \
-
"https://bsky.social/xrpc/com.atproto.server.createSession")
-
-
account1_jwt=$(echo "$account1_response" | jq -r '.accessJwt')
-
-
if [[ -z "$account1_jwt" || "$account1_jwt" == "null" ]]; then
-
echo "Failed to authenticate account $ACCOUNT1"
-
echo "Response: $account1_response"
-
return 1
-
fi
-
-
# Generate JWT for ACCOUNT2
-
local account2_response=$(curl -s -X POST \
-
-H "Content-Type: application/json" \
-
-d '{
-
"identifier": "'$ACCOUNT2'",
-
"password": "'$ACCOUNT2_PASSWORD'"
-
}' \
-
"https://bsky.social/xrpc/com.atproto.server.createSession")
-
-
account2_jwt=$(echo "$account2_response" | jq -r '.accessJwt')
-
-
if [[ -z "$account2_jwt" || "$account2_jwt" == "null" ]]; then
-
echo "Failed to authenticate account $ACCOUNT2"
-
echo "Response: $account2_response"
-
return 1
-
fi
-
-
# Post to ACCOUNT1 as a.status.updates
-
local account1_post_response=$(curl -s -X POST \
-
-H "Content-Type: application/json" \
-
-H "Authorization: Bearer $account1_jwt" \
-
-d '{
-
"collection": "a.status.update",
-
"repo": "'$ACCOUNT1'",
-
"record": {
-
"$type": "a.status.update",
-
"text": "'"$message"'",
-
"createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
-
}
-
}' \
-
"https://bsky.social/xrpc/com.atproto.repo.createRecord")
-
-
if [[ $(echo "$account1_post_response" | jq -r 'has("error")') == "true" ]]; then
-
echo "Error posting to $ACCOUNT1:"
-
echo "$account1_post_response" | jq
-
return 1
-
fi
-
-
# Post to ACCOUNT2 as normal post
-
local account2_post_response=$(curl -s -X POST \
-
-H "Content-Type: application/json" \
-
-H "Authorization: Bearer $account2_jwt" \
-
-d '{
-
"collection": "app.bsky.feed.post",
-
"repo": "'$ACCOUNT2'",
-
"record": {
-
"$type": "app.bsky.feed.post",
-
"text": "'"$message"'",
-
"createdAt": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
-
}
-
}' \
-
"https://bsky.social/xrpc/com.atproto.repo.createRecord")
-
-
if [[ $(echo "$account2_post_response" | jq -r 'has("error")') == "true" ]]; then
-
echo "Error posting to $ACCOUNT2:"
-
echo "$account2_post_response" | jq
-
return 1
-
fi
-
-
echo "done"
-
}
-
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}'
zstyle ':completion:*' list-colors "''${(s.:.)LS_COLORS}"
zstyle ':completion:*' menu no
···
};
home.packages = with pkgs; [
-
inputs.terminal-wakatime.packages.${pkgs.system}.default
+
tangled-setup
+
assh
+
hackatime-summary
+
now
+
ghostty-setup
+
pkgs.unstable.wakatime-cli
+
inputs.terminal-wakatime.packages.${pkgs.stdenv.hostPlatform.system}.default
unzip
dog
dust
···
fd
eza
bat
+
ripgrep
ripgrep-all
neofetch
+
glow
];
atelier.shell.git.enable = lib.mkDefault true;
+212
modules/nixos/services/README.md
···
+
# NixOS Service Modules
+
+
This directory contains reusable NixOS service modules for deploying applications.
+
+
## Architecture
+
+
Each service module follows a common pattern for deploying TypeScript/Bun applications:
+
+
### Directory Structure
+
- **Data Directory**: `/var/lib/<service-name>/`
+
- `app/` - Git repository clone
+
- `data/` - Application data (databases, uploads, etc.)
+
+
### Systemd Service Pattern
+
+
1. **ExecStartPre** (runs as root with `+` prefix):
+
- Creates data directories
+
- Sets ownership to service user
+
- Ensures proper permissions
+
+
2. **preStart** (runs as service user):
+
- Clones git repository if needed
+
- Pulls latest changes (if `autoUpdate` enabled)
+
- Runs `bun install`
+
- Initializes database if needed
+
+
3. **ExecStart** (runs as service user):
+
- Starts the application with `bun start`
+
+
### Common Options
+
+
All service modules support:
+
+
```nix
+
atelier.services.<service-name> = {
+
enable = true; # Enable the service
+
domain = "app.example.com"; # Domain for Caddy reverse proxy
+
port = 3000; # Port the app listens on
+
dataDir = "/var/lib/<service>"; # Data storage location
+
secretsFile = path; # agenix secrets file
+
repository = "https://..."; # Git repository URL
+
autoUpdate = true; # Git pull on service restart
+
};
+
```
+
+
### Secrets Management
+
+
Secrets are managed using [agenix](https://github.com/ryantm/agenix):
+
+
1. Add secret to `secrets/secrets.nix`:
+
```nix
+
"service-name.age".publicKeys = [ kierank ];
+
```
+
+
2. Create and encrypt the secret:
+
```bash
+
agenix -e secrets/service-name.age
+
```
+
+
3. Add environment variables (one per line):
+
```
+
DATABASE_URL=postgres://...
+
API_KEY=xxxxx
+
SECRET_TOKEN=yyyyy
+
```
+
+
4. Reference in machine config:
+
```nix
+
age.secrets.service-name = {
+
file = ../../secrets/service-name.age;
+
owner = "service-name";
+
};
+
+
atelier.services.service-name = {
+
secretsFile = config.age.secrets.service-name.path;
+
};
+
```
+
+
### Reverse Proxy (Caddy)
+
+
Each service automatically configures a Caddy virtual host with:
+
- Cloudflare DNS challenge for TLS
+
- Reverse proxy to the application port
+
+
## GitHub Actions Deployment
+
+
Services can be deployed via GitHub Actions using SSH over Tailscale.
+
+
### Prerequisites
+
+
1. **Tailscale OAuth Client**:
+
- Create at https://login.tailscale.com/admin/settings/oauth
+
- Required scope: `auth_keys` (to authenticate ephemeral nodes)
+
- Add to GitHub repo secrets:
+
- `TS_OAUTH_CLIENT_ID`
+
- `TS_OAUTH_SECRET`
+
+
2. **SSH Access**:
+
- Add the service user to Tailscale SSH ACLs
+
- Example in `tailscale.com/admin/acls`:
+
```json
+
"ssh": [
+
{
+
"action": "accept",
+
"src": ["tag:ci"],
+
"dst": ["tag:server"],
+
"users": ["cachet", "hn-alerts", "root"]
+
}
+
]
+
```
+
+
### Workflow Template
+
+
Create `.github/workflows/deploy-<service>.yaml`:
+
+
```yaml
+
name: Deploy <Service Name>
+
+
on:
+
push:
+
branches:
+
- main
+
workflow_dispatch:
+
+
jobs:
+
deploy:
+
runs-on: ubuntu-latest
+
steps:
+
- uses: actions/checkout@v3
+
+
- name: Setup Tailscale
+
uses: tailscale/github-action@v3
+
with:
+
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
+
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
+
tags: tag:ci
+
use-cache: "true"
+
+
- name: Configure SSH
+
run: |
+
mkdir -p ~/.ssh
+
echo "StrictHostKeyChecking no" >> ~/.ssh/config
+
+
- name: Deploy to server
+
run: |
+
ssh <service-user>@<hostname> << 'EOF'
+
cd /var/lib/<service>/app
+
git fetch --all
+
git reset --hard origin/main
+
bun install
+
sudo /run/current-system/sw/bin/systemctl restart <service>.service
+
EOF
+
+
- name: Wait for service to start
+
run: sleep 10
+
+
- name: Health check
+
run: |
+
HEALTH_URL="https://<domain>/health"
+
MAX_RETRIES=6
+
RETRY_DELAY=5
+
+
for i in $(seq 1 $MAX_RETRIES); do
+
echo "Health check attempt $i/$MAX_RETRIES..."
+
+
RESPONSE=$(curl -s -w "\n%{http_code}" "$HEALTH_URL" || echo "000")
+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
+
BODY=$(echo "$RESPONSE" | sed '$d')
+
+
if [ "$HTTP_CODE" = "200" ]; then
+
echo "✅ Service is healthy"
+
echo "$BODY"
+
exit 0
+
fi
+
+
echo "❌ Health check failed with HTTP $HTTP_CODE"
+
echo "$BODY"
+
+
if [ $i -lt $MAX_RETRIES ]; then
+
echo "Retrying in ${RETRY_DELAY}s..."
+
sleep $RETRY_DELAY
+
fi
+
done
+
+
echo "❌ Health check failed after $MAX_RETRIES attempts"
+
exit 1
+
```
+
+
### Deployment Flow
+
+
1. Push to `main` branch triggers workflow
+
2. GitHub Actions runner joins Tailscale network
+
3. SSH to service user on target server
+
4. Git pull latest changes
+
5. Install dependencies
+
6. Restart systemd service
+
7. Verify health check endpoint
+
+
## Creating a New Service Module
+
+
1. Copy an existing module (e.g., `cachet.nix` or `hn-alerts.nix`)
+
2. Update service name, user, and group
+
3. Adjust environment variables as needed
+
4. Add database initialization if required
+
5. Configure secrets in `secrets/secrets.nix`
+
6. Import in machine config
+
7. Create GitHub Actions workflow (if needed)
+
+
## Example Services
+
+
- **cachet** - Slack emoji/profile cache
+
- **hn-alerts** - Hacker News monitoring and alerts
+162
modules/nixos/services/battleship-arena.nix
···
+
{ config, lib, pkgs, ... }:
+
+
with lib;
+
+
let
+
cfg = config.atelier.services.battleship-arena;
+
in
+
{
+
options.atelier.services.battleship-arena = {
+
enable = mkEnableOption "battleship-arena service";
+
+
domain = mkOption {
+
type = types.str;
+
default = "battleship.dunkirk.sh";
+
description = "Domain name for the web interface";
+
};
+
+
sshPort = mkOption {
+
type = types.port;
+
default = 2222;
+
description = "SSH port for battleship arena";
+
};
+
+
webPort = mkOption {
+
type = types.port;
+
default = 8081;
+
description = "Web interface port";
+
};
+
+
uploadDir = mkOption {
+
type = types.str;
+
default = "/var/lib/battleship-arena/submissions";
+
description = "Directory for uploaded submissions";
+
};
+
+
resultsDb = mkOption {
+
type = types.str;
+
default = "/var/lib/battleship-arena/results.db";
+
description = "Path to results database";
+
};
+
+
adminPasscode = mkOption {
+
type = types.str;
+
default = "battleship-admin-override";
+
description = "Admin passcode for batch uploads";
+
};
+
+
secretsFile = mkOption {
+
type = types.nullOr types.path;
+
default = null;
+
description = "Path to agenix secrets file containing BATTLESHIP_ADMIN_PASSCODE";
+
};
+
+
package = mkOption {
+
type = types.package;
+
description = "The battleship-arena package to use";
+
};
+
};
+
+
config = mkIf cfg.enable {
+
users.users.battleship-arena = {
+
isSystemUser = true;
+
group = "battleship-arena";
+
home = "/var/lib/battleship-arena";
+
createHome = true;
+
};
+
+
users.groups.battleship-arena = {};
+
+
systemd.services.battleship-arena = {
+
description = "Battleship Arena SSH/Web Service";
+
after = [ "network.target" ];
+
wantedBy = [ "multi-user.target" ];
+
+
environment = {
+
BATTLESHIP_HOST = "0.0.0.0";
+
BATTLESHIP_SSH_PORT = toString cfg.sshPort;
+
BATTLESHIP_WEB_PORT = toString cfg.webPort;
+
BATTLESHIP_UPLOAD_DIR = cfg.uploadDir;
+
BATTLESHIP_RESULTS_DB = cfg.resultsDb;
+
BATTLESHIP_ADMIN_PASSCODE = cfg.adminPasscode;
+
BATTLESHIP_EXTERNAL_URL = "https://${cfg.domain}";
+
BATTLESHIP_ENGINE_PATH = "/var/lib/battleship-arena/battleship-engine";
+
CPLUS_INCLUDE_PATH = "/var/lib/battleship-arena/battleship-engine/include";
+
};
+
+
path = [ pkgs.gcc pkgs.coreutils ];
+
+
serviceConfig = {
+
Type = "simple";
+
User = "battleship-arena";
+
Group = "battleship-arena";
+
WorkingDirectory = "/var/lib/battleship-arena";
+
ExecStart = "${cfg.package}/bin/battleship-arena";
+
Restart = "always";
+
RestartSec = "10s";
+
+
# Load secrets if provided
+
EnvironmentFile = mkIf (cfg.secretsFile != null) cfg.secretsFile;
+
+
# Security hardening
+
NoNewPrivileges = true;
+
PrivateTmp = true;
+
ProtectSystem = "strict";
+
ProtectHome = true;
+
ReadWritePaths = [ "/var/lib/battleship-arena" ];
+
};
+
+
preStart = ''
+
mkdir -p ${cfg.uploadDir}
+
mkdir -p $(dirname ${cfg.resultsDb})
+
chown -R battleship-arena:battleship-arena ${cfg.uploadDir}
+
chmod -R u+rwX ${cfg.uploadDir}
+
+
# Generate SSH host key if it doesn't exist
+
if [ ! -f /var/lib/battleship-arena/.ssh/battleship_arena ]; then
+
mkdir -p /var/lib/battleship-arena/.ssh
+
${pkgs.openssh}/bin/ssh-keygen -t ed25519 -f /var/lib/battleship-arena/.ssh/battleship_arena -N ""
+
chown -R battleship-arena:battleship-arena /var/lib/battleship-arena/.ssh
+
fi
+
+
# Copy battleship-engine to writable directory
+
chmod -R u+w /var/lib/battleship-arena/battleship-engine 2>/dev/null || true
+
rm -rf /var/lib/battleship-arena/battleship-engine
+
cp -r ${cfg.package}/share/battleship-arena/battleship-engine /var/lib/battleship-arena/
+
chown -R battleship-arena:battleship-arena /var/lib/battleship-arena/battleship-engine
+
chmod -R u+rwX /var/lib/battleship-arena/battleship-engine
+
'';
+
};
+
+
# Service to recalculate Glicko-2 ratings (manual trigger only)
+
# Ratings automatically recalculate after each round-robin
+
# Use: sudo systemctl start battleship-arena-recalculate
+
systemd.services.battleship-arena-recalculate = {
+
description = "Recalculate Battleship Arena Glicko-2 Ratings";
+
+
environment = {
+
BATTLESHIP_RESULTS_DB = cfg.resultsDb;
+
};
+
+
serviceConfig = {
+
Type = "oneshot";
+
User = "battleship-arena";
+
Group = "battleship-arena";
+
WorkingDirectory = "/var/lib/battleship-arena";
+
ExecStart = "${cfg.package}/bin/battleship-arena recalculate-ratings";
+
};
+
};
+
+
# Allow battleship-arena user to create transient systemd units for sandboxing
+
security.polkit.extraConfig = ''
+
polkit.addRule(function(action, subject) {
+
if (action.id == "org.freedesktop.systemd1.manage-units" &&
+
subject.user == "battleship-arena") {
+
return polkit.Result.YES;
+
}
+
});
+
'';
+
+
networking.firewall.allowedTCPPorts = [ cfg.sshPort ];
+
};
+
}
+97
modules/nixos/services/bore/404.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>404 - bore</title>
+
<link rel="icon"
+
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚇</text></svg>">
+
<style>
+
* {
+
margin: 0;
+
padding: 0;
+
box-sizing: border-box;
+
}
+
+
body {
+
font-family: 'SF Mono', 'Monaco', monospace;
+
background: #0d1117;
+
color: #e6edf3;
+
min-height: 100vh;
+
display: flex;
+
align-items: center;
+
justify-content: center;
+
padding: 2rem;
+
}
+
+
.container {
+
text-align: center;
+
max-width: 600px;
+
}
+
+
.error-code {
+
font-size: 8rem;
+
font-weight: 700;
+
color: #8b949e;
+
line-height: 1;
+
margin-bottom: 1rem;
+
}
+
+
.emoji {
+
font-size: 4rem;
+
margin-bottom: 2rem;
+
opacity: 0.5;
+
}
+
+
h1 {
+
font-size: 2rem;
+
margin-bottom: 1rem;
+
color: #e6edf3;
+
}
+
+
p {
+
color: #8b949e;
+
font-size: 1rem;
+
margin-bottom: 2rem;
+
line-height: 1.6;
+
}
+
+
.countdown {
+
color: #14b8a6;
+
font-size: 1.2rem;
+
font-weight: 600;
+
font-variant-numeric: tabular-nums;
+
}
+
</style>
+
</head>
+
+
<body>
+
<div class="container">
+
<div class="emoji">🚇</div>
+
<div class="error-code">404</div>
+
<h1>wrong stop!</h1>
+
<p>this tunnel doesn't go anywhere. maybe it was never built, or perhaps it collapsed?</p>
+
<p>redirecting in <span class="countdown" id="countdown">5.000</span>s</p>
+
</div>
+
+
<script>
+
let timeLeft = 5000; // 5 seconds in milliseconds
+
const countdownEl = document.getElementById('countdown');
+
const startTime = Date.now();
+
+
const interval = setInterval(() => {
+
const elapsed = Date.now() - startTime;
+
timeLeft = Math.max(0, 5000 - elapsed);
+
+
countdownEl.textContent = (timeLeft / 1000).toFixed(3);
+
+
if (timeLeft <= 0) {
+
clearInterval(interval);
+
window.location.href = 'https://bore.dunkirk.sh';
+
}
+
}, 10); // Update every 10ms for smooth countdown
+
</script>
+
</body>
+
+
</html>
+48
modules/nixos/services/bore/README.md
···
+
# Bore
+
+
![screenshot](https://hc-cdn.hel1.your-objectstorage.com/s/v3/7652f29dacb8f76d_screenshot_2025-12-09_at_16.57.47.png)
+
+
Bore is a lightweight wrapper around `frp` which provides a dashboard and a nice `gum` based cli. It supports HTTP, TCP, and UDP tunneling. If you would like to run this in your own nix flake then simplify vendor this folder and `./modules/home/bore` and import the folders into the appropriate home manager and nixos configurations.
+
+
## Client Configuration
+
+
```nix
+
atelier = {
+
bore = {
+
enable = true;
+
authTokenFile = osConfig.age.secrets.bore.path
+
};
+
}
+
```
+
+
and be sure to have a definition for your agenix secret in the osConfig as well:
+
+
```nix
+
age = {
+
identityPaths = [
+
"path/to/ssh/key"
+
];
+
secrets = {
+
bore = {
+
file = ./path/to/bore.age;
+
owner = "username";
+
};
+
};
+
}
+
```
+
+
## Server Configuration
+
+
For TCP and UDP tunneling support, configure the server with allowed port ranges:
+
+
```nix
+
atelier.services.frps = {
+
enable = true;
+
domain = "bore.dunkirk.sh";
+
authTokenFile = config.age.secrets.bore.path;
+
allowedTCPPorts = [ 20000 20001 20002 20003 20004 ];
+
allowedUDPPorts = [ 20000 20001 20002 20003 20004 ];
+
};
+
```
+
+
The secret file is just a oneline file with the key in it. If you do end up deploying this feel free to email me and let me know! I would love to hear about your setup!
+190
modules/nixos/services/bore/bore.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
let
+
cfg = config.atelier.services.frps;
+
in
+
{
+
options.atelier.services.frps = {
+
enable = lib.mkEnableOption "frp server for tunneling services";
+
+
bindAddr = lib.mkOption {
+
type = lib.types.str;
+
default = "0.0.0.0";
+
description = "Address to bind frp server to";
+
};
+
+
bindPort = lib.mkOption {
+
type = lib.types.port;
+
default = 7000;
+
description = "Port for frp control connection";
+
};
+
+
vhostHTTPPort = lib.mkOption {
+
type = lib.types.port;
+
default = 7080;
+
description = "Port for HTTP virtual host traffic";
+
};
+
+
allowedTCPPorts = lib.mkOption {
+
type = lib.types.listOf lib.types.port;
+
default = lib.lists.range 20000 20099;
+
example = [ 20000 20001 20002 20003 20004 ];
+
description = "TCP port range to allow for TCP tunnels (default: 20000-20099)";
+
};
+
+
allowedUDPPorts = lib.mkOption {
+
type = lib.types.listOf lib.types.port;
+
default = lib.lists.range 20000 20099;
+
example = [ 20000 20001 20002 20003 20004 ];
+
description = "UDP port range to allow for UDP tunnels (default: 20000-20099)";
+
};
+
+
authToken = lib.mkOption {
+
type = lib.types.nullOr lib.types.str;
+
default = null;
+
description = "Authentication token for clients (deprecated: use authTokenFile)";
+
};
+
+
authTokenFile = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
default = null;
+
description = "Path to file containing authentication token";
+
};
+
+
domain = lib.mkOption {
+
type = lib.types.str;
+
example = "bore.dunkirk.sh";
+
description = "Base domain for subdomains (e.g., *.bore.dunkirk.sh)";
+
};
+
+
enableCaddy = lib.mkOption {
+
type = lib.types.bool;
+
default = true;
+
description = "Automatically configure Caddy reverse proxy for wildcard domain";
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
assertions = [
+
{
+
assertion = cfg.authToken != null || cfg.authTokenFile != null;
+
message = "Either authToken or authTokenFile must be set for frps";
+
}
+
];
+
+
# Open firewall ports for frp control connection and TCP/UDP tunnels
+
networking.firewall.allowedTCPPorts = [ cfg.bindPort ] ++ cfg.allowedTCPPorts;
+
networking.firewall.allowedUDPPorts = cfg.allowedUDPPorts;
+
+
# frp server service
+
systemd.services.frps =
+
let
+
tokenConfig =
+
if cfg.authTokenFile != null then
+
''
+
auth.tokenSource.type = "file"
+
auth.tokenSource.file.path = "${cfg.authTokenFile}"
+
''
+
else
+
''auth.token = "${cfg.authToken}"'';
+
+
configFile = pkgs.writeText "frps.toml" ''
+
bindAddr = "${cfg.bindAddr}"
+
bindPort = ${toString cfg.bindPort}
+
vhostHTTPPort = ${toString cfg.vhostHTTPPort}
+
+
# Dashboard and Prometheus metrics
+
webServer.addr = "127.0.0.1"
+
webServer.port = 7400
+
enablePrometheus = true
+
+
# Authentication token - clients need this to connect
+
auth.method = "token"
+
${tokenConfig}
+
+
# Subdomain support for *.${cfg.domain}
+
subDomainHost = "${cfg.domain}"
+
+
# Allow port ranges for TCP/UDP tunnels
+
# Format: [[{"start": 20000, "end": 20099}]]
+
allowPorts = [
+
{ start = 20000, end = 20099 }
+
]
+
+
# Custom 404 page
+
custom404Page = "${./404.html}"
+
+
# Logging
+
log.to = "console"
+
log.level = "info"
+
'';
+
in
+
{
+
description = "frp server for ${cfg.domain} tunneling";
+
after = [ "network.target" ];
+
wantedBy = [ "multi-user.target" ];
+
serviceConfig = {
+
Type = "simple";
+
Restart = "on-failure";
+
RestartSec = "5s";
+
ExecStart = "${pkgs.frp}/bin/frps -c ${configFile}";
+
};
+
};
+
+
# Automatically configure Caddy for wildcard domain
+
services.caddy = lib.mkIf cfg.enableCaddy {
+
# Dashboard for base domain
+
virtualHosts."${cfg.domain}" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
+
# Proxy /api/* to frps dashboard
+
handle /api/* {
+
reverse_proxy localhost:7400
+
}
+
+
# Serve dashboard HTML
+
handle {
+
root * ${./.}
+
try_files dashboard.html
+
file_server
+
}
+
'';
+
};
+
+
# Wildcard subdomain proxy to frps
+
virtualHosts."*.${cfg.domain}" = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
header {
+
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
+
}
+
reverse_proxy localhost:${toString cfg.vhostHTTPPort} {
+
header_up X-Forwarded-Proto {scheme}
+
header_up X-Forwarded-For {remote}
+
header_up Host {host}
+
}
+
handle_errors {
+
@404 expression {http.error.status_code} == 404
+
handle @404 {
+
root * ${./.}
+
rewrite * /404.html
+
file_server
+
}
+
}
+
'';
+
};
+
};
+
};
+
}
+24
modules/nixos/services/bore/bore.toml.example
···
+
# bore tunnel configuration
+
# Save this file as "bore.toml" in your project directory
+
+
[myapp]
+
port = 8000
+
+
[api]
+
port = 3000
+
protocol = "http"
+
label = "dev"
+
+
[frontend]
+
port = 5173
+
label = "local"
+
+
[database]
+
port = 5432
+
protocol = "tcp"
+
label = "postgres"
+
+
[game-server]
+
port = 27015
+
protocol = "udp"
+
label = "game"
+626
modules/nixos/services/bore/dashboard.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>bore</title>
+
<meta name="description" content="bore - secure tunneling service for exposing local services to the internet">
+
<meta property="og:title" content="bore - tunnel dashboard">
+
<meta property="og:description" content="secure tunneling service powered by frp on bore.dunkirk.sh">
+
<meta property="og:type" content="website">
+
<meta property="og:url" content="https://bore.dunkirk.sh">
+
<meta name="twitter:card" content="summary">
+
<meta name="twitter:title" content="bore - tunnel dashboard">
+
<meta name="twitter:description" content="secure tunneling service powered by frp on bore.dunkirk.sh">
+
<link rel="icon"
+
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🚇</text></svg>">
+
<style>
+
* {
+
margin: 0;
+
padding: 0;
+
box-sizing: border-box;
+
}
+
+
body {
+
font-family: 'SF Mono', 'Monaco', monospace;
+
background: #0d1117;
+
color: #e6edf3;
+
padding: 2rem;
+
line-height: 1.5;
+
min-height: 100vh;
+
display: flex;
+
flex-direction: column;
+
}
+
+
body.loading .container {
+
opacity: 0;
+
}
+
+
.loading-bar {
+
position: fixed;
+
top: 0;
+
left: 0;
+
width: 100%;
+
height: 3px;
+
background: transparent;
+
z-index: 9999;
+
overflow: hidden;
+
}
+
+
.loading-bar::before {
+
content: '';
+
position: absolute;
+
top: 0;
+
left: 0;
+
width: 100%;
+
height: 100%;
+
background: linear-gradient(90deg, #14b8a6, #fb923c);
+
animation: loading 1.5s ease-in-out infinite;
+
}
+
+
body:not(.loading) .loading-bar {
+
display: none;
+
}
+
+
@keyframes loading {
+
0% {
+
transform: translateX(-100%);
+
}
+
50% {
+
transform: translateX(0%);
+
}
+
100% {
+
transform: translateX(100%);
+
}
+
}
+
+
.container {
+
max-width: 1200px;
+
margin: 0 auto;
+
flex: 1;
+
width: 100%;
+
opacity: 1;
+
transition: opacity 0.3s ease-in-out;
+
}
+
+
header {
+
margin-bottom: 3rem;
+
}
+
+
h1 {
+
font-size: 2.5rem;
+
background: linear-gradient(135deg, #14b8a6, #fb923c);
+
-webkit-background-clip: text;
+
-webkit-text-fill-color: transparent;
+
margin-bottom: 0.5rem;
+
}
+
+
.subtitle {
+
color: #8b949e;
+
font-size: 0.95rem;
+
}
+
+
.stats {
+
display: grid;
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+
gap: 1rem;
+
margin-bottom: 2rem;
+
}
+
+
.stat-card {
+
background: #161b22;
+
border: 1px solid #30363d;
+
border-radius: 0;
+
padding: 1.5rem;
+
}
+
+
.stat-label {
+
color: #8b949e;
+
font-size: 0.85rem;
+
margin-bottom: 0.5rem;
+
}
+
+
.stat-value {
+
font-size: 2rem;
+
font-weight: 600;
+
color: #14b8a6;
+
}
+
+
.stat-value.orange {
+
color: #fb923c;
+
}
+
+
.section {
+
background: #161b22;
+
border: 1px solid #30363d;
+
border-radius: 0;
+
padding: 1.5rem;
+
margin-bottom: 2rem;
+
}
+
+
h2 {
+
color: #14b8a6;
+
font-size: 1.2rem;
+
margin-bottom: 1.5rem;
+
display: flex;
+
align-items: center;
+
gap: 0.5rem;
+
}
+
+
.tunnel-list {
+
display: flex;
+
flex-direction: column;
+
gap: 1rem;
+
}
+
+
.tunnel {
+
background: #0d1117;
+
border: 1px solid #30363d;
+
border-radius: 0;
+
padding: 1rem;
+
display: grid;
+
grid-template-columns: 1fr auto;
+
gap: 1rem;
+
align-items: center;
+
}
+
+
.tunnel-icon {
+
display: none;
+
}
+
+
.tunnel-info {
+
flex: 1;
+
}
+
+
.tunnel-name {
+
color: #e6edf3;
+
font-weight: 600;
+
margin-bottom: 0.25rem;
+
display: flex;
+
align-items: center;
+
gap: 0.5rem;
+
}
+
+
.tunnel-label {
+
display: inline-block;
+
padding: 0.125rem 0.5rem;
+
font-size: 0.7rem;
+
font-weight: 500;
+
border-radius: 0;
+
margin-left: 0.25rem;
+
border: 1px solid;
+
}
+
+
.tunnel-url {
+
color: #8b949e;
+
font-size: 0.85rem;
+
}
+
+
.tunnel-url a {
+
color: #14b8a6;
+
text-decoration: none;
+
}
+
+
.tunnel-url a:hover {
+
text-decoration: underline;
+
}
+
+
.tunnel-status {
+
padding: 0.25rem 0.75rem;
+
border-radius: 0;
+
font-size: 0.8rem;
+
font-weight: 500;
+
}
+
+
.status-online {
+
background: rgba(20, 184, 166, 0.2);
+
color: #14b8a6;
+
border: 1px solid #14b8a6;
+
}
+
+
.empty-state {
+
text-align: center;
+
padding: 3rem 1rem;
+
color: #8b949e;
+
}
+
+
.empty-icon {
+
font-size: 3rem;
+
margin-bottom: 1rem;
+
opacity: 0.5;
+
}
+
+
code {
+
background: #0d1117;
+
padding: 0.2rem 0.5rem;
+
border-radius: 0;
+
color: #fb923c;
+
font-size: 0.9rem;
+
}
+
+
.usage {
+
background: #0d1117;
+
padding: 1rem;
+
border-radius: 0;
+
margin-top: 1rem;
+
}
+
+
.usage pre {
+
color: #8b949e;
+
font-size: 0.9rem;
+
overflow-x: auto;
+
}
+
+
.last-updated {
+
text-align: center;
+
color: #8b949e;
+
font-size: 0.8rem;
+
margin-top: 2rem;
+
padding: 2rem 0;
+
}
+
+
.last-updated a {
+
color: #14b8a6;
+
text-decoration: none;
+
}
+
+
.last-updated a:hover {
+
text-decoration: underline;
+
}
+
+
.offline-tunnels {
+
margin-top: 1.5rem;
+
padding-top: 1.5rem;
+
border-top: 1px solid #30363d;
+
}
+
+
.offline-tunnel {
+
padding: 0.5rem 0;
+
color: #8b949e;
+
font-size: 0.85rem;
+
display: flex;
+
justify-content: space-between;
+
align-items: center;
+
}
+
+
.offline-tunnel-name {
+
opacity: 0.6;
+
}
+
+
.offline-tunnel-stats {
+
font-size: 0.75rem;
+
opacity: 0.5;
+
}
+
</style>
+
</head>
+
+
<body class="loading">
+
<div class="loading-bar"></div>
+
<main class="container">
+
<header>
+
<h1>🚇 bore</h1>
+
<p class="subtitle">fancy tunnels @ terebithia</p>
+
</header>
+
+
<div class="stats">
+
<div class="stat-card">
+
<div class="stat-label">active tunnels</div>
+
<div class="stat-value" id="activeTunnels">—</div>
+
</div>
+
<div class="stat-card">
+
<div class="stat-label">active connections</div>
+
<div class="stat-value" id="totalConnections">—</div>
+
</div>
+
<div class="stat-card">
+
<div class="stat-label">server status</div>
+
<div class="stat-value orange" id="serverStatus">—</div>
+
</div>
+
<div class="stat-card">
+
<div class="stat-label">total upload</div>
+
<div class="stat-value" id="totalUpload">—</div>
+
</div>
+
<div class="stat-card">
+
<div class="stat-label">total download</div>
+
<div class="stat-value" id="totalDownload">—</div>
+
</div>
+
</div>
+
+
<section class="section">
+
<h2>~boreholes</h2>
+
<div class="tunnel-list" id="tunnelList">
+
<div class="empty-state">
+
<div class="empty-icon">🚇</div>
+
<p>no active tunnels</p>
+
</div>
+
</div>
+
</section>
+
</main>
+
+
<footer class="last-updated">
+
last updated: <span id="lastUpdated">never</span><br>
+
made with ♥︎ by <a href="https://dunkirk.sh" target="_blank">kieran klukas</a>
+
</footer>
+
+
<script>
+
let fetchFailCount = 0;
+
const MAX_FAIL_COUNT = 3;
+
let lastProxiesState = null;
+
+
// Predefined color palette for labels
+
const labelColors = [
+
{ color: '#a78bfa', bg: 'rgba(167, 139, 250, 0.2)' }, // purple
+
{ color: '#f472b6', bg: 'rgba(244, 114, 182, 0.2)' }, // pink
+
{ color: '#facc15', bg: 'rgba(250, 204, 21, 0.2)' }, // yellow
+
{ color: '#60a5fa', bg: 'rgba(96, 165, 250, 0.2)' }, // blue
+
{ color: '#f87171', bg: 'rgba(248, 113, 113, 0.2)' }, // red
+
{ color: '#38bdf8', bg: 'rgba(56, 189, 248, 0.2)' }, // sky
+
{ color: '#c084fc', bg: 'rgba(192, 132, 252, 0.2)' }, // violet
+
{ color: '#fb7185', bg: 'rgba(251, 113, 133, 0.2)' }, // rose
+
];
+
+
// Hash string to index
+
function stringToColorIndex(str) {
+
let hash = 0;
+
for (let i = 0; i < str.length; i++) {
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
+
}
+
return Math.abs(hash) % labelColors.length;
+
}
+
+
// Get label color and styles
+
function getLabelStyle(label) {
+
const trimmedLabel = label.trim();
+
if (trimmedLabel === 'prod') {
+
return {
+
color: '#22c55e',
+
bgColor: 'rgba(34, 197, 94, 0.2)',
+
borderColor: '#22c55e'
+
};
+
}
+
+
if (trimmedLabel === 'dev') {
+
return {
+
color: '#fb923c',
+
bgColor: 'rgba(251, 146, 60, 0.2)',
+
borderColor: '#fb923c'
+
};
+
}
+
+
const colorIndex = stringToColorIndex(trimmedLabel);
+
const colorScheme = labelColors[colorIndex];
+
return {
+
color: colorScheme.color,
+
bgColor: colorScheme.bg,
+
borderColor: colorScheme.color
+
};
+
}
+
+
async function fetchStats() {
+
try {
+
// Fetch server info
+
const serverResponse = await fetch('/api/serverinfo');
+
if (!serverResponse.ok) throw new Error('API unavailable');
+
const serverData = await serverResponse.json();
+
+
// Fetch HTTP proxies (tunnels)
+
const proxiesResponse = await fetch('/api/proxy/http');
+
const proxiesData = await proxiesResponse.json();
+
+
// Reset fail count on success
+
fetchFailCount = 0;
+
+
// Update stats
+
document.getElementById('activeTunnels').textContent = serverData.clientCounts || 0;
+
document.getElementById('serverStatus').textContent = 'online';
+
document.getElementById('totalConnections').textContent = serverData.curConns || 0;
+
document.getElementById('totalUpload').textContent = formatBytes(serverData.totalTrafficOut || 0);
+
document.getElementById('totalDownload').textContent = formatBytes(serverData.totalTrafficIn || 0);
+
+
// Update page title
+
const tunnelCount = serverData.clientCounts || 0;
+
const totalTraffic = formatBytes((serverData.totalTrafficIn || 0) + (serverData.totalTrafficOut || 0));
+
document.title = tunnelCount > 0
+
? `bore - ${tunnelCount} active • ${totalTraffic}`
+
: 'bore';
+
+
// Check if tunnel list structure changed
+
const proxies = proxiesData.proxies || [];
+
const currentState = JSON.stringify(proxies.map(p => ({ name: p.name, status: p.status })));
+
+
if (currentState !== lastProxiesState) {
+
// Structure changed, rebuild DOM
+
lastProxiesState = currentState;
+
renderTunnelList(proxies);
+
} else {
+
// Structure unchanged, just update data
+
updateTunnelData(proxies);
+
}
+
+
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
+
+
// Remove loading class after first successful fetch
+
document.body.classList.remove('loading');
+
} catch (error) {
+
fetchFailCount++;
+
document.getElementById('serverStatus').textContent = 'offline';
+
console.error('Failed to fetch stats:', error);
+
+
// Reload page if failed multiple times (server might have updated)
+
if (fetchFailCount >= MAX_FAIL_COUNT) {
+
console.log('Multiple fetch failures detected, reloading page...');
+
window.location.reload();
+
}
+
}
+
}
+
+
function renderTunnelList(proxies) {
+
const tunnelList = document.getElementById('tunnelList');
+
const onlineTunnels = proxies.filter(p => p.status === 'online');
+
const offlineTunnels = proxies.filter(p => p.status !== 'online');
+
+
if (onlineTunnels.length === 0 && offlineTunnels.length === 0) {
+
tunnelList.innerHTML = `
+
<div class="empty-state">
+
<div class="empty-icon">🚇</div>
+
<p>no active tunnels</p>
+
</div>
+
`;
+
} else {
+
let html = '';
+
+
// Render online tunnels
+
if (onlineTunnels.length > 0) {
+
html += onlineTunnels.map(proxy => {
+
const subdomain = proxy.conf?.subdomain || 'unknown';
+
const url = `https://${subdomain}.bore.dunkirk.sh`;
+
+
// Parse labels from proxy name (format: subdomain[label1,label2])
+
const labelMatch = proxy.name.match(/\[([^\]]+)\]$/);
+
const labels = labelMatch ? labelMatch[1].split(',') : [];
+
const displayName = labels.length > 0 ? proxy.name.replace(/\[[^\]]+\]$/, '') : proxy.name;
+
+
const labelHtml = labels.map(label => {
+
const trimmedLabel = label.trim();
+
const style = getLabelStyle(trimmedLabel);
+
return `<span class="tunnel-label" style="color: ${style.color}; background: ${style.bgColor}; border-color: ${style.borderColor};">${trimmedLabel}</span>`;
+
}).join('');
+
+
return `
+
<div class="tunnel" data-tunnel="${proxy.name}">
+
<div class="tunnel-info">
+
<div class="tunnel-name">
+
${displayName || 'unnamed'}
+
${labelHtml}
+
</div>
+
<div class="tunnel-url">
+
<a href="${url}" target="_blank">${url}</a>
+
</div>
+
<div style="color: #8b949e; font-size: 0.75rem; margin-top: 0.25rem;">
+
started: <span data-start-time="${proxy.lastStartTime || ''}"></span> • traffic in: <span data-traffic-in="${proxy.name}">0 B</span> • out: <span data-traffic-out="${proxy.name}">0 B</span>
+
</div>
+
</div>
+
<div class="tunnel-status status-online">online</div>
+
</div>
+
`;
+
}).join('');
+
}
+
+
// Render offline tunnels
+
if (offlineTunnels.length > 0) {
+
html += '<div class="offline-tunnels">';
+
html += '<div style="color: #8b949e; font-size: 0.85rem; margin-bottom: 0.75rem;">recently disconnected</div>';
+
html += offlineTunnels.map(proxy => {
+
// Parse labels from proxy name (format: subdomain[label1,label2])
+
const labelMatch = proxy.name.match(/\[([^\]]+)\]$/);
+
const labels = labelMatch ? labelMatch[1].split(',').map(l => l.trim()) : [];
+
const displayName = labels.length > 0 ? proxy.name.replace(/\[[^\]]+\]$/, '') : proxy.name;
+
const labelStr = labels.length > 0 ? ` [${labels.join(', ')}]` : '';
+
+
if (!proxy.conf) {
+
return `
+
<div class="offline-tunnel" data-tunnel="${proxy.name}">
+
<span class="offline-tunnel-name">${displayName || 'unnamed'}${labelStr}</span>
+
<span class="offline-tunnel-stats">in: <span data-traffic-in="${proxy.name}">0 B</span> • out: <span data-traffic-out="${proxy.name}">0 B</span></span>
+
</div>
+
`;
+
}
+
+
const subdomain = proxy.conf.subdomain || 'unknown';
+
const url = `https://${subdomain}.bore.dunkirk.sh`;
+
return `
+
<div class="offline-tunnel" data-tunnel="${proxy.name}">
+
<span class="offline-tunnel-name">${displayName || 'unnamed'}${labelStr} → ${url}</span>
+
<span class="offline-tunnel-stats">in: <span data-traffic-in="${proxy.name}">0 B</span> • out: <span data-traffic-out="${proxy.name}">0 B</span></span>
+
</div>
+
`;
+
}).join('');
+
html += '</div>';
+
}
+
+
tunnelList.innerHTML = html;
+
+
// Update all relative times
+
updateRelativeTimes();
+
+
+
}
+
+
// Update data
+
updateTunnelData(proxies);
+
}
+
+
function updateTunnelData(proxies) {
+
proxies.forEach(proxy => {
+
const trafficInEl = document.querySelector(`[data-traffic-in="${proxy.name}"]`);
+
const trafficOutEl = document.querySelector(`[data-traffic-out="${proxy.name}"]`);
+
+
if (trafficInEl) trafficInEl.textContent = formatBytes(proxy.todayTrafficIn || 0);
+
if (trafficOutEl) trafficOutEl.textContent = formatBytes(proxy.todayTrafficOut || 0);
+
+
+
});
+
+
// Update relative times
+
updateRelativeTimes();
+
}
+
+
function formatBytes(bytes) {
+
if (bytes === 0) return '0 B';
+
const k = 1024;
+
const sizes = ['B', 'KB', 'MB', 'GB'];
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
+
}
+
+
function formatTime(timeStr) {
+
// Input format: "12-08 20:15:20" (MM-DD HH:MM:SS)
+
const [datePart, timePart] = timeStr.split(' ');
+
const [month, day] = datePart.split('-');
+
const [hour, minute, second] = timePart.split(':');
+
+
const now = new Date();
+
const inputDate = new Date(now.getFullYear(), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second));
+
+
const diffInSeconds = (now.getTime() - inputDate.getTime()) / 1000;
+
const diffInMinutes = Math.round(diffInSeconds / 60);
+
const diffInHours = Math.round(diffInMinutes / 60);
+
+
if (diffInSeconds < 60) {
+
return 'just now';
+
} else if (diffInHours < 1) {
+
return diffInMinutes === 1 ? '1 minute ago' : `${diffInMinutes} minutes ago`;
+
} else if (now.toDateString() === inputDate.toDateString()) {
+
return 'today';
+
} else if (
+
new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1).toDateString() ===
+
inputDate.toDateString()
+
) {
+
return 'yesterday';
+
} else {
+
return inputDate.toLocaleTimeString([], {
+
month: 'numeric',
+
day: 'numeric',
+
hour: 'numeric',
+
minute: 'numeric',
+
});
+
}
+
}
+
+
function updateRelativeTimes() {
+
document.querySelectorAll('[data-start-time]').forEach(element => {
+
const timeStr = element.getAttribute('data-start-time');
+
element.textContent = formatTime(timeStr);
+
});
+
}
+
+
// Fetch immediately and then every 5 seconds
+
fetchStats();
+
setInterval(fetchStats, 5000);
+
+
// Update relative times every 10 seconds
+
setInterval(updateRelativeTimes, 10000);
+
</script>
+
</body>
+
+
</html>
+131
modules/nixos/services/cachet.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
let
+
cfg = config.atelier.services.cachet;
+
in
+
{
+
options.atelier.services.cachet = {
+
enable = lib.mkEnableOption "Cachet Slack emoji/profile cache";
+
+
domain = lib.mkOption {
+
type = lib.types.str;
+
description = "Domain to serve cachet on";
+
};
+
+
port = lib.mkOption {
+
type = lib.types.port;
+
default = 3000;
+
description = "Port to run cachet on";
+
};
+
+
dataDir = lib.mkOption {
+
type = lib.types.path;
+
default = "/var/lib/cachet";
+
description = "Directory to store cachet data";
+
};
+
+
secretsFile = lib.mkOption {
+
type = lib.types.path;
+
description = "Path to secrets file containing SLACK_TOKEN, SLACK_SIGNING_SECRET, BEARER_TOKEN";
+
};
+
+
repository = lib.mkOption {
+
type = lib.types.str;
+
default = "https://github.com/taciturnaxolotl/cachet.git";
+
description = "Git repository URL (optional, for auto-deployment)";
+
};
+
+
autoUpdate = lib.mkEnableOption "Automatically git pull on service restart";
+
};
+
+
config = lib.mkIf cfg.enable {
+
users.groups.services = { };
+
+
users.users.cachet = {
+
isSystemUser = true;
+
group = "cachet";
+
extraGroups = [ "services" ];
+
home = cfg.dataDir;
+
createHome = true;
+
shell = pkgs.bash;
+
};
+
+
users.groups.cachet = { };
+
+
security.sudo.extraRules = [
+
{
+
users = [ "cachet" ];
+
commands = [
+
{
+
command = "/run/current-system/sw/bin/systemctl restart cachet.service";
+
options = [ "NOPASSWD" ];
+
}
+
];
+
}
+
];
+
+
systemd.services.cachet = {
+
description = "Cachet Slack emoji/profile cache";
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
+
path = [ pkgs.git ];
+
+
preStart = ''
+
if [ ! -d ${cfg.dataDir}/app/.git ]; then
+
${pkgs.git}/bin/git clone ${cfg.repository} ${cfg.dataDir}/app
+
fi
+
+
cd ${cfg.dataDir}/app
+
'' + lib.optionalString cfg.autoUpdate ''
+
${pkgs.git}/bin/git pull
+
'' + ''
+
+
if [ ! -f src/index.ts ]; then
+
echo "No code found at ${cfg.dataDir}/app/src/index.ts"
+
exit 1
+
fi
+
+
echo "Installing dependencies..."
+
${pkgs.unstable.bun}/bin/bun install
+
'';
+
+
serviceConfig = {
+
Type = "simple";
+
User = "cachet";
+
Group = "cachet";
+
EnvironmentFile = cfg.secretsFile;
+
Environment = [
+
"NODE_ENV=production"
+
"PORT=${toString cfg.port}"
+
"DATABASE_PATH=${cfg.dataDir}/data/cachet.db"
+
];
+
ExecStart = "${pkgs.bash}/bin/bash -c 'cd ${cfg.dataDir}/app && ${pkgs.unstable.bun}/bin/bun run src/index.ts'";
+
Restart = "always";
+
RestartSec = "10s";
+
};
+
+
serviceConfig.ExecStartPre = [
+
"+${pkgs.writeShellScript "cachet-setup" ''
+
mkdir -p ${cfg.dataDir}/data
+
mkdir -p ${cfg.dataDir}/app
+
chown -R cachet:services ${cfg.dataDir}
+
chmod -R g+rwX ${cfg.dataDir}
+
''}"
+
];
+
};
+
+
services.caddy.virtualHosts.${cfg.domain} = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
+
reverse_proxy localhost:${toString cfg.port}
+
'';
+
};
+
};
+
}
+139
modules/nixos/services/emojibot.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
let
+
cfg = config.atelier.services.emojibot;
+
in
+
{
+
options.atelier.services.emojibot = {
+
enable = lib.mkEnableOption "Emojibot Slack emoji management service";
+
+
domain = lib.mkOption {
+
type = lib.types.str;
+
description = "Domain to serve emojibot on";
+
};
+
+
port = lib.mkOption {
+
type = lib.types.port;
+
default = 3002;
+
description = "Port to run emojibot on";
+
};
+
+
dataDir = lib.mkOption {
+
type = lib.types.path;
+
default = "/var/lib/emojibot";
+
description = "Directory to store emojibot data";
+
};
+
+
secretsFile = lib.mkOption {
+
type = lib.types.path;
+
description = ''
+
Path to secrets file containing:
+
- SLACK_SIGNING_SECRET
+
- SLACK_BOT_TOKEN
+
- SLACK_APP_TOKEN
+
- SLACK_BOT_USER_TOKEN (get from browser, see emojibot README)
+
- SLACK_COOKIE (get from browser, see emojibot README)
+
- SLACK_WORKSPACE (e.g. "myworkspace" for myworkspace.slack.com)
+
- SLACK_CHANNEL (channel ID where emojis are posted)
+
- ADMINS (comma-separated list of slack user IDs)
+
'';
+
};
+
+
repository = lib.mkOption {
+
type = lib.types.str;
+
default = "https://github.com/taciturnaxolotl/emojibot.git";
+
description = "Git repository URL (optional, for auto-deployment)";
+
};
+
+
autoUpdate = lib.mkEnableOption "Automatically git pull on service restart";
+
};
+
+
config = lib.mkIf cfg.enable {
+
users.groups.services = { };
+
+
users.users.emojibot = {
+
isSystemUser = true;
+
group = "emojibot";
+
extraGroups = [ "services" ];
+
home = cfg.dataDir;
+
createHome = true;
+
shell = pkgs.bash;
+
};
+
+
users.groups.emojibot = { };
+
+
security.sudo.extraRules = [
+
{
+
users = [ "emojibot" ];
+
commands = [
+
{
+
command = "/run/current-system/sw/bin/systemctl restart emojibot.service";
+
options = [ "NOPASSWD" ];
+
}
+
];
+
}
+
];
+
+
systemd.services.emojibot = {
+
description = "Emojibot Slack emoji management service";
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
+
path = [ pkgs.git ];
+
+
preStart = ''
+
if [ ! -d ${cfg.dataDir}/app/.git ]; then
+
${pkgs.git}/bin/git clone ${cfg.repository} ${cfg.dataDir}/app
+
fi
+
+
cd ${cfg.dataDir}/app
+
'' + lib.optionalString cfg.autoUpdate ''
+
${pkgs.git}/bin/git pull
+
'' + ''
+
+
if [ ! -f src/index.ts ]; then
+
echo "No code found at ${cfg.dataDir}/app/src/index.ts"
+
exit 1
+
fi
+
+
echo "Installing dependencies..."
+
${pkgs.unstable.bun}/bin/bun install
+
'';
+
+
serviceConfig = {
+
Type = "simple";
+
User = "emojibot";
+
Group = "emojibot";
+
EnvironmentFile = cfg.secretsFile;
+
Environment = [
+
"NODE_ENV=production"
+
"PORT=${toString cfg.port}"
+
];
+
ExecStart = "${pkgs.bash}/bin/bash -c 'cd ${cfg.dataDir}/app && ${pkgs.unstable.bun}/bin/bun run src/index.ts'";
+
Restart = "always";
+
RestartSec = "10s";
+
};
+
+
serviceConfig.ExecStartPre = [
+
"+${pkgs.writeShellScript "emojibot-setup" ''
+
mkdir -p ${cfg.dataDir}/app
+
chown -R emojibot:services ${cfg.dataDir}
+
chmod -R g+rwX ${cfg.dataDir}
+
''}"
+
];
+
};
+
+
services.caddy.virtualHosts.${cfg.domain} = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
+
reverse_proxy localhost:${toString cfg.port}
+
'';
+
};
+
};
+
}
+124
modules/nixos/services/hn-alerts.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
let
+
cfg = config.atelier.services.hn-alerts;
+
in
+
{
+
options.atelier.services.hn-alerts = {
+
enable = lib.mkEnableOption "HN Alerts Hacker News monitoring service";
+
+
domain = lib.mkOption {
+
type = lib.types.str;
+
description = "Domain to serve hn-alerts on";
+
};
+
+
port = lib.mkOption {
+
type = lib.types.port;
+
default = 3001;
+
description = "Port to run hn-alerts on";
+
};
+
+
dataDir = lib.mkOption {
+
type = lib.types.path;
+
default = "/var/lib/hn-alerts";
+
description = "Directory to store hn-alerts data";
+
};
+
+
secretsFile = lib.mkOption {
+
type = lib.types.path;
+
description = "Path to secrets file containing SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, SLACK_CHANNEL, SENTRY_DSN, DATABASE_URL";
+
};
+
+
repository = lib.mkOption {
+
type = lib.types.str;
+
default = "https://github.com/taciturnaxolotl/hn-alerts.git";
+
description = "Git repository URL (optional, for auto-deployment)";
+
};
+
+
autoUpdate = lib.mkEnableOption "Automatically git pull on service restart";
+
};
+
+
config = lib.mkIf cfg.enable {
+
users.groups.services = { };
+
+
users.users.hn-alerts = {
+
isSystemUser = true;
+
group = "hn-alerts";
+
extraGroups = [ "services" ];
+
home = cfg.dataDir;
+
createHome = true;
+
shell = pkgs.bash;
+
};
+
+
users.groups.hn-alerts = { };
+
+
security.sudo.extraRules = [
+
{
+
users = [ "hn-alerts" ];
+
commands = [
+
{
+
command = "/run/current-system/sw/bin/systemctl restart hn-alerts.service";
+
options = [ "NOPASSWD" ];
+
}
+
];
+
}
+
];
+
+
systemd.services.hn-alerts = {
+
description = "HN Alerts Hacker News monitoring service";
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
+
path = [ pkgs.git ];
+
+
preStart = ''
+
if [ ! -d ${cfg.dataDir}/app/.git ]; then
+
${pkgs.git}/bin/git clone ${cfg.repository} ${cfg.dataDir}/app
+
fi
+
+
cd ${cfg.dataDir}/app
+
'' + lib.optionalString cfg.autoUpdate ''
+
${pkgs.git}/bin/git pull
+
'' + ''
+
+
if [ ! -f src/index.ts ]; then
+
echo "No code found at ${cfg.dataDir}/app/src/index.ts"
+
exit 1
+
fi
+
+
echo "Installing dependencies..."
+
${pkgs.unstable.bun}/bin/bun install
+
+
echo "Initializing database..."
+
${pkgs.unstable.bun}/bin/bun run db:push
+
'';
+
+
serviceConfig = {
+
Type = "simple";
+
User = "hn-alerts";
+
Group = "hn-alerts";
+
EnvironmentFile = cfg.secretsFile;
+
Environment = [
+
"NODE_ENV=production"
+
"PORT=${toString cfg.port}"
+
];
+
ExecStart = "${pkgs.bash}/bin/bash -c 'cd ${cfg.dataDir}/app && ${pkgs.unstable.bun}/bin/bun start'";
+
Restart = "always";
+
RestartSec = "10s";
+
};
+
};
+
+
services.caddy.virtualHosts.${cfg.domain} = {
+
extraConfig = ''
+
tls {
+
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
+
}
+
+
reverse_proxy localhost:${toString cfg.port}
+
'';
+
};
+
};
+
}
+185
modules/nixos/services/knot-sync.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
+
let
+
cfg = config.atelier.services.knot-sync;
+
in
+
{
+
options.atelier.services.knot-sync = {
+
enable = lib.mkEnableOption "Knot to GitHub sync service";
+
+
repoDir = lib.mkOption {
+
type = lib.types.str;
+
default = "/home/git/did:plc:krxbvxvis5skq7jj6eot23ul";
+
description = "Directory containing git repositories";
+
};
+
+
githubUsername = lib.mkOption {
+
type = lib.types.str;
+
default = "taciturnaxolotl";
+
description = "GitHub username";
+
};
+
+
secretsFile = lib.mkOption {
+
type = lib.types.path;
+
description = "Path to secrets file containing GITHUB_TOKEN";
+
};
+
+
logFile = lib.mkOption {
+
type = lib.types.str;
+
default = "/home/git/knot-sync.log";
+
description = "Log file location";
+
};
+
+
interval = lib.mkOption {
+
type = lib.types.str;
+
default = "*/5 * * * *";
+
description = "Cron schedule for sync (default: every 5 minutes)";
+
};
+
};
+
+
config = lib.mkIf cfg.enable {
+
systemd.services.knot-sync = {
+
description = "Sync Knot repositories to GitHub";
+
serviceConfig = {
+
Type = "oneshot";
+
User = "git";
+
EnvironmentFile = cfg.secretsFile;
+
ExecStart = pkgs.writeShellScript "knot-sync" ''
+
set -euo pipefail
+
+
# Variables
+
REPO_DIR="${cfg.repoDir}"
+
GITHUB_USERNAME="${cfg.githubUsername}"
+
LOG_FILE="${cfg.logFile}"
+
+
# Log function
+
log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"; }
+
+
# Create the post-receive hook template
+
cat <<'EOF' > /tmp/post-receive.template
+
#!${pkgs.bash}/bin/bash
+
# post-receive hook to sync to GitHub - AUTOGENERATED
+
+
# Load environment variables from secrets file
+
if [ -f "${cfg.secretsFile}" ]; then
+
source "${cfg.secretsFile}"
+
fi
+
+
# Variables
+
GITHUB_USERNAME="${cfg.githubUsername}"
+
LOG_FILE="${cfg.logFile}"
+
REPO_NAME=$(basename $(pwd))
+
+
# Log function
+
log() { echo "$(date +'%Y-%m-%d %H:%M:%S'): $1" >> "''${LOG_FILE}"; }
+
+
# Check for nosync marker
+
if [ -f "$(pwd)/.nosync" ]; then
+
log "Skipping sync for $REPO_NAME (nosync marker present)"
+
exit 0
+
fi
+
+
# Function to sync to GitHub
+
sync_to_github() {
+
log "Syncing $REPO_NAME to GitHub"
+
expected_url="https://''${GITHUB_USERNAME}:''${GITHUB_TOKEN}@github.com/''${GITHUB_USERNAME}/''${REPO_NAME}.git"
+
current_url=$(${pkgs.git}/bin/git remote get-url origin 2>/dev/null || echo "")
+
+
if [ -z "$current_url" ]; then
+
log "Adding origin remote"
+
${pkgs.git}/bin/git remote add origin "$expected_url"
+
elif [ "$current_url" != "$expected_url" ]; then
+
log "Updating origin remote URL"
+
${pkgs.git}/bin/git remote set-url origin "$expected_url"
+
fi
+
+
# Mirror push everything (refs, tags, branches)
+
if ${pkgs.git}/bin/git push --mirror origin 2>&1 | tee -a "''${LOG_FILE}"; then
+
log "Sync succeeded for $REPO_NAME"
+
return 0
+
else
+
log "Sync failed for $REPO_NAME"
+
return 1
+
fi
+
}
+
+
# Main
+
while read oldrev newrev refname; do
+
log "Received push for ref '$refname' (old revision: $oldrev, new revision: $newrev)"
+
sync_to_github
+
done
+
EOF
+
+
HOOK_TEMPLATE="/tmp/post-receive.template"
+
+
# Create the post-receive hook
+
create_hook() {
+
local new_repo_path="$1"
+
local hook_path="$new_repo_path/hooks/post-receive.d/forward"
+
local nosync_marker="$new_repo_path/.nosync"
+
+
# Skip if .nosync marker exists
+
if [ -f "$nosync_marker" ]; then
+
log "Skipping $new_repo_path (nosync marker present)"
+
return 0
+
fi
+
+
if [ -d "$new_repo_path" ] && [ ! -f "$hook_path" ]; then
+
# Check that it's a git repository, specifically a bare repo
+
if [ -f "$new_repo_path/config" ]; then
+
# Create hooks directory if it doesn't exist
+
mkdir -p "$(dirname "$hook_path")"
+
# Create hook from the template file, substituting variables.
+
if cat "$HOOK_TEMPLATE" > "$hook_path" && chmod +x "$hook_path"; then
+
log "Created hook for $new_repo_path"
+
# Check if repo has any commits before pushing
+
if (cd "$new_repo_path" && ${pkgs.git}/bin/git rev-parse HEAD >/dev/null 2>&1); then
+
# Auto push by simulating a post-receive hook trigger
+
log "Triggering initial push for $new_repo_path"
+
(cd "$new_repo_path" && \
+
echo "0000000000000000000000000000000000000000 $(${pkgs.git}/bin/git rev-parse HEAD) refs/heads/main" | \
+
"$hook_path")
+
fi
+
else
+
log "Hook creation failed for $new_repo_path"
+
fi
+
fi
+
fi
+
}
+
+
# Keep track of hooks created
+
hooks_created=0
+
+
# Find all directories that look like bare Git repos without a post-receive hook
+
${pkgs.findutils}/bin/find "$REPO_DIR" -mindepth 1 -maxdepth 1 -type d \! -name ".*" -print0 |
+
while IFS= read -r -d $'\0' repo_path; do
+
create_hook "$repo_path"
+
if [ $? -eq 0 ]; then
+
hooks_created=$((hooks_created + 1))
+
fi
+
done
+
+
# Only log completion if hooks were actually created
+
if [ $hooks_created -gt 0 ]; then
+
log "Sync job complete - Created $hooks_created hooks"
+
fi
+
'';
+
};
+
};
+
+
systemd.paths.knot-sync = {
+
description = "Watch for new Knot repositories";
+
wantedBy = [ "multi-user.target" ];
+
pathConfig = {
+
PathModified = cfg.repoDir;
+
Unit = "knot-sync.service";
+
MakeDirectory = true;
+
};
+
};
+
};
+
}
+48
packages/zmx.nix
···
+
{ pkgs, lib, stdenv, fetchurl, autoPatchelfHook }:
+
+
stdenv.mkDerivation rec {
+
pname = "zmx";
+
version = "0.1.0";
+
+
src = fetchurl {
+
url = if stdenv.isLinux then
+
(if stdenv.isAarch64 then
+
"https://zmx.sh/a/zmx-${version}-linux-aarch64.tar.gz"
+
else
+
"https://zmx.sh/a/zmx-${version}-linux-x86_64.tar.gz")
+
else if stdenv.isDarwin then
+
(if stdenv.isAarch64 then
+
"https://zmx.sh/a/zmx-${version}-macos-aarch64.tar.gz"
+
else
+
"https://zmx.sh/a/zmx-${version}-macos-x86_64.tar.gz")
+
else throw "Unsupported platform";
+
+
hash = if stdenv.isLinux && stdenv.isAarch64 then
+
"sha256-sv83lR4DLJE+gsMtqCk6VCFdo5n4lhI0P1loxAf0iOg="
+
else if stdenv.isLinux then
+
"sha256-c+wCUcm7DEO55wXuHq0aP0Kn908jj1FM5Z+JQJnKE0M="
+
else if stdenv.isDarwin && stdenv.isAarch64 then
+
"sha256-dM6MFikdbpN+n8BK6fLbzyJfi88xetCWL9H5VfGB07o="
+
else
+
"sha256-B52NC8NEjVPDNSG11qPb0uRNExB66bllnK7ivXMJbHk=";
+
};
+
+
nativeBuildInputs = lib.optionals stdenv.isLinux [ autoPatchelfHook ];
+
+
sourceRoot = ".";
+
+
installPhase = ''
+
runHook preInstall
+
mkdir -p $out/bin
+
cp zmx $out/bin/
+
chmod +x $out/bin/zmx
+
runHook postInstall
+
'';
+
+
meta = with lib; {
+
description = "Session persistence for terminal processes";
+
homepage = "https://zmx.sh";
+
license = licenses.mit;
+
platforms = platforms.unix;
+
};
+
}
secrets/battleship-arena.age

This is a binary file and will not be displayed.

secrets/cachet.age

This is a binary file and will not be displayed.

secrets/cloudflare.age

This is a binary file and will not be displayed.

+13
secrets/context7.age
···
+
age-encryption.org/v1
+
-> ssh-rsa DqcG0Q
+
e03DRMRMDI6LCR+XT2n+EtefyS+4QSjNyFQ84ehZl4fu0ZAsSaPicWSIJWxCM1ny
+
D6SPJG7Ucll47iTX+Z3IFfr9jj4fI9G+iltoKUYy8SuOlINjE8k2b8J8+4qnbIkz
+
wWrqP6VABT2wcudvYMqXwaAn+W/RBDZO7YDlMqBra5w5EUhiOwdJecAXMUP01hH5
+
DNuKwR9HVTEOcm23BgerexFYoXC+/VtCTqLt2c13NkIBuY7XwLznpz5RxyJr8nSc
+
9cqObb7rvwPtlSzhpMihmEsrrhDTdsZFMCH/mFjxw6jlt4TNqX9EYgdrgWeUY0db
+
iiQzoD7zYRmVu6+b0VDCR4qVjjx7qJCUAHVJGMXVQ8zZ7VaEcLOs+GwkJOi6Ds3+
+
L1IgntlBwFXB23EMt9nKMkqSwbrTnyoL8HOhHLkDNPnagOCqMon+qLOsZfKoNSBC
+
uMYO81sApaQ7sWv3U70PzCPunCk5uyeTH5+L+myhBdLJMqJXGBvIuhat5suunxjA
+
+
--- lYbv0hQZlIU5UiHiweDvJQfJNZpfz77h7qHUF0PFvBY
+
��rY1Iq�خ���q�E ��B�< �����Nn���U�bmũ肃���HF��@��ϭ�Z��I(��M��J/~�
+13
secrets/crush.age
···
+
age-encryption.org/v1
+
-> ssh-rsa DqcG0Q
+
pXxvBC3KC0KrKcLNiAR4OpgTm45dqZur0YzNB9lMU9Wapc89sXc0iFRClIo9wOwK
+
239l5rvUN44nSAangwU5ch6kRZHezXwr52kgkEKKTWHK16hO9lXTpxhRH5XP9RA3
+
2hfdDy8SABxpfAi1utmY/6CCX7z+DMIYCKnjKjfiFJhvAljMXHRj7TG39qicrdtA
+
DAtqDgDflMpkb7gENdqwlwS53VV8AYRUicenJGuH0XT8xs0HYp71g+mJZFLmqdMg
+
Uwh7Hrt2sfWf9sydJm49ob0nfyqpmygQh+wy1FmRRG+4HYxQQXLswYPmsU+o+v39
+
pwg6ixzBWCojWnG3VQagmzpHiAU58JidYAxbkR3MMeaKbMlsY2OaRl9VuxcQJ9cg
+
ONaEHZvIfotLVa4arH/DHc8IyFAwVdWx8SRS6ahMYt3l7iNXVRHNIzoxbpGocraK
+
uMeLhCaG+ZeLRkdXNiOps50O7C5Y89+FfWv/qYk0wnHtFinBV3GcXrAzmaHue2nF
+
+
--- sIuSUqv8QbgqVt8GnCtTYhg7Q1HUrEtjT0v48eHeXuE
+
��W��^B- �b�՚G�!oϦ���������UqN#��G��Dm� {�ziU ��4d��I�*q$�ŀ,��J�0
secrets/emojibot.age

This is a binary file and will not be displayed.

+13
secrets/frp-auth-token.age
···
+
age-encryption.org/v1
+
-> ssh-rsa DqcG0Q
+
O9AWztjhnwqJ/hh/jmHu0E+7DTK27ZB7A4eJBowY2hoMrEPabrGBPhZlLoTlSk6x
+
8GTzzdC4cU6jBBwJm3CrrVgxRlIBL23Vz0AWOKfyoFUraYZxDsoifeNAqQ/hgQ1Y
+
BntX55o5z5UhE7M1Dwa57haRnBD/2K2TRQR3BNrFGjHUhiiC4ovavULf3/Ac9trh
+
+umeYLBLK7pPQGhGytrVIIOmGdRq4ZzV8bfUsZyaJiVO5VACshsQWkx0Pj4szKXk
+
/gRerjo2P8yZJ7kg+aRn7cD6WdcpCMQZVbKgtIbe5BE15AzCdxprgQBz4N0Uthox
+
J53hLGvAOgHYdR8CHebymxBNMFdaYBPjBwhHyAlTi5TrPy5S9XtyBEg6h0mHeo+L
+
kSlUacifevkE4qZM5pVVf29YCvAPEC6VlQLAb3m4bppnpg2NBZJQo8iXNP88G53X
+
3axYCAufQttqcUe97yzcHYqXin8UXN8yJXjFNBXPtQ3ScVuSPWO+2V9pyDPRZhk8
+
+
--- 1JfGe2s3sQu5LP07sNvuBwqGUOiOQnrtQXp9+pE4ms8
+
(I ��Pfc�h,X��d�� ��x��O��p��p�F���'`�����u�
+13
secrets/github-knot-sync.age
···
+
age-encryption.org/v1
+
-> ssh-rsa DqcG0Q
+
eG7RPDx4xt/jE8lDjJ/whTT6ekS5ccZgLLzEqvJZ1bOsZm7ycfKRUjs5mZoNn8/Y
+
n6Rk6FyhTXSb0HrhCpSOSH23nCHwMFK/1H/pKdFpXTJAdx2LkX4VRI807/XjPHK4
+
Iyy0E1xL53RepvC6JPHmmW/Z7h/yamvP8MDE4Mds7oiBZYf/0Whdfw+PlKA5Xy7s
+
0xAQgQlODwSSwlNdtwgf0i1tcEkhDC1ts2o8/2NSoLj3s2348RAkv2n8yV0eu1bh
+
9UaeCOuyUxhCOYta7vWNp9xLy6MQuqptT2MNAjjYGHMZ0i5xH0lnvU1twqvN52Kf
+
wGuzKLw//qz13XWWgMaPbLz6L38j4gvl7eWd51vRQGX9Z9QDOHpu7iFcyY6bUeLh
+
3n9Quin11JrFl2Fx2qqDi8aILua9/Oq4bPchPURqbTUnZ+SR/aRFgN3aFVHmC0gL
+
l0BGhkCTFmb9+FrhILhmyKiln4dTaGigMhaSs9GuuXZ85FmCKjkp+6eRDDWP4WS9
+
+
--- T+md4gE0kZczI05P8evIAnS6wGnFPnMoTpaQM5DS1ao
+
mn̂�;�צ$`_�p�vR�1�7Fl�ç����� �+!����3�-�EYG��Q�*���������V�Զ����a�F��O����'}H��b���IHG�Z)t�����$�lXv�����F~sw%]G��H�s�LTv(�
secrets/hn-alerts.age

This is a binary file and will not be displayed.

+27
secrets/secrets.nix
···
"iodine.age".publicKeys = [
kierank
];
+
"crush.age".publicKeys = [
+
kierank
+
];
+
"context7.age".publicKeys = [
+
kierank
+
];
+
"cloudflare.age".publicKeys = [
+
kierank
+
];
+
"cachet.age".publicKeys = [
+
kierank
+
];
+
"hn-alerts.age".publicKeys = [
+
kierank
+
];
+
"github-knot-sync.age".publicKeys = [
+
kierank
+
];
+
"emojibot.age".publicKeys = [
+
kierank
+
];
+
"battleship-arena.age".publicKeys = [
+
kierank
+
];
+
"frp-auth-token.age".publicKeys = [
+
kierank
+
];
}
+11 -20
secrets/wakatime.age
···
age-encryption.org/v1
-> ssh-rsa DqcG0Q
-
N7lTwh9ZyUYJ4flAybyAwi78XiWYtx/+JHfKsq22nxzZ5nRpMrBKZau1k3elxOSG
-
bzrrOrKm4u1fKmJrQIxxDa4x8w/0pMlhgo3KVPwRnnUkLJt3McXdJJT6fEA8tIXs
-
Mnj/d/+gI47BdSgWro4k9oK2ZIJKepj9HB0CWCKXioPnaMIAw8V5SzSSAzTAz32T
-
9kQsuxt32HDspue1aHuK1ydyyt7Yce3gn/FHEThOCLNx7m3TDHG/ka7aDmhCQU9O
-
TA1cc45KE3NbqKFut86xVYz1o5R5B8v1MmUzNJq4tJXk4C4iFLN3O4g3CAP+JUix
-
6sBpbAmi/89S9SXf/pbvlexpR7wT1ADAytw9NSt4d3tE0OthtLP3LmyLUPXA1Lrm
-
ziWsuCtYvXUuNTvs8t24tZ1GWanPQBEOAKUehUJTlENArBB+DzCtjlo+kaQbDNpB
-
6S5JQCH2wfaeszaeMRe3pD0VUjj4jOJWRHZBcJizUuZ4J90tHwsZgtpJNiNxOIz6
+
qXw3jNnNgKgOF1O9wTUoZfRUxOmd2rg6W3LBPQfdEb33MEk2lchTAf3xAszNzdJY
+
Q8G0xwO3GYCXxC2L7N8WN1QpXu9ddbAxN9v0PmCoDZfu4k8+QTnvQemtlNk5VOyz
+
PQUJpMFm+1I+TYJeo2krkKbkUNfu+2bB/bq6oIKEwCWNB9wXKTSZgYxix99O8eQa
+
3KGL40KA01kkxwamQIO6W5gKNR3UipNKf2JVPBMenN2E6vg7izJaA6Udvf11eYQ1
+
Y2KYDt7SLpfz9NA+R+c3JE6vCLiEqu1Mv632ehjger6M0UQk6GJq/s5Eh9uYLsN3
+
QeZqgOwKSpkxHD75QM+99JrMO5CJDTfk+OLUS+HfsiLVWun3JXyl4lMQH0tB9xk/
+
7REZFef1TofWIDfjgf+xHRPUpaQQqd/0ffyBoNRbXV9EMUC4FcPflrUfcqDuKsV4
+
taFdp2lB1ucN0vCCXoirO/FdgOKNZ1RcnX4IgCqWwqznzyQi7iHuUMDGrVvrTyEH
-
-> ssh-rsa DqcG0Q
-
JwyR7GdIQx2o5hsk4ZI4VgGM4mqAiH8RjeZ8IfDIKIm5at0wfr9jcGPoVXUzrkif
-
ZjJPQYqrQRw1hR2xV+CuGFkYax8I7rfgIiEH30Hb9PS+udX57OiVImPOThsO66oU
-
weKwPU5ubR7WX+CiW72qeEpXU0teXtz2fPsldbgoICFwDoLzU6eh/zXRzaAeqd4g
-
uulpc21m7hhAQ/BBVa02wIo4+2YjoMJ7zwNZBM5lzbZK8Me+eXj+CgSZgKgNajvG
-
ksOkkX/YmQ/h7RQFh6jXy44VWQUm41x1km4Ko0xaAZ0sMAVQHTi3EY+2QOS/UrhA
-
CoDsOc2O5iMvMv4RcUpPI9I5X0ipX8X3gVgSHk5coIR+cJxfV4LfXRxqbcr4bLpe
-
OeFW7/wmfOLaRkMjbOvJj1tkCVRfnSPWHAVEV0KWecHFuOy3N1y7Fa+H+0sniA0U
-
h4I0CjiS2n6YRkocewKAuINbAHteQFcg4I6J47NLgMrZQHteEdVVWOniM35CvZXx
-
-
--- JjFWBtI4P5wKIVPG8Pw6/dosUXAKTbXighpt3bbNWdo
-
9� 6�$���eҜ��+�����}*!�I��㒷�˼B��gH�8��$�L����e��Uv��vr���: E�L ��I`��!�n�{�%:�Ժ| ��ܵ�"Ӡi�?LnSm�U�7B5�>���E��A��0����"�GR �+)$����f量
+
--- uN0vT6nH7JLi0IowMHSVGNAxNBHf5zp+pkeXFA87Sho
+
�Tઇ�f�.T�ў��W/(mA�M�������7`sz�W1����[����aI���� W��� ��<�b
+
��>@��qU}�.�+��] M�Yjt���i�|�@��Qt�ZRX�ɘMr�iiN�\@��Q�/O�0Z�fJ������c/* ������Z�ꦲ�`T�YgaC����