Compare commits
28 Commits
624cf64c3b
...
master
Author | SHA1 | Date | |
---|---|---|---|
0b0be06a4d | |||
d10f4ceb69 | |||
805a60db72 | |||
ab18085b8c | |||
1e4a188acb | |||
61b3f2da7b | |||
09bb22ea1a | |||
da013ff6ce | |||
edf9665502 | |||
8b2c19b7c6 | |||
cf4827c09c | |||
c424f0b32a | |||
33c27c1cba | |||
ac5c83506d | |||
0f3b0ccafa | |||
7e3bd6f665 | |||
4e860d14e6 | |||
3b96c59bcf | |||
d933723fce | |||
b7f9afb3f0 | |||
9b25a0b93a | |||
c9861ddbfc | |||
4a386dd4dc | |||
c1e9013353 | |||
bd510b5e89 | |||
4c1fcc60ed | |||
9cf9067a64 | |||
9f34c2d544 |
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -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
|
||||||
|
13
Dockerfile
13
Dockerfile
@@ -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
67
Jenkinsfile
vendored
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
build.sh
9
build.sh
@@ -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} .
|
||||||
|
@@ -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
9
debug_local.sh
Executable 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
6
src/.buildinfo.json
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"tag": "jc-ng-localtest:",
|
||||||
|
"date": "2025-07-21",
|
||||||
|
"host": "jake-pc",
|
||||||
|
"user": "jake"
|
||||||
|
}
|
@@ -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'))
|
||||||
|
@@ -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 '''
|
||||||
|
@@ -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]
|
||||||
|
@@ -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)
|
@@ -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
43
src/sitemap.py
Executable 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
BIN
src/static/images/technology/svn.svg
(Stored with Git LFS)
Executable file
Binary file not shown.
4
src/static/robots.txt
Executable file
4
src/static/robots.txt
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://jakecharman.co.uk/sitemap.xml
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -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));
|
||||||
}
|
}
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user