initial commit

This commit is contained in:
2026-06-02 01:00:27 +02:00
commit d2a8072a47
64 changed files with 26467 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { requireAuth } = require('./auth');
const { ensureDefaultAdmin } = require('./users');
const audit = require('./audit');
const secrets = require('./secrets');
const schedule = require('node-schedule');
const zonesRouter = require('./routes/zones');
const secretsRouter = require('./routes/secrets');
const ipamRouter = require('./routes/ipam');
const healthRouter = require('./routes/health');
const recordsRouter = require('./routes/records');
const settingsRouter = require('./routes/settings');
const authRouter = require('./routes/auth');
const usersRouter = require('./routes/users');
const db = require('./db');
// Create default admin user if none exist
ensureDefaultAdmin();
const app = express();
app.use(cors());
app.use(express.json());
// ─── Public routes (no auth required) ────────────────────────────────────────
app.use('/api/auth', authRouter);
// ─── Protected routes (JWT required) ─────────────────────────────────────────
app.use('/api/zones', requireAuth, zonesRouter);
app.use('/api/records', requireAuth, recordsRouter);
app.use('/api/settings', requireAuth, settingsRouter);
app.use('/api/users', usersRouter);
app.use('/api/secrets', secretsRouter);
app.use('/api/ipam', ipamRouter);
app.use('/api/health', healthRouter); // requireAuth applied inside router
app.get('/api/audit', requireAuth, (req, res) => {
const { limit, offset, user, action, provider, category } = req.query;
res.json(audit.getEntries({
limit: Math.min(parseInt(limit) || 100, 500),
offset: parseInt(offset) || 0,
user, action, provider, category,
}));
});
app.get('/api/sync-status/:provider', requireAuth, (req, res) => {
res.json(db.getProviderSyncStatus(req.params.provider));
});
app.get('/api/domains', requireAuth, (req, res) => {
res.json(db.getAllZones());
});
app.get('/api/stats', requireAuth, (req, res) => {
res.json(db.getStats());
});
app.get('/api/providers', requireAuth, (req, res) => {
const providers = [];
const disabled = new Set((process.env.DISABLED_PROVIDERS ?? '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean));
if (!disabled.has('cloudflare') && process.env.CLOUDFLARE_API_TOKEN) providers.push({ id: 'cloudflare', name: 'Cloudflare' });
if (!disabled.has('loopia') && process.env.LOOPIA_USER && process.env.LOOPIA_PASSWORD) providers.push({ id: 'loopia', name: 'Loopia' });
if (!disabled.has('pihole') && process.env.PIHOLE_URL && process.env.PIHOLE_PASSWORD) providers.push({ id: 'pihole', name: 'Pi-hole', url: process.env.PIHOLE_URL });
if (!disabled.has('azure') && process.env.AZURE_TENANT_ID && process.env.AZURE_CLIENT_ID && process.env.AZURE_CLIENT_SECRET && process.env.AZURE_SUBSCRIPTION_ID) providers.push({ id: 'azure', name: 'Azure DNS' });
if (!disabled.has('cpanel') && process.env.CPANEL_URL && process.env.CPANEL_USERNAME && process.env.CPANEL_API_TOKEN) providers.push({ id: 'cpanel', name: 'cPanel', url: process.env.CPANEL_URL });
res.json(providers);
});
// ─── Daily secrets expiry check (runs at 08:00 every day) ────────────────────
const { notify } = require('./notify');
const LAST_CHECK_PATH = require('path').join(__dirname, '..', '.last-secret-check');
function getLastCheckDate() {
try { return require('fs').readFileSync(LAST_CHECK_PATH, 'utf8').trim(); } catch { return null; }
}
function saveLastCheckDate(date) {
require('fs').writeFileSync(LAST_CHECK_PATH, date, 'utf8');
}
async function checkSecretExpiry() {
const expiring = secrets.getExpiring();
if (expiring.length === 0) return;
const lines = expiring.map(s =>
s.status === 'expired'
? `✕ EXPIRED — ${s.name}`
: `${s.daysLeft}d left — ${s.name}`
);
await notify(
`🦥 Sloth Manager — Secrets Alert`,
`${expiring.length} secret${expiring.length !== 1 ? 's' : ''} need attention:\n\n${lines.join('\n')}`
);
}
async function checkSecretExpiryOnce() {
const today = new Date().toDateString();
if (getLastCheckDate() === today) return; // already ran today, persisted to disk
saveLastCheckDate(today);
await checkSecretExpiry();
}
// Run at startup only if not already checked today (persisted to disk), then every day at 08:00
checkSecretExpiryOnce();
schedule.scheduleJob('0 8 * * *', () => {
saveLastCheckDate(''); // clear so the scheduled job always fires
checkSecretExpiry();
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => console.log(`Sloth Manager backend running on port ${PORT}`));