Compare commits

47 Commits

Author SHA1 Message Date
50bf391450 Start implementing comments 2025-11-06 21:26:09 +00:00
8ad638f496 Fix some sizing issues 2025-10-17 19:11:27 +01:00
6838f603a1 Revert "Add AdSense"
This reverts commit 32b1c4a3b4.
2025-10-16 22:24:06 +01:00
32b1c4a3b4 Add AdSense 2025-10-16 21:42:33 +01:00
dff93fb807 CSS Tweaks 2025-10-10 17:21:28 +01:00
a5a3da62eb CSS Tweaks 2025-10-10 17:19:26 +01:00
60c4e01778 CSS Tweaks 2025-10-10 17:17:03 +01:00
d64e7f4fa5 Prevent security test from failing build 2025-10-10 17:10:46 +01:00
bd735cb1e5 Prevent security test from failing build 2025-10-10 17:08:48 +01:00
5a09d74670 Prevent security test from failing build 2025-10-10 17:06:45 +01:00
c9927f8301 Bump Python 2025-10-10 17:02:50 +01:00
7286199411 Merge branch 'master' of git.jakecharman.co.uk:jake/jc-ng 2025-10-10 17:00:23 +01:00
c35e2b3ef0 Bump Python 2025-10-10 16:59:24 +01:00
6b338ce9ff CSS Tweaks 2025-10-10 16:52:04 +01:00
587b321205 Fix short articles 2025-09-26 14:12:57 +01:00
fe06477bec Swap image and video 2025-09-26 13:15:21 +01:00
ffaea83e09 Additions to racing 2025-08-10 15:30:32 +01:00
52e85b806c Update racing section 2025-08-09 21:49:41 +01:00
a78a2df86f Fix background image on desktop 2025-08-03 19:53:30 +01:00
0b0be06a4d Only scan when there is a new build 2025-07-21 22:16:12 +01:00
d10f4ceb69 move SVGs to lfs 2025-07-21 22:08:51 +01:00
805a60db72 track SVGs 2025-07-21 22:06:58 +01:00
ab18085b8c Design improvements on mobile, updated technology section 2025-07-21 22:06:05 +01:00
1e4a188acb Make cert logos smaller on desktop 2025-07-19 00:26:22 +01:00
61b3f2da7b Add Oracle cert 2025-07-19 00:06:11 +01:00
09bb22ea1a Jenkinsfile fixes 2025-07-18 23:11:48 +01:00
da013ff6ce Fix GCP Deployment 2025-07-18 15:06:02 +01:00
edf9665502 Fix typo 2025-07-18 15:01:20 +01:00
8b2c19b7c6 Specify GCP project 2025-07-18 15:00:28 +01:00
cf4827c09c Automate deployment to GCP 2025-07-18 14:57:02 +01:00
c424f0b32a Improved logging 2025-07-18 14:45:01 +01:00
33c27c1cba Improved logging 2025-07-18 14:38:14 +01:00
ac5c83506d Add push to GCP 2025-07-18 13:33:29 +01:00
0f3b0ccafa Move registry to Gitea 2025-07-18 10:51:28 +01:00
7e3bd6f665 Always use HTTPS 2025-06-15 15:57:32 +01:00
4e860d14e6 Add site map 2025-06-15 15:41:17 +01:00
3b96c59bcf Upgrade to Python 3.13 2025-06-11 21:46:41 +01:00
d933723fce Fix scrollbars 2025-06-08 18:50:54 +01:00
b7f9afb3f0 Fix build 2025-06-08 16:16:17 +01:00
9b25a0b93a Changes for code blocks 2025-06-08 15:59:49 +01:00
c9861ddbfc Bump setuptools to avoid GHSA-5rjg-fvgr-3xxf 2025-06-04 22:12:33 +01:00
4a386dd4dc Upgrade pip 2025-06-04 21:58:42 +01:00
c1e9013353 Fix typo 2025-06-04 21:55:27 +01:00
bd510b5e89 Fix typo 2025-06-04 21:53:31 +01:00
4c1fcc60ed Fix typo 2025-06-04 21:51:47 +01:00
9cf9067a64 Merge branch 'master' of git.jakecharman.co.uk:jake/jc-ng 2025-06-04 21:49:48 +01:00
9f34c2d544 Scan for vulnerabilities on build 2025-06-04 21:46:00 +01:00
44 changed files with 454 additions and 67 deletions

