4 Commits

Author SHA1 Message Date
cbcf6a731b library version updates 2024-05-09 14:41:42 +10:00
9ad8e1aa9b move the assets to remote location and change blog cover to Next/Image 2024-03-13 14:23:11 +11:00
bea1c3aba3 updated dependency, standalone seems to work
updated pnpm version
added dockerignore
revert the standalone output
2024-02-23 14:56:19 +11:00
5c8a3f56dd WIP: build attempted 2024-02-22 23:42:04 +11:00
17 changed files with 3309 additions and 2185 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
**/node_modules
.git
.gitignore
.local
.env*

29
.idea/dataSources.local.xml generated Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="dataSourceStorageLocal" created-in="WS-241.15989.105">
<data-source name="devblog@mysql-server" uuid="32913bc6-cafd-416c-b070-eee7c73cf755">
<database-info product="MySQL" version="8.3.0" jdbc-version="4.2" driver-name="MySQL Connector/J" driver-version="mysql-connector-java-8.0.25 (Revision: 08be9e9b4cba6aa115f9b27b215887af40b159e0)" dbms="MYSQL" exact-version="8.3.0" exact-driver-version="8.0">
<extra-name-characters>#@</extra-name-characters>
<identifier-quote-string>`</identifier-quote-string>
</database-info>
<case-sensitivity plain-identifiers="exact" quoted-identifiers="exact" />
<secret-storage>master_key</secret-storage>
<user-name>devblog</user-name>
<schema-mapping>
<introspection-scope>
<node kind="schema">
<name qname="@" />
<name qname="devblog" />
</node>
</introspection-scope>
</schema-mapping>
<ssl-config use-ide-store="true" use-java-store="true" use-system-store="true">
<ca-cert>$USER_HOME$/Documents/db-ssl/ca.crt</ca-cert>
<client-cert>$USER_HOME$/Documents/db-ssl/mbp.crt</client-cert>
<client-key>$USER_HOME$/Documents/db-ssl/mbp.key</client-key>
<enabled>true</enabled>
<mode>VERIFY_FULL</mode>
</ssl-config>
</data-source>
</component>
</project>

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="devblog@mysql-server" uuid="32913bc6-cafd-416c-b070-eee7c73cf755">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://mysql-server.suyono.dev:13306/devblog</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

6
.idea/sqldialects.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

View File

@@ -1,2 +1,2 @@
nodejs 20.8.1 nodejs 20.12.2
pnpm 8.9.2 pnpm 9.0.6

37
Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
FROM node:lts-alpine as base
FROM base as builder
USER 1000:1000
ADD --chown=1000:1000 . /home/node/nextts
WORKDIR /home/node/nextts
RUN wget -qO- https://get.pnpm.io/install.sh | PNPM_VERSION="8.15.3" ENV="/home/node/.shrc" SHELL="$(which sh)" sh -
ENV PATH=/home/node/.local/share/pnpm:$PATH
RUN pnpm install && pnpm run build
FROM base as runtime
RUN npm install -g pm2
COPY --from=builder /home/node/nextts/public /home/node/nextts/public
COPY --from=builder /home/node/nextts/.next/standalone /home/node/nextts
COPY --from=builder /home/node/nextts/.next/static /home/node/nextts/.next/static
ADD --chown=1000:1000 pm2.config.js /home/node/nextts/
ADD --chown=1000:1000 dummies /home/node/nextts/dummies
RUN chown -R 1000:1000 /home/node/nextts
USER 1000:1000
WORKDIR /home/node/nextts
ENV PORT 3000
ENV NODE_ENV production
ENV HOME /home/node
ENV HOSTNAME "0.0.0.0"
RUN wget -qO- https://get.pnpm.io/install.sh | PNPM_VERSION="8.15.3" ENV="/home/node/.shrc" SHELL="$(which sh)" sh -
ENV PATH=/home/node/.local/share/pnpm:$PATH
RUN pnpm install
CMD ["pm2-runtime", "pm2.config.js"]
#CMD ["node", "server.js"]

View File

@@ -1,9 +1,9 @@
import { promisePool } from "@/backend/db"; import { getPromisePool } from "@/backend/db";
import {RowDataPacket} from "mysql2"; import {RowDataPacket} from "mysql2";
async function query() { async function query() {
try { try {
const [rows, fields] = await promisePool.query<RowDataPacket[]>('select slug from post limit 1;') const [rows, fields] = await getPromisePool().query<RowDataPacket[]>('select slug from post limit 1;')
return(rows[0]['slug'] as string) return(rows[0]['slug'] as string)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
@@ -11,11 +11,18 @@ async function query() {
} }
} }
export default async function DbCheck() { export default async function DbCheck({ searchParams }: { searchParams: { [key: string]: string | string[] | undefined }}) {
let flag = "empty";
if (typeof searchParams["flag"] === 'string') {
flag = searchParams["flag"]
}
return( return(
<div className={`flex flex-col`}> <div className={`flex flex-col`}>
<p>Env: { process.env.MYSQL_HOST }</p> <p>Env: { process.env.MYSQL_HOST }</p>
<p>Result: { await query() }</p> <p>Result: { await query() }</p>
<p>Flag: { flag }</p>
</div> </div>
) )
} }

View File

@@ -4,66 +4,73 @@ import { raleway, syne, questrial } from "@/app/fonts";
export default function Home() { export default function Home() {
return ( return (
<div className={`flex flex-col`}> <div className={`flex flex-col`}>
<div className={`bg-cover bg-center flex flex-col-reverse bg-blog-cover`}> <div className={`grid grid-rows-1 grid-cols-1 justify-items-center`}>
<div className={`bg-neutral-100 bg-opacity-30 flex flex-col py-10`}> <Image
<p className={`${raleway.className} text-white text-center text-7xl font-thin mb-6`}> src={`https://assets.suyono.me/placeholder.webp`}
SUYONO alt={`blog cover`}
</p> className={`object-cover col-start-1 row-start-1 w-screen h-192 z-0`}
<p className={`${raleway.className} text-white text-center font-thin text-xl mb-10`}> width={1581}
A Tech Archive height={759}
</p> />
</div> <div className={`flex flex-col-reverse col-start-1 row-start-1 w-screen`}>
<div className={`h-64`}></div> {/* spacer */} <div className={`bg-neutral-100 bg-opacity-30 flex flex-col py-10 z-10`}>
<div className={`h-64`}></div> {/* spacer */} <p className={`${raleway.className} text-white text-center text-7xl font-thin mb-6`}>
</div> SUYONO
<div className={`flex flex-row justify-center my-8`}>
<div className={`border border-slate-100 flex flex-col`}>
<Link
href="/post/nginx-ssl-client-certificate-verification-manage-access-to-a-site"
className={`flex flex-row max-w-4xl items-center`}
>
<Image
src="/assets/pthumb.webp"
alt="post thumbnail"
width={454}
height={341}
/>
<div className={`flex flex-col mx-10`}>
<p className={`${syne.className} text-2xl`}>
Nginx + SSL Client Certificate Verification: Manage Access to a
site
</p> </p>
<p className={`${questrial.className} line-clamp-3 mt-4`}> <p className={`${raleway.className} text-white text-center font-thin text-xl mb-10`}>
Access control is a fundamental part of security. Most entities A Tech Archive
rely on the combination of username and password, sometimes with
additional multi-factor authentication to improve security. Some
entities also use the SSL client certificate verification to
manage access to specific resources. One of the use cases where
SSL client certificate verification fits perfectly is managing
access to internet-facing development or staging servers. In
this post, I&apos;ll share how to set up the certificates and
configure nginx to verify users based on their certificates.
</p> </p>
</div> </div>
</Link> </div>
</div>
<div className={`flex flex-row justify-center my-8`}>
<div className={`border border-slate-100 flex flex-col`}>
<Link
href="/post/nginx-ssl-client-certificate-verification-manage-access-to-a-site"
className={`flex flex-row max-w-4xl items-center`}
>
<Image
src="https://assets.suyono.me/pthumb.webp"
alt="post thumbnail"
width={454}
height={341}
/>
<div className={`flex flex-col mx-10`}>
<p className={`${syne.className} text-2xl`}>
Nginx + SSL Client Certificate Verification: Manage Access to a
site
</p>
<p className={`${questrial.className} line-clamp-3 mt-4`}>
Access control is a fundamental part of security. Most entities
rely on the combination of username and password, sometimes with
additional multi-factor authentication to improve security. Some
entities also use the SSL client certificate verification to
manage access to specific resources. One of the use cases where
SSL client certificate verification fits perfectly is managing
access to internet-facing development or staging servers. In
this post, I&apos;ll share how to set up the certificates and
configure nginx to verify users based on their certificates.
</p>
</div>
</Link>
</div>
</div>
<div className={`flex flex-row bg-teal-50 justify-center`}>
<div className={`max-w-4xl py-28 px-10`}>
<p className={`text-3xl ${raleway.className}`}>Hi There</p>
<p className={`text-base ${raleway.className} my-4`}>
a new take on experience is the best teacher
</p>
<p className={`${raleway.className} text-sm`}>
I started this blog as an archive of my experiences and knowledge.
By writing them out, I hope it will help me unlearn and relearn the
various knowledge and skills I&apos;ve accumulated. I hope the
articles, source code examples, and server config examples I wrote
will help you somehow. Read on and enjoy!
</p>
</div>
</div> </div>
</div> </div>
<div className={`flex flex-row bg-teal-50 justify-center`}>
<div className={`max-w-4xl py-28 px-10`}>
<p className={`text-3xl ${raleway.className}`}>Hi There</p>
<p className={`text-base ${raleway.className} my-4`}>
a new take on experience is the best teacher
</p>
<p className={`${raleway.className} text-sm`}>
I started this blog as an archive of my experiences and knowledge.
By writing them out, I hope it will help me unlearn and relearn the
various knowledge and skills I&apos;ve accumulated. I hope the
articles, source code examples, and server config examples I wrote
will help you somehow. Read on and enjoy!
</p>
</div>
</div>
</div>
); );
} }

