Decommission JobSync service (#308)
All checks were successful
Build Container / detect (push) Successful in 3s

## Summary

- Remove all JobSync infrastructure: ArgoCD app, k8s manifests, container build (nix), Caddy reverse proxy entry, Homepage dashboard entry, service-versions tracking, and all documentation
- Runtime teardown already completed: ArgoCD app cascade-deleted (removes deployment, PVC, service, ingress, external-secret), forge mirror deleted, 1Password item archived, local clone removed

## Motivation

Replacing JobSync with a datasette-based job tracking pipeline driven by mise tasks and a Claude agent frontend. JobSync's Next.js server actions don't expose a useful API for automation.

## Remaining manual steps after merge

- Provision Caddy to remove the stale proxy route: `mise run provision-indri -- --tags caddy`
- Sync Homepage: `argocd app sync homepage`
- Verify namespace cleanup on ringtail: `kubectl get ns jobsync --context=k3s-ringtail` (should be gone)

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

Reviewed-on: #308
This commit is contained in:
Erich Blume 2026-03-24 08:44:23 -07:00
commit fc45989a6c
18 changed files with 1 additions and 588 deletions

View file

@ -85,9 +85,6 @@ caddy_services:
- name: ntfy
host: "ntfy.{{ caddy_domain }}"
backend: "https://ntfy.tail8d86e.ts.net"
- name: jobsync
host: "jobsync.{{ caddy_domain }}"
backend: "https://jobsync.tail8d86e.ts.net"
- name: ollama
host: "ollama.{{ caddy_domain }}"
backend: "https://ollama.tail8d86e.ts.net"

View file

@ -1,18 +0,0 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: jobsync
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/jobsync
destination:
server: https://ringtail.tail8d86e.ts.net:6443
namespace: jobsync
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -68,11 +68,6 @@
enableBlocks: true
enableNowPlaying: false
fields: ["movies", "series", "episodes"]
- Services:
- JobSync:
href: https://jobsync.ops.eblu.me
icon: mdi-briefcase-search
description: Job application tracker
- Infrastructure:
- Authentik:
href: https://authentik.ops.eblu.me

View file

@ -1,78 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jobsync
namespace: jobsync
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: jobsync
template:
metadata:
labels:
app: jobsync
spec:
containers:
- name: jobsync
image: blumeops/jobsync:kustomized
ports:
- containerPort: 3000
name: http
env:
- name: DATABASE_URL
value: "file:/data/dev.db"
- name: NEXTAUTH_URL
value: "https://jobsync.ops.eblu.me"
- name: AUTH_TRUST_HOST
value: "true"
- name: NEXT_TELEMETRY_DISABLED
value: "1"
- name: TZ
value: "America/Los_Angeles"
- name: OLLAMA_BASE_URL
value: "http://ollama.ollama.svc.cluster.local:11434"
- name: AUTH_SECRET
valueFrom:
secretKeyRef:
name: jobsync-secrets
key: auth_secret
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: jobsync-secrets
key: encryption_key
- name: RAPIDAPI_KEY
valueFrom:
secretKeyRef:
name: jobsync-secrets
key: rapidapi_key
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: jobsync-data

View file

@ -1,27 +0,0 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: jobsync-secrets
namespace: jobsync
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: jobsync-secrets
creationPolicy: Owner
data:
- secretKey: auth_secret
remoteRef:
key: JobSync
property: auth_secret
- secretKey: encryption_key
remoteRef:
key: JobSync
property: encryption_key
- secretKey: rapidapi_key
remoteRef:
key: JobSync
property: rapidapi_key

View file

@ -1,26 +0,0 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jobsync-tailscale
namespace: jobsync
annotations:
tailscale.com/proxy-class: "default"
tailscale.com/proxy-group: "ingress"
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "JobSync"
gethomepage.dev/group: "Services"
gethomepage.dev/icon: "mdi-briefcase-search"
gethomepage.dev/description: "Job application tracker"
gethomepage.dev/href: "https://jobsync.ops.eblu.me"
gethomepage.dev/pod-selector: "app=jobsync"
spec:
ingressClassName: tailscale
defaultBackend:
service:
name: jobsync
port:
number: 3000
tls:
- hosts:
- jobsync

View file

@ -1,15 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: jobsync
resources:
- pvc.yaml
- external-secret.yaml
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
images:
- name: blumeops/jobsync
newName: registry.ops.eblu.me/blumeops/jobsync
newTag: "v1.1.4-3a811fb-nix"

View file

@ -1,13 +0,0 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jobsync-data
namespace: jobsync
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 5Gi

View file

@ -1,13 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: jobsync
namespace: jobsync
spec:
selector:
app: jobsync
ports:
- name: http
port: 3000
targetPort: 3000

View file

@ -1,126 +0,0 @@
# Nix-built JobSync container
# Next.js job application tracker with Prisma/SQLite
# Built with dockerTools.buildLayeredImage for efficient layer caching
{ pkgs ? import <nixpkgs> { } }:
let
version = "1.1.4";
prismaEngines = pkgs.prisma-engines;
src = pkgs.fetchgit {
url = "https://forge.ops.eblu.me/mirrors/jobsync.git";
rev = "v${version}";
hash = "sha256-59W5OF36yD67jEK5xa9jSL4EVN9RG+Ez/w9Mq2VykSA=";
};
jobsync = pkgs.buildNpmPackage {
inherit src version;
pname = "jobsync";
npmDepsHash = "sha256-yRNOxtz66qSlmfjR3QDPUQe0C8sdg06tBbuK1Ws1gEA=";
nodejs = pkgs.nodejs_20;
# Patch out Google Fonts import (nix sandbox blocks network access at
# build time). Replace with a simple object; app uses system sans-serif.
postPatch = ''
substituteInPlace src/app/layout.tsx \
--replace-fail 'import { Inter } from "next/font/google";' "" \
--replace-fail 'const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});' 'const inter = { variable: "" };'
'';
# Point Prisma at nixpkgs-built engines (no network download in sandbox)
env = {
PRISMA_QUERY_ENGINE_LIBRARY = "${prismaEngines}/lib/libquery_engine.node";
PRISMA_QUERY_ENGINE_BINARY = "${prismaEngines}/bin/query-engine";
PRISMA_SCHEMA_ENGINE_BINARY = "${prismaEngines}/bin/schema-engine";
PRISMA_FMT_BINARY = "${prismaEngines}/bin/prisma-fmt";
PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING = "1";
DATABASE_URL = "file:/tmp/build.db";
NEXT_TELEMETRY_DISABLED = "1";
};
buildPhase = ''
runHook preBuild
# Generate Prisma client using nixpkgs engines
npx prisma generate
# Build Next.js
npm run build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/app
# Copy Next.js standalone output
cp -r .next/standalone/. $out/app/
cp -r .next/static $out/app/.next/static
cp -r public $out/app/public
# Copy Prisma schema and migrations for runtime migrate deploy
cp -r prisma $out/app/prisma
# Copy entrypoint
cp ${./entrypoint.sh} $out/app/entrypoint.sh
runHook postInstall
'';
dontNpmBuild = true;
};
entrypoint = pkgs.writeShellScript "jobsync-entrypoint" ''
cd ${jobsync}/app
exec ${pkgs.bash}/bin/bash entrypoint.sh "$@"
'';
in
pkgs.dockerTools.buildLayeredImage {
name = "blumeops/jobsync";
tag = "latest";
contents = [
jobsync
prismaEngines
pkgs.nodejs_20
pkgs.cacert
pkgs.tzdata
pkgs.bash
pkgs.coreutils
];
# Create writable directories and FHS symlinks for nix container
extraCommands = ''
mkdir -p tmp data usr/bin
ln -s ${pkgs.coreutils}/bin/env usr/bin/env
'';
config = {
Entrypoint = [ "${entrypoint}" ];
WorkingDir = "${jobsync}/app";
Env = [
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
"TZDIR=${pkgs.tzdata}/share/zoneinfo"
"NODE_ENV=production"
"PORT=3000"
"DATABASE_URL=file:/data/dev.db"
"PRISMA_QUERY_ENGINE_LIBRARY=${prismaEngines}/lib/libquery_engine.node"
"PRISMA_SCHEMA_ENGINE_BINARY=${prismaEngines}/bin/schema-engine"
"PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1"
];
ExposedPorts = {
"3000/tcp" = { };
};
Volumes = {
"/data" = { };
};
};
}

View file

@ -1,15 +0,0 @@
#!/bin/sh
set -e
# Auto-generate AUTH_SECRET if not provided
if [ -z "$AUTH_SECRET" ]; then
AUTH_SECRET="$(node -e "console.log(require('crypto').randomBytes(32).toString('base64'))")"
export AUTH_SECRET
echo "AUTH_SECRET was not set — generated a temporary secret for this container."
fi
# Run Prisma migrations (npx -y downloads prisma if not in local node_modules)
npx -y prisma@6.19.0 migrate deploy
# Start the Next.js server
exec node server.js

View file

@ -0,0 +1 @@
Decommission JobSync service — removed ArgoCD app, k8s manifests, container build, Caddy proxy, Homepage entry, docs, and forge mirror. Replaced by datasette-based job tracking (coming soon).

View file

@ -1,62 +0,0 @@
---
title: Build JobSync Container
modified: 2026-03-11
last-reviewed: 2026-03-11
tags:
- how-to
- jobsync
- nix
---
# Build JobSync Container
Build and release the JobSync nix container image.
```fish
mise run container-release jobsync 1.1.4
```
The derivation is at `containers/jobsync/default.nix`. It uses `buildNpmPackage` for the Next.js app and `dockerTools.buildLayeredImage` for the container. The entrypoint (`containers/jobsync/entrypoint.sh`) runs `prisma migrate deploy` then starts `node server.js`.
## Upgrading JobSync
1. Verify the forge mirror is current: check `https://forge.eblu.me/mirrors/jobsync` (mirrors sync automatically)
2. Update `version` in `default.nix` to match the new upstream tag
3. Clear `hash` in `fetchgit` (set to `""`), build, grab the correct hash from the error
4. Clear `npmDepsHash` (set to `""`), build again, grab the correct hash
5. Check if `postPatch` still applies — the Google Fonts import may change between versions
6. `mise run container-release jobsync <new-version>`
7. Update `newTag` in `argocd/manifests/jobsync/kustomization.yaml`
## Nix + Prisma + Next.js Pitfalls
### Prisma engine downloads blocked by sandbox
Prisma tries to download platform-specific engine binaries during `prisma generate`. The nix sandbox blocks network access at build time.
**Fix:** Use `pkgs.prisma-engines` from nixpkgs and set env vars pointing at the nix store paths: `PRISMA_QUERY_ENGINE_LIBRARY`, `PRISMA_QUERY_ENGINE_BINARY`, `PRISMA_SCHEMA_ENGINE_BINARY`, `PRISMA_FMT_BINARY`. Set `PRISMA_ENGINES_CHECKSUM_IGNORE_MISSING=1` to tolerate minor version mismatch.
### Google Fonts blocked by sandbox
`next/font/google` fetches from `fonts.googleapis.com` during `next build`.
**Fix:** Patch `src/app/layout.tsx` in `postPatch` to replace the Google font import with a no-op object. The app falls back to system sans-serif.
### Missing FHS paths in nix containers
Nix containers lack `/usr/bin/env`, `/tmp`, etc. `npx`-downloaded packages use `#!/usr/bin/env node` shebangs.
**Fix:** In `extraCommands`: `mkdir -p tmp data usr/bin` and `ln -s ${pkgs.coreutils}/bin/env usr/bin/env`.
### Runtime migrations via npx
The nix sandbox blocks network at build time, but runtime has full network access. Use `npx -y prisma@<version> migrate deploy` in the entrypoint — npx downloads the prisma CLI on first run.
### Build on ringtail, not via Dagger
The Dagger `build-nix` pipeline runs in host architecture. On macOS (arm64), this produces arm64 images. Build on ringtail (x86_64) using the CI workflow or `mise run container-release`.
## Related
- [[deploy-jobsync]]
- [[build-container-image]]

View file

@ -1,74 +0,0 @@
---
title: Deploy JobSync
modified: 2026-03-13
last-reviewed: 2026-03-13
tags:
- how-to
- jobsync
---
# Deploy JobSync
[JobSync](https://github.com/Gsync/jobsync) is a self-hosted job application tracker (Next.js + Prisma/SQLite) running on ringtail's k3s cluster via ArgoCD.
- **URL:** `https://jobsync.ops.eblu.me`
- **Auth:** Local accounts (email/password), no SSO
- **Storage:** 5Gi PVC at `/data` (SQLite DB + resume uploads)
- **AI:** Ollama at `ollama.ollama.svc.cluster.local:11434`
## Manifests
All in `argocd/manifests/jobsync/`:
| File | Purpose |
|------|---------|
| `deployment.yaml` | Single-replica deployment |
| `service.yaml` | ClusterIP on port 3000 |
| `ingress-tailscale.yaml` | Tailscale Ingress (ProxyGroup) |
| `pvc.yaml` | 5Gi local-path for `/data` |
| `external-secret.yaml` | `auth_secret` + `encryption_key` from 1Password |
| `kustomization.yaml` | Image tag override |
## Environment Variables
| Variable | Source | Purpose |
|----------|--------|---------|
| `DATABASE_URL` | Hardcoded | `file:/data/dev.db` |
| `AUTH_SECRET` | ExternalSecret | NextAuth session signing |
| `ENCRYPTION_KEY` | ExternalSecret | AES-256-GCM for stored API keys |
| `NEXTAUTH_URL` | Hardcoded | `https://jobsync.ops.eblu.me` |
| `AUTH_TRUST_HOST` | Hardcoded | `true` |
| `NEXT_TELEMETRY_DISABLED` | Hardcoded | `1` (opt out of Next.js telemetry) |
| `TZ` | Hardcoded | `America/Los_Angeles` |
| `OLLAMA_BASE_URL` | Hardcoded | `http://ollama.ollama.svc.cluster.local:11434` |
| `RAPIDAPI_KEY` | ExternalSecret | JSearch job search API key |
## Updating the Container
1. Build and push: `mise run container-release jobsync <version>`
2. Update `newTag` in `kustomization.yaml` to the full tag (e.g. `v1.1.4-3a811fb-nix`)
3. Sync: `argocd app sync jobsync`
See [[build-jobsync-container]] for nix build details.
## Notes
- **1Password item:** "JobSync" in blumeops vault, fields `auth_secret`, `encryption_key`, and `rapidapi_key`
- **Caddy route:** `jobsync.ops.eblu.me``https://jobsync.tail8d86e.ts.net` (in `ansible/roles/caddy/defaults/main.yml`)
- **`service-versions.yaml`:** Must have a `jobsync` entry or the pre-commit hook rejects container changes
## Observability
JobSync has no metrics endpoint. Logs are collected by Alloy on ringtail and shipped to Loki. Query in Grafana:
```logql
{namespace="jobsync", app="jobsync"}
```
The app runs a scheduled job search daily at 4 AM. Search failures appear in logs during those executions.
## Related
- [[jobsync]] — Service reference card
- [[build-jobsync-container]]
- [[deploy-k8s-service]]

View file

@ -70,7 +70,6 @@ Sync order: `1password-connect-ringtail` -> `external-secrets-crds-ringtail` ->
| [[authentik]] | `authentik` | OIDC identity provider |
| [[ntfy]] | `ntfy` | Push notification server |
| [[ollama]] | `ollama` | LLM inference with GPU (RTX 4080) |
| [[jobsync]] | `jobsync` | Job application tracker |
| nvidia-device-plugin | `nvidia-device-plugin` | Exposes GPU to pods via CDI + nvidia RuntimeClass |
### Manual Cluster Registration

View file

@ -1,104 +0,0 @@
---
title: JobSync
modified: 2026-03-08
tags:
- service
- job-search
---
# JobSync
Self-hosted job application tracker. Tracks job applications, automates job searching via the JSearch API, and provides AI-powered resume tailoring via [[ollama|Ollama]].
## Quick Reference
| Property | Value |
|----------|-------|
| **URL** | https://jobsync.ops.eblu.me |
| **Tailscale URL** | https://jobsync.tail8d86e.ts.net |
| **Namespace** | `jobsync` |
| **Cluster** | ringtail k3s |
| **Image** | `blumeops/jobsync` (Nix-built) |
| **Upstream** | https://github.com/Gsync/jobsync |
| **Manifests** | `argocd/manifests/jobsync/` |
| **Port** | 3000 |
## Architecture
```
Browser ──HTTPS──► Caddy (jobsync.ops.eblu.me)
Tailscale ProxyGroup
JobSync (Next.js)
┌───────┴───────┐
│ │
SQLite (/data) Ollama (in-cluster)
│ │
PVC 5Gi GPU-accelerated LLM
```
- **Framework:** Next.js 15 + Prisma ORM
- **Database:** SQLite on a 5Gi PVC at `/data`
- **Auth:** Local email/password accounts (NextAuth v5), no SSO
- **AI:** Ollama at `http://ollama.ollama.svc.cluster.local:11434` for resume tailoring
- **Job Search:** JSearch API via RapidAPI (requires `RAPIDAPI_KEY`)
## Job Search (JSearch / RapidAPI)
The automated job search feature uses the [JSearch API](https://rapidapi.com/letscrape-6bRBa3QguO5/api/jsearch) on RapidAPI. The API key can be configured two ways (checked in order):
1. **Per-user:** Added via Settings > API Keys in the web UI (encrypted with `ENCRYPTION_KEY`)
2. **Environment variable:** `RAPIDAPI_KEY` env var as a fallback for all users
Without either, job search automations fail with: `Search failed: network - RAPIDAPI_KEY is not configured`
The free tier allows 200 requests/month. The key is stored in 1Password ("JobSync" item, `rapidapi_key` field) and synced via ExternalSecret.
## Secrets
All secrets are in the **JobSync** 1Password item (blumeops vault), synced by ExternalSecret:
| Secret | 1Password Field | Purpose |
|--------|-----------------|---------|
| `auth_secret` | `auth_secret` | NextAuth session signing |
| `encryption_key` | `encryption_key` | AES-256-GCM for stored API keys |
| `rapidapi_key` | `rapidapi_key` | JSearch job search API |
## Observability
JobSync has no metrics endpoint or Grafana dashboard. Logs are collected by [[alloy|Alloy]] on ringtail and shipped to Loki on indri.
**Querying logs in Grafana:**
```logql
{namespace="jobsync", app="jobsync"}
```
To search for job search errors specifically:
```logql
{namespace="jobsync", app="jobsync"} |~ "(?i)(rapid|search failed|error)"
```
The app runs a scheduled job search daily at 4 AM. Failures appear in logs during those executions.
## Container
Built with Nix on ringtail (x86_64). See [[build-jobsync-container]] for details.
```fish
mise run container-release jobsync <version>
```
Update `newTag` in `argocd/manifests/jobsync/kustomization.yaml` after building, then `argocd app sync jobsync`.
## Related
- [[ollama]] — AI backend for resume tailoring
- [[ringtail]] — Host node
- [[deploy-jobsync]] — Deployment how-to
- [[build-jobsync-container]] — Container build guide
- [[apps]] — ArgoCD application registry

View file

@ -161,7 +161,6 @@ check_http "CV" "https://cv.ops.eblu.me/"
check_http "Ntfy" "https://ntfy.ops.eblu.me/v1/health"
check_http "Authentik" "https://authentik.ops.eblu.me/-/health/live/"
check_http "Frigate" "https://nvr.ops.eblu.me/api/version"
check_http "JobSync" "https://jobsync.ops.eblu.me/"
echo ""
echo "Frigate (via alerting):"

View file

@ -157,13 +157,6 @@ services:
current-version: "2026.2.0"
upstream-source: https://github.com/goauthentik/authentik/releases
- name: jobsync
type: argocd
last-reviewed: 2026-03-11
current-version: "1.1.4"
upstream-source: https://github.com/Gsync/jobsync/releases
notes: Job application tracker; nix container on ringtail k3s
- name: ollama
type: argocd
last-reviewed: "2026-03-02"