1
.gitattributes vendored
View File

@@ -1,3 +1,4 @@
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,8 +1,11 @@
FROM httpd:2.4
FROM python:3.14-bookworm
RUN apt-get update
RUN apt-get -y install libapache2-mod-wsgi-py3 python3 python3-pip
RUN apt-get -y install apache2 apache2-dev
COPY src/requirements.txt /var/www/jc/requirements.txt
RUN pip3 install -r /var/www/jc/requirements.txt || pip3 install --break-system-packages -r /var/www/jc/requirements.txt
COPY --chown=www-data:www-data config/httpd.conf /usr/local/apache2/conf/httpd.conf
RUN /usr/local/bin/pip3 install --upgrade pip
RUN /usr/local/bin/pip3 install -r /var/www/jc/requirements.txt
COPY --chown=www-data:www-data config/httpd.conf /etc/apache2/apache2.conf
COPY --chown=www-data:www-data src/ /var/www/jc
RUN httpd -t
RUN apache2 -t
EXPOSE 80
ENTRYPOINT ["apache2", "-D", "FOREGROUND"]

69
Jenkinsfile vendored
View File

@@ -33,19 +33,39 @@ pipeline {
credentialsId: 'Git',
url: 'git@git.jakecharman.co.uk:jake/jc-ng.git'
sh "./build.sh registry.jakecharman.co.uk/jakecharman.co.uk $BUILD_NUMBER"
sh "./build.sh git.jakecharman.co.uk/jake/jakecharman.co.uk $BUILD_NUMBER"
sh "./build.sh europe-west2-docker.pkg.dev/jakecharman/web/jakecharman.co.uk $BUILD_NUMBER"
}
}
stage('Push to registry') {
stage('Security scan') {
when {
expression {
return params.Build == true
}
}
steps {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
sh "docker kill sectest || true"
sh "docker rm sectest || true"
sh "docker run -d --name sectest git.jakecharman.co.uk/jake/jakecharman.co.uk:$BUILD_NUMBER"
sh "docker exec sectest pip3 install pip-audit --break-system-packages"
sh "docker exec sectest pip-audit"
sh "docker stop sectest"
sh "docker rm sectest"
}
}
}
stage('Push to local registry') {
when {
expression {
return params.Build == true
}
}
steps {
sh "docker push registry.jakecharman.co.uk/jakecharman.co.uk:$BUILD_NUMBER"
sh "docker push registry.jakecharman.co.uk/jakecharman.co.uk:latest"
sh "docker push git.jakecharman.co.uk/jake/jakecharman.co.uk:$BUILD_NUMBER"
sh "docker push git.jakecharman.co.uk/jake/jakecharman.co.uk:latest"
}
}
@@ -57,10 +77,10 @@ pipeline {
}
steps{
node('web-staging') {
sh "docker pull registry.jakecharman.co.uk/jakecharman.co.uk:latest"
sh "docker pull git.jakecharman.co.uk/jake/jakecharman.co.uk:latest"
sh "docker stop jake || true"
sh "docker rm jake || true"
sh "docker run --name jake -e DISCORD_ERR_HOOK=$DISCORD_ERR_STAGING -e DISCORD_WEBHOOK=$DISCORD -e TURNSTILE_SECRET=$TS --restart always --network containers_default -v /opt/containers/jc/projects/:/var/www/jc/projects/ -d registry.jakecharman.co.uk/jakecharman.co.uk:latest"
sh "docker run --name jake -e DISCORD_ERR_HOOK=$DISCORD_ERR_STAGING -e DISCORD_WEBHOOK=$DISCORD -e TURNSTILE_SECRET=$TS --restart always --network containers_default -v /opt/containers/jc/projects/:/var/www/jc/projects/ -d git.jakecharman.co.uk/jake/jakecharman.co.uk:latest"
}
}
}
@@ -89,20 +109,25 @@ pipeline {
}
}
stage('Deploy to production server') {
stage('Push to GCP registry') {
when {
expression {
return params.Build == true
}
}
steps {
sh "docker push europe-west2-docker.pkg.dev/jakecharman/web/jakecharman.co.uk:latest"
}
}
stage('Deploy to production') {
when {
expression {
return params.Deploy == true
}
}
steps{
node('web-server') {
sh "docker pull registry.jakecharman.co.uk/jakecharman.co.uk:latest"
sh "docker stop jake || true"
sh "docker rm jake || true"
sh "docker run --name jake -e DISCORD_ERR_HOOK=$DISCORD_ERR_PROD -e DISCORD_WEBHOOK=$DISCORD -e TURNSTILE_SECRET=$TS --restart always --network containers_default -v /opt/containers/jc/projects/:/var/www/jc/projects/ -d registry.jakecharman.co.uk/jakecharman.co.uk:latest"
sh "/home/jenkins/clearCFCache/clearCache.py a514fb61e1413b88aabbb19df16b8508"
}
sh "gcloud run deploy --project jakecharman --region europe-west1 --image europe-west2-docker.pkg.dev/jakecharman/web/jakecharman.co.uk:latest jakecharman-co-uk"
}
}
@@ -113,12 +138,16 @@ pipeline {
}
}
steps {
node('web-server') {
git branch: 'master',
credentialsId: 'Git',
url: 'git@git.jakecharman.co.uk:jake/jc-content.git'
sh "rsync -rv --delete ./ /opt/containers/jc/projects/"
}
git branch: 'master',
credentialsId: 'Git',
url: 'git@git.jakecharman.co.uk:jake/jc-content.git'
sh "gsutil rsync -rcd . gs://jakecharman.co.uk"
}
}
stage('Clear cache') {
steps{
sh "/var/lib/jenkins/clearCFCache/clearCache.py a514fb61e1413b88aabbb19df16b8508"
}
}
}

