From 7ef9a86d3eddf4b6a7c2d1b456753e3bb9110082 Mon Sep 17 00:00:00 2001 From: GeorgeWebberley Date: Mon, 26 Jan 2026 15:39:35 +0100 Subject: [PATCH] Initial commit, with basic home page and CICD setup --- .woodpecker.yaml | 43 ++++++++++ Dockerfile | 32 +++++++ app/page.tsx | 204 +++++++++++++++++++++++++++++++------------- docker-compose.yaml | 15 ++++ next.config.ts | 2 +- package-lock.json | 53 ++++++++++++ package.json | 2 + 7 files changed, 291 insertions(+), 60 deletions(-) create mode 100644 .woodpecker.yaml create mode 100644 Dockerfile create mode 100644 docker-compose.yaml diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..9e8e810 --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,43 @@ +variables: + - &app_name "portfolio" + +when: + - event: release + +steps: + build-and-push: + image: woodpeckerci/plugin-docker-buildx + privileged: true + settings: + build_args: + - APP_VERSION=${CI_COMMIT_TAG} + platforms: linux/amd64 + registry: git.georgew.dev + repo: git.georgew.dev/georgew/${CI_REPO_NAME} + tags: + - latest + - ${CI_COMMIT_TAG##v} + dockerfile: Dockerfile + username: + from_secret: FORGEJO_USER + password: + from_secret: FORGEJO_TOKEN + + deploy: + image: docker:28-cli + privileged: true + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /home/george:/home/george + environment: + APP_NAME: *app_name + FORGEJO_USER: + from_secret: FORGEJO_USER + FORGEJO_TOKEN: + from_secret: FORGEJO_TOKEN + commands: + - echo $FORGEJO_TOKEN | docker login git.georgew.dev -u $FORGEJO_USER --password-stdin + - mkdir -p /home/george/$APP_NAME + - cp docker-compose.yaml /home/george/$APP_NAME/ + - docker compose -p $APP_NAME -f /home/george/$APP_NAME/docker-compose.yaml pull + - docker compose -p $APP_NAME -f /home/george/$APP_NAME/docker-compose.yaml up -d --force-recreate --remove-orphans \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d67a19b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM node:20-alpine AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +FROM node:20-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +ARG APP_VERSION=v0.0.0 +ENV NEXT_PUBLIC_APP_VERSION=$APP_VERSION +ENV NEXT_TELEMETRY_DISABLED 1 +RUN npm run build + + +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs +EXPOSE 3000 +ENV PORT 3000 +CMD ["node", "server.js"] \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 295f8fd..f4dfa86 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,151 @@ -import Image from "next/image"; +"use client"; +import { motion } from "framer-motion"; +import { Server, Globe, Smartphone, Gamepad2, Activity } from "lucide-react"; export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. +

+ {/* Header section */} +
+

George W.

+

Senior Full Stack Engineer & Tech Lead

+
+ + {/* Bento Grid */} +
+ + {/* About Me - Large Card */} + +
+

The Architect

+

+ Engineering high-scale web systems and mobile experiences. + Passionate about self-hosting, clean architecture, and performance. +

+
+
+ #NextJS #Flutter #Docker +
+
+ + {/* Live Pulse Card */} + + {/* The Monitor Map (Easily editable) */} + {(() => { + const monitors = [ + { id: 2, name: "Datasaur" }, + { id: 6, name: "Audiobookshelf" }, + { id: 7, name: "Woodpecker CI" }, + { id: 8, name: "Forgejo Git" }, + { id: 9, name: "Server dashboard" }, + { id: 10, name: "Ratoong" }, + ]; + + return ( + <> + {/* Default View */} +
+
+
+
+ Hetzner Node-01 +
+
+

System Status:

+ Overall Status +
+
+ +
+ + {/* Hover View: Friendly Names */} +
+

Service Registry

+
+ {monitors.map((m) => ( +
+ {m.name} +
+ up + ms +
+
+ ))} +
+
+ + ); + })()} + + + {/* Project One */} + + +

Prod Website

+

Case Study 01

+
+ + {/* Project Two */} + + +

Mobile App

+

Active Dev

+
+ + {/* Game Teaser / The Lab */} + +
+ +
+
+

The Forge

+

Indie Game Dev & Prototypes

+
+
+ +
+ + {/* Deployment Footer */} +
-
+ +
); -} +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..0ad3c65 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,15 @@ +services: + app: + image: git.georgew.dev/georgew/portfolio:latest + container_name: portfolio_app + restart: unless-stopped + ports: + - "3000:3000" + environment: + - NODE_ENV=production + networks: + - web_traffic + +networks: + web_traffic: + external: true \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index e9ffa30..b9f4418 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: 'standalone' }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 67a4d01..e17a53b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "portfolio", "version": "0.1.0", "dependencies": { + "framer-motion": "^12.29.2", + "lucide-react": "^0.563.0", "next": "16.1.4", "react": "19.2.3", "react-dom": "19.2.3" @@ -3585,6 +3587,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/framer-motion": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.29.2.tgz", + "integrity": "sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.29.2", + "motion-utils": "^12.29.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4833,6 +4862,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.563.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", + "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -4900,6 +4938,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion-dom": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.29.2.tgz", + "integrity": "sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.29.2" + } + }, + "node_modules/motion-utils": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 0bd0e85..dbe54e1 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "lint": "eslint" }, "dependencies": { + "framer-motion": "^12.29.2", + "lucide-react": "^0.563.0", "next": "16.1.4", "react": "19.2.3", "react-dom": "19.2.3"