Compare commits

..

28 Commits

Author SHA1 Message Date
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
40 changed files with 348 additions and 61 deletions

5
.gitattributes vendored
View File

@@ -1,3 +1,8 @@
*.jpeg filter=lfs diff=lfs merge=lfs -text *.jpeg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text
*.svg filter=lfs diff=lfs merge=lfs -text
src/static/images/technology/hadoop.svg filter=lfs diff=lfs merge=lfs -text
src/static/images/technology/hive.svg filter=lfs diff=lfs merge=lfs -text
src/static/images/technology/nginx.svg filter=lfs diff=lfs merge=lfs -text
src/static/images/technology/svn.svg filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,8 +1,11 @@
FROM httpd:2.4 FROM python:3.13-bookworm
RUN apt-get update 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 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 RUN /usr/local/bin/pip3 install --upgrade pip
COPY --chown=www-data:www-data config/httpd.conf /usr/local/apache2/conf/httpd.conf 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 COPY --chown=www-data:www-data src/ /var/www/jc
RUN httpd -t RUN apache2 -t
EXPOSE 80
ENTRYPOINT ["apache2", "-D", "FOREGROUND"]

67
Jenkinsfile vendored
View File

@@ -33,19 +33,37 @@ pipeline {
credentialsId: 'Git', credentialsId: 'Git',
url: 'git@git.jakecharman.co.uk:jake/jc-ng.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 { when {
expression { expression {
return params.Build == true return params.Build == true
} }
} }
steps { steps {
sh "docker push registry.jakecharman.co.uk/jakecharman.co.uk:$BUILD_NUMBER" sh "docker kill sectest || true"
sh "docker push registry.jakecharman.co.uk/jakecharman.co.uk:latest" 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 git.jakecharman.co.uk/jake/jakecharman.co.uk:$BUILD_NUMBER"
sh "docker push git.jakecharman.co.uk/jake/jakecharman.co.uk:latest"
} }
} }
@@ -57,10 +75,10 @@ pipeline {
} }
steps{ steps{
node('web-staging') { 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 stop jake || true"
sh "docker rm 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 +107,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 { when {
expression { expression {
return params.Deploy == true return params.Deploy == true
} }
} }
steps{ steps{
node('web-server') { sh "gcloud run deploy --project jakecharman --region europe-west1 --image europe-west2-docker.pkg.dev/jakecharman/web/jakecharman.co.uk:latest jakecharman-co-uk"
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"
}
} }
} }
@@ -113,12 +136,16 @@ pipeline {
} }
} }
steps { steps {
node('web-server') { git branch: 'master',
git branch: 'master', credentialsId: 'Git',
credentialsId: 'Git', url: 'git@git.jakecharman.co.uk:jake/jc-content.git'
url: 'git@git.jakecharman.co.uk:jake/jc-content.git' sh "gsutil rsync -rcd . gs://jakecharman.co.uk"
sh "rsync -rv --delete ./ /opt/containers/jc/projects/" }
} }
stage('Clear cache') {
steps{
sh "/var/lib/jenkins/clearCFCache/clearCache.py a514fb61e1413b88aabbb19df16b8508"
} }
} }
} }

View File

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

View File