View File

@@ -5,6 +5,15 @@ set -x -o pipefail
tag=$1
build=$2
cat <<EOF >src/.buildinfo.json
{
"tag": "${tag}:${build}",
"date": "$(date -I)",
"host": "$(hostname -f)",
"user": "${USER}"
}
EOF
docker build -t ${tag}:latest .
if [[ $build != "" ]]; then
docker build -t ${tag}:${build} .

View File

@@ -28,7 +28,7 @@
# same ServerRoot for multiple httpd daemons, you will need to change at
# least PidFile.
#
ServerRoot "/usr/local/apache2"
ServerRoot "/usr/lib/apache2"
#
# Mutex: Allows you to set the mutex mechanism and mutex file directory
@@ -123,7 +123,7 @@ LoadModule deflate_module modules/mod_deflate.so
#LoadModule brotli_module modules/mod_brotli.so
LoadModule mime_module modules/mod_mime.so
#LoadModule ldap_module modules/mod_ldap.so
LoadModule log_config_module modules/mod_log_config.so
#LoadModule log_config_module modules/mod_log_config.so
#LoadModule log_debug_module modules/mod_log_debug.so
#LoadModule log_forensic_module modules/mod_log_forensic.so
#LoadModule logio_module modules/mod_logio.so
@@ -137,7 +137,7 @@ LoadModule headers_module modules/mod_headers.so
#LoadModule usertrack_module modules/mod_usertrack.so
#LoadModule unique_id_module modules/mod_unique_id.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
#LoadModule version_module modules/mod_version.so
#LoadModule remoteip_module modules/mod_remoteip.so
#LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_connect_module modules/mod_proxy_connect.so
@@ -171,7 +171,7 @@ LoadModule version_module modules/mod_version.so
#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
LoadModule unixd_module modules/mod_unixd.so
#LoadModule unixd_module modules/mod_unixd.so
#LoadModule heartbeat_module modules/mod_heartbeat.so
#LoadModule heartmonitor_module modules/mod_heartmonitor.so
#LoadModule dav_module modules/mod_dav.so
@@ -198,8 +198,7 @@ LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
#LoadModule rewrite_module modules/mod_rewrite.somod_wsgi-express start-server
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so
LoadModule wsgi_module /usr/local/lib/python3.14/site-packages/mod_wsgi/server/mod_wsgi-py314.cpython-314-x86_64-linux-gnu.so
<IfModule unixd_module>
#
@@ -368,7 +367,7 @@ LogLevel warn
# TypesConfig points to the file containing the list of mappings from
# filename extension to MIME-type.
#
TypesConfig conf/mime.types
TypesConfig /etc/mime.types
#
# AddType allows you to add to or override the MIME configuration
@@ -423,7 +422,6 @@ ErrorDocument 503 /error/503
ErrorDocument 505 /error/505
WSGISocketPrefix /var/run/wsgi
WSGIDaemonProcess jc-wsgi user=www-data group=www-data threads=5
WSGIProcessGroup jc-wsgi
WSGIScriptAlias / /var/www/jc/projects.wsgi
@@ -434,6 +432,14 @@ WSGIScriptAlias / /var/www/jc/projects.wsgi
Require all granted
</Directory>
Alias "/static" "/var/www/jc/static/"
<Directory /var/www/jc/static>
Order allow,Deny
Allow from all
</Directory>
Alias "/robots.txt" "/var/www/jc/static/robots.txt"
<Files jc.wsgi>
Require all granted
</Files>