View File

@@ -1,39 +1,42 @@
import mysql, { PoolOptions } from "mysql2"; import mysql, { PoolOptions, Pool } from "mysql2";
import { Pool as pPool } from "mysql2/promise"
import * as fs from 'fs'; import * as fs from 'fs';
import * as appEnv from "./env"; import * as appEnv from "./env";
if (typeof process.env.MYSQL_SSL_CA === 'undefined') { let pool: Pool | undefined;
throw new Error("missing MYSQL_SSL_CA") let promisePool: pPool | undefined;
}
if (typeof process.env.MYSQL_SSL_KEY === 'undefined') { export function getPool(): Pool {
throw new Error("missing MYSQL_SSL_KEY") const access: PoolOptions = {
} host: appEnv.getMysqlHost(),
port: appEnv.getMysqlPort(),
if (typeof process.env.MYSQL_SSL_CERT === 'undefined') { user: appEnv.getMysqlUser(),
throw new Error("missing MYSQL_SSL_CERT") password: appEnv.getMysqlPassword(),
} database: appEnv.getMysqlDatabase(),
waitForConnections: true,
const access: PoolOptions = { connectionLimit: 10,
host: appEnv.getMysqlHost(), maxIdle: 10,
port: appEnv.getMysqlPort(), idleTimeout: 60000,
user: appEnv.getMysqlUser(), queueLimit: 0,
password: appEnv.getMysqlPassword(), enableKeepAlive: true,
database: appEnv.getMysqlDatabase(), keepAliveInitialDelay: 0,
waitForConnections: true, ssl: {
connectionLimit: 10, ca: fs.readFileSync(appEnv.getMysqlSslCaFile()),
maxIdle: 10, key: fs.readFileSync(appEnv.getMysqlSslKeyFile()),
idleTimeout: 60000, cert: fs.readFileSync(appEnv.getMysqlSslCertFile())
queueLimit: 0, }
enableKeepAlive: true,
keepAliveInitialDelay: 0,
ssl: {
ca: fs.readFileSync(appEnv.getMysqlSslCaFile()),
key: fs.readFileSync(appEnv.getMysqlSslKeyFile()),
cert: fs.readFileSync(appEnv.getMysqlSslCertFile())
} }
if (typeof pool === 'undefined') {
pool = mysql.createPool(access)
}
return pool
} }
export const pool = mysql.createPool(access) export function getPromisePool(): pPool {
if (typeof promisePool === 'undefined') {
export const promisePool = pool.promise() promisePool = getPool().promise()
}
return promisePool
}

