diff --git a/apps/infra/eslint.config.mjs b/apps/infra/eslint.config.mjs
new file mode 100644
index 0000000..b7f6277
--- /dev/null
+++ b/apps/infra/eslint.config.mjs
@@ -0,0 +1,3 @@
+import baseConfig from '../../eslint.config.mjs';
+
+export default [...baseConfig];
diff --git a/apps/infra/index.html b/apps/infra/index.html
new file mode 100644
index 0000000..eed77cf
--- /dev/null
+++ b/apps/infra/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+ IMPHNEN Infrastructure
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/infra/postcss.config.mjs b/apps/infra/postcss.config.mjs
new file mode 100644
index 0000000..63252e5
--- /dev/null
+++ b/apps/infra/postcss.config.mjs
@@ -0,0 +1,9 @@
+import { join } from 'path';
+
+export default {
+ plugins: {
+ '@tailwindcss/postcss': {
+ base: join(import.meta.dirname, '../../'),
+ },
+ },
+};
diff --git a/apps/infra/project.json b/apps/infra/project.json
new file mode 100644
index 0000000..86bb3f7
--- /dev/null
+++ b/apps/infra/project.json
@@ -0,0 +1,8 @@
+{
+ "name": "infra",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "apps/infra/src",
+ "projectType": "application",
+ "tags": [],
+ "targets": {}
+}
diff --git a/apps/infra/public/logos/logo.svg b/apps/infra/public/logos/logo.svg
new file mode 100644
index 0000000..ca0f36e
--- /dev/null
+++ b/apps/infra/public/logos/logo.svg
@@ -0,0 +1,9 @@
+
diff --git a/apps/infra/public/logos/simple.svg b/apps/infra/public/logos/simple.svg
new file mode 100644
index 0000000..0fd70f3
--- /dev/null
+++ b/apps/infra/public/logos/simple.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/apps/infra/src/app/App.tsx b/apps/infra/src/app/App.tsx
new file mode 100644
index 0000000..0ce8e76
--- /dev/null
+++ b/apps/infra/src/app/App.tsx
@@ -0,0 +1,235 @@
+const apps = [
+ { name: 'Landing', domain: 'imphnen.dev', type: 'Next.js', color: 'blue' },
+ { name: 'Gacha', domain: 'gacha.imphnen.dev', type: 'Vite', color: 'green' },
+ { name: 'Backoffice', domain: 'backoffice.imphnen.dev', type: 'Vite', color: 'green' },
+ { name: 'Dimentorin', domain: 'dimentorin.imphnen.dev', type: 'Vite', color: 'green' },
+ { name: 'Hackathon', domain: 'hackathon.imphnen.dev', type: 'Vite', color: 'green' },
+ { name: 'Infra', domain: 'infra.imphnen.dev', type: 'Vite', color: 'green' },
+];
+
+const techStack = [
+ { category: 'OS', items: ['NixOS 24.11'] },
+ { category: 'Web Server', items: ['Nginx'] },
+ { category: 'SSL', items: ['Let\'s Encrypt (ACME)'] },
+ { category: 'Build', items: ['Nix Flakes', 'Nx Monorepo'] },
+ { category: 'Frontend', items: ['React', 'Next.js', 'Vite', 'TailwindCSS'] },
+ { category: 'Secrets', items: ['sops-nix'] },
+];
+
+export default function App() {
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ I
+
+
IMPHNEN Infrastructure
+
+
+ GitHub
+
+
+
+
+
+
+ {/* Hero */}
+
+
+ Infrastructure Overview
+
+
+ Declarative NixOS infrastructure powering IMPHNEN's frontend applications
+ on Hetzner Cloud.
+
+
+
+ {/* Architecture Diagram */}
+
+ Architecture
+
+
+ {/* Internet */}
+
+ 🌐
+ Internet
+
+
+
+ {/* Cloudflare */}
+
+
Cloudflare
+
DNS + CDN + DDoS Protection
+
+
+
+ {/* Hetzner VPS */}
+
+
+
Hetzner Cloud VPS
+
NixOS | 167.235.70.37
+
+
+ {/* Nginx */}
+
+
+
Nginx
+
Reverse Proxy + Static Files + SSL Termination
+
+
+
+ {/* Apps Grid */}
+
+ {apps.map((app) => (
+
+
{app.name}
+
{app.domain}
+
+ {app.type}
+
+
+ ))}
+
+
+
+
+
+
+ {/* Deployed Apps */}
+
+ Deployed Applications
+
+
+
+ {/* Tech Stack */}
+
+ Technology Stack
+
+ {techStack.map((tech) => (
+
+
+ {tech.category}
+
+
+ {tech.items.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ ))}
+
+
+
+ {/* Deployment Flow */}
+
+ Deployment Flow
+
+
+
+
+ 1
+
+
Push to GitHub
+
develop branch
+
+
→
+
+
+ 2
+
+
Update Flake
+
nix flake update
+
+
→
+
+
+ 3
+
+
Remote Build
+
nixos-rebuild
+
+
→
+
+
+ ✓
+
+
Live
+
Zero downtime
+
+
+
+
+
+ {/* Repositories */}
+
+
+
+ {/* Footer */}
+
+
+ );
+}
diff --git a/apps/infra/src/index.css b/apps/infra/src/index.css
new file mode 100644
index 0000000..62299c4
--- /dev/null
+++ b/apps/infra/src/index.css
@@ -0,0 +1,58 @@
+@import 'tailwindcss';
+
+@theme {
+ --color-primary: #3b82f6;
+ --color-primary-hover: #2563eb;
+}
+
+@layer base {
+ * {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ background-color: #0a0a0a;
+ color: #ffffff;
+ line-height: 1.6;
+ }
+}
+
+@layer components {
+ .card {
+ background: #141414;
+ border: 1px solid #27272a;
+ border-radius: 12px;
+ padding: 1.5rem;
+ }
+
+ .badge {
+ display: inline-block;
+ padding: 0.25rem 0.75rem;
+ border-radius: 9999px;
+ font-size: 0.75rem;
+ font-weight: 500;
+ }
+
+ .badge-blue {
+ background: rgba(59, 130, 246, 0.2);
+ color: #60a5fa;
+ }
+
+ .badge-green {
+ background: rgba(34, 197, 94, 0.2);
+ color: #4ade80;
+ }
+
+ .badge-purple {
+ background: rgba(168, 85, 247, 0.2);
+ color: #c084fc;
+ }
+
+ .badge-orange {
+ background: rgba(249, 115, 22, 0.2);
+ color: #fb923c;
+ }
+}
diff --git a/apps/infra/src/main.tsx b/apps/infra/src/main.tsx
new file mode 100644
index 0000000..808a90e
--- /dev/null
+++ b/apps/infra/src/main.tsx
@@ -0,0 +1,14 @@
+import { createRoot } from 'react-dom/client';
+import { StrictMode } from 'react';
+import App from './app/App';
+import './index.css';
+
+const rootElement = document.getElementById('root');
+
+if (!rootElement) throw new Error('Failed to find the root element');
+
+createRoot(rootElement).render(
+
+
+
+);
diff --git a/apps/infra/tsconfig.app.json b/apps/infra/tsconfig.app.json
new file mode 100644
index 0000000..4750700
--- /dev/null
+++ b/apps/infra/tsconfig.app.json
@@ -0,0 +1,9 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "types": ["node", "vite/client"]
+ },
+ "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "src/**/*.spec.tsx", "src/**/*.test.tsx"],
+ "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
+}
diff --git a/apps/infra/tsconfig.json b/apps/infra/tsconfig.json
new file mode 100644
index 0000000..fe609d7
--- /dev/null
+++ b/apps/infra/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowJs": false,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "types": ["vite/client"]
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.app.json"
+ }
+ ],
+ "extends": "../../tsconfig.base.json"
+}
diff --git a/apps/infra/vite.config.ts b/apps/infra/vite.config.ts
new file mode 100644
index 0000000..8062251
--- /dev/null
+++ b/apps/infra/vite.config.ts
@@ -0,0 +1,27 @@
+///
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
+import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
+
+export default defineConfig(() => ({
+ root: __dirname,
+ cacheDir: '../../node_modules/.vite/apps/infra',
+ server: {
+ port: 3006,
+ host: 'localhost',
+ },
+ preview: {
+ port: 3007,
+ host: 'localhost',
+ },
+ plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
+ build: {
+ outDir: '../../dist/apps/infra',
+ emptyOutDir: true,
+ reportCompressedSize: true,
+ commonjsOptions: {
+ transformMixedEsModules: true,
+ },
+ },
+}));
diff --git a/flake.nix b/flake.nix
index 8a1023a..cc3ed3d 100644
--- a/flake.nix
+++ b/flake.nix
@@ -32,6 +32,7 @@
gacha = mkViteApp { name = "gacha"; buildScript = "gacha:build"; };
dimentorin = mkViteApp { name = "dimentorin"; buildScript = "dimentorin:build"; };
hackathon = mkViteApp { name = "hackathon"; buildScript = "hackathon:build"; };
+ infra = mkViteApp { name = "infra"; buildScript = "infra:build"; };
# Next.js app
landing = landingApp;
@@ -82,6 +83,7 @@
gacha = import ./nix/modules/static-app.nix { inherit self; } { appName = "gacha"; };
dimentorin = import ./nix/modules/static-app.nix { inherit self; } { appName = "dimentorin"; };
hackathon = import ./nix/modules/static-app.nix { inherit self; } { appName = "hackathon"; };
+ infra = import ./nix/modules/static-app.nix { inherit self; } { appName = "infra"; };
# All-in-one module that enables all apps
all = { config, lib, pkgs, ... }: {
@@ -91,6 +93,7 @@
self.nixosModules.gacha
self.nixosModules.dimentorin
self.nixosModules.hackathon
+ self.nixosModules.infra
];
};
};
@@ -103,6 +106,7 @@
gacha = self.packages.${final.system}.gacha;
dimentorin = self.packages.${final.system}.dimentorin;
hackathon = self.packages.${final.system}.hackathon;
+ infra = self.packages.${final.system}.infra;
# Function to build hackathon with custom environment variables (e.g., Supabase)
mkHackathonWithEnv = envVars: import ./nix/mkViteApp.nix {
pkgs = final;