9
debug_local.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash --noprofile
run() {
python3 src/projects.wsgi || run
}
export DISCORD_ERR_HOOK='dummy'
run
unset DISCORD_ERR_HOOK

6
src/.buildinfo.json Normal file
View File

@@ -0,0 +1,6 @@
{
"tag": "jc-ng-localtest:",
"date": "2025-11-02",
"host": "jake-e580",
"user": "jake"
}

30
src/comments.py Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/python3
import sqlite3
from os import path
from datetime import datetime
import json
from flask import request, Response, redirect
from index import app
from projects import md_directory, get_by_meta_key
database = '/tmp/db' #path.join(md_directory, 'comments.db')
with sqlite3.Connection(database) as db:
db.execute('CREATE TABLE IF NOT EXISTS comments (date INTEGER, article TEXT, name TEXT, comment TEXT)')
db.commit()
@app.route('/comments/<article>', methods=['GET', 'POST'])
def post_comments(article: str):
match request.method:
case 'POST':
if len(get_by_meta_key(md_directory, 'id', article)) == 0:
return Response(status=404)
with sqlite3.Connection(database) as db:
db.execute('INSERT INTO comments (date, article, name, comment) VALUES (?, ?, ?, ?)', (datetime.now(), article, request.form.get('name'), request.form.get('comment')))
db.commit()
return redirect(f'/projects/{article}')
case 'GET':
if len(get_by_meta_key(md_directory, 'id', article)) == 1:
with sqlite3.Connection(database) as db:
res = db.execute('SELECT * FROM comments WHERE `article` = ?', (article,))
return json.dumps([{'author': x[2], 'date': x[0], 'comment': x[3]} for x in res.fetchall()])

View File

