Compare commits
65 Commits
758e0fb3ba
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3251639b39 | |||
| 1b21f2c962 | |||
| 04509ceade | |||
| 8c2143c3b2 | |||
| 5a3a8e053d | |||
| 5fb34c7188 | |||
| 75d1bcf15f | |||
| a62d07ca6c | |||
| 3f5c9b24a4 | |||
| 4f26431fcb | |||
| d9083651c2 | |||
| 5fc9da84d3 | |||
| 0ca2ab2401 | |||
| d24a905516 | |||
| 9bf0cabd8d | |||
| ceb89d92fe | |||
| ee5966306a | |||
| 01b92733c3 | |||
| e52ae3f451 | |||
| 567a59f9b1 | |||
| 4d6357cc74 | |||
| ab27bb1183 | |||
| a5e2ce944e | |||
| 9f9a2fdc2c | |||
| 169dc7e2bf | |||
| 7e1ed7cf54 | |||
| dad37e8015 | |||
| e32f08391b | |||
| 60c633a1db | |||
| 61fa5e4e33 | |||
| a4f49c42f7 | |||
| 1611df4ec8 | |||
| 641c6bd5c3 | |||
| 95beb54b32 | |||
| b6a8d96585 | |||
| d37726fcc9 | |||
| cce76cdbc2 | |||
| 7242f64b0c | |||
| b074a02edf | |||
| d36486c935 | |||
| 906b6d6c0d | |||
| e08252dc17 | |||
| 695a6723ce | |||
| b2fdc5a3c4 | |||
| 7ec08abcb2 | |||
| f0b6b7b08f | |||
| 768a7cf235 | |||
| b0f36e989c | |||
| bfc60bf27c | |||
| 6301d82dff | |||
| fe10f7615c | |||
| b6b19a3950 | |||
| a79f524b6c | |||
| 2e9b40fba0 | |||
| 6eeaed33a4 | |||
| 6e6c1dc530 | |||
| c9fc909727 | |||
| ffc69352fa | |||
| 660be9736b | |||
| 5daa737dab | |||
| 80b48ca458 | |||
| 096cf1cc2d | |||
| 5d2f7b5ce0 | |||
| 4470db7960 | |||
| f7accecaae |
@@ -50,6 +50,23 @@ jobs:
|
||||
kubectl apply -f kubernetes/homepage/
|
||||
kubectl rollout restart deployment/homepage -n homepage
|
||||
|
||||
- name: gitea
|
||||
env:
|
||||
CLOUDFLARED_GITEA_TOKEN: ${{ secrets.CLOUDFLARED_GITEA_TOKEN }}
|
||||
run: |
|
||||
kubectl apply -f kubernetes/gitea/namespace.yml
|
||||
kubectl create configmap gitea-landing-page \
|
||||
-n gitea \
|
||||
--from-file=home.tmpl=kubernetes/gitea/landingpage.html \
|
||||
--from-file=custom-landing.css=kubernetes/gitea/landingpage.css \
|
||||
--from-file=custom-landing.js=kubernetes/gitea/landingpage.js \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
for file in kubernetes/gitea/*.yml; do
|
||||
cat "$file" | envsubst | kubectl apply -f -
|
||||
done
|
||||
|
||||
kubectl rollout restart deployment/gitea-web -n gitea
|
||||
|
||||
notify-on-failure:
|
||||
runs-on: home-server
|
||||
|
||||
29
.gitea/workflows/minecraft.yml
Normal file
29
.gitea/workflows/minecraft.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: deploy minecraft
|
||||
on: [workflow_dispatch]
|
||||
jobs:
|
||||
minecraft:
|
||||
runs-on: home-server
|
||||
env:
|
||||
KUBECONFIG: /home/gitea-runner/.kube/config
|
||||
defaults:
|
||||
run:
|
||||
working-directory: /home/gitea-runner/infrastructure
|
||||
steps:
|
||||
- name: checkout repo
|
||||
working-directory: /home/gitea-runner
|
||||
run: |
|
||||
if [ -d "infrastructure" ]; then
|
||||
cd infrastructure
|
||||
echo "Infrastructure folder exists. Resetting to the most recent commit."
|
||||
git reset --hard HEAD
|
||||
git pull https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.alexmickelson.guru/${{ gitea.repository }} $(git rev-parse --abbrev-ref HEAD)
|
||||
else
|
||||
git clone https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.alexmickelson.guru/${{ gitea.repository }}.git
|
||||
fi
|
||||
- name: deploy minecraft
|
||||
env:
|
||||
CF_API_KEY: ${{ secrets.CF_API_KEY }}
|
||||
run: |
|
||||
for file in kubernetes/minecraft/*.yml; do
|
||||
cat "$file" | envsubst | kubectl apply -f -
|
||||
done
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Notify NTFY
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
title:
|
||||
required: true
|
||||
@@ -20,13 +20,12 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
secrets:
|
||||
NTFY_CHANNEL:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
send-notification:
|
||||
runs-on: [home-server]
|
||||
env:
|
||||
NTFY_CHANNEL: ${{ secrets.NTFY_CHANNEL }}
|
||||
steps:
|
||||
- name: Send ntfy notification
|
||||
working-directory: /home/gitea-runner
|
||||
@@ -38,7 +37,7 @@ jobs:
|
||||
-H "Tags: ${{ inputs.tags }}" \
|
||||
-H "Actions: view, View Logs, ${{ inputs.action_url }}" \
|
||||
--data-binary "@-" \
|
||||
"https://ntfy.sh/${{ secrets.NTFY_CHANNEL }}"
|
||||
"https://ntfy.sh/$NTFY_CHANNEL"
|
||||
${{ inputs.message }}
|
||||
EOF
|
||||
else
|
||||
@@ -46,7 +45,7 @@ jobs:
|
||||
-H "Priority: ${{ inputs.priority }}" \
|
||||
-H "Tags: ${{ inputs.tags }}" \
|
||||
--data-binary "@-" \
|
||||
"https://ntfy.sh/${{ secrets.NTFY_CHANNEL }}"
|
||||
"https://ntfy.sh/$NTFY_CHANNEL"
|
||||
${{ inputs.message }}
|
||||
EOF
|
||||
fi
|
||||
|
||||
37
kubernetes/gitea/cloudflare.yml
Normal file
37
kubernetes/gitea/cloudflare.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: cloudflared-gitea-token
|
||||
namespace: gitea
|
||||
type: Opaque
|
||||
stringData:
|
||||
token: $CLOUDFLARED_GITEA_TOKEN
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: cloudflared-gitea
|
||||
namespace: gitea
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cloudflared-gitea
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cloudflared-gitea
|
||||
spec:
|
||||
containers:
|
||||
- name: cloudflared
|
||||
image: cloudflare/cloudflared:latest
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- tunnel
|
||||
- run
|
||||
env:
|
||||
- name: TUNNEL_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: cloudflared-gitea-token
|
||||
key: token
|
||||
358
kubernetes/gitea/landingpage.css
Normal file
358
kubernetes/gitea/landingpage.css
Normal file
@@ -0,0 +1,358 @@
|
||||
:root {
|
||||
--color-bg-page: #0d1117;
|
||||
--color-bg-card: #161b22;
|
||||
--color-bg-card-hover: #1c2128;
|
||||
--color-border: #21262d;
|
||||
--color-border-muted: #30363d;
|
||||
--color-text: #e6edf3;
|
||||
--color-text-muted: #8b949e;
|
||||
--color-text-subtle: #6e7681;
|
||||
--color-text-faint: #484f58;
|
||||
--color-accent: #58a6ff;
|
||||
--color-accent-subtle: #58a6ff11;
|
||||
--color-accent-shadow: #58a6ff1a;
|
||||
--color-success: #238636;
|
||||
--color-success-hover: #2ea043;
|
||||
--color-white: #fff;
|
||||
|
||||
/* Spacing */
|
||||
--space-xl: 80px;
|
||||
--space-lg: 24px;
|
||||
--space-md: 20px;
|
||||
--space-sm: 16px;
|
||||
--space-xs: 12px;
|
||||
--space-2xs: 10px;
|
||||
|
||||
/* Border radius */
|
||||
--radius-lg: 12px;
|
||||
--radius-md: 8px;
|
||||
--radius-sm: 4px;
|
||||
|
||||
/* Font sizes */
|
||||
--text-hero: 3rem;
|
||||
--text-heading: 1.5rem;
|
||||
--text-btn: 0.95rem;
|
||||
--text-base: 0.875rem;
|
||||
--text-sm: 0.8rem;
|
||||
--text-xs: 0.75rem;
|
||||
}
|
||||
|
||||
/* override gitea defaults */
|
||||
.page-content > :first-child:not(.secondary-nav) {
|
||||
margin-top: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
#alex-landing {
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg-page);
|
||||
color: var(--color-text);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: var(--space-xl);
|
||||
padding-right: var(--space-lg);
|
||||
padding-bottom: 60px;
|
||||
padding-left: var(--space-lg);
|
||||
text-align: center;
|
||||
}
|
||||
.hero-inner {
|
||||
max-width: 640px;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: var(--text-hero);
|
||||
font-weight: 800;
|
||||
margin: 0 0 var(--space-xs);
|
||||
background: linear-gradient(135deg, var(--color-text) 0%, var(--color-accent) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Projects section */
|
||||
.projects-section {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-lg) var(--space-xl);
|
||||
}
|
||||
.section-header {
|
||||
margin-bottom: var(--space-lg);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-xs);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.section-header h2 {
|
||||
font-size: var(--text-heading);
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
|
||||
/* Grid */
|
||||
.repo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
/* Skeleton loaders */
|
||||
.skeleton-card {
|
||||
height: 160px;
|
||||
border-radius: var(--radius-lg);
|
||||
background: linear-gradient(90deg, var(--color-bg-card) 25%, var(--color-border) 50%, var(--color-bg-card) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.4s infinite;
|
||||
}
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
|
||||
/* Repo cards */
|
||||
.repo-card {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-md);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2xs);
|
||||
transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.repo-card:hover {
|
||||
border-color: var(--color-accent);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px var(--color-accent-shadow);
|
||||
}
|
||||
.repo-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2xs);
|
||||
}
|
||||
.repo-icon {
|
||||
font-size: 1.1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.repo-name {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-accent);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.repo-private {
|
||||
font-size: 0.7rem;
|
||||
background: var(--color-border);
|
||||
border: 1px solid var(--color-border-muted);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 1px 6px;
|
||||
color: var(--color-text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.repo-desc {
|
||||
font-size: var(--text-base);
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.repo-commit {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-subtle);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding-top: var(--space-2xs);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.commit-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-success);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.commit-msg {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
.commit-time {
|
||||
color: var(--color-text-faint);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.repo-meta {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-subtle);
|
||||
}
|
||||
.repo-meta span { display: flex; align-items: center; gap: 4px; }
|
||||
|
||||
/* Error state */
|
||||
.error-msg {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--text-btn);
|
||||
}
|
||||
|
||||
/* Activity section */
|
||||
.activity-section {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-lg) var(--space-xl);
|
||||
}
|
||||
.view-all-link {
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
margin-left: auto;
|
||||
}
|
||||
.view-all-link:hover { text-decoration: underline; }
|
||||
.activity-feed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
.skeleton-activity {
|
||||
height: 52px;
|
||||
background: linear-gradient(90deg, var(--color-bg-card) 25%, var(--color-border) 50%, var(--color-bg-card) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.4s infinite;
|
||||
border-top: 1px solid var(--color-bg-page);
|
||||
}
|
||||
.skeleton-activity:first-child { border-top: none; }
|
||||
.activity-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-sm) var(--space-sm);
|
||||
background: var(--color-bg-card);
|
||||
border-top: 1px solid var(--color-border);
|
||||
font-size: var(--text-base);
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.activity-item:first-child { border-top: none; }
|
||||
.activity-item:hover { background: var(--color-bg-card-hover); }
|
||||
.activity-op-icon {
|
||||
flex-shrink: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--text-sm);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.activity-body { flex: 1; min-width: 0; }
|
||||
.activity-headline-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-xs);
|
||||
min-width: 0;
|
||||
}
|
||||
.activity-headline {
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.activity-headline a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
.activity-headline a:hover { text-decoration: underline; }
|
||||
.activity-commits {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
.activity-commit-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-text-muted);
|
||||
overflow: hidden;
|
||||
}
|
||||
.activity-commit-sha {
|
||||
font-family: monospace;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-subtle);
|
||||
background: var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 1px 5px;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
.activity-commit-sha:hover { color: var(--color-accent); text-decoration: none; }
|
||||
.activity-commit-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.activity-time {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-faint);
|
||||
white-space: nowrap;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
/* ── Heatmap ─────────────────────────────────────────────── */
|
||||
.heatmap-section {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-lg) var(--space-xl);
|
||||
}
|
||||
.activity-heatmap {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.heatmap-svg {
|
||||
display: block;
|
||||
}
|
||||
.heatmap-month {
|
||||
font-size: 9px;
|
||||
fill: var(--color-text-muted, #8b949e);
|
||||
font-family: inherit;
|
||||
}
|
||||
.heatmap-day {
|
||||
font-size: 9px;
|
||||
fill: var(--color-text-muted, #8b949e);
|
||||
font-family: inherit;
|
||||
}
|
||||
51
kubernetes/gitea/landingpage.html
Normal file
51
kubernetes/gitea/landingpage.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{{template "base/head" .}}
|
||||
<div class="page-content home" id="alex-landing">
|
||||
<section class="hero">
|
||||
<div class="hero-inner">
|
||||
<h1>Alex Mickelson</h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="projects-section">
|
||||
<div class="section-header">
|
||||
<h2>Recent Projects</h2>
|
||||
</div>
|
||||
<div id="repo-grid" class="repo-grid">
|
||||
<div class="skeleton-card"></div>
|
||||
<div class="skeleton-card"></div>
|
||||
<div class="skeleton-card"></div>
|
||||
<div class="skeleton-card"></div>
|
||||
<div class="skeleton-card"></div>
|
||||
<div class="skeleton-card"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="heatmap-section">
|
||||
<div class="section-header">
|
||||
<h2>Activity</h2>
|
||||
</div>
|
||||
<div id="activity-heatmap" class="activity-heatmap"></div>
|
||||
</section>
|
||||
|
||||
<section class="activity-section">
|
||||
<div class="section-header">
|
||||
<h2>Recent Activity</h2>
|
||||
<a href="/alex" class="view-all-link">View full profile →</a>
|
||||
</div>
|
||||
<div id="activity-feed" class="activity-feed">
|
||||
<div class="skeleton-activity"></div>
|
||||
<div class="skeleton-activity"></div>
|
||||
<div class="skeleton-activity"></div>
|
||||
<div class="skeleton-activity"></div>
|
||||
<div class="skeleton-activity"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<script>
|
||||
window.GITEA_APP_URL = "{{AppUrl}}";
|
||||
window.GITEA_SUB_URL = "{{AppSubUrl}}";
|
||||
</script>
|
||||
<!-- update version when changed to reset cloudflare cache -->
|
||||
<script src="{{AppSubUrl}}/assets/js/custom-landing.js?v=9"></script>
|
||||
<link href="{{AppSubUrl}}/assets/css/custom-landing.css?v=9" rel="stylesheet" />
|
||||
{{template "base/footer" .}}
|
||||
396
kubernetes/gitea/landingpage.js
Normal file
396
kubernetes/gitea/landingpage.js
Normal file
@@ -0,0 +1,396 @@
|
||||
const baseUrl = window.GITEA_SUB_URL || "";
|
||||
const httpService = {
|
||||
|
||||
async fetchRss() {
|
||||
const resp = await fetch(`${baseUrl}/alex.rss`);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const text = await resp.text();
|
||||
return new DOMParser().parseFromString(text, "application/xml");
|
||||
},
|
||||
|
||||
async fetchHeatmap(username = "alex") {
|
||||
const resp = await fetch(`${baseUrl}/api/v1/users/${username}/heatmap`);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
return resp.json(); // [{timestamp: unix_seconds, contributions: number}]
|
||||
},
|
||||
};
|
||||
|
||||
const dataDomain = {
|
||||
timeAgo(dateStr) {
|
||||
const diff = (Date.now() - new Date(dateStr)) / 1000;
|
||||
if (diff < 60) return "just now";
|
||||
if (diff < 3600) return Math.floor(diff / 60) + "m ago";
|
||||
if (diff < 86400) return Math.floor(diff / 3600) + "h ago";
|
||||
if (diff < 2592000) return Math.floor(diff / 86400) + "d ago";
|
||||
if (diff < 31536000) return Math.floor(diff / 2592000) + "mo ago";
|
||||
return Math.floor(diff / 31536000) + "y ago";
|
||||
},
|
||||
|
||||
esc(str) {
|
||||
return (str || "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
},
|
||||
|
||||
safeTitleHtml(rawTitleText) {
|
||||
const doc = new DOMParser().parseFromString(rawTitleText, "text/html");
|
||||
doc.body
|
||||
.querySelectorAll("*:not(a)")
|
||||
.forEach((el) => el.replaceWith(el.textContent));
|
||||
return doc.body.innerHTML;
|
||||
},
|
||||
|
||||
titlePlainText(rawTitleText) {
|
||||
const doc = new DOMParser().parseFromString(rawTitleText, "text/html");
|
||||
return doc.body.textContent || rawTitleText;
|
||||
},
|
||||
|
||||
activityIcon(titleText) {
|
||||
const t = titleText.toLowerCase();
|
||||
if (t.includes("push") || t.includes("commit")) return "📤";
|
||||
if (t.includes("creat") && t.includes("repo")) return "📁";
|
||||
if (t.includes("fork")) return "🍴";
|
||||
if (t.includes("open") && t.includes("issue")) return "🔴";
|
||||
if (t.includes("clos") && t.includes("issue")) return "🟢";
|
||||
if (t.includes("pull request") || t.includes("merge")) return "🔀";
|
||||
if (t.includes("tag")) return "🏷️";
|
||||
if (t.includes("branch")) return "🌿";
|
||||
if (t.includes("comment")) return "💬";
|
||||
if (t.includes("release")) return "🚀";
|
||||
return "⚡";
|
||||
},
|
||||
|
||||
parseCommits(descriptionText) {
|
||||
const doc = new DOMParser().parseFromString(descriptionText, "text/html");
|
||||
return Array.from(doc.querySelectorAll("a")).map((anchor) => {
|
||||
const sha = anchor.textContent.trim().slice(0, 7);
|
||||
const href = anchor.getAttribute("href") || "#";
|
||||
let msg = "";
|
||||
let node = anchor.nextSibling;
|
||||
while (node) {
|
||||
const t = (node.textContent || "").trim();
|
||||
if (t) {
|
||||
msg = t;
|
||||
break;
|
||||
}
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return { sha, href, msg };
|
||||
});
|
||||
},
|
||||
|
||||
parseRepos(xmlDoc) {
|
||||
const items = Array.from(xmlDoc.querySelectorAll("channel > item"));
|
||||
const seen = new Map();
|
||||
for (const item of items) {
|
||||
const titleHtml = item.querySelector("title")?.textContent || "";
|
||||
const titleDoc = new DOMParser().parseFromString(titleHtml, "text/html");
|
||||
const anchors = titleDoc.querySelectorAll("a");
|
||||
if (anchors.length < 2) continue;
|
||||
const repoAnchor = anchors[anchors.length - 1];
|
||||
const repoName = repoAnchor.textContent.trim();
|
||||
if (!repoName || seen.has(repoName)) continue;
|
||||
seen.set(repoName, {
|
||||
repoName,
|
||||
repoUrl: repoAnchor.getAttribute("href") || "#",
|
||||
shortName: repoName.includes("/")
|
||||
? repoName.split("/").pop()
|
||||
: repoName,
|
||||
pubDate: item.querySelector("pubDate")?.textContent || "",
|
||||
firstCommit:
|
||||
dataDomain.parseCommits(
|
||||
item.querySelector("description")?.textContent || "",
|
||||
)[0] || null,
|
||||
});
|
||||
}
|
||||
return Array.from(seen.values());
|
||||
},
|
||||
|
||||
parseAllActivityDates(xmlDoc) {
|
||||
const counts = new Map();
|
||||
for (const item of Array.from(xmlDoc.querySelectorAll("channel > item"))) {
|
||||
const pubDate = item.querySelector("pubDate")?.textContent || "";
|
||||
if (!pubDate) continue;
|
||||
const d = new Date(pubDate);
|
||||
if (isNaN(d)) continue;
|
||||
const key = d.toISOString().slice(0, 10);
|
||||
counts.set(key, (counts.get(key) || 0) + 1);
|
||||
}
|
||||
return counts;
|
||||
},
|
||||
|
||||
parseActivity(xmlDoc, limit = 20) {
|
||||
return Array.from(xmlDoc.querySelectorAll("channel > item"))
|
||||
.slice(0, limit)
|
||||
.map((item) => {
|
||||
const rawTitle = item.querySelector("title")?.textContent || "";
|
||||
const titleText = dataDomain.titlePlainText(rawTitle);
|
||||
return {
|
||||
titleHtmlSafe: dataDomain.safeTitleHtml(rawTitle),
|
||||
titleText,
|
||||
link: item.querySelector("link")?.textContent || "#",
|
||||
pubDate: item.querySelector("pubDate")?.textContent || "",
|
||||
icon: dataDomain.activityIcon(titleText),
|
||||
commits: dataDomain.parseCommits(
|
||||
item.querySelector("description")?.textContent || "",
|
||||
).slice(0, 3),
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const uiRendering = {
|
||||
async renderRepos(xmlDoc) {
|
||||
const grid = document.getElementById("repo-grid");
|
||||
if (!grid) return;
|
||||
|
||||
const repos = dataDomain.parseRepos(xmlDoc);
|
||||
if (repos.length === 0) {
|
||||
grid.innerHTML = `<div class="error-msg">No repositories found in feed.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = "";
|
||||
for (const {
|
||||
shortName,
|
||||
repoName,
|
||||
repoUrl,
|
||||
pubDate,
|
||||
firstCommit,
|
||||
} of repos) {
|
||||
const when = dataDomain.timeAgo(pubDate);
|
||||
const commitMsg = firstCommit?.msg || firstCommit?.sha || "";
|
||||
|
||||
const card = document.createElement("a");
|
||||
card.className = "repo-card";
|
||||
card.href = dataDomain.esc(repoUrl);
|
||||
card.innerHTML = `
|
||||
<div class="repo-card-header">
|
||||
<span class="repo-icon">📦</span>
|
||||
<span class="repo-name">${dataDomain.esc(shortName)}</span>
|
||||
</div>
|
||||
<div class="repo-desc">${dataDomain.esc(repoName)}</div>
|
||||
<div class="repo-commit">
|
||||
<span class="commit-dot"></span>
|
||||
<span class="commit-msg">${dataDomain.esc(commitMsg)}</span>
|
||||
<span class="commit-time">${dataDomain.esc(when)}</span>
|
||||
</div>
|
||||
`.trim();
|
||||
grid.appendChild(card);
|
||||
}
|
||||
},
|
||||
|
||||
async renderActivity(xmlDoc) {
|
||||
const feed = document.getElementById("activity-feed");
|
||||
if (!feed) return;
|
||||
|
||||
const items = dataDomain.parseActivity(xmlDoc);
|
||||
if (items.length === 0) {
|
||||
feed.innerHTML = `<div class="error-msg">No public activity yet.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
feed.innerHTML = "";
|
||||
for (const { titleHtmlSafe, icon, pubDate, commits } of items) {
|
||||
const when = dataDomain.timeAgo(pubDate);
|
||||
|
||||
const commitsHtml =
|
||||
commits.length === 0
|
||||
? ""
|
||||
: `<div class="activity-commits">` +
|
||||
commits
|
||||
.map(
|
||||
({ sha, href, msg }) => `
|
||||
<div class="activity-commit-line">
|
||||
<a class="activity-commit-sha" href="${dataDomain.esc(href)}">${dataDomain.esc(sha)}</a>
|
||||
<span class="activity-commit-text">${dataDomain.esc(msg)}</span>
|
||||
</div>`,
|
||||
)
|
||||
.join("") +
|
||||
`</div>`;
|
||||
|
||||
const el = document.createElement("div");
|
||||
el.className = "activity-item";
|
||||
el.innerHTML = `
|
||||
<div class="activity-op-icon">${icon}</div>
|
||||
<div class="activity-body">
|
||||
<div class="activity-headline-row">
|
||||
<div class="activity-headline">${titleHtmlSafe}</div>
|
||||
<span class="activity-time">${when}</span>
|
||||
</div>
|
||||
${commitsHtml}
|
||||
</div>
|
||||
`;
|
||||
feed.appendChild(el);
|
||||
}
|
||||
},
|
||||
|
||||
async activityMapRender() {
|
||||
const container = document.getElementById("activity-heatmap");
|
||||
if (!container) return;
|
||||
|
||||
let heatmapData;
|
||||
try {
|
||||
heatmapData = await httpService.fetchHeatmap();
|
||||
} catch (e) {
|
||||
container.innerHTML = `<div class="error-msg">Could not load heatmap (${e.message})</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Build counts map from API data
|
||||
const counts = new Map();
|
||||
for (const { timestamp, contributions } of heatmapData) {
|
||||
const d = new Date(timestamp * 1000);
|
||||
const key = d.toISOString().slice(0, 10);
|
||||
counts.set(key, (counts.get(key) || 0) + (contributions || 1));
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
// Align start to Sunday 52 weeks ago
|
||||
const startDate = new Date(today);
|
||||
startDate.setDate(startDate.getDate() - 52 * 7);
|
||||
startDate.setDate(startDate.getDate() - startDate.getDay());
|
||||
|
||||
const cellSize = 11;
|
||||
const gap = 2;
|
||||
const step = cellSize + gap;
|
||||
const cols = 53;
|
||||
const rows = 7;
|
||||
const padLeft = 28;
|
||||
const padTop = 20;
|
||||
const svgW = padLeft + cols * step;
|
||||
const svgH = padTop + rows * step;
|
||||
|
||||
const LEVELS = ["#2d333b", "#0e4429", "#006d32", "#26a641", "#39d353"];
|
||||
const countToLevel = (n) =>
|
||||
n === 0 ? 0 : n === 1 ? 1 : n <= 3 ? 2 : n <= 6 ? 3 : 4;
|
||||
|
||||
// Collect month labels (one per column where the month changes)
|
||||
const monthLabels = new Map();
|
||||
let lastMonth = -1;
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const d = new Date(startDate);
|
||||
d.setDate(d.getDate() + col * 7);
|
||||
if (d.getMonth() !== lastMonth) {
|
||||
lastMonth = d.getMonth();
|
||||
monthLabels.set(col, d.toLocaleString("default", { month: "short" }));
|
||||
}
|
||||
}
|
||||
|
||||
const ns = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(ns, "svg");
|
||||
svg.setAttribute("width", svgW);
|
||||
svg.setAttribute("height", svgH);
|
||||
svg.setAttribute("class", "heatmap-svg");
|
||||
svg.setAttribute("aria-label", "Activity heatmap");
|
||||
|
||||
// Month labels
|
||||
for (const [col, name] of monthLabels) {
|
||||
const t = document.createElementNS(ns, "text");
|
||||
t.setAttribute("x", padLeft + col * step);
|
||||
t.setAttribute("y", 12);
|
||||
t.setAttribute("class", "heatmap-month");
|
||||
t.textContent = name;
|
||||
svg.appendChild(t);
|
||||
}
|
||||
|
||||
// Day-of-week labels (Sun / Tue / Thu / Sat)
|
||||
["Sun", "", "Tue", "", "Thu", "", "Sat"].forEach((label, i) => {
|
||||
if (!label) return;
|
||||
const t = document.createElementNS(ns, "text");
|
||||
t.setAttribute("x", 0);
|
||||
t.setAttribute("y", padTop + i * step + cellSize - 2);
|
||||
t.setAttribute("class", "heatmap-day");
|
||||
t.textContent = label;
|
||||
svg.appendChild(t);
|
||||
});
|
||||
|
||||
// Day cells
|
||||
for (let col = 0; col < cols; col++) {
|
||||
for (let row = 0; row < rows; row++) {
|
||||
const d = new Date(startDate);
|
||||
d.setDate(d.getDate() + col * 7 + row);
|
||||
if (d > today) continue;
|
||||
|
||||
const key = d.toISOString().slice(0, 10);
|
||||
const count = counts.get(key) || 0;
|
||||
|
||||
const rect = document.createElementNS(ns, "rect");
|
||||
rect.setAttribute("x", padLeft + col * step);
|
||||
rect.setAttribute("y", padTop + row * step);
|
||||
rect.setAttribute("width", cellSize);
|
||||
rect.setAttribute("height", cellSize);
|
||||
rect.setAttribute("rx", 2);
|
||||
rect.setAttribute("fill", LEVELS[countToLevel(count)]);
|
||||
rect.setAttribute("data-date", key);
|
||||
rect.setAttribute("data-count", count);
|
||||
|
||||
const title = document.createElementNS(ns, "title");
|
||||
title.textContent = count > 0
|
||||
? `${count} activit${count === 1 ? "y" : "ies"} on ${key}`
|
||||
: `No activity on ${key}`;
|
||||
rect.appendChild(title);
|
||||
svg.appendChild(rect);
|
||||
}
|
||||
}
|
||||
|
||||
// Legend
|
||||
const legendY = svgH + 6;
|
||||
const legendG = document.createElementNS(ns, "g");
|
||||
const legendLabel = document.createElementNS(ns, "text");
|
||||
legendLabel.setAttribute("x", padLeft);
|
||||
legendLabel.setAttribute("y", legendY + cellSize - 2);
|
||||
legendLabel.setAttribute("class", "heatmap-day");
|
||||
legendLabel.textContent = "Less";
|
||||
legendG.appendChild(legendLabel);
|
||||
LEVELS.forEach((color, i) => {
|
||||
const r = document.createElementNS(ns, "rect");
|
||||
r.setAttribute("x", padLeft + 32 + i * step);
|
||||
r.setAttribute("y", legendY);
|
||||
r.setAttribute("width", cellSize);
|
||||
r.setAttribute("height", cellSize);
|
||||
r.setAttribute("rx", 2);
|
||||
r.setAttribute("fill", color);
|
||||
legendG.appendChild(r);
|
||||
});
|
||||
const moreLabel = document.createElementNS(ns, "text");
|
||||
moreLabel.setAttribute("x", padLeft + 32 + LEVELS.length * step + 4);
|
||||
moreLabel.setAttribute("y", legendY + cellSize - 2);
|
||||
moreLabel.setAttribute("class", "heatmap-day");
|
||||
moreLabel.textContent = "More";
|
||||
legendG.appendChild(moreLabel);
|
||||
svg.setAttribute("height", svgH + cellSize + 12);
|
||||
svg.appendChild(legendG);
|
||||
|
||||
container.innerHTML = "";
|
||||
container.appendChild(svg);
|
||||
},
|
||||
|
||||
async render() {
|
||||
const baseUrl = httpService.baseUrl;
|
||||
|
||||
try {
|
||||
const xmlDoc = await httpService.fetchRss();
|
||||
await Promise.all([
|
||||
uiRendering.renderRepos(xmlDoc),
|
||||
uiRendering.renderActivity(xmlDoc),
|
||||
uiRendering.activityMapRender(),
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error("Gitea landing: RSS fetch failed", e);
|
||||
const grid = document.getElementById("repo-grid");
|
||||
const feed = document.getElementById("activity-feed");
|
||||
if (grid)
|
||||
grid.innerHTML = `<div class="error-msg">Could not load feed (${e.message}). <a href="${baseUrl}/explore/repos" style="color:#58a6ff">Browse manually →</a></div>`;
|
||||
if (feed)
|
||||
feed.innerHTML = `<div class="error-msg">Could not load activity (${e.message})</div>`;
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", uiRendering.render);
|
||||
@@ -34,18 +34,33 @@ spec:
|
||||
value: "gitea"
|
||||
- name: GITEA__database__PASSWD
|
||||
value: wauiofnasufnweaiufbsdklfjb23456
|
||||
- name: GITEA__server__ROOT_URL
|
||||
value: "https://git.alexmickelson.guru/"
|
||||
- name: GITEA__server__PROTOCOL
|
||||
value: "http"
|
||||
- name: GITEA__server__DOMAIN
|
||||
value: "git.alexmickelson.guru"
|
||||
- name: GITEA__server__PUBLIC_URL_DETECTION
|
||||
value: "auto"
|
||||
- name: GITEA__server__LOCAL_ROOT_URL
|
||||
value: "http://gitea-web-svc.gitea.svc.cluster.local:3000/"
|
||||
- name: GITEA__server__SSH_DOMAIN
|
||||
value: "gitea-gitea-web-svc.beefalo-newton.ts.net"
|
||||
- name: GITEA__server__SSH_PORT
|
||||
value: "22"
|
||||
# security
|
||||
- name: GITEA__service__ENABLE_BASIC_AUTHENTICATION
|
||||
value: "false"
|
||||
- name: GITEA__service__DISABLE_REGISTRATION
|
||||
value: "true"
|
||||
- name: GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION
|
||||
value: "false"
|
||||
- name: GITEA__openid__ENABLE_OPENID_SIGNIN
|
||||
value: "false"
|
||||
- name: GITEA__openid__ENABLE_OPENID_SIGNUP
|
||||
value: "false"
|
||||
- name: GITEA__ui__DEFAULT_THEME
|
||||
value: "gitea-dark"
|
||||
- name: GITEA__ui__THEMES
|
||||
value: "gitea-dark"
|
||||
volumeMounts:
|
||||
- name: gitea-data
|
||||
mountPath: /data
|
||||
@@ -55,6 +70,18 @@ spec:
|
||||
- name: localtime
|
||||
mountPath: /etc/localtime
|
||||
readOnly: true
|
||||
- name: landing-page
|
||||
mountPath: /data/gitea/templates/home.tmpl
|
||||
subPath: home.tmpl
|
||||
readOnly: true
|
||||
- name: landing-page
|
||||
mountPath: /data/gitea/public/assets/css/custom-landing.css
|
||||
subPath: custom-landing.css
|
||||
readOnly: true
|
||||
- name: landing-page
|
||||
mountPath: /data/gitea/public/assets/js/custom-landing.js
|
||||
subPath: custom-landing.js
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: gitea-data
|
||||
hostPath:
|
||||
@@ -66,6 +93,9 @@ spec:
|
||||
- name: localtime
|
||||
hostPath:
|
||||
path: /etc/localtime
|
||||
- name: landing-page
|
||||
configMap:
|
||||
name: gitea-landing-page
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -66,6 +66,43 @@ spec:
|
||||
memory: "6Gi"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
- name: minecraft-cobblemon
|
||||
image: itzg/minecraft-server:java21
|
||||
stdin: true
|
||||
tty: true
|
||||
env:
|
||||
- name: EULA
|
||||
value: "true"
|
||||
- name: TYPE
|
||||
value: "AUTO_CURSEFORGE"
|
||||
- name: CF_SLUG
|
||||
value: "cobbleverse-cobblemon"
|
||||
- name: CF_MODPACK_ZIP
|
||||
value: "/modpacks/COBBLEVERSE-1.7.30-CF.zip"
|
||||
- name: CF_API_KEY
|
||||
value: "$CF_API_KEY"
|
||||
- name: MEMORY
|
||||
value: "4G"
|
||||
- name: SERVER_PORT
|
||||
value: "2222"
|
||||
- name: RCON_PORT
|
||||
value: "25576"
|
||||
- name: CF_OVERRIDES_EXCLUSIONS
|
||||
value: |
|
||||
# Not applicable for server side
|
||||
shaderpacks/**
|
||||
resourcepacks/**
|
||||
volumeMounts:
|
||||
- name: cobblemon-data
|
||||
mountPath: /data
|
||||
- name: modpacks
|
||||
mountPath: /modpacks
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
limits:
|
||||
memory: "6Gi"
|
||||
volumes:
|
||||
- name: minecraft-data
|
||||
hostPath:
|
||||
@@ -83,18 +120,7 @@ spec:
|
||||
hostPath:
|
||||
path: /dev/net/tun
|
||||
type: CharDevice
|
||||
---
|
||||
# apiVersion: v1
|
||||
# kind: Service
|
||||
# metadata:
|
||||
# name: minecraft
|
||||
# namespace: minecraft
|
||||
# spec:
|
||||
# selector:
|
||||
# app: minecraft
|
||||
# ports:
|
||||
# - name: minecraft
|
||||
# protocol: TCP
|
||||
# port: 25565
|
||||
# targetPort: 25565
|
||||
# type: ClusterIP
|
||||
- name: cobblemon-data
|
||||
hostPath:
|
||||
path: /data/minecraft/cobblemon-data
|
||||
type: DirectoryOrCreate
|
||||
|
||||
@@ -103,21 +103,20 @@
|
||||
iperf
|
||||
mangohud
|
||||
mlocate
|
||||
kdePackages.kdeconnect-kde
|
||||
|
||||
wineWowPackages.stable
|
||||
wine
|
||||
(wine.override { wineBuild = "wine64"; })
|
||||
wine64
|
||||
wineWowPackages.staging
|
||||
winetricks
|
||||
wineWowPackages.waylandFull
|
||||
# wineWowPackages.stable
|
||||
# wine
|
||||
# (wine.override { wineBuild = "wine64"; })
|
||||
# wine64
|
||||
# wineWowPackages.staging
|
||||
# winetricks
|
||||
# wineWowPackages.waylandFull
|
||||
|
||||
mesa-gl-headers
|
||||
mesa
|
||||
driversi686Linux.mesa
|
||||
mesa-demos
|
||||
|
||||
android-tools
|
||||
# mesa-gl-headers
|
||||
# mesa
|
||||
# driversi686Linux.mesa
|
||||
# mesa-demos
|
||||
];
|
||||
services.tailscale.enable = true;
|
||||
services.openssh.enable = true;
|
||||
@@ -160,6 +159,25 @@
|
||||
};
|
||||
};
|
||||
|
||||
# fingerprint
|
||||
# services.fprintd = {
|
||||
# enable = true;
|
||||
# package = pkgs.fprintd.override {
|
||||
# libfprint = pkgs.libfprint;
|
||||
# };
|
||||
# };
|
||||
# services.gnome.gnome-keyring.enable = true;
|
||||
# security.polkit.enable = true;
|
||||
|
||||
# security.pam.services.gdm.fprintAuth = true;
|
||||
# security.pam.services.gdm.enableGnomeKeyring = true;
|
||||
|
||||
# security.pam.services.sudo.fprintAuth = true;
|
||||
# services.udev.extraRules = ''
|
||||
# ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="04f3", ATTR{idProduct}=="0c3d", TEST=="power/control", ATTR{power/control}="on"
|
||||
# '';
|
||||
|
||||
|
||||
# This value determines the NixOS release from which the default
|
||||
# settings for stateful data, like file locations and database versions
|
||||
# on your system were taken. It‘s perfectly fine and recommended to leave
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = [ ./fish.home.nix ];
|
||||
|
||||
customFish = {
|
||||
dotnetPackage = pkgs.dotnetCorePackages.sdk_8_0;
|
||||
bitwardenSshAgent = true;
|
||||
};
|
||||
|
||||
home.packages = with pkgs; [
|
||||
vscode-fhs
|
||||
gnome-software
|
||||
@@ -43,37 +50,7 @@
|
||||
package = pkgs.gnome-themes-extra;
|
||||
};
|
||||
};
|
||||
programs.fish = {
|
||||
enable = true;
|
||||
shellInit = ''
|
||||
function commit
|
||||
git add --all
|
||||
git commit -m "$argv"
|
||||
git push
|
||||
end
|
||||
|
||||
# have ctrl+backspace delete previous word
|
||||
bind \e\[3\;5~ kill-word
|
||||
# have ctrl+delete delete following word
|
||||
bind \b backward-kill-word
|
||||
|
||||
set -U fish_user_paths ~/.local/bin $fish_user_paths
|
||||
#set -U fish_user_paths ~/.dotnet $fish_user_paths
|
||||
#set -U fish_user_paths ~/.dotnet/tools $fish_user_paths
|
||||
|
||||
export VISUAL=vim
|
||||
export EDITOR="$VISUAL"
|
||||
export DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
export DOTNET_ROOT=${pkgs.dotnetCorePackages.sdk_8_0}
|
||||
|
||||
set -x LIBVIRT_DEFAULT_URI qemu:///system
|
||||
|
||||
set -x TERM xterm-256color # ghostty
|
||||
export SSH_AUTH_SOCK=/home/alex/.bitwarden-ssh-agent.sock # ssh agent
|
||||
|
||||
'';
|
||||
};
|
||||
home.file = {
|
||||
".config/lazydocker/config.yml".text = ''
|
||||
gui:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = [ ./fish.home.nix ];
|
||||
|
||||
home.packages = with pkgs; [
|
||||
vscode-fhs
|
||||
gnome-software
|
||||
@@ -38,31 +40,7 @@
|
||||
package = pkgs.gnome-themes-extra;
|
||||
};
|
||||
};
|
||||
programs.fish = {
|
||||
enable = true;
|
||||
shellInit = ''
|
||||
function commit
|
||||
git add --all
|
||||
git commit -m "$argv"
|
||||
git push
|
||||
end
|
||||
|
||||
# have ctrl+backspace delete previous word
|
||||
bind \e\[3\;5~ kill-word
|
||||
# have ctrl+delete delete following word
|
||||
bind \b backward-kill-word
|
||||
|
||||
set -U fish_user_paths ~/.local/bin $fish_user_paths
|
||||
#set -U fish_user_paths ~/.dotnet $fish_user_paths
|
||||
#set -U fish_user_paths ~/.dotnet/tools $fish_user_paths
|
||||
|
||||
export VISUAL=vim
|
||||
export EDITOR="$VISUAL"
|
||||
export DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
set -x LIBVIRT_DEFAULT_URI qemu:///system
|
||||
'';
|
||||
};
|
||||
home.file = {
|
||||
".config/lazydocker/config.yml".text = ''
|
||||
gui:
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
imports = [ ./fish.home.nix ];
|
||||
|
||||
customFish = {
|
||||
bluetuiAliases = true;
|
||||
};
|
||||
home.packages = with pkgs; [
|
||||
k9s
|
||||
jwt-cli
|
||||
@@ -36,37 +41,7 @@
|
||||
home.sessionVariables = {
|
||||
EDITOR = "vim";
|
||||
};
|
||||
programs.fish = {
|
||||
enable = true;
|
||||
shellInit = ''
|
||||
alias blue="bluetui"
|
||||
|
||||
function commit
|
||||
git add --all
|
||||
git commit -m "$argv"
|
||||
git pull
|
||||
git push
|
||||
end
|
||||
|
||||
# have ctrl+backspace delete previous word
|
||||
bind \e\[3\;5~ kill-word
|
||||
# have ctrl+delete delete following word
|
||||
bind \b backward-kill-word
|
||||
|
||||
set -U fish_user_paths ~/.local/bin $fish_user_paths
|
||||
#set -U fish_user_paths ~/.dotnet $fish_user_paths
|
||||
#set -U fish_user_paths ~/.dotnet/tools $fish_user_paths
|
||||
|
||||
export VISUAL=vim
|
||||
export EDITOR="$VISUAL"
|
||||
export DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
set -x LIBVIRT_DEFAULT_URI qemu:///system
|
||||
|
||||
alias blue="bluetui"
|
||||
alias jelly="jellyfin-tui"
|
||||
'';
|
||||
};
|
||||
home.file = {
|
||||
".config/lazydocker/config.yml".text = ''
|
||||
gui:
|
||||
|
||||
@@ -4,16 +4,11 @@
|
||||
vscode-fhs
|
||||
gnome-software
|
||||
gnome-tweaks
|
||||
# nvtopPackages.nvidia
|
||||
nerd-fonts.fira-code
|
||||
nerd-fonts.droid-sans-mono
|
||||
# fira-code
|
||||
# (nerdfonts.override { fonts = [ "FiraCode" "DroidSansMono" ]; })
|
||||
kubernetes-helm
|
||||
busybox
|
||||
ghostty
|
||||
elixir_1_18
|
||||
inotify-tools # needed for elixir hot-reloading
|
||||
nodejs_24
|
||||
pnpm
|
||||
legcord
|
||||
@@ -22,7 +17,14 @@
|
||||
bitwarden-desktop
|
||||
jellyfin-tui
|
||||
bluetui
|
||||
nexusmods-app-unfree
|
||||
# bitwarden-desktop
|
||||
|
||||
lazydocker
|
||||
|
||||
elixir
|
||||
elixir-ls
|
||||
inotify-tools
|
||||
watchman
|
||||
];
|
||||
|
||||
programs.ghostty = {
|
||||
|
||||
75
nix/home-manager/fish.home.nix
Normal file
75
nix/home-manager/fish.home.nix
Normal file
@@ -0,0 +1,75 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
|
||||
let
|
||||
cfg = config.customFish;
|
||||
in {
|
||||
options.customFish = {
|
||||
# Opt-in: only enable if the relevant tools are installed on this machine
|
||||
|
||||
bluetuiAliases = lib.mkEnableOption "bluetui/jellyfin-tui shell aliases";
|
||||
|
||||
dotnetPackage = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.package;
|
||||
default = null;
|
||||
description = "Enable dotnet env vars and PATH entries. Set to the desired SDK package (e.g. pkgs.dotnetCorePackages.sdk_8_0).";
|
||||
};
|
||||
|
||||
bitwardenSshAgent = lib.mkEnableOption "Bitwarden SSH agent (sets SSH_AUTH_SOCK)";
|
||||
};
|
||||
|
||||
config = {
|
||||
programs.fish = {
|
||||
enable = true;
|
||||
shellInit = lib.concatStringsSep "\n" (lib.filter (s: s != "") [
|
||||
|
||||
# https://gist.github.com/thomd/7667642
|
||||
''
|
||||
export LS_COLORS=':di=95'
|
||||
|
||||
function commit
|
||||
git add --all
|
||||
git commit -m "$argv"
|
||||
for remote in (git remote)
|
||||
git pull $remote
|
||||
git push $remote
|
||||
end
|
||||
end
|
||||
|
||||
# have ctrl+backspace delete previous word
|
||||
bind \e\[3\;5~ kill-word
|
||||
# have ctrl+delete delete following word
|
||||
bind \b backward-kill-word
|
||||
set -U fish_user_paths ~/.local/bin ~/bin ~/.dotnet ~/.dotnet/tools $fish_user_paths
|
||||
set fish_pager_color_selected_background --background='00399c'
|
||||
|
||||
export VISUAL=vim
|
||||
export EDITOR="$VISUAL"
|
||||
|
||||
set -x LIBVIRT_DEFAULT_URI qemu:///system
|
||||
set -x TERM xterm-256color
|
||||
|
||||
if test -f "$HOME/.cargo/env.fish"
|
||||
source "$HOME/.cargo/env.fish"
|
||||
end
|
||||
''
|
||||
|
||||
(lib.optionalString cfg.bluetuiAliases ''
|
||||
alias blue="bluetui"
|
||||
alias jelly="jellyfin-tui"
|
||||
'')
|
||||
|
||||
|
||||
(lib.optionalString (cfg.dotnetPackage != null) ''
|
||||
export DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
export DOTNET_ROOT=${cfg.dotnetPackage}
|
||||
'')
|
||||
|
||||
(lib.optionalString cfg.bitwardenSshAgent ''
|
||||
export SSH_AUTH_SOCK=$HOME/.bitwarden-ssh-agent.sock
|
||||
'')
|
||||
|
||||
]);
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,14 @@ let
|
||||
(fetchTarball "https://github.com/nix-community/nixGL/archive/main.tar.gz")
|
||||
{ };
|
||||
in {
|
||||
imports = [ ./fish.home.nix ];
|
||||
|
||||
customFish = {
|
||||
bluetuiAliases = true;
|
||||
dotnetPackage = pkgs.dotnetCorePackages.sdk_8_0;
|
||||
bitwardenSshAgent = true;
|
||||
};
|
||||
|
||||
home.username = "alexm";
|
||||
home.homeDirectory = "/home/alexm";
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
@@ -16,16 +24,16 @@ in {
|
||||
jwt-cli
|
||||
fish
|
||||
kubectl
|
||||
(lazydocker.overrideAttrs (oldAttrs: rec {
|
||||
version = "0.24.1";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "jesseduffield";
|
||||
repo = "lazydocker";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-cVjDdrxmGt+hj/WWP9B3BT739k9SSr4ryye5qWb3XNM=";
|
||||
};
|
||||
}))
|
||||
# lazydocker
|
||||
# (lazydocker.overrideAttrs (oldAttrs: rec {
|
||||
# version = "0.24.4";
|
||||
# src = pkgs.fetchFromGitHub {
|
||||
# owner = "jesseduffield";
|
||||
# repo = "lazydocker";
|
||||
# rev = "v${version}";
|
||||
# hash = "sha256-cW90/yblSLBkcR4ZdtcSI9MXFjOUxyEectjRn9vZwvg=";
|
||||
# };
|
||||
# }))
|
||||
lazydocker
|
||||
traceroute
|
||||
(with dotnetCorePackages; combinePackages [ sdk_8_0 sdk_9_0 ])
|
||||
nodejs_22
|
||||
@@ -41,7 +49,6 @@ in {
|
||||
iperf
|
||||
#makemkv
|
||||
#elixir_1_18
|
||||
#inotify-tools
|
||||
# gnome-themes-extra
|
||||
uv
|
||||
ghostty
|
||||
@@ -66,7 +73,11 @@ in {
|
||||
# vscode-fhs
|
||||
# aider-chat-full
|
||||
|
||||
codex
|
||||
# codex
|
||||
elixir
|
||||
elixir-ls
|
||||
inotify-tools
|
||||
watchman
|
||||
];
|
||||
fonts.fontconfig.enable = true;
|
||||
programs.firefox = {
|
||||
@@ -87,47 +98,7 @@ in {
|
||||
window-width = "120";
|
||||
};
|
||||
};
|
||||
programs.fish = {
|
||||
enable = true;
|
||||
shellInit = ''
|
||||
# https://gist.github.com/thomd/7667642
|
||||
export LS_COLORS=':di=95'
|
||||
|
||||
function commit
|
||||
git add --all
|
||||
git commit -m "$argv"
|
||||
git pull
|
||||
git push
|
||||
end
|
||||
|
||||
# have ctrl+backspace delete previous word
|
||||
bind \e\[3\;5~ kill-word
|
||||
# have ctrl+delete delete following word
|
||||
bind \b backward-kill-word
|
||||
|
||||
alias blue="bluetui"
|
||||
alias jelly="jellyfin-tui"
|
||||
|
||||
set -U fish_user_paths ~/.local/bin $fish_user_paths
|
||||
set -U fish_user_paths ~/bin $fish_user_paths
|
||||
set -U fish_user_paths ~/.dotnet $fish_user_paths
|
||||
set -U fish_user_paths ~/.dotnet/tools $fish_user_paths
|
||||
set fish_pager_color_selected_background --background='00399c'
|
||||
|
||||
export VISUAL=vim
|
||||
export EDITOR="$VISUAL"
|
||||
export DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
export DOTNET_ROOT=${pkgs.dotnetCorePackages.sdk_8_0}
|
||||
|
||||
set -x LIBVIRT_DEFAULT_URI qemu:///system
|
||||
set -x TERM xterm-256color # ghostty
|
||||
|
||||
source "$HOME/.cargo/env.fish"
|
||||
|
||||
export SSH_AUTH_SOCK=/home/alexm/.bitwarden-ssh-agent.sock # ssh agent
|
||||
'';
|
||||
};
|
||||
home.file = {
|
||||
".config/lazydocker/config.yml".text = ''
|
||||
gui:
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
kubectl
|
||||
kubernetes-helm
|
||||
curl
|
||||
nodejs_24
|
||||
openssl
|
||||
gettext
|
||||
];
|
||||
settings = {
|
||||
container = {
|
||||
@@ -48,6 +51,9 @@
|
||||
extraGroups = [ "docker" ];
|
||||
packages = with pkgs; [
|
||||
kubernetes-helm
|
||||
nodejs_24
|
||||
openssl
|
||||
gettext
|
||||
];
|
||||
shell = pkgs.bash;
|
||||
};
|
||||
@@ -86,7 +92,10 @@
|
||||
User = lib.mkForce "gitea-runner";
|
||||
Group = lib.mkForce "gitea-runner";
|
||||
|
||||
Environment = lib.mkForce [ "PATH=/run/wrappers/bin:/run/current-system/sw/bin" ];
|
||||
Environment = lib.mkForce [
|
||||
"PATH=/run/wrappers/bin:/etc/profiles/per-user/gitea-runner/bin:/run/current-system/sw/bin"
|
||||
"NIX_PATH=nixpkgs=${pkgs.path}"
|
||||
];
|
||||
|
||||
DynamicUser = lib.mkForce false;
|
||||
PrivateDevices = lib.mkForce false;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
@@ -6,17 +5,27 @@
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
#boot.kernelPackages = pkgs.linuxKernel.kernels.linux_6_6;
|
||||
# boot.extraModulePackages = with config.boot.kernelPackages; [ xpadneo ];
|
||||
# boot.kernelModules = [ "hid_xpadneo" ];
|
||||
# boot.kernelModules = [
|
||||
# "hid_microsoft" # Xbox One Elite 2 controller driver preferred by Steam
|
||||
# "uinput"
|
||||
# ];
|
||||
|
||||
boot.kernelPackages = pkgs.linuxPackages_6_6;
|
||||
boot.kernelParams = [
|
||||
"amdgpu.discovery=1"
|
||||
];
|
||||
# boot.kernelPackages = pkgs.linuxPackages_6_1;
|
||||
services.xserver.enable = true;
|
||||
|
||||
services.xserver.displayManager.gdm = {
|
||||
enable = true;
|
||||
wayland = false;
|
||||
};
|
||||
|
||||
services.xserver.desktopManager.gnome.enable = true;
|
||||
#boot.kernelParams = [
|
||||
# "amdgpu.discovery=1"
|
||||
#];
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
|
||||
|
||||
@@ -37,9 +46,6 @@
|
||||
LC_TIME = "en_US.UTF-8";
|
||||
};
|
||||
|
||||
services.xserver.enable = true;
|
||||
services.displayManager.gdm.enable = true;
|
||||
services.desktopManager.gnome.enable = true;
|
||||
services.xserver.xkb = {
|
||||
layout = "us";
|
||||
variant = "";
|
||||
@@ -87,6 +93,9 @@
|
||||
libcec
|
||||
flirc
|
||||
|
||||
|
||||
kdePackages.kdeconnect-kde
|
||||
|
||||
];
|
||||
services.openssh.enable = true;
|
||||
services.tailscale.enable = true;
|
||||
@@ -109,4 +118,4 @@
|
||||
|
||||
system.stateVersion = "25.11"; # Did you read the comment?
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user