@@ -28,7 +28,7 @@
# same ServerRoot for multiple httpd daemons, you will need to change at # same ServerRoot for multiple httpd daemons, you will need to change at
# least PidFile. # least PidFile.
# #
ServerRoot "/usr/local/apache2" ServerRoot "/usr/lib/apache2"
# #
# Mutex: Allows you to set the mutex mechanism and mutex file directory # 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 brotli_module modules/mod_brotli.so
LoadModule mime_module modules/mod_mime.so LoadModule mime_module modules/mod_mime.so
#LoadModule ldap_module modules/mod_ldap.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_debug_module modules/mod_log_debug.so
#LoadModule log_forensic_module modules/mod_log_forensic.so #LoadModule log_forensic_module modules/mod_log_forensic.so
#LoadModule logio_module modules/mod_logio.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 usertrack_module modules/mod_usertrack.so
#LoadModule unique_id_module modules/mod_unique_id.so #LoadModule unique_id_module modules/mod_unique_id.so
LoadModule setenvif_module modules/mod_setenvif.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 remoteip_module modules/mod_remoteip.so
#LoadModule proxy_module modules/mod_proxy.so #LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_connect_module modules/mod_proxy_connect.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_bytraffic_module modules/mod_lbmethod_bytraffic.so
#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so #LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.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 heartbeat_module modules/mod_heartbeat.so
#LoadModule heartmonitor_module modules/mod_heartmonitor.so #LoadModule heartmonitor_module modules/mod_heartmonitor.so
#LoadModule dav_module modules/mod_dav.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 alias_module modules/mod_alias.so
#LoadModule rewrite_module modules/mod_rewrite.somod_wsgi-express start-server #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.13/site-packages/mod_wsgi/server/mod_wsgi-py313.cpython-313-x86_64-linux-gnu.so
<IfModule unixd_module> <IfModule unixd_module>
# #
@@ -368,7 +367,7 @@ LogLevel warn
# TypesConfig points to the file containing the list of mappings from # TypesConfig points to the file containing the list of mappings from
# filename extension to MIME-type. # filename extension to MIME-type.
# #
TypesConfig conf/mime.types TypesConfig /etc/mime.types
# #
# AddType allows you to add to or override the MIME configuration # AddType allows you to add to or override the MIME configuration
@@ -423,7 +422,6 @@ ErrorDocument 503 /error/503
ErrorDocument 505 /error/505 ErrorDocument 505 /error/505
WSGISocketPrefix /var/run/wsgi WSGISocketPrefix /var/run/wsgi
WSGIDaemonProcess jc-wsgi user=www-data group=www-data threads=5 WSGIDaemonProcess jc-wsgi user=www-data group=www-data threads=5
WSGIProcessGroup jc-wsgi WSGIProcessGroup jc-wsgi
WSGIScriptAlias / /var/www/jc/projects.wsgi WSGIScriptAlias / /var/www/jc/projects.wsgi
@@ -434,6 +432,14 @@ WSGIScriptAlias / /var/www/jc/projects.wsgi
Require all granted Require all granted
</Directory> </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> <Files jc.wsgi>
Require all granted Require all granted
</Files> </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 Executable file
View File

@@ -0,0 +1,6 @@
{
"tag": "jc-ng-localtest:",
"date": "2025-07-21",
"host": "jake-pc",
"user": "jake"
}

View File

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

View File

@@ -12,6 +12,7 @@ app = Flask(__name__)
# These imports need to come after our app is defined as they add routes to it. # These imports need to come after our app is defined as they add routes to it.
import projects # 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 contact # pylint: disable=wrong-import-position,unused-import
import sitemap # pylint: disable=wrong-import-position,unused-import
class DiscordLogger(logging.Handler): class DiscordLogger(logging.Handler):
''' Simple logging handler to send a message to Discord ''' ''' Simple logging handler to send a message to Discord '''

View File

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

View File

@@ -4,3 +4,6 @@ import sys
sys.path.append('/var/www/jc') sys.path.append('/var/www/jc')
from index import app as application 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>=2.2.3
flask-markdown>=0.3 flask-markdown>=0.3
markdown>=3.4.1 markdown>=3.4.1
@@ -5,3 +6,4 @@ beautifulsoup4>=4.11.1
python-frontmatter>=1.1.0 python-frontmatter>=1.1.0
requests>=2.32.3 requests>=2.32.3
pillow>=11.0.0 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/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.

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,7 +1,7 @@
@media (min-width: 1000px) { @media (min-width: 1000px) {
#technology{ #technology{
background-position-x: 35vw; background-position-x: 35vw;
height: 75vh; height: fit-content;
min-height: 75vh; min-height: 75vh;
} }
@@ -9,14 +9,16 @@
background-position-y: -180px; background-position-y: -180px;
background-position-x: -200px; background-position-x: -200px;
background-size: auto; background-size: auto;
height: fit-content;
min-height: 75vh; min-height: 75vh;
height: 75vh;
} }
.text{ .text{
width: 50%; width: 50%;
height: 100%; height: 100%;
align-items: flex-start; align-items: flex-start;
height: fit-content;
min-height: 75vh
} }
.text-right{ .text-right{
@@ -29,6 +31,13 @@
margin-right: auto; 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{ .gradient-left{
background-image: linear-gradient(to right, rgba(23, 22, 20, 1), rgba(23, 22, 20, 1), rgba(23, 22, 20, 0)); background-image: linear-gradient(to right, rgba(23, 22, 20, 1), rgba(23, 22, 20, 1), rgba(23, 22, 20, 0));
} }
@@ -67,4 +76,20 @@
#article>p>img{ #article>p>img{
display: inline; display: inline;
} }
#certs>a {
max-width: 15%;
}
#techlogos {
max-height: 40%;
}
#techlogos>a{
max-width: 5%;
}
.spacer {
display: none;
}
} }