View File

@@ -1,9 +1,9 @@
import { RowDataPacket } from "mysql2"; import { RowDataPacket } from "mysql2";
import { promisePool } from "@/backend/db"; import { getPromisePool } from "@/backend/db";
export async function getPost(slug: string): Promise<string> { export async function getPost(slug: string): Promise<string> {
try { try {
const [rows, fields] = await promisePool.query<RowDataPacket[]>( const [rows, fields] = await getPromisePool().query<RowDataPacket[]>(
'select content from post where slug = ?', [slug]) 'select content from post where slug = ?', [slug])
return rows[0]['content'] return rows[0]['content']
} catch (e) { } catch (e) {

View File

@@ -1,8 +1,18 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
output: "standalone",
webpack: (config) => { webpack: (config) => {
config.externals = [...config.externals, "jsdom"]; config.externals = [...config.externals, "jsdom"];
return config; return config;
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "assets.suyono.me",
pathname: "/**"
},
]
} }
} }

View File

@@ -9,24 +9,25 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@types/dompurify": "^3.0.3", "@types/dompurify": "^3.0.5",
"@types/jsdom": "^21.1.3", "@types/jsdom": "^21.1.6",
"@types/node": "20.6.5", "@types/node": "20.6.5",
"@types/react": "18.2.22", "@types/react": "18.2.22",
"@types/react-dom": "18.2.7", "@types/react-dom": "18.2.7",
"autoprefixer": "10.4.16", "autoprefixer": "10.4.16",
"bright": "^0.8.4", "bright": "^0.8.5",
"dompurify": "^3.0.6", "dompurify": "^3.1.2",
"eslint": "8.50.0", "eslint": "8.57.0",
"eslint-config-next": "13.5.2", "eslint-config-next": "14.2.3",
"html-react-parser": "^4.2.2", "html-react-parser": "^4.2.10",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"mysql2": "^3.6.1", "mysql2": "^3.9.7",
"next": "13.5.2", "next": "14.2.3",
"postcss": "8.4.30", "postcss": "8.4.30",
"react": "18.2.0", "react": "18.3.1",
"react-dom": "18.2.0", "react-dom": "18.3.1",
"redis": "^4.6.10", "redis": "^4.6.13",
"sharp": "^0.33.3",
"tailwindcss": "3.3.3", "tailwindcss": "3.3.3",
"typescript": "5.2.2" "typescript": "5.2.2"
}, },

7
pm2.config.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
apps: [{
script: "server.js",
instances: 4,
exec_mode: "cluster"
}]
}

5152
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -12,10 +12,12 @@ const config: Config = {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic': 'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
'blog-cover': "url('/assets/placeholder.webp')"
}, },
width: { width: {
'224': '56rem', '224': '56rem',
},
height: {
'192': '48rem',
} }
}, },
}, },