Compare commits

..

32 Commits

Author SHA1 Message Date
027a9c675d feat: add fitdata Nomad job
Static port 5311 for MCP access (5/3/1 reference).
OIDC protected via Traefik middleware.

Setup: sudo mkdir -p /data/services/fitdata && sudo chown 1000:1000 /data/services/fitdata

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 17:52:28 +00:00
14d267e12d Fix brain.hcl: add port mapping to 3000 2026-01-25 17:30:58 +00:00
2e8e11ecec Deploy brain.hcl - SilverBullet web UI 2026-01-25 17:29:03 +00:00
f90fa5c23b Install Amp. 2026-01-25 13:38:18 +00:00
caa6d0aafd Update flake. 2026-01-25 13:32:32 +00:00
29043896c8 Add ham radio profile with FLEcli package
Introduces a custom packages overlay (pkgs/) for packages not in nixpkgs.
Adds FLEcli v0.1.7 for processing amateur radio logs (SOTA, POTA, WWFF).
Enables ham-radio profile on beefy.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 10:31:53 +00:00
1af9053cd5 Initial config. 2026-01-25 10:05:46 +00:00
2dcd03cbb0 Add brain fish function for externalized executive function system
Launches Claude in ~/brain directory with welcome message showing
available commands (wrap, inbox).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:40:18 +00:00
b5f0cdb429 Docker on beefy. 2026-01-22 17:17:00 +00:00
b63abca296 Add MAILGUN_URL for EU region
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 22:29:08 +00:00
1311aadffb Remove phaseflow-cron batch job
No longer needed - cron scheduling now handled by instrumentation.ts
inside the main phaseflow app.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 22:16:20 +00:00
f903ddeee5 Update phaseflow secrets for Mailgun email provider
Switch from resend_api_key to mailgun_api_key and mailgun_domain.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 19:37:45 +00:00
33f3ddd7e9 Update flake. 2026-01-20 18:17:22 +00:00
1cdedf824c Meta and more RAM. Still not working. 2026-01-20 18:17:11 +00:00
beb856714e Install beads. 2026-01-18 07:34:15 +00:00
fcb2067059 Sync later in the mornings. 2026-01-17 16:28:20 +00:00
cebd236b1f Update flake. 2026-01-16 12:46:13 +00:00
8cc818f6b2 Rename deprecated options. 2026-01-16 10:54:09 +00:00
305a7a5115 Remove unknown option. 2026-01-16 10:41:29 +00:00
526888cd26 Improve phaseflow-cron logging on failure
Show the API response body in logs instead of silently failing
with curl exit code 22.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 07:40:28 +00:00
8d97d09b07 Add phaseflow-cron job and PocketBase admin credentials
- New periodic job for daily Garmin sync at 6 AM
- Added pocketbase_admin_email and pocketbase_admin_password to
  secrets template for cron job authentication

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 07:13:36 +00:00
3f481e0a16 Set the right vars. 2026-01-11 21:22:11 +00:00
15dea7a249 Make PocketBase admin UI accessible. 2026-01-11 17:22:42 +00:00
e1bace9044 Fix phaseflow PocketBase URL to use Nomad address
Docker containers in Nomad don't share network namespace by default.
Use NOMAD_ADDR_pocketbase interpolation instead of localhost.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 17:06:40 +00:00
09f2d2b013 More RAM. 2026-01-11 13:12:09 +00:00
d195efdb0e Add phaseflow service
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:20:40 +00:00
3277c810a5 Update flake. 2026-01-10 20:04:51 +00:00
f2baf3daf6 Move to new domain. 2026-01-09 06:17:20 +00:00
931470ee0a Remove farmOS service
Egg harvest data (849 logs, 6704 eggs, Nov 2023 - Jan 2026) exported
to CSV before shutdown. Database and user dropped from PostgreSQL.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 13:31:57 +00:00
41b30788fe Update flake. 2026-01-08 13:03:48 +00:00
01ebff3596 Migrate to alo organization
Update all registry paths from ppetru/* to alo/* and workflow
references from ppetru/alo-cluster to alo/alo-cluster.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 10:49:38 +00:00
ed2c899915 Add reusable CI/CD workflow and documentation
- .gitea/workflows/deploy-nomad.yaml: Shared workflow for build/push/deploy
- docs/CICD_SETUP.md: Guide for adding CI/CD to new services
- nix-runner/README.md: Document the custom Nix runner image

Services can now use a 10-line workflow that calls the shared one:
  uses: ppetru/alo-cluster/.gitea/workflows/deploy-nomad.yaml@master

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 07:47:01 +00:00
26 changed files with 894 additions and 128 deletions

View File

@@ -0,0 +1,96 @@
# ABOUTME: Reusable workflow for building Nix Docker images and deploying to Nomad.
# ABOUTME: Called by service repos with: uses: alo/alo-cluster/.gitea/workflows/deploy-nomad.yaml@master
name: Deploy to Nomad
on:
workflow_call:
inputs:
service_name:
required: true
type: string
description: "Nomad job name (must match job ID in services/*.hcl)"
flake_output:
required: false
type: string
default: "dockerImage"
description: "Flake output to build (default: dockerImage)"
registry:
required: false
type: string
default: "gitea.v.paler.net"
description: "Container registry hostname"
secrets:
REGISTRY_USERNAME:
required: true
REGISTRY_PASSWORD:
required: true
NOMAD_ADDR:
required: true
jobs:
build-and-deploy:
runs-on: nix
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
echo "Building .#${{ inputs.flake_output }}..."
nix build ".#${{ inputs.flake_output }}" --out-link result
- name: Push to registry
run: |
echo "Pushing to ${{ inputs.registry }}/alo/${{ inputs.service_name }}:latest..."
skopeo copy \
--dest-creds "${{ secrets.REGISTRY_USERNAME }}:${{ secrets.REGISTRY_PASSWORD }}" \
--insecure-policy \
docker-archive:result \
"docker://${{ inputs.registry }}/alo/${{ inputs.service_name }}:latest"
- name: Deploy to Nomad
env:
NOMAD_ADDR: ${{ secrets.NOMAD_ADDR }}
SERVICE: ${{ inputs.service_name }}
run: |
echo "Deploying $SERVICE to Nomad..."
# Fetch current job, update UUID to force deployment
JOB=$(curl -sS "$NOMAD_ADDR/v1/job/$SERVICE")
NEW_UUID=$(cat /proc/sys/kernel/random/uuid)
echo "New deployment UUID: $NEW_UUID"
UPDATED_JOB=$(echo "$JOB" | jq --arg uuid "$NEW_UUID" '.Meta.uuid = $uuid')
# Submit updated job
RESULT=$(echo "{\"Job\": $UPDATED_JOB}" | curl -sS -X POST "$NOMAD_ADDR/v1/jobs" \
-H "Content-Type: application/json" -d @-)
echo "Submit result: $RESULT"
# Monitor deployment
sleep 3
DEPLOY_ID=$(curl -sS "$NOMAD_ADDR/v1/job/$SERVICE/deployments" | jq -r '.[0].ID')
echo "Deployment ID: $DEPLOY_ID"
if [ "$DEPLOY_ID" = "null" ]; then
echo "ERROR: No deployment created. Ensure job has 'update' stanza with 'auto_revert = true'"
exit 1
fi
echo "Monitoring deployment..."
for i in $(seq 1 30); do
STATUS=$(curl -sS "$NOMAD_ADDR/v1/deployment/$DEPLOY_ID" | jq -r '.Status')
echo "[$i/30] Deployment status: $STATUS"
case $STATUS in
successful)
echo "Deployment successful!"
exit 0
;;
failed|cancelled)
echo "Deployment failed or cancelled"
exit 1
;;
esac
sleep 10
done
echo "Timeout waiting for deployment"
exit 1

8
common/ham-radio.nix Normal file
View File

@@ -0,0 +1,8 @@
# ABOUTME: Ham radio tools profile for amateur radio operators.
# ABOUTME: Provides CLI tools for logging and processing ham radio contacts.
{ pkgs, ... }:
{
environment.systemPackages = [
pkgs.custom.flecli
];
}

206
docs/CICD_SETUP.md Normal file
View File

@@ -0,0 +1,206 @@
# CI/CD Setup for Nomad Services
Guide for adding automated builds and deployments to a service.
## Prerequisites
### 1. Service Repository
Your service needs a `flake.nix` that exports a Docker image:
```nix
{
outputs = { self, nixpkgs, ... }: {
# The workflow looks for this output by default
dockerImage = pkgs.dockerTools.buildImage {
name = "gitea.v.paler.net/alo/<service>";
tag = "latest";
# ... image config
};
};
}
```
**Important**: Use `extraCommands` instead of `runAsRoot` in your Docker build - the CI runner doesn't have KVM.
### 2. Nomad Job
Your job in `services/<name>.hcl` needs:
```hcl
job "<service>" {
# Required: UUID changes trigger deployments
meta {
uuid = uuidv4()
}
# Required: enables deployment tracking and auto-rollback
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "30s"
healthy_deadline = "5m"
auto_revert = true
}
# Required: pulls new image on each deployment
task "app" {
config {
force_pull = true
}
# Recommended: health check for deployment validation
service {
check {
type = "http"
path = "/healthz"
interval = "10s"
timeout = "5s"
}
}
}
}
```
## Quick Start
### 1. Create Workflow
Add `.gitea/workflows/deploy.yaml` to your service repo:
```yaml
name: Deploy
on:
push:
branches: [master]
workflow_dispatch:
jobs:
deploy:
uses: alo/alo-cluster/.gitea/workflows/deploy-nomad.yaml@master
with:
service_name: <your-service> # Must match Nomad job ID
secrets: inherit
```
### 2. Add Secrets
In Gitea → Your Repo → Settings → Actions → Secrets, add:
| Secret | Value |
|--------|-------|
| `REGISTRY_USERNAME` | Your Gitea username |
| `REGISTRY_PASSWORD` | Gitea access token with `packages:write` |
| `NOMAD_ADDR` | `http://nomad.service.consul:4646` |
### 3. Push
Push to `master` branch. The workflow will:
1. Build your Docker image with Nix
2. Push to Gitea registry
3. Update the Nomad job to trigger deployment
4. Monitor until deployment succeeds or fails
## Workflow Options
The shared workflow accepts these inputs:
| Input | Default | Description |
|-------|---------|-------------|
| `service_name` | (required) | Nomad job ID |
| `flake_output` | `dockerImage` | Flake output to build |
| `registry` | `gitea.v.paler.net` | Container registry |
Example with custom flake output:
```yaml
jobs:
deploy:
uses: alo/alo-cluster/.gitea/workflows/deploy-nomad.yaml@master
with:
service_name: myservice
flake_output: packages.x86_64-linux.docker
secrets: inherit
```
## How It Works
```
Push to master
Build: nix build .#dockerImage
Push: skopeo → gitea.v.paler.net/alo/<service>:latest
Deploy: Update job meta.uuid → Nomad creates deployment
Monitor: Poll deployment status for up to 5 minutes
Success: Deployment healthy
OR
Failure: Nomad auto-reverts to previous version
```
## Troubleshooting
### Build fails with KVM error
```
Required system: 'x86_64-linux' with features {kvm}
```
Use `extraCommands` instead of `runAsRoot` in your `docker.nix`:
```nix
# Bad - requires KVM
runAsRoot = ''
mkdir -p /tmp
'';
# Good - no KVM needed
extraCommands = ''
mkdir -p tmp
chmod 1777 tmp
'';
```
### No deployment created
Ensure your Nomad job has the `update` stanza with `auto_revert = true`.
### Image not updating
Check that `force_pull = true` is set in the Nomad job's Docker config.
### Deployment fails health checks
- Check your `/healthz` endpoint works
- Increase `healthy_deadline` if startup is slow
- Check `nomad alloc logs <alloc-id>` for errors
### Workflow can't access alo-cluster
If Gitea can't pull the reusable workflow, you may need to make alo-cluster public or use a token. As a fallback, copy the workflow content directly.
## Manual Deployment
If CI fails, you can deploy manually:
```bash
cd <service-repo>
nix build .#dockerImage
skopeo copy --dest-authfile ~/.docker/config.json \
docker-archive:result \
docker://gitea.v.paler.net/alo/<service>:latest
nomad run /path/to/alo-cluster/services/<service>.hcl
```
## Rollback
Nomad auto-reverts on health check failure. For manual rollback:
```bash
nomad job history <service> # List versions
nomad job revert <service> <version> # Revert to specific version
```

View File

@@ -4,4 +4,4 @@
* renovate system of some kind * renovate system of some kind
* vector (or other log ingestion) everywhere, consider moving it off docker if possible * vector (or other log ingestion) everywhere, consider moving it off docker if possible
* monitor backup-persist success/fail * monitor backup-persist success/fail
* gitea organization is public -> at least from the internal network, anyone can pull images and probably also clone repos. there should be absolutely zero secrets in the repos (and the ones that are now should be changed before stored somewhere else) and the nomad workers should authenticate to pull images

153
flake.lock generated
View File

@@ -25,11 +25,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1766518262, "lastModified": 1769196967,
"narHash": "sha256-ITihWSOexQX1a/i8ZiMt3M609KxX7sLhz63F9rnnguY=", "narHash": "sha256-js2jXLzaZbXNFkYTszQntIS8QUJYJumSFK+3bR5nhlo=",
"owner": "nix-community", "owner": "nix-community",
"repo": "browser-previews", "repo": "browser-previews",
"rev": "1e4c4310443ace75d0df9efaeb4c6fc6f8c8678c", "rev": "edc3b1c0455abc74bfe2d6e029abe5fc778b0d62",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -68,11 +68,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1764011051, "lastModified": 1768818222,
"narHash": "sha256-M7SZyPZiqZUR/EiiBJnmyUbOi5oE/03tCeFrTiUZchI=", "narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
"owner": "numtide", "owner": "numtide",
"repo": "devshell", "repo": "devshell",
"rev": "17ed8d9744ebe70424659b0ef74ad6d41fc87071", "rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -88,11 +88,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1766150702, "lastModified": 1768923567,
"narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=", "narHash": "sha256-GVJ0jKsyXLuBzRMXCDY6D5J8wVdwP1DuQmmvYL/Vw/Q=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378", "rev": "00395d188e3594a1507f214a2f15d4ce5c07cb28",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -115,11 +115,11 @@
"treefmt-nix": "treefmt-nix" "treefmt-nix": "treefmt-nix"
}, },
"locked": { "locked": {
"lastModified": 1766221270, "lastModified": 1769298686,
"narHash": "sha256-YDNOESYOfafsoqW7qEM1Gkuv6DZVZyn6Wrs7+/I7u0A=", "narHash": "sha256-ZwsxXeLyrb5VinFsdjrjt/J7Tp5O2A9yy7lxWaw/h78=",
"owner": "nix-community", "owner": "nix-community",
"repo": "ethereum.nix", "repo": "ethereum.nix",
"rev": "cd43a1333b8a2e519815d468b791b893d103e618", "rev": "d52663e0592ced611098f80224b45e57d7223453",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -149,11 +149,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1763759067, "lastModified": 1768135262,
"narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=", "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0", "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -170,11 +170,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1765835352, "lastModified": 1768135262,
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=", "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "a34fae9c08a15ad73f295041fec82323541400a9", "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -237,11 +237,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1764753179, "lastModified": 1767517855,
"narHash": "sha256-2u7C/aKEcCpND60JfREYhKaMg/0cZ/pk1HviizWrCl8=", "narHash": "sha256-LnZosb07bahYAyFw07JFzSXslx9j1dCe+npWDZdPFZg=",
"owner": "shazow", "owner": "shazow",
"repo": "foundry.nix", "repo": "foundry.nix",
"rev": "8c21d3ac7a03da27af55d3c94fe95620c9d6f316", "rev": "ee376e8a93f537c2865dda9811e748e4567a7aaf",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -258,11 +258,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1766553861, "lastModified": 1768949235,
"narHash": "sha256-ZbnG01yA3O8Yr1vUm3+NQ2qk9iRhS5bloAnuXHHy7+c=", "narHash": "sha256-TtjKgXyg1lMfh374w5uxutd6Vx2P/hU81aEhTxrO2cg=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "0999ed8f965bbbd991437ad9c5ed3434cecbc30e", "rev": "75ed713570ca17427119e7e204ab3590cc3bf2a5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -272,13 +272,38 @@
"type": "github" "type": "github"
} }
}, },
"impermanence": { "home-manager_2": {
"inputs": {
"nixpkgs": [
"impermanence",
"nixpkgs"
]
},
"locked": { "locked": {
"lastModified": 1737831083, "lastModified": 1768598210,
"narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=", "narHash": "sha256-kkgA32s/f4jaa4UG+2f8C225Qvclxnqs76mf8zvTVPg=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "c47b2cc64a629f8e075de52e4742de688f930dc6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"impermanence": {
"inputs": {
"home-manager": "home-manager_2",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1768941735,
"narHash": "sha256-OyxsfXNcOkt06/kM+4bnuC8moDx+t7Qr+RB0BBa83Ig=",
"owner": "nix-community", "owner": "nix-community",
"repo": "impermanence", "repo": "impermanence",
"rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", "rev": "69ecf31e8fddc9354a4b418f3a517445d486bb54",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -328,11 +353,11 @@
}, },
"nixos-hardware": { "nixos-hardware": {
"locked": { "locked": {
"lastModified": 1766568855, "lastModified": 1769302137,
"narHash": "sha256-UXVtN77D7pzKmzOotFTStgZBqpOcf8cO95FcupWp4Zo=", "narHash": "sha256-QEDtctEkOsbx8nlFh4yqPEOtr4tif6KTqWwJ37IM2ds=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "c5db9569ac9cc70929c268ac461f4003e3e5ca80", "rev": "a351494b0e35fd7c0b7a1aae82f0afddf4907aa8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -344,27 +369,27 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1766622938, "lastModified": 1768564909,
"narHash": "sha256-Eovt/DOCYjFFBZuYbbG9j5jhklzxdNbUGVYYxh3lG3s=", "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
"owner": "NixOS", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5900a0a8850cbba98e16d5a7a6ed389402dfcf4f", "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "nixos",
"ref": "nixos-25.11", "ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1761765539, "lastModified": 1765674936,
"narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=", "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "719359f4562934ae99f5443f20aa06c2ffff91fc", "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -390,11 +415,11 @@
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1765270179, "lastModified": 1769092226,
"narHash": "sha256-g2a4MhRKu4ymR4xwo+I+auTknXt/+j37Lnf0Mvfl1rE=", "narHash": "sha256-6h5sROT/3CTHvzPy9koKBmoCa2eJKh4fzQK8eYFEgl8=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "677fbe97984e7af3175b6c121f3c39ee5c8d62c9", "rev": "b579d443b37c9c5373044201ea77604e37e748c8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -406,11 +431,11 @@
}, },
"nixpkgs-unstable_2": { "nixpkgs-unstable_2": {
"locked": { "locked": {
"lastModified": 1766651565, "lastModified": 1769170682,
"narHash": "sha256-QEhk0eXgyIqTpJ/ehZKg9IKS7EtlWxF3N7DXy42zPfU=", "narHash": "sha256-oMmN1lVQU0F0W2k6OI3bgdzp2YOHWYUAw79qzDSjenU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "3e2499d5539c16d0d173ba53552a4ff8547f4539", "rev": "c5296fdd05cfa2c187990dd909864da9658df755",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -420,6 +445,22 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": {
"locked": {
"lastModified": 1769089682,
"narHash": "sha256-9yA/LIuAVQq0lXelrZPjLuLVuZdm03p8tfmHhnDIkms=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "078d69f03934859a181e81ba987c2bb033eebfc5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixvim": { "nixvim": {
"inputs": { "inputs": {
"flake-parts": "flake-parts_2", "flake-parts": "flake-parts_2",
@@ -429,11 +470,11 @@
"systems": "systems_4" "systems": "systems_4"
}, },
"locked": { "locked": {
"lastModified": 1766721995, "lastModified": 1769247851,
"narHash": "sha256-2qZLSojZFP3AzbC6UNF3ASCIDLahNniR2XP7l/qINm4=", "narHash": "sha256-fbsopU0qWfqq1WRKjWYpYCMxmEYyq+Cmw++VXVke5Ns=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixvim", "repo": "nixvim",
"rev": "66a5dc70e2d8433034bccdbb9c3c7bcecd86f9a6", "rev": "34a7d94cdcd2b034eb06202992bed1345aa046c9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -453,7 +494,7 @@
"nix-colors": "nix-colors", "nix-colors": "nix-colors",
"nix-index-database": "nix-index-database", "nix-index-database": "nix-index-database",
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs_2",
"nixpkgs-unstable": "nixpkgs-unstable_2", "nixpkgs-unstable": "nixpkgs-unstable_2",
"nixvim": "nixvim", "nixvim": "nixvim",
"sops-nix": "sops-nix" "sops-nix": "sops-nix"
@@ -466,11 +507,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1766289575, "lastModified": 1769314333,
"narHash": "sha256-BOKCwOQQIP4p9z8DasT5r+qjri3x7sPCOq+FTjY8Z+o=", "narHash": "sha256-+Uvq9h2eGsbhacXpuS7irYO7fFlz514nrhPCSTkASlw=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "9836912e37aef546029e48c8749834735a6b9dad", "rev": "2eb9eed7ef48908e0f02985919f7eb9d33fa758f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -547,11 +588,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1766000401, "lastModified": 1768158989,
"narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=", "narHash": "sha256-67vyT1+xClLldnumAzCTBvU0jLZ1YBcf4vANRWP3+Ak=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd", "rev": "e96d59dff5c0d7fddb9d113ba108f03c3ef99eca",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -58,15 +58,17 @@
overlay-unstable = final: prev: { overlay-unstable = final: prev: {
unstable = import nixpkgs-unstable { unstable = import nixpkgs-unstable {
inherit (prev) system; system = prev.stdenv.hostPlatform.system;
config.allowUnfree = true; config.allowUnfree = true;
}; };
}; };
overlay-browser-previews = final: prev: { overlay-browser-previews = final: prev: {
browser-previews = browser-previews.packages.${prev.system}; browser-previews = browser-previews.packages.${prev.stdenv.hostPlatform.system};
}; };
overlay-custom = import ./pkgs;
mkHost = mkHost =
system: profile: modules: system: profile: modules:
let let
@@ -79,7 +81,7 @@
( (
{ config, pkgs, ... }: { config, pkgs, ... }:
{ {
nixpkgs.overlays = [ overlay-unstable overlay-browser-previews ]; nixpkgs.overlays = [ overlay-unstable overlay-browser-previews overlay-custom ];
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
} }
) )

View File

@@ -5,6 +5,8 @@ let
cliPkgs = with pkgs; [ cliPkgs = with pkgs; [
ast-grep ast-grep
yq yq
unstable.amp-cli
unstable.beads
unstable.claude-code unstable.claude-code
unstable.codex unstable.codex
unstable.gemini-cli unstable.gemini-cli

View File

@@ -15,9 +15,5 @@
natural_scroll = false; natural_scroll = false;
}; };
}; };
gestures = lib.mkDefault {
workspace_swipe = false;
};
}; };
} }

View File

@@ -339,6 +339,16 @@
set pure_show_prefix_root_prompt true set pure_show_prefix_root_prompt true
set sponge_regex_patterns 'password|passwd' set sponge_regex_patterns 'password|passwd'
''; '';
functions = {
brain = ''
echo "🧠 Brain session starting..."
echo " wrap - end session with notes"
echo " inbox: <thought> - quick capture"
echo ""
cd ~/brain && claude
'';
};
}; };
fzf = { fzf = {
@@ -347,8 +357,12 @@
git = { git = {
enable = true; enable = true;
userEmail = "petru@paler.net"; settings = {
userName = "Petru Paler"; user = {
email = "petru@paler.net";
name = "Petru Paler";
};
};
}; };
home-manager = { home-manager = {

View File

@@ -6,6 +6,8 @@
# Desktop environment is imported via flake.nix for desktop profile # Desktop environment is imported via flake.nix for desktop profile
../../common/cluster-member.nix # Consul + storage clients ../../common/cluster-member.nix # Consul + storage clients
../../common/cluster-tools.nix # Nomad CLI (no service) ../../common/cluster-tools.nix # Nomad CLI (no service)
../../common/docker.nix # Docker daemon
../../common/ham-radio.nix # Ham radio tools (FLEcli)
./hardware.nix ./hardware.nix
]; ];

100
nix-runner/README.md Normal file
View File

@@ -0,0 +1,100 @@
# Nix Runner for Gitea Actions
Custom Docker image for running Nix builds in CI.
## What's Included
- **Nix** with flakes enabled (`experimental-features = nix-command flakes`)
- **Node.js 20** for JavaScript-based GitHub Actions
- **Tools**: git, curl, jq, skopeo, bash, coreutils
- **Binary caches**:
- `c3.mule-stork.ts.net:8501` (local cache proxy)
- `cache.nixos.org` (official)
## Usage
In your workflow:
```yaml
jobs:
build:
runs-on: nix
steps:
- uses: actions/checkout@v4
- run: nix build .#myPackage
```
The `nix` label is configured in `services/act-runner.hcl`.
## Current Version
**Tag**: `v4`
**Image**: `gitea.v.paler.net/alo/nix-runner:v4`
## Updating the Runner
### 1. Edit `flake.nix`
Make your changes, then bump the tag:
```nix
tag = "v5"; # was v4
```
### 2. Build
```bash
cd nix-runner
nix build
```
### 3. Push to Registry
```bash
skopeo copy --dest-authfile ~/.docker/config.json \
docker-archive:result \
docker://gitea.v.paler.net/alo/nix-runner:v5
```
### 4. Update act-runner
Edit `services/act-runner.hcl`:
```hcl
GITEA_RUNNER_LABELS = "ubuntu-latest:docker://node:20-bookworm,nix:docker://gitea.v.paler.net/alo/nix-runner:v5"
```
### 5. Re-register Runner
```bash
sudo rm /data/services/act-runner/.runner
nomad run services/act-runner.hcl
```
The runner will re-register with the new labels.
## Configuration
The image uses `NIX_CONFIG` environment variable for Nix settings:
```
experimental-features = nix-command flakes
sandbox = false
build-users-group =
substituters = http://c3.mule-stork.ts.net:8501 https://cache.nixos.org
trusted-public-keys = cache.nixos.org-1:... c3:...
```
## Troubleshooting
### Build fails with `build-users-group` error
The image runs as root without the nixbld group. This is handled by `build-users-group =` in NIX_CONFIG.
### Can't fetch from cache
Check that the runner container can reach `c3.mule-stork.ts.net:8501` (Tailscale network).
### Missing tool
Add it to `paths` in `flake.nix` and rebuild/push a new version.

View File

@@ -14,7 +14,7 @@
pkgs = import nixpkgs { inherit system; }; pkgs = import nixpkgs { inherit system; };
in { in {
packages.default = pkgs.dockerTools.buildImage { packages.default = pkgs.dockerTools.buildImage {
name = "gitea.v.paler.net/ppetru/nix-runner"; name = "gitea.v.paler.net/alo/nix-runner";
tag = "v4"; tag = "v4";
copyToRoot = pkgs.buildEnv { copyToRoot = pkgs.buildEnv {

7
pkgs/default.nix Normal file
View File

@@ -0,0 +1,7 @@
# ABOUTME: Custom packages overlay for packages not in nixpkgs.
# ABOUTME: Adds packages under pkgs.custom.* namespace.
final: prev: {
custom = {
flecli = final.callPackage ./flecli.nix { };
};
}

28
pkgs/flecli.nix Normal file
View File

@@ -0,0 +1,28 @@
# ABOUTME: FLEcli - Fast Log Entry CLI for amateur radio logging.
# ABOUTME: Processes FLE-formatted files into ADIF for SOTA, POTA, WWFF, etc.
{
lib,
buildGoModule,
fetchFromGitHub,
}:
buildGoModule rec {
pname = "flecli";
version = "0.1.7";
src = fetchFromGitHub {
owner = "on4kjm";
repo = "FLEcli";
rev = "v${version}";
hash = "sha256-6OFcShgUaK9RwonP6cl8eOD6Cu+F5LHZEUFPjCfWNV0=";
};
vendorHash = "sha256-6m01rcewPyy8pUXnIMwjyW+7I08pyJaTDliwTNp3fOM=";
meta = with lib; {
description = "Fast Log Entry CLI - process amateur radio logs";
homepage = "https://github.com/on4kjm/FLEcli";
license = licenses.mit;
mainProgram = "FLEcli";
};
}

View File

@@ -53,7 +53,7 @@ EOH
env { env {
GITEA_INSTANCE_URL = "https://gitea.v.paler.net" GITEA_INSTANCE_URL = "https://gitea.v.paler.net"
GITEA_RUNNER_LABELS = "ubuntu-latest:docker://node:20-bookworm,nix:docker://gitea.v.paler.net/ppetru/nix-runner:v4" GITEA_RUNNER_LABELS = "ubuntu-latest:docker://node:20-bookworm,nix:docker://gitea.v.paler.net/alo/nix-runner:v4"
} }
# Template needed for nomadVar interpolation (secrets) and Nomad runtime vars # Template needed for nomadVar interpolation (secrets) and Nomad runtime vars

View File

@@ -34,7 +34,7 @@ job "animaltrack" {
user = "1000" user = "1000"
config { config {
image = "gitea.v.paler.net/ppetru/animaltrack:latest" image = "gitea.v.paler.net/alo/animaltrack:latest"
ports = ["http"] ports = ["http"]
force_pull = true force_pull = true
volumes = ["/data/services/animaltrack:/var/lib/animaltrack"] volumes = ["/data/services/animaltrack:/var/lib/animaltrack"]
@@ -68,6 +68,7 @@ EOH
"traefik.enable=true", "traefik.enable=true",
"traefik.http.routers.animaltrack.entryPoints=websecure", "traefik.http.routers.animaltrack.entryPoints=websecure",
"traefik.http.routers.animaltrack.middlewares=oidc-auth@file", "traefik.http.routers.animaltrack.middlewares=oidc-auth@file",
"traefik.http.routers.animaltrack.rule=Host(`farm.alo.land`)",
] ]
check { check {

View File

@@ -19,7 +19,7 @@ job "beancount" {
user = "1000" user = "1000"
config { config {
image = "gitea.v.paler.net/ppetru/fava:latest" image = "gitea.v.paler.net/alo/fava:latest"
ports = ["http"] ports = ["http"]
volumes = [ volumes = [
"/data/services/beancount:/beancount", "/data/services/beancount:/beancount",

50
services/brain.hcl Normal file
View File

@@ -0,0 +1,50 @@
# ABOUTME: Brain - Petre's externalized executive function system
# ABOUTME: SilverBullet for markdown web UI + ttyd for web terminal
job "brain" {
datacenters = ["alo"]
group "web" {
volume "services" {
type = "host"
read_only = false
source = "services"
}
network {
port "silverbullet" {
to = 3000
}
}
task "silverbullet" {
driver = "docker"
user = "1000"
config {
image = "zefhemel/silverbullet:latest"
ports = ["silverbullet"]
volumes = ["/data/services/brain:/space"]
}
resources {
memory = 512
}
service {
name = "brain"
port = "silverbullet"
tags = [
"traefik.enable=true",
"traefik.http.routers.brain.entryPoints=websecure",
"traefik.http.routers.brain.middlewares=oidc-auth@file",
]
}
}
# TODO: terminal task with ttyd for web-based amp/claude access
# Needs custom image with tmux + amp + claude-code installed
# For now, use SSH or local terminal for amp sessions
}
}

View File

@@ -1,51 +0,0 @@
job "farmos" {
datacenters = ["alo"]
meta {
uuid = uuidv4()
}
group "os" {
network {
port "http" {
to = 80
}
}
task "server" {
driver = "docker"
config {
image = "gitea.v.paler.net/ppetru/farmos:latest"
ports = ["http"]
volumes = [
"/data/services/farmos/sites:/opt/drupal/web/sites",
"/data/services/farmos/keys:/opt/drupal/keys",
]
}
service {
name = "farmos"
port = "http"
check {
type = "http"
port = "http"
path = "/health"
interval = "30s"
timeout = "2s"
}
tags = [
"traefik.enable=true",
"traefik.http.routers.farmos.entryPoints=websecure",
"traefik.http.routers.farmos.rule=Host(`farm.alo.land`)",
]
}
resources {
cpu = 2000
memory = 1024
}
}
}
}

74
services/fitdata.hcl Normal file
View File

@@ -0,0 +1,74 @@
# ABOUTME: Nomad job for Fitdata - fitness data analysis and MCP server.
# ABOUTME: Runs FastHTML Python app with SQLite, static port for MCP access.
# Setup required before running:
# sudo mkdir -p /data/services/fitdata && sudo chown 1000:1000 /data/services/fitdata
job "fitdata" {
datacenters = ["alo"]
meta {
uuid = uuidv4()
}
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "30s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
}
group "web" {
network {
port "http" {
static = 5311
}
}
task "app" {
driver = "docker"
user = "1000"
config {
image = "gitea.v.paler.net/alo/fitdata:latest"
ports = ["http"]
force_pull = true
volumes = ["/data/services/fitdata:/var/lib/fitdata"]
}
env {
FITDATA_DATA_DIR = "/var/lib/fitdata"
FITDATA_DB_PATH = "/var/lib/fitdata/fitdata.db"
}
resources {
memory = 512
}
service {
name = "fitdata"
port = "http"
tags = [
"traefik.enable=true",
"traefik.http.routers.fitdata.entryPoints=websecure",
"traefik.http.routers.fitdata.middlewares=oidc-auth@file",
]
check {
type = "http"
path = "/healthz"
interval = "10s"
timeout = "5s"
check_restart {
limit = 3
grace = "60s"
}
}
}
}
}
}

View File

@@ -13,7 +13,7 @@ job "igsync" {
driver = "docker" driver = "docker"
config { config {
image = "gitea.v.paler.net/ppetru/igsync:latest" image = "gitea.v.paler.net/alo/igsync:latest"
# Mount the data directory for .env, database, and media files # Mount the data directory for .env, database, and media files
volumes = [ volumes = [

138
services/phaseflow.hcl Normal file
View File

@@ -0,0 +1,138 @@
# ABOUTME: Nomad job for PhaseFlow - menstrual cycle tracking and training app.
# ABOUTME: Runs Next.js app with PocketBase sidecar for data persistence.
# Setup required before running:
# sudo mkdir -p /data/services/phaseflow/pb_data && sudo chown 1000:1000 /data/services/phaseflow /data/services/phaseflow/pb_data
# nomad var put secrets/phaseflow \
# mailgun_api_key="key-xxxx" \
# mailgun_domain="paler.net" \
# encryption_key="$(openssl rand -hex 16)" \
# cron_secret="$(openssl rand -hex 32)" \
# pocketbase_admin_email="admin@example.com" \
# pocketbase_admin_password="your-admin-password"
job "phaseflow" {
datacenters = ["alo"]
# Force re-pull of :latest images on each nomad run
meta {
uuid = uuidv4()
}
update {
max_parallel = 1
health_check = "checks"
min_healthy_time = "30s"
healthy_deadline = "5m"
progress_deadline = "10m"
auto_revert = true
}
group "app" {
network {
port "http" {
to = 3000
}
port "pocketbase" {
to = 8090
}
}
# PocketBase sidecar - starts first, provides database
task "pocketbase" {
driver = "docker"
user = "1000"
lifecycle {
hook = "prestart"
sidecar = true
}
config {
image = "ghcr.io/muchobien/pocketbase:latest"
ports = ["pocketbase"]
volumes = ["/data/services/phaseflow/pb_data:/pb_data"]
}
env {
PB_DATA_DIR = "/pb_data"
}
resources {
memory = 256
}
service {
name = "pocketbase-phaseflow"
port = "pocketbase"
tags = [
"traefik.enable=true",
"traefik.http.routers.pocketbase-phaseflow.entryPoints=websecure",
]
}
}
# Main Next.js application
task "app" {
driver = "docker"
user = "1000"
config {
image = "gitea.v.paler.net/alo/phaseflow:latest"
ports = ["http"]
force_pull = true
}
env {
NODE_ENV = "production"
POCKETBASE_URL = "http://${NOMAD_ADDR_pocketbase}"
NEXT_PUBLIC_POCKETBASE_URL = "https://pocketbase-phaseflow.v.paler.net"
APP_URL = "https://phaseflow.v.paler.net"
EMAIL_FROM = "phaseflow@paler.net"
MAILGUN_URL = "https://api.eu.mailgun.net"
}
# Secrets from Nomad variables
template {
destination = "secrets/env.env"
env = true
data = <<EOH
MAILGUN_API_KEY={{ with nomadVar "secrets/phaseflow" }}{{ .mailgun_api_key }}{{ end }}
MAILGUN_DOMAIN={{ with nomadVar "secrets/phaseflow" }}{{ .mailgun_domain }}{{ end }}
ENCRYPTION_KEY={{ with nomadVar "secrets/phaseflow" }}{{ .encryption_key }}{{ end }}
CRON_SECRET={{ with nomadVar "secrets/phaseflow" }}{{ .cron_secret }}{{ end }}
POCKETBASE_ADMIN_EMAIL={{ with nomadVar "secrets/phaseflow" }}{{ .pocketbase_admin_email }}{{ end }}
POCKETBASE_ADMIN_PASSWORD={{ with nomadVar "secrets/phaseflow" }}{{ .pocketbase_admin_password }}{{ end }}
EOH
}
resources {
memory = 512
}
service {
name = "phaseflow"
port = "http"
tags = [
"traefik.enable=true",
"traefik.http.routers.phaseflow.entryPoints=websecure",
"metrics",
]
check {
type = "http"
path = "/api/health"
interval = "10s"
timeout = "5s"
check_restart {
limit = 3
grace = "60s"
}
}
}
}
}
}

View File

@@ -4,6 +4,10 @@
job "urbit" { job "urbit" {
datacenters = ["alo"] datacenters = ["alo"]
meta {
uuid = uuidv4()
}
group "os" { group "os" {
network { network {
port "http" { port "http" {
@@ -25,7 +29,7 @@ job "urbit" {
# You can also set a variable loom size (Urbit memory size) using # You can also set a variable loom size (Urbit memory size) using
# --loom=$LOOM_SIZE. Passing /bin/start-urbit --loom=32 for example, would set up # --loom=$LOOM_SIZE. Passing /bin/start-urbit --loom=32 for example, would set up
# a 4GiB loom (2^32 bytes = 4GiB). The default loom size is 31 (2GiB). # a 4GiB loom (2^32 bytes = 4GiB). The default loom size is 31 (2GiB).
"--loom=31", "--loom=32",
] ]
volumes = [ volumes = [
"/data/services/urbit:/urbit", "/data/services/urbit:/urbit",
@@ -45,7 +49,7 @@ job "urbit" {
resources { resources {
# dependent on --loom setting + some buffer # dependent on --loom setting + some buffer
memory = 2100 memory = 4200
} }
} }
} }

48
services/wavelog.hcl Normal file
View File

@@ -0,0 +1,48 @@
# ABOUTME: Nomad job spec for Wavelog amateur radio logging application
# ABOUTME: Uses MySQL database and OIDC auth via Traefik middleware
job "wavelog" {
datacenters = ["alo"]
group "wavelog" {
network {
port "http" {
to = 80
}
}
task "wavelog" {
driver = "docker"
config {
image = "ghcr.io/wavelog/wavelog:latest"
ports = ["http"]
volumes = [
"/data/services/wavelog/config:/var/www/html/application/config/docker",
"/data/services/wavelog/uploads:/var/www/html/uploads",
"/data/services/wavelog/userdata:/var/www/html/userdata",
]
}
env {
CI_ENV = "docker"
}
resources {
cpu = 300
memory = 512
}
service {
name = "wavelog"
port = "http"
tags = [
"traefik.enable=true",
"traefik.http.routers.wavelog.entryPoints=websecure",
"traefik.http.routers.wavelog.middlewares=oidc-auth@file",
]
}
}
}
}

View File

@@ -19,7 +19,7 @@ job "weewx" {
driver = "docker" driver = "docker"
config { config {
image = "gitea.v.paler.net/ppetru/weewx:latest" image = "gitea.v.paler.net/alo/weewx:latest"
# to be able to receive UDP broadcast packets from the weatherlink # to be able to receive UDP broadcast packets from the weatherlink
network_mode = "host" network_mode = "host"
volumes = [ volumes = [
@@ -29,7 +29,7 @@ job "weewx" {
} }
resources { resources {
memory = 1024 memory = 1500
} }
} }
@@ -54,7 +54,7 @@ job "weewx" {
driver = "docker" driver = "docker"
config { config {
image = "gitea.v.paler.net/ppetru/opensprinkler-weather:latest" image = "gitea.v.paler.net/alo/opensprinkler-weather:latest"
ports = [ "osweather" ] ports = [ "osweather" ]
} }

View File

@@ -17,7 +17,7 @@ job "wordpress" {
user = "237" user = "237"
config { config {
image = "gitea.v.paler.net/ppetru/wordpress" image = "gitea.v.paler.net/alo/wordpress"
ports = ["http"] ports = ["http"]
volumes = [ volumes = [
"/data/services/wordpress:/var/www/html", "/data/services/wordpress:/var/www/html",