View File

@@ -13,13 +13,13 @@ h1, h2, h3{
} }
header{ header{
background-color: 4c4c4c; background-color: #4c4c4c;
height: 25vh; height: 25vh;
width: 100%; width: 100%;
} }
footer{ footer{
background-color: 4c4c4c; background-color: #4c4c4c;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -48,33 +48,32 @@ footer h2, section h2{
#technology{ #technology{
background-image: url(../images/njr-code.png); background-image: url(../images/njr-code.png);
height: 100vh; height: fit-content;
margin: 0; margin: 0;
background-position: center; background-position: center;
background-position-x: -400px; background-position-x: -400px;
background-position-y: 400px;
} }
#motorsport{ #motorsport{
background-image: url(../images/topfuel_startline.jpg.jpeg); background-image: url(../images/topfuel_startline.jpg.jpeg);
height: 100vh; height: fit-content;
margin: 0; margin: 0;
background-position: center; background-position: center;
background-position-x: -65px; background-position-x: -65px;
background-position-y: -90px; background-position-y: -100px;
background-size: 200%; background-size: 200%;
} }
.gradient{ .gradient{
width: 100%; width: 100%;
height: 100%; height: fit-content;
background-image: linear-gradient(to top, rgba(23, 22, 20, 1) 70%, rgba(23, 22, 20, 0));
display: flex;
align-items: flex-end;
} }
.text{ .text{
height: 70%; height: fit-content;
width: 100%; width: 100%;
background-color: rgba(23, 22, 20, 1);
} }
.text>div{ .text>div{
@@ -215,4 +214,48 @@ label{
display: block; display: block;
margin: 0 auto 0 auto; margin: 0 auto 0 auto;
max-width: 100%; max-width: 100%;
}
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));
} }

View File

@@ -2,18 +2,46 @@
<main> <main>
<section id="technology"> <section id="technology">
<div class="gradient gradient-left"> <div class="gradient gradient-left">
<div class="spacer"></div>
<div class="text text-left"> <div class="text text-left">
<div> <div>
<h2>Technology</h2> <h2>Technology</h2>
<hr> <hr>
<p>Working with technology is my day job, I currently specialise in:</p> <p>Technology is my day job. I design and build distributed systems for large companies around the world.</p>
<ul> <p>
<li>Linux (primarily RHEL & Debian based)</li> 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.
<li>SCM with Git and Subversion</li> </p>
<li>Big Data (Hadoop & Cloud storage)</li> <p>
<li>Programming (Python, Bash & C#)</li> Below are some of the technologies I'm using most at the moment.
</ul> 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>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>
<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"> <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://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> <a class="button" href="https://github.com/jcharman/"><i class="fa-brands fa-github"></i></a>
@@ -24,6 +52,7 @@
</section> </section>
<section id="motorsport"> <section id="motorsport">
<div class="gradient gradient-right"> <div class="gradient gradient-right">
<div class="spacer"></div>
<div class="text text-right"> <div class="text text-right">
<div> <div>
<h2>Racing</h2> <h2>Racing</h2>