@@ -6,7 +6,7 @@ from flask import request, render_template
from requests import post, get
from uuid import uuid4
from textwrap import dedent
from traceback import format_exc
def validate_turnstile(response: str, ip: str) -> bool:
turnstile_secret = environ['TURNSTILE_SECRET']
cf_response = post(
@@ -16,7 +16,8 @@ def validate_turnstile(response: str, ip: str) -> bool:
'response': response,
'remoteip': ip,
'idempotency_key': uuid4()
}
},
timeout=30
).json()
return cf_response.get('success', False)
@@ -24,7 +25,8 @@ def validate_turnstile(response: str, ip: str) -> bool:
def send_to_discord(form: dict) -> bool:
try:
discord_hook = environ['DISCORD_WEBHOOK']
except:
except KeyError:
app.logger.error(format_exc())
return False
discord_msg = dedent(
f'''
@@ -42,11 +44,12 @@ def send_to_discord(form: dict) -> bool:
data={
'username': form.get('name'),
'content': discord_msg
}
).status_code
if discord_response == 204:
},
timeout=30
)
if discord_response.status_code == 204:
return True
app.logger.error(discord_response.status_code, discord_response.text)
return False
@app.route('/contact/', methods=('GET', 'POST'))

View File

@@ -10,8 +10,10 @@ from flask import Flask, render_template, Response
app = Flask(__name__)
# These imports need to come after our app is defined as they add routes to it.
import sitemap # pylint: disable=wrong-import-position,unused-import
import projects # pylint: disable=wrong-import-position,unused-import
import contact # pylint: disable=wrong-import-position,unused-import
import comments # pylint: disable=wrong-import-position,unused-import
class DiscordLogger(logging.Handler):
''' Simple logging handler to send a message to Discord '''
@@ -81,3 +83,6 @@ def error(code) -> str:
return render_template('error.html',
error=f'{code}: {error_definitions.get(int(code))}',
description=error_desc.get(int(code)))
if __name__ == '__main__':
app.run()

View File

@@ -45,7 +45,7 @@ def to_html(content: str) -> str:
''' Jninja filter to wrap markdown '''
return markdown(content)
def get_all_posts(directory: str) -> list:
def get_all_posts(directory: str = md_directory) -> list:
''' Get all posts in the posts directory '''
abs_paths = [path.join(directory, x) for x in glob(f'{directory}/*.md')]
return [frontmatter.load(x) for x in abs_paths]

View File

@@ -4,3 +4,6 @@ import sys
sys.path.append('/var/www/jc')
from index import app as application
if __name__ == '__main__':
application.run(debug=True)

View File

@@ -1,3 +1,4 @@
setuptools>=78.1.1
flask>=2.2.3
flask-markdown>=0.3
markdown>=3.4.1
@@ -5,3 +6,4 @@ beautifulsoup4>=4.11.1
python-frontmatter>=1.1.0
requests>=2.32.3
pillow>=11.0.0
mod_wsgi>=5.0.2

43
src/sitemap.py Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/python3
import xml.etree.ElementTree as ET
import json
from flask import url_for, request, Response
from re import match
from index import app
from projects import get_all_posts
def get_routes() -> list:
routes = []
for rule in app.url_map.iter_rules():
if 0 >= len(rule.arguments):
url = url_for(rule.endpoint, **(rule.defaults or {}))
routes.append(url)
return routes
def get_build_date():
try:
with open('/var/www/jc/.buildinfo.json', encoding='utf8') as build:
build_json = json.load(build)
return build_json['date']
except:
return '1970-01-01'
@app.route('/sitemap.xml')
def sitemap():
date = get_build_date()
root = ET.Element('urlset', xmlns='http://www.sitemaps.org/schemas/sitemap/0.9')
base_url = match(r'^https?:\/\/.+:?\d*(?=\/)', request.base_url).group()
base_url = base_url.replace('http://', 'https://')
for route in get_routes():
url = ET.SubElement(root, 'url')
ET.SubElement(url, 'loc').text = base_url + route
ET.SubElement(url, 'lastmod').text = date
for article in get_all_posts():
if 'link' in article.metadata:
continue
url = ET.SubElement(root, 'url')
ET.SubElement(url, 'loc').text = f'{base_url}/projects/{article.metadata['id']}'
ET.SubElement(url, 'lastmod').text = article.metadata['date'].strftime('%Y-%m-%d')
return Response(ET.tostring(root, encoding='utf-8'), 200, {'content-type': 'application/xml'})

BIN
src/static/images/certs/OCIF2023CA.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/jake_tf_1.jpg (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/ansible.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/aws.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/azure.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/csharp.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/debian.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/digitalocean.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/docker.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/freeipa.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/gcloud.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/git.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/grafana.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/hadoop.svg (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/hive.svg (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/java.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/jenkins.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/mariadb.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/nginx.svg (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/proxmox.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/python.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/redhat.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/rocky.png (Stored with Git LFS) Executable file

Binary file not shown.

BIN
src/static/images/technology/svn.svg (Stored with Git LFS) Executable file

Binary file not shown.

View File

4
src/static/robots.txt Executable file
View File

@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://jakecharman.co.uk/sitemap.xml

View File

@@ -1,22 +1,25 @@
@media (min-width: 1000px) {
#technology{
background-position-x: 35vw;
height: 75vh;
height: fit-content;
min-height: 75vh;
background-position-y: 780px;
}
#motorsport{
background-position-y: -180px;
background-position-x: -200px;
background-size: auto;
height: fit-content;
min-height: 75vh;
height: 75vh;
}
.text{
width: 50%;
height: 100%;
align-items: flex-start;
height: fit-content;
min-height: 75vh
}
.text-right{
@@ -29,6 +32,13 @@
margin-right: auto;
}
.gradient {
display: flex;
align-items: flex-end;
background-image: linear-gradient(to top, rgba(23, 22, 20, 1) 70%, rgba(23, 22, 20, 0));
min-height: 75vh;
}
.gradient-left{
background-image: linear-gradient(to right, rgba(23, 22, 20, 1), rgba(23, 22, 20, 1), rgba(23, 22, 20, 0));
}
@@ -38,7 +48,7 @@
}
#projects{
align-items: center;
align-items: stretch;
justify-content: center;
flex-direction: row;
flex-wrap: wrap;
@@ -48,7 +58,6 @@
.project{
width: 20vw;
height: 25vw;
}
#top-nav{
@@ -67,4 +76,42 @@
#article>p>img{
display: inline;
}
#article>video{
display: inline;
}
#certs>a {
max-width: 15%;
}
#techlogos {
max-height: 40%;
}
#techlogos>a{
max-width: 5%;
}
.spacer {
display: none;
}
.yt {
width: calc(66% - 40px);
min-height: 100%;
}
.gallery {
display: flex;
height: fit-content;
padding: 10px 0;
}
.gallery * {
padding: 0 20px;
}
.gallery>img {
width: calc(33% - 40px);
}
}

View File

@@ -13,13 +13,17 @@ h1, h2, h3{
}
header{
background-color: 4c4c4c;
background-color: #4c4c4c;
height: 25vh;
width: 100%;
}
main{
min-height: 65vh;
}
footer{
background-color: 4c4c4c;
background-color: #4c4c4c;
display: flex;
justify-content: center;
align-items: center;
@@ -48,33 +52,32 @@ footer h2, section h2{
#technology{
background-image: url(../images/njr-code.png);
height: 100vh;
height: fit-content;
margin: 0;
background-position: center;
background-position-x: -400px;
background-position-y: 400px;
}
#motorsport{
background-image: url(../images/topfuel_startline.jpg.jpeg);
height: 100vh;
height: fit-content;
margin: 0;
background-position: center;
background-position-x: -65px;
background-position-y: -90px;
background-position-y: -100px;
background-size: 200%;
}
.gradient{
width: 100%;
height: 100%;
background-image: linear-gradient(to top, rgba(23, 22, 20, 1) 70%, rgba(23, 22, 20, 0));
display: flex;
align-items: flex-end;
height: fit-content;
}
.text{
height: 70%;
height: fit-content;
width: 100%;
background-color: rgba(23, 22, 20, 1);
}
.text>div{
@@ -209,10 +212,73 @@ label{
#article{
padding: 0 10px 0 10px;
margin-bottom: 25px;
}
#article>p>img{
display: block;
margin: 0 auto 0 auto;
max-width: 100%;
max-height: 50vh;
}
#article>video{
display: block;
margin: 0 auto 0 auto;
max-width: 100%;
max-height: 50vh;
}
pre{
overflow-x: auto;
background-color: #36332f;
padding: 10px;
}
#certs {
display: flex;
flex-direction: row;
justify-content: center;
padding: 20px 0;
}
#certs>a {
max-width: 25%;
}
#certs>a>img {
width: 100%;
}
#techlogos {
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
max-height:30%;
}
#techlogos>a {
max-width: 10%;
padding: 10px;
}
#techlogos>a>img {
width: 100%;
}
.spacer {
height: 30vh;
width: 100%;
background-image: linear-gradient(to top, rgba(23, 22, 20, 1) 10%, rgba(23, 22, 20, 0));
}
.yt {
width: 100%;
height: 22vh;
}
.gallery * {
padding: 10px;
max-width: 80vw;
}

View File

@@ -6,5 +6,21 @@
<hr />
{{post|safe}}
</section>
<section id="comments">
<h2>{{ comments | length }} comments</h2>
{% for comment in comments %}
<div class="comment">
<strong>{{ comment[2] }}</strong>
<p>{{ comment[3] }}</p>
</div>
{% endfor %}
<form action="/comments/{{ metadata.id }}" method="post">
<label for="name">Name:</label>
<input type="text" name="name" />
<label for="comment">Comment:</label>
<textarea name="comment"></textarea>
<input type="submit" name="submit" value="Submit">
</form>
</section>
</main>
{% include 'footer.html' %}

View File

@@ -2,18 +2,46 @@
<main>
<section id="technology">
<div class="gradient gradient-left">
<div class="spacer"></div>
<div class="text text-left">
<div>
<h2>Technology</h2>
<hr>
<p>Working with technology is my day job, I currently specialise in:</p>
<ul>
<li>Linux (primarily RHEL & Debian based)</li>
<li>SCM with Git and Subversion</li>
<li>Big Data (Hadoop & Cloud storage)</li>
<li>Programming (Python, Bash & C#)</li>
</ul>
<p>I also run some services for personal use and occasionally write software. I may write about some of the services I run in the future. For now, my code can be found on <a href="https://github.com/jcharman">GitHub</a>.</p>
<p>Technology is my day job. I design and build distributed systems for large companies around the world.</p>
<p>
I also work with technology in my spare time, building systems for <a href="#motorsport">Nitro Junkie</a>, and for me to use personally. For example, the current iteration of this website is written in Python and deployed via a Jenkins pipeline to Google Cloud Run as a Docker container.
</p>
<p>
Below are some of the technologies I'm using most at the moment.
My personal projects can also be found over on the <a href="/projects/">projects</a> page. Be aware though that if I'm able to write about a project here, it most likely wasn't done professionally so some may be a little rough around the edges.
</p>
<div id="techlogos">
<a href="https://ansible.com"><img src="/static/images/technology/ansible.png" /></a>
<a href="https://aws.amazon.com/"><img src="/static/images/technology/aws.png" /></a>
<a href="https://azure.microsoft.com/en-gb"><img src="/static/images/technology/azure.png" /></a>
<a href="https://dotnet.microsoft.com/en-us/languages/csharp"><img src="/static/images/technology/csharp.png" /></a>
<a href="https://www.debian.org/"><img src="/static/images/technology/debian.png" /></a>
<a href="https://www.digitalocean.com/"><img src="/static/images/technology/digitalocean.png" /></a>
<a href="https://www.docker.com/"><img src="/static/images/technology/docker.png" /></a>
<a href="https://www.freeipa.org/"><img src="/static/images/technology/freeipa.png" /></a>
<a href="https://cloud.google.com/"><img src="/static/images/technology/gcloud.png" /></a>
<a href="https://git-scm.com/"><img src="/static/images/technology/git.png" /></a>
<a href="https://www.grafana.com/"><img src="/static/images/technology/grafana.png" /></a>
<a href="https://hadoop.apache.org/"><img src="/static/images/technology/hadoop.svg" /></a>
<a href="https://hive.apache.org/"><img src="/static/images/technology/hive.svg" /></a>
<a href="https://www.java.com/"><img src="/static/images/technology/java.png" /></a>
<a href="https://www.jenkins.io/"><img src="/static/images/technology/jenkins.png" /></a>
<a href="https://mariadb.org/"><img src="/static/images/technology/mariadb.png" /></a>
<a href="https://nginx.org/"><img src="/static/images/technology/nginx.svg" /></a>
<a href="https://www.proxmox.com/"><img src="/static/images/technology/proxmox.png" /></a>
<a href="https://www.python.org/"><img src="/static/images/technology/python.png" /></a>
<a href="https://www.redhat.com/en/technologies/linux-platforms/enterprise-linux"><img src="/static/images/technology/redhat.png" /></a>
<a href="https://rockylinux.org/"><img src="/static/images/technology/rocky.png" /></a>
<a href="https://subversion.apache.org/"><img src="/static/images/technology/svn.svg" /></a>
</div>
<div id="certs">
<a href="https://catalog-education.oracle.com/ords/certview/sharebadge?id=A6C9B3D3D21627EB9C2504395194FAF772E01412911D4ADC78063133D54579ED"><img src="/static/images/certs/OCIF2023CA.png" /></a>
</div>
<div class="social">
<a class="button" href="https://www.linkedin.com/in/jakecharman/"><i class="fa-brands fa-linkedin-in"></i></a>
<a class="button" href="https://github.com/jcharman/"><i class="fa-brands fa-github"></i></a>
@@ -24,13 +52,18 @@
</section>
<section id="motorsport">
<div class="gradient gradient-right">
<div class="spacer"></div>
<div class="text text-right">
<div>
<h2>Racing</h2>
<hr />
<p>When not working on tech, I can usually be found at <a href="https://santapod.com">Santa Pod Raceway</a> working on my Dad's <a href="https://nitrojunkie.uk">NitroJunkie.UK</a> "All In" Top Fuel Bike. The bike is purpose built from the ground up with a supercharged, nitromethane injected engine capable of producing approximately 1000 horsepower. </p>
<p>The photo for this section was taken by <a href="https://www.facebook.com/BlackettPhotography">Blackett Photography</a></p>
<p>You can read more about our racing at <a href="https://nitrojunkie.uk">nitrojunkie.uk</a></p>
<p>When I'm not at work, I can often be found at <a href="https://santapod.com">Santa Pod Raceway</a> with <a href="https://nitrojunkie.uk">Nitro Junkie Racing</a></p>
<p>We run a Top Fuel motorcycle purpose built for drag racing. The engine is loosely based on a Kawasaki Z 1000, but supercharged, and nitromethane injected to produce around 1,000 horsepower.</p>
<p>I've also had the opportunity to work on a 10,000 horsepower Top Fuel Dragster. I <a href="https://nitrojunkie.uk/news/post/10000_horses">wrote about this on the Nitro Junkie website.</a></p>
<div class="gallery">
<iframe class="yt" src="https://www.youtube.com/embed/Wa-V8mQTXFk?si=W-LzfR3Qj_jgAdkY&amp;clip=UgkxV9lBj4pP1cvnR5seb82RQpWeE7RdnOXB&amp;clipt=EOrWtgsYyqu6Cw&amp;autoplay=1&amp;mute=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<img src="/static/images/jake_tf_1.jpg">
</div>
<div class="social">
<a class="button" href="https://nitrojunkie.uk"><i class="fa-solid fa-globe"></i></a>
<a class="button" href="https://www.youtube.com/@NitroJunkieUK"><i class="fa-brands fa-youtube"></i></a>