initial commit

This commit is contained in:
Bruno Carlin 2025-06-07 02:33:02 +02:00
commit 4aab9f1242
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG key ID: 8E254EA0FFEB9B6D
66 changed files with 5328 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/public
/resources

0
.hugo_build.lock Normal file
View file

4
TODO.md Normal file
View file

@ -0,0 +1,4 @@
- Add permalinks to sections
- Add summmary to articles
- Add open graph metadata
- Add json+ld metadata

5
archetypes/default.md Normal file
View file

@ -0,0 +1,5 @@
+++
date = '{{ .Date }}'
draft = true
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
+++

0
assets/.gitkeep Normal file
View file

4
build.sh Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
rm -rf public
hugo build --minify

25
content/_index.md Normal file
View file

@ -0,0 +1,25 @@
+++
title = 'Home'
date = 2025-06-03T23:00:00Z
draft = false
+++
With a dual experience in marketing and technical fields, I have contributed to
the success of numerous projects by adopting a comprehensive vision of them.
Graduate from a business school (Skema Business School), I completed a full
training in management (accounting, finance, project management, HR, strategy,
etc.) and specialized in marketing.
Passionate about computer science from a young age, I am also a self-taught
versatile engineer, performing tasks in system and network architecture and
administration, development, support, and training.
I now have over 18 years of experience in the open-source software world,
including 8 years professionally.
Curious about the continuous innovations offered by all free software, I
dedicate part of my time to technological watch, both on productivity tools and
system and network tools, which allows me to work on the design and
implementation of complex distributed systems as well as on the management and
execution of application integration projects within an information system.

View file

@ -0,0 +1,45 @@
---
title: Setup Nginx for Mediawiki
date: "2010-09-15T00:00:00+02:00"
tags:
- Nginx
- Mediawiki
slug: setup-nginx-for-mediawiki
summary: >
A simple configuration to serve Mediawiki with Nginx and FastCGI
---
Two weeks ago, I migrated a server from Apache/mod_php to nginx/php-fpm. Only
today did i succeed to remove all side effects. The latest one:
Static files must not go through php-fpm, but a simple test on extensions
is ineffective, as url like `http://server/File:name_of_the_file.png`
must be processed by PHP.
Here is my final setup, that corrects all the errors I encountered:
```nginx
server {
listen 80;
server_name server_name;
index index.php;
root /path/to/www/;
# Serve static files with a far future expiration
# date for browser caches
location ^~ /images/ {
expires 1y;
}
location ^~ /skins/ {
expires 1y;
}
# Pass the request to php-cgi
location / {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_index index.php;
include fastcgi_params;
}
}
```

View file

@ -0,0 +1,32 @@
---
title: Build the latest PgPool-II on Debian Etch
date: "2010-12-14T00:00:00+01:00"
tags: [Debian, PgPool-II]
slug: build-pgpool-on-debian
summary: >
Building PgPool-II on RHEL 5.5 to avoid the "libpq is not installed or
libpq is old" error
---
After having build PgPool-II on Red Hat Enterprise Linux 5.5 without any
problem, I tried to build it on a fresh Debian Etch. The catch is that I did
not want to install PostgreSQL 9.0, but just extract it from the binary
packages provided by Entreprisedb (with option `--extract-only 1`).
Whatever options I passed to `./configure`, it resulted in the same error:
{{< highlight text >}}
checking for PQexecPrepared in -lpq... no
configure: error: libpq is not installed or libpq is old
{{< /highlight >}}
Here is the answer: the binary package contains the libpq with the name
`libcrypto.so.0.9.8` (the RHEL name) when pgpool is looking `libcrypto.so.6`
on Debian. The same applies to `libssl`. So a simple
{{< highlight bash >}}
ln -s libcrypto.so.0.9.8 libcrypto.so.0.9.8
ln -s libssl.so.0.9.8 libssl.so.6
{{< /highlight >}}
before your `./configure` will solve it!

View file

@ -0,0 +1,87 @@
---
slug: aptana-eclipse-and-xulrunner
title: Aptana Studio/Eclipse and Xulrunner
tags: [Aptana Studio, Eclipse, Xulrunner, Arch Linux]
date: "2011-12-16T00:00:00+01:00"
summary: >
How to solve the "Unhandled event loop exception" error in Aptana Studio and Eclipse 3.7 with Xulrunner
---
Since a few months, I encountered an annoying error in Aptana Studio and
Eclipse 3.7 (the autonomous packages, not the packages from the repositories)
whenever I tried to do a git or hg action.
I could live without until now, but today, it was really bothering me.
The error is:
{{< highlight text >}}
Unhandled event loop exception
No more handles [Unknown Mozilla path (MOZILLA_FIVE_HOME not set)]
{{< /highlight >}}
The log file showed the following backtrace:
{{< highlight text >}}
!ENTRY org.eclipse.ui 4 0 2011-12-16 17:17:30.825
!MESSAGE Unhandled event loop exception
!STACK 0
org.eclipse.swt.SWTError: No more handles [Unknown Mozilla path (MOZILLA_FIVE_HOME not set)]
at org.eclipse.swt.SWT.error(SWT.java:4109)
at org.eclipse.swt.browser.Mozilla.initMozilla(Mozilla.java:1739)
at org.eclipse.swt.browser.Mozilla.create(Mozilla.java:656)
at org.eclipse.swt.browser.Browser.<init>(Browser.java:119)
at com.aptana.git.ui.internal.actions.CommitDialog.createDiffArea(CommitDialog.java:237)
at com.aptana.git.ui.internal.actions.CommitDialog.createDialogArea(CommitDialog.java:158)
[...]
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:620)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:575)
at org.eclipse.equinox.launcher.Main.run(Main.java:1408)
at org.eclipse.equinox.launcher.Main.main(Main.java:1384)
{{< /highlight >}}
To make it short, after having read [a](https://bugs.archlinux.org/task/5149)
[lot](https://bugs.archlinux.org/task/27130)
[of](https://github.com/eclipse-color-theme/eclipse-color-theme/issues/50)
[posts](https://bbs.archlinux.org/viewtopic.php?id=129982)
[about](http://forums.gentoo.org/viewtopic-t-827838-view-previous.html?sid=546c5717e2167c45d9b02f9f20ab36f4)
[this](<http://stackoverflow.com/questions/1017945/problem-with-aptana-studio-xulrunner-8-1)
[problem](http://www.eclipse.org/swt/faq.php#gtk64), it seemed it was enough
to give the path to Xulrunner to Aptana.
On my Arch Linux, it was
{{< highlight bash >}}
export MOZILLA_FIVE_HOME=/usr/lib/xulrunner-8.0
{{< /highlight >}}
Trying to start Aptana Studio, I had a new error. It simply stated
{{< highlight text >}}
XPCOM error -2147467261
{{< /highlight >}}
The solution is that Aptana Studio cannot work with the version of Xulrunner
in Arch Linux repositories because it is too recent.
To solve this problem, I had to install xulrunner 1.9.2 from AUR:
{{< highlight bash >}}
yaourt -S xulrunner192
{{< /highlight >}}
The PKGBUILD was broken this morning and ended in a 404 Error when fetching
sources. If you have the same problem, `here is an updated PKGBUILD
<https://gist.github.com/1486851>`__
Finally, I put
{{< highlight bash >}}
-Dorg.eclipse.swt.browser.XULRunnerPath=/usr/lib/xulrunner-1.9.2
{{< /highlight >}}
at the end of the `AptanaStudio3.ini` file in the Aptana Studio folder. For
the package in the Arch Linux repositories, this file is
`/usr/share/aptana/AptanaStudio3.ini`.

View file

@ -0,0 +1,178 @@
---
tags: [Python, Buzhug, Database, Locks]
slug: locking-buzhug
title: Locking Buzhug
date: "2012-02-07T00:00:00+01:00"
summary: >
How to implement a cross-process, system-wide lock for Buzhug
---
I have recently decided to work with [Buzhug] on a project. As far as I can tell,
it has proven efficient, fast, easy to use and to maintain. However, I ran into
a few gotchas.
[Buzhug]: http://buzhug.sourceforge.net
Simple solutions are often the best
===================================
I came to use Buzhug for the following requirements:
- I needed a single table
- I did not want to add additional dependencies to the project
- The size of the table will average 5K entries (without having more than
10k entries in peaks)
And an additional (personal) one:
- I did not want to bother with SQL. Really not. no way!
That left me one option: pure-python embedded database.
After having considered a few libraries, I have been seduced by the way Buzhug
interface is close to manipulating python objects. And the benchmarks seemed
to show that it is performant enough for this project.
After a quick prototyping (1 day), the choice was done.
Then came a few weeks of development and the first stress tests...
And the real world came back fast
=================================
A few times a day, the application backed by this database is intensely used:
- It can be run up to 50 times simultaneously in separate python process
- Each run makes a read and a write/delete operation
This causes a race condition on the files used to store data, and concurent
writes corrupts database.
Using `buzhug.TS_Base` instead of `buzhug.Base` did not solve anything,
as the problem is not thread, but processes. What I need is a system-wide
cross-process lock.
Here is the answer
==================
First step was to find how to implement a cross-process, system-wide lock.
As it only has to work on Linux, the
[Lock class given by Chris from
Vmfarms](http://blog.vmfarms.com/2011/03/cross-process-locking-and.html) fits
perfectly. Here is a version slightly modified to make it a context manager :
{{< highlight python >}}
import fcntl
class PsLock:
"""
Taken from:
http://blog.vmfarms.com/2011/03/cross-process-locking-and.html
"""
def __init__(self, filename):
self.filename = filename
self.handle = open(filename, 'w')
# Bitwise OR fcntl.LOCK_NB if you need a non-blocking lock
def acquire(self):
fcntl.flock(self.handle, fcntl.LOCK_EX)
def release(self):
fcntl.flock(self.handle, fcntl.LOCK_UN)
def __del__(self):
self.handle.close()
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
pass
self.release()
def __enter__(self):
self.acquire()
{{< /highlight >}}
The second step is to define a new class that inheritates from `buzhug.Base`
that uses `PsLock` (inspired by `TS_Base`):
{{< highlight python >}}
import buzhug
_lock = PsLock("/tmp/buzhug.lck")
class PS_Base(buzhug.Base):
def create(self,*args,**kw):
with _lock:
res = buzhug.Base.create(self,*args,**kw)
return res
def open(self,*args,**kw):
with _lock:
res = buzhug.Base.open(self,*args,**kw)
return res
def close(self,*args,**kw):
with _lock:
res = buzhug.Base.close(self,*args,**kw)
return res
def destroy(self,*args,**kw):
with _lock:
res = buzhug.Base.destroy(self,*args,**kw)
return res
def set_default(self,*args,**kw):
with _lock:
res = buzhug.Base.set_default(self,*args,**kw)
return res
def insert(self,*args,**kw):
with _lock:
res = buzhug.Base.insert(self,*args,**kw)
return res
def update(self,*args,**kw):
with _lock:
res = buzhug.Base.update(self,*args,**kw)
return res
def delete(self,*args,**kw):
with _lock:
res = buzhug.Base.delete(self,*args,**kw)
return res
def cleanup(self,*args,**kw):
with _lock:
res = buzhug.Base.cleanup(self,*args,**kw)
return res
def commit(self,*args,**kw):
with _lock:
res = buzhug.Base.commit(self,*args,**kw)
return res
def add_field(self,*args,**kw):
with _lock:
res = buzhug.Base.add_field(self,*args,**kw)
return res
def drop_field(self,*args,**kw):
with _lock:
res = buzhug.Base.drop_field(self,*args,**kw)
return res
{{< /highlight >}}
Now I just use
{{< highlight python >}}
database = PS_Base( ... )
{{< /highlight >}}
And all the errors have vanished.

View file

@ -0,0 +1,35 @@
---
tags: [Sublime Text 2]
slug: automatically-open-sublime-text-projects-in-a-directory
title: Automatically open Sublime Text projects in a directory
date: "2013-05-15T00:00:00+02:00"
summary: >
How to automatically open Sublimetext with a file, a project or the current
directory according to the context.
---
I usually start Sublime Text 2 from the command line to work, depending
on the case, on the content of a directory or on a project (materialized
with a `*.sublime-project` file).
It ends up with one of the following commands :
- `subl .`
- `subl my-project.sublime-project`
Here is the snippet I added to my .bashrc file to have the `subl`
command automatically "guess" what I want. It does the following:
- If a path is given (subl "my/file.txt"), it opens the file.
- If nothing is given and a .sublime-project file exists in the current
directory, it opens it
- If nothing is given and no .sublime-project file has been found, it
opens the folder.
{{< highlight bash >}}
function project_aware_subl {
project_file=$(ls *.sublime-project 2>/dev/null | head -n 1)
subl ${*:-${project_file:-.}}
}
alias subl="project_aware_subl"
{{< /highlight >}}

View file

@ -0,0 +1,395 @@
---
title: Discourse without Docker
slug: discourse-without-docker
date: "2016-06-27T00:00:00+02:00"
tags: [discourse, docker]
summary: Detailed instructions on how to install Discourse and plugins without Docker.
---
{{< warning >}}
The only official method is [with docker]. You might not be able
to get support from Discourse by following this method.
[with docker]: http://blog.discourse.org/2014/04/install-discourse-in-under-30-minutes/
{{< /warning >}}
The team behind [Discourse] has chosen to only release Docker images of
their software. The rational behind it is: it is easier to only support
a single setup. I will not discuss that. It is their choice.
However, I don't like to use docker to deploy apps in prodution. I even
hate it. If you are like me, here are the steps I used to install it
and to set it up.
I use Debian servers in production, so the steps below are all debian
oriented.
{{< note >}}
This is not intended as a comprehensive guide. A lot of commands and
configuration files might need to be adapted to your environment.
It does not even tries to talk about important topics in production such as
security. This is left as an exercise to the reader.
{{< /note >}}
## Installation
After all, Discourse is a rails application. It can be installed like
any other rails application:
First things first: Discourse uses Redis and PostgreSQL (or at least,
I prefer to use Postgres). I also use Nginx as a proxy to the
application. Install the external dependencies:
```sh
# Add the reposirory for Redis
echo "deb http://packages.dotdeb.org jessie all" > /etc/apt/sources.list.d/dotdeb.list
wget https://www.dotdeb.org/dotdeb.gpg -O - | apt-key add -
# Add the repository for PostgreSQL:
echo "deb http://apt.postgresql.org/pub/repos/apt/ jessie-pgdg main" > /etc/apt/sources.list.d/postgresql.list
wget -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
apt-get update
apt-get install postgresql-9.5 redis-server nginx
```
Then, create a database for the application. Enter postgres command
line interface:
```sh
su - postgres -c psql
```
and enter the following commands:
```sql
CREATE DATABASE discourse;
CREATE USER discourse;
ALTER USER discourse WITH ENCRYPTED PASSWORD 'password';
ALTER DATABASE discourse OWNER TO discourse;
\connect discourse
CREATE EXTENSION hstore;
CREATE EXTENSION pg_trgm;
```
Then, you can checkout the Discourse code:
```sh
git clone https://github.com/discourse/discourse.git /path/to/discourse
# Optionally, checkout a specific tag
cd /path/to/discourse
git checkout v1.5.3
```
Then, go in the application top directory, and set it up as any rails
application:
```bash
# Optionally setup rvm with ruby 1.9.3 minimum (I use 2.3.0)
rvm install 2.3.0
rvm use 2.3.0
# install dependencies
cd /path/to/discourse
RAILS_ENV bundle install
```
It's time to configure the application.
Here, Discourse has a little particularity: The production
configuration is located in the file `./config/discourse.conf`.
Create this file :
```bash
cp config/discourse_defaults.conf config/discourse.conf
```
And edit it with your configuration. The main areas of interest are
configuration for the database and for the email server:
```ini
# host address for db server
# This is set to blank so it tries to use sockets first
db_host = localhost
# port running db server, no need to set it
db_port = 5432
# database name running discourse
db_name = discourse
# username accessing database
db_username = discourse
# password used to access the db
db_password = password
```
and for the SMTP server (in this example, we use Gmail):
```ini
# address of smtp server used to send emails
smtp_address = smtp.gmail.com
# port of smtp server used to send emails
smtp_port = 587
# domain passed to smtp server
smtp_domain = gmail.com
# username for smtp server
smtp_user_name = your-address@gmail.com
# password for smtp server
smtp_password = password
# smtp authentication mechanism
smtp_authentication = plain
# enable TLS encryption for smtp connections
smtp_enable_start_tls = true
```
Now, we can prepare discourse for production:
```bash
RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake assets:precompile
```
It is time to start the application. I usually use Puma to deploy
Rails app.
Create the file `config/puma.rb` in discourse directory. Following
content should be enough (for more info, see
[Puma's documentation]):
```ruby
#!/usr/bin/env puma
application_path = '/home/discuss.waarp.org/discourse'
directory application_path
environment 'production'
daemonize false
pidfile "#{application_path}/tmp/pids/puma.pid"
state_path "#{application_path}/tmp/pids/puma.state"
bind "unix://#{application_path}/tmp/sockets/puma.socket"
```
From there, the application can be run with the following command :
```bash
bundle exec puma -C config/puma.rb
```
Finally, setup nginx to forward requests to Discourse. Create the file
`/etc/nginx/conf.d/discourse.conf` with the following content :
```nginx
upstream discourse {
server unix:/path/to/discourse/tmp/sockets/puma.socket;
}
server {
listen 80;
server_name example.com;
location / {
try_files $uri @proxy;
}
location @proxy {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://discourse;
}
}
```
Your very own forum with Discourse is setup!
## Service Management
According to your workflow, you can add systemd units to run discourse.
It needs at least two service definition:
1. Sidekiq, which is used to process asynchronous background tasks
2. Rails, for Discource itself.
With the services setup, services can be started/stopped/enabled with
`systemctl` commands.
But before that, if you use RVM, you must create a wrapper for the
environment (local ruby, and optional gemset) used by Discourse:
```bash
rvm wrapper 2.3.0 systemd bundle
```
This creates an executable in `$rvm_bin_path` that you can call
in lieu of bundle that will automatically load the right envirnoment.
### Sidekiq
First, create a configuration for sidekiq. Create the file
`config/sidekiq.yml` in your discoure project with the following
content (for more info, see [Sidekiq's documentation]):
```yaml
---
:concurrency: 5
:pidfile: tmp/pids/sidekiq.pid
staging:
:concurrency: 10
production:
:concurrency: 20
:queues:
- default
- critical
- low
```
Then, create the service unit for Sidekiq. Create the file
`/etc/systemd/system/discourse-sidekiq.service` with the
following content:
```ini
[Unit]
Description=discourse sidekiq service
After=multi-user.target
[Service]
WorkingDirectory=/path/to/discourse
Environment=RAILS_ENV=production
ExecStart=/path/to/rvm/.rvm/bin/systemd_bundle exec sidekiq -C config/sidekiq.yml
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
### Discourse
For Discourse, just create the service unit for Puma. Create the file
`/etc/systemd/system/discourse.service` with the
following content:
```ini
[Unit]
Description=discourse service
After=discourse-sidekiq.service
Requires=discourse-sidekiq.service
[Service]
WorkingDirectory=/path/to/discourse
Environment=RAILS_ENV=production
ExecStart=/path/to/rvm/.rvm/bin/systemd_bundle exec puma -C config/puma.rb
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
## Upgrades
Upgrades are even easier:
First read the release notes.
Then make backups of the code and the database.
Now you can checkout the newest version:
```bash
cd /path/to/discourse
git checkout vX.X.X
```
Install the new dependencies, run the migrations and rebuild the
assets:
```bash
RAILS_ENV=production bundle install
RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake assets:precompile
```
Restart Discourse:
```bash
systemctl restart discourse
```
What can go wrong? If if I do not give any solution here, it is always
recoverable (hence the backups!).
- The database migration failed (restore the database with your backup,
fix the problem and try again!)
- The plugins are not compatible with the latest version (rollback to
the previous working solution and wit for them to be compatible)
## Plugins
Discourse plugins can be handles the same way.
### Plugin Installation
Install the plugin with the url of its repository:
```bash
cd /path/to discourse
RAILS_ENV=production bundle exec rake plugin:install[URL]
```
Install the new dependencies, run the migrations and rebuild the
assets:
```bash
RAILS_ENV=production bundle install
RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake assets:precompile
```
Restart Discourse:
```bash
systemctl restart discourse
```
### Upgrade
To upgrade a specific plugin, use the following command:
```bash
RAILS_ENV=production bundle exec rake plugin:update[ID]
```
You can also upgrade all plugins at once with the command:
```bash
RAILS_ENV=production bundle exec rake plugin:update_all
```
Then, install the new dependencies, run the migrations and rebuild the
assets:
```bash
RAILS_ENV=production bundle install
RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake assets:precompile
```
and restart Discourse:
```bash
systemctl restart discourse
```
[Discourse]: http://www.discourse.org/
[Sidekiq's documentation]: https://github.com/mperham/sidekiq/wiki/Advanced-Options
[Puma's documentation]: https://github.com/puma/puma

6
content/blog/_index.md Normal file
View file

@ -0,0 +1,6 @@
+++
title = 'Blog'
date = 2025-06-04T23:00:00Z
draft = false
+++

0
data/.gitkeep Normal file
View file

68
hugo.yaml Normal file
View file

@ -0,0 +1,68 @@
baseURL: 'https://bcarlin.net/'
languageCode: 'fr-FR'
title: 'Bruno Carlin'
theme: ['bcarlin']
uglyUrls: true
copyright: '© 2025 Bruno Carlin'
summaryLength: 15
params:
author:
email: mail@bcarlin.net
name: Bruno Carlin
outputs:
home:
- html
section:
- html
- rss
taxonomy:
- html
term:
- html
- rss
outputFormats:
rss:
noUgly: false
markup:
highlight:
noClasses: false
menus:
main:
- name: 'Home'
pageRef: '/'
weight: 10
params:
class: "u-url"
- name: 'Blog'
pageRef: '/blog'
weight: 20
secondary:
- name: '@bcarlin@hachyderm.io'
url: 'https://hachyderm.io/@bcarlin'
weight: 10
params:
rel: "me"
iconName: "mastodon-fill"
iconTitle: "Mastodon"
- name: 'LinkedIn'
url: 'https://www.linkedin.com/in/brunocarlin'
weight: 20
params:
rel: "me"
iconName: "linkedin-fill"
iconTitle: "LinkedIn"
- name: 'GPG Key'
url: '/bcarlin.gpg'
weight: 30
params:
class: "u-key"
iconName: "key-fill"
iconTitle: "GPG Public Key"

0
i18n/.gitkeep Normal file
View file

0
layouts/.gitkeep Normal file
View file

View file

@ -0,0 +1,73 @@
==================================================================
https://keybase.io/bcarlin
--------------------------------------------------------------------
I hereby claim:
* I am an admin of https://bcarlin.net
* I am bcarlin (https://keybase.io/bcarlin) on keybase.
* I have a public key with fingerprint 08C4 4FD4 4533 1DEF 6696 FB5D 2298 63E4 199F C2F8
To do so, I am signing this object:
{
"body": {
"key": {
"eldest_kid": "01018f3e0c8806ef31d1cb43ec77b9eca048310cde351f76ee10a02cdfc97418eb660a",
"fingerprint": "08c44fd445331def6696fb5d229863e4199fc2f8",
"host": "keybase.io",
"key_id": "229863e4199fc2f8",
"kid": "01018f3e0c8806ef31d1cb43ec77b9eca048310cde351f76ee10a02cdfc97418eb660a",
"uid": "5b44e763b8950ddd1fb7d76f03513f19",
"username": "bcarlin"
},
"service": {
"hostname": "bcarlin.net",
"protocol": "https:"
},
"type": "web_service_binding",
"version": 1
},
"ctime": 1748993287,
"expire_in": 157680000,
"prev": "14f3ba62d0615cfe0295d5c6924e5175fe57e3e414def1bdd48cfd793cdc78ad",
"seqno": 2,
"tag": "signature"
}
which yields the signature:
-----BEGIN PGP MESSAGE-----
owGtUmtMHFUY3aVgZStKLZWqJaQDjegSOrPz3vDYxFoRQVpoqwiWzOMOO90nOwNI
t8hqC1sCC0hbG9jV1gbCaxuLUNTSlECFaEwQxIipMQTE1m2DgKaBpJE6Q+wPE396
/9zcc88593zf/ZqjNml02sbdr114cNdv034TYjXmjJqn3RDr4CshoxuygI0NWHkg
ycUWkYeMEIzACCWgAOYoCiaAgCI8wrEYCjiSZGnAMTBGoQjM8QDFEYEkAEBgBjZw
vMDRJIZQgCUImIGSIUG0lwCX0yXaZdWW4jBM4DEMRxVHIBAETQgszhsMNEWgAENo
WuAMAqUIzQ5JVSjhWEYCKaJDwZRD8Ua8/+D/z7nLNuxwFsMASaAsReMwz/OIwJI8
SQiwokYFhFaJEnDZGRtQ2CzHuKyiHapKhhSwXOSA2le1kH8TUuxAVpROl0N2cA6r
cmGWZadkVIVypVNlVgC2+B+PYla080oXFUU5cEmiww4ZEYXJyaJqipAYRdOogSKT
IfC2U3SBYlFl4CRBwcpS3wHliiWCCSjLEAb+MEwgOCcA2EDjPM4RtAEDOELiAsBJ
oDYVU34GYXkeoziBJ2mU4zmSYnhILavU7oCMBiUnU6J4SmKJnZHLXACq0tWFoeEa
rU4T99Su8NjY1v01+3IShvWTCQ8nLyJMHTuNLjL6IfLmXLQmcD7ksVFNfWtfZFw4
7kOuDdZ9dTJq6Yyr1Hs9lri/9Iq1d22s1pK4tSAm1d/wwOiToaNLe+C1QO6ch25M
6q4c6/N1ts51hJ80h/oHZ24NZs6vN4Wqj9cHPxma3bL3crCA60idSa36aMtEgceX
vGx01xt0loCuZcesdqgp/d5zkeyVvvSUg/mPPxt92lzddeLc1e2YfnlxWZt4o/ax
MGmbH7vr3byPyHk0PjI8RiJKGwqvt+Xkgt9au0591n5Tk17kPeEfTDzkBrXl+gKb
3c2k9W3tHm3WbloKnlv+YFVz9HzL5JHxmRf/9JR8mXcg4Gs7/bERaYxr/dn0+Y63
gDmgy2uQWrJfYAa2TUzV7X59z3f777zcOzT1rSdT/PRdc+jYk4fz3su6uoKmcDWm
l+7ELSZAI5HP+2/o/1jN8l5ZS9o+OWrL90DW3vSBgxnVm99of3Uy23FgYZfQKcYU
zcdG1K7csvrTz85UyLnV98um/rqY9sw7AVPloZG5az0NP0YVOpHfFwsL9A1dq4v9
EeV7vx83Rf4Q2jm+Xt3+yC9ed+ECIHqPBFf4S3U9w7cT7g3M9p+dyOxstsyvHXvC
lL1afzs/7/0e4etg/HAwaf1y2s4z08KHXdO+n4riLTfxhV8r4lBppLKIKVw2NWX1
XGRaO7pH8elL+gUetelyotrax3x/Aw==
=JR35
-----END PGP MESSAGE-----
And finally, I am proving ownership of this host by posting or
appending to this document.
View my publicly-auditable identity here: https://keybase.io/bcarlin
==================================================================

148
static/bcarlin.gpg Normal file
View file

@ -0,0 +1,148 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=ebDO
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,5 @@
+++
date = '{{ .Date }}'
draft = true
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
+++

View file

@ -0,0 +1,353 @@
@import url("/static/css/syntax-light.css") only screen and (prefers-color-scheme: light);
@import url('/static/css/syntax-dark.css') only screen and (prefers-color-scheme: dark);
@font-face {
font-family: 'Gentium Plus';
src: url('/static/fonts/GentiumPlus-Regular.woff2');
}
@font-face {
font-family: 'Gentium Plus';
font-style: italic;
src: url('/static/fonts/GentiumPlus-Italic.woff2');
}
@font-face {
font-family: 'Gentium Plus';
font-weight: bold;
src: url('/static/fonts/GentiumPlus-Bold.woff2');
}
@font-face {
font-family: 'Gentium Plus';
font-weight: bold;
font-style: italic;
src: url('/static/fonts/GentiumPlus-BoldItalic.woff2');
}
@font-face {
font-family: 'Nunito Sans';
font-style: italic;
font-weight: 200 1000;
font-stretch: 100%;
src: url('/static/fonts/NunitoSans-Italic.woff2') format('woff2');
}
@font-face {
font-family: 'Nunito Sans';
font-style: normal;
font-weight: 200 1000;
font-stretch: 100%;
src: url('/static/fonts/NunitoSans.woff2') format('woff2');
}
/*
* pico.css variables overrides
*/
:root {
--pico-font-family: 'Gentium Plus', serif, var(--pico-font-family-emoji);
--pico-font-family-sans-serif: 'Nunito Sans', sans-serif, var(--pico-font-family-emoji);
--pico-card-sectioning-background-color: transparent;
--pico-typography-spacing-vertical: calc(1.5 * var(--pico-spacing));
--markup-color: rgb(194 198 207 / 100%);
}
@media only screen and (prefers-color-scheme: light) {
:root:not([data-theme=dark]), [data-theme=light] {
--pico-background-color: rgb(247 249 252 / 100%);
--pico-card-background-color: rgb(255 255 255 / 100%);
--pico-card-sectioning-background-color: transparent;
}
.admonition.warning {
--admonition-background-color: rgb(255 240 219 / 100%);
--admonition-border-color: rgb(255 208 143 / 100%);
}
.admonition.note {
--admonition-background-color: rgb(219 238 255 / 100%);
--admonition-border-color: rgb(142 202 255 / 100%);
}
}
@media only screen and (prefers-color-scheme: dark) {
.admonition.warning {
--admonition-background-color: rgb(52 46 38 / 100%);
--admonition-border-color: rgb(255 208 143 / 100%);
}
.admonition.note {
--admonition-background-color: rgb(48 59 68 / 100%);
--admonition-border-color: rgb(142 202 255 / 100%);
}
}
article {
--pico-card-box-shadow: none;
}
header nav :where(a:not([role="button"])):is([aria-current]:not([aria-current="false"]),:hover,:active,:focus),
header nav [role="link"]:is([aria-current]:not([aria-current="false"]),:hover,:active,:focus) {
--pico-text-decoration: none;
}
header :where(a:not([role="button"])) {
--pico-text-decoration: none;
}
.container {
max-width: 40em;
margin-left: auto;
margin-right: auto;
}
/*
* Layout Styling
*/
.mobile-header {
padding: 0 var(--pico-block-spacing-horizontal);
position: sticky;
top: 0;
left: 0;
background-color: rgb(from var(--pico-background-color) r g b / 80%);
backdrop-filter: blur(3px);
font-family: var(--pico-font-family-sans-serif);
font-weight: bold;
}
.mobile-header img {
height: 2em;
}
#menu-close, #menu-close:hover, #menu-toggle {
color: inherit;
background-color: inherit;
border: none;
text-decoration: none;
}
body {
background-color: var(--pico-background-color);
}
body > header {
width: 100vw;
text-align: center;
height: 100vh;
position: fixed;
left: -100vw;
top: 0;
background-color: var(--pico-background-color);
z-index: 100;
transition: left 0.5s;
overflow: hidden auto;
}
body > header.active {
left: 0;
}
body > header nav, body > header nav ul {
flex-direction: column;
}
body > header .u-logo {
width: 5em;
}
body > header .title {
font-size: 1.5rem;
font-weight: bold;
font-family: var(--pico-font-family-sans-serif);
margin-top: 1em;
}
body > header nav {
font-size: 1rem;
font-family: var(--pico-font-family-sans-serif);
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
body > header nav li {
padding: 0;
padding: calc( var(--pico-nav-element-spacing-vertical) / 4)
calc( var(--pico-nav-element-spacing-horizontal) / 4);
}
body > header a {
color: var(--pico-color);
padding: 0;
}
body > header > a {
float: right;
padding: 0 1.5em;
}
.menu-primary {
font-weight: bold;
}
body > footer p {
font-size: 0.7em;
text-align: center;
}
@media (width >= 1024px) {
.mobile-header, #menu-close {
display: none;
}
body > header {
position: sticky;
top: 0;
left: 0;
width: 15em;
float: left;
}
.container {
margin-right: 0;
margin-left: 18em;
}
}
/*
* Content Styling
*/
main > article {
text-align: justify;
}
main > article > header {
margin-bottom: calc(2 * var(--pico-block-spacing-vertical));
}
.metadata {
display: flex;
gap: 0.5em;
}
ul.tags, ul.tags li {
list-style-type: none;
display: inline;
padding-left: 0;
margin-bottom: 0;
}
.tags li:not(:last-child)::after {
content: ", ";
}
ul > li {
list-style-type: disc;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--pico-font-family-sans-serif);
text-align: left;
}
h1 {
font-size: 1.7rem;
}
h1::before {
content: "# ";
color: var(--markup-color);
}
h2 {
font-size: 1.3rem;
}
h2::before {
content: "## ";
color: var(--markup-color);
}
h3 {
font-size: 1.2rem;
}
h3::before {
content: "### ";
color: var(--markup-color);
}
h4 {
font-size: 1.1rem;
}
h4::before {
content: "#### ";
color: var(--markup-color);
}
h5 {
font-size: 1rem;
}
h5::before {
content: "##### ";
color: var(--markup-color);
}
h6 {
font-size: 1rem;
}
h6::before {
content: "###### ";
color: var(--markup-color);
}
article header h1, article header p {
margin-bottom: calc(0.1 * var(--pico-typography-spacing-vertical));
}
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
color: var(--pico-muted-color);
text-decoration: none;
margin-left: 0.4rem;
font-size: 1rem;
}
pre {
margin-bottom: calc(1.5 * var(--pico-spacing));
}
code, kdb, pre {
font-size: 0.8em;
}
/*
* Admonitions
*/
.admonition {
font-size: 0.9em;
padding: 1em;
border-left: solid 4px var(--admonition-border-color);
background-color: var(--admonition-background-color);
margin-bottom: var(--pico-typography-spacing-vertical);
border-radius: var(--pico-border-radius);
}
/*
* Post lists
*/
.h-feed article:not(:last-child) {
border-bottom: var(--pico-border-width) solid var(--pico-card-border-color);
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
'use strict';
// Listens for clicks on the mobile menu toggle and the main menu close toggle.
window.addEventListener('click', function(e) {
const menu = document.getElementById('menu'),
menuToggle = document.getElementById('menu-toggle'),
body = document.body;
if (e.target.matches('#menu-toggle, #menu-toggle i')) {
menu.classList.toggle('active');
menu.ariaHidden = false;
menuToggle.ariaExpanded = true;
body.style.overflow = 'hidden';
}
if (e.target.matches('#menu-close, #menu-close i')) {
menu.classList.remove('active');
menu.ariaHidden = true;
menuToggle.ariaExpanded = false;
body.style.overflow = 'auto';
}
});

View file

@ -0,0 +1,9 @@
+++
title = 'Home'
date = 2023-01-01T08:00:00-07:00
draft = false
+++
Laborum voluptate pariatur ex culpa magna nostrud est incididunt fugiat
pariatur do dolor ipsum enim. Consequat tempor do dolor eu. Non id id anim anim
excepteur excepteur pariatur nostrud qui irure ullamco.

24
themes/bcarlin/hugo.toml Normal file
View file

@ -0,0 +1,24 @@
baseURL = 'https://example.org/'
languageCode = 'en-US'
title = 'My New Hugo Site'
[menus]
[[menus.main]]
name = 'Home'
pageRef = '/'
weight = 10
[[menus.main]]
name = 'Posts'
pageRef = '/posts'
weight = 20
[[menus.main]]
name = 'Tags'
pageRef = '/tags'
weight = 30
[module]
[module.hugoVersion]
extended = false
min = '0.146.0'

View file

@ -0,0 +1,15 @@
<p>
© {{ now.Year }} Bruno Carlin
<br>
The content of this site is licensed under
<a href="https://creativecommons.org/licenses/by-nc/4.0/" rel="license">
Creative Commons Attribution-NonCommercial 4.0
International
</a>
{{partial "icon.html" (dict "icon" "creative-commons-fill" "label" "Creative Commons Logo")}}
{{partial "icon.html" (dict "icon" "creative-commons-by-fill" "label" "Creative Commons Attribution Logo")}}
{{partial "icon.html" (dict "icon" "creative-commons-nc-fill" "label" "Creative Commons Non Commercial Logo")}}
<br>
Generated with <a href="https://gohugo.io">Hugo</a> using a custom theme.
</p>

View file

@ -0,0 +1,24 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
{{ with .Site.GetPage "/blog" }}
{{- with .OutputFormats.Get "rss" }}
{{- printf `<link rel=%q type=%q href=%q title=%q>` .Rel .MediaType.Type .Permalink site.Title | safeHTML }}
{{- end }}
{{- end }}
{{ if eq .Kind "term" }}
{{- with .OutputFormats.Get "rss" }}
{{- printf `<link rel=%q type=%q href=%q title='%s | %s'>` .Rel .MediaType.Type .Permalink $.Title site.Title | safeHTML }}
{{- end }}
{{- end }}
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="bcarlin.net" />
<link rel="manifest" href="/site.webmanifest" />
<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}</title>
{{ partialCached "head/css.html" . }}
{{ partialCached "head/js.html" . }}

View file

@ -0,0 +1,29 @@
{{- with resources.Get "static/css/pico.min.css" }}
{{- if hugo.IsDevelopment }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- else }}
{{- with . | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- end }}
{{- end }}
{{- with resources.Get "static/css/remixicon.css" }}
{{- if hugo.IsDevelopment }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- else }}
{{- with . | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- end }}
{{- end }}
{{- with resources.Get "static/css/bcarlin.css" }}
{{- if hugo.IsDevelopment }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- else }}
{{- with . | minify | fingerprint }}
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
{{- end }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,16 @@
{{- with resources.Get "static/js/bcarlin.js" }}
{{- $opts := dict
"minify" (not hugo.IsDevelopment)
"sourceMap" (cond hugo.IsDevelopment "external" "")
"targetPath" "static/js/bcarlin.js"
}}
{{- with . | js.Build $opts }}
{{- if hugo.IsDevelopment }}
<script src="{{ .RelPermalink }}"></script>
{{- else }}
{{- with . | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous"></script>
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,20 @@
<a id="menu-close">
{{partial "icon.html" (dict "icon" "close-large-fill" "label" "Close the menu")}}
</a>
<nav id="main-menu" aria-label="Main menu">
<ul>
<li>
<a href="/" class="contrast">
<img class="u-logo" src="/static/logo.svg" alt="Logo"/>
</a>
</li>
<li>
<span class="title p-name"><a href="/" class="contrast">Bruno Carlin</a></span>
</li>
</ul>
{{ partial "menu.html" (dict "menuID" "main" "class" "menu-primary" "page" .) }}
{{ partial "menu.html" (dict "menuID" "secondary" "class" "menu-secondary" "page" .) }}
</nav>

View file

@ -0,0 +1 @@
<i class="ri-{{ .icon }}" role="img" title="{{ .label }}"></i>

View file

@ -0,0 +1,62 @@
{{- /*
Renders a menu for the given menu ID.
@context {page} page The current page.
@context {string} menuID The menu ID.
@example: {{ partial "menu.html" (dict "menuID" "main" "page" .) }}
*/}}
{{- $page := .page }}
{{- $menuID := .menuID }}
{{- $menuClass := .class }}
{{- with index site.Menus $menuID }}
<ul class="{{ $menuClass }}">
{{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }}
</ul>
{{- end }}
{{- define "_partials/inline/menu/walk.html" }}
{{- $page := .page }}
{{- range .menuEntries }}
{{- $attrs := dict "href" .URL "class" "" }}
{{- if $page.IsMenuCurrent .Menu . }}
{{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }}
{{- else if $page.HasMenuCurrent .Menu .}}
{{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }}
{{- end }}
{{- $attrs = merge $attrs (dict "class" (print $attrs.class " contrast")) }}
{{- if (ne .Params.class nil) }}
{{- $attrs = merge $attrs (dict "class" (print $attrs.class " " .Params.class)) }}
{{- end }}
{{- if (ne .Params.rel nil) }}
{{- $attrs = merge $attrs (dict "rel" .Params.rel) }}
{{- end }}
{{- $name := .Name }}
{{- with .Identifier }}
{{- with T . }}
{{- $name = . }}
{{- end }}
{{- end }}
<li>
<a
{{- range $k, $v := $attrs }}
{{- with $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end -}}
>
{{- if (ne .Params.iconName nil) }}
{{partial "icon.html" (dict "icon" .Params.iconName "label" .Params.iconTitle)}}
{{- end }}
{{ $name }}
</a>
{{- with .Children }}
<ul>
{{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }}
</ul>
{{- end }}
</li>
{{- end }}
{{- end }}

View file

@ -0,0 +1,10 @@
{{- with .GetTerms "tags" }}
<div>
{{ partial "icon.html" (dict "icon" "hashtag" "label" "Tags") }}
<ul class="tags">
{{- range . }}
<li><a href="{{ .RelPermalink }}" rel="tag" class="p-category">{{ .LinkTitle }}</a></li>
{{- end }}
</ul>
</div>
{{- end }}

View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="{{ site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
<head>
{{ partial "head.html" . }}
</head>
<body>
<nav class="mobile-header" aria-label="Mobile menu">
<ul>
<li><a href="/"><img src="/static/logo.svg" alt="Logo"/></a></li>
</ul>
<ul>
<li><a href="/" class="contrast">Bruno Carlin</a></li>
</ul>
<ul>
<li>
<a
id="menu-toggle"
role="button"
aria-controls="menu"
aria-expanded="false"
aria-label="Toggle the main menu"
>
{{partial "icon.html" (dict "icon" "menu-fill" "label" "Open the menu")}}
</a>
</li>
</ul>
</nav>
<header id="menu" class="h-card">
{{ partial "header.html" . }}
</header>
<main class="container">
{{ block "main" . }}{{ end }}
</main>
<footer class="container">
{{ partial "footer.html" . }}
</footer>
</body>
</html>

View file

@ -0,0 +1,29 @@
{{ define "main" }}
<article class="h-feed">
<header>
<h1 class="p-name">{{ .Title }}</h1>
</header>
{{ .Content }}
{{ range .Site.RegularPages.GroupByDate "2006" }}
<section>
<h2>{{ .Key }}</h2>
{{- range .Pages }}
{{ $dateMachine := .Date | time.Format "2006-01-02T15:04:05-07:00" }}
{{ $dateHuman := .Date | time.Format "2006-01-02" }}
<article class="h-entry">
<p>
<a class="u-url p-name" href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
(<time class="dt-published" datetime="{{ $dateMachine }}">{{ $dateHuman }}</time>)
</p>
<p>
{{ .Summary }}
</p>
</article>
{{- end }}
</section>
{{- end }}
</article>
{{ end }}

View file

@ -0,0 +1,21 @@
{{ define "main" }}
{{ $dateMachine := .Date | time.Format "2006-01-02T15:04:05-07:00" }}
{{ $dateHuman := .Date | time.Format "2006-01-02" }}
<article class="h-entry">
<header>
<h1 class="p-name">{{ .Title }}</h1>
<div class="metadata">
<p>
Posted on
<time class="dt-published" datetime="{{ $dateMachine }}">{{ $dateHuman }}</time>
</p>
<p> :: </p>
{{ partial "tags.html" . }}
</div>
</header>
<section class="e-content">
{{ .Content }}
</section>
</article>
{{ end }}

View file

@ -0,0 +1,27 @@
{{ define "main" }}
<article>
<header>
<h1>{{ .Title }}</h1>
</header>
{{ .Content }}
<section class="h-feed">
<h2 class="p-name">Recent posts</h2>
{{- $posts := where .Site.RegularPages "Section" "blog" }}
{{- range first 5 $posts }}
{{ $dateMachine := .Date | time.Format "2006-01-02T15:04:05-07:00" }}
{{ $dateHuman := .Date | time.Format "2006-01-02" }}
<article class="h-entry">
<p>
<a class="u-url p-name" href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
(<time class="dt-published" datetime="{{ $dateMachine }}">{{ $dateHuman }}</time>)
</p>
<p>
{{ .Summary }}
</p>
</article>
{{- end }}
</section>
</article>
{{ end }}

View file

@ -0,0 +1,18 @@
{{ define "main" }}
{{ $dateMachine := .Date | time.Format "2006-01-02T15:04:05-07:00" }}
{{ $dateHuman := .Date | time.Format "2006-01-02" }}
<article class="h-entry">
<header>
<h1 class="p-name">{{ .Title }}</h1>
<p>
Posted on
<time class="dt-published" datetime="{{ $dateMachine }}">{{ $dateHuman }}</time>
</p>
</header>
<section class="e-content">
{{ .Content }}
</section>
{{ partial "terms.html" (dict "taxonomy" "tags" "page" .) }}
</article>
{{ end }}

View file

@ -0,0 +1,9 @@
{{ define "main" }}
<article>
<header>
<h1>{{ .Title }}</h1>
</header>
{{ .Content }}
<article>
{{ end }}

View file

@ -0,0 +1,59 @@
{{- $authorEmail := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .email }}
{{- $authorEmail = . }}
{{- end }}
{{- end }}
{{- end }}
{{- $authorName := "" }}
{{- with site.Params.author }}
{{- if reflect.IsMap . }}
{{- with .name }}
{{- $authorName = . }}
{{- end }}
{{- else }}
{{- $authorName = . }}
{{- end }}
{{- end }}
{{- $pctx := . }}
{{- if .IsHome }}{{ $pctx = .Site }}{{ end }}
{{- $pages := slice }}
{{- if or $.IsHome $.IsSection }}
{{- $pages = $pctx.RegularPages }}
{{- else }}
{{- $pages = $pctx.Pages }}
{{- end }}
{{- $limit := .Site.Config.Services.RSS.Limit }}
{{- if ge $limit 1 }}
{{- $pages = $pages | first $limit }}
{{- end }}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{ . }} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo</generator>
<language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
<copyright>{{ . }}</copyright>{{ end }}{{ if not .Date.IsZero }}
<lastBuildDate>{{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{- with .OutputFormats.Get "RSS" }}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end }}
{{- range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
<guid>{{ .Permalink }}</guid>
<description>{{ .Content | transform.XMLEscape | safeHTML }}</description>
</item>
{{- end }}
</channel>
</rss>

View file

@ -0,0 +1,4 @@
<aside class="admonition note">
<p class="title">{{partial "icon.html" (dict "icon" "information-fill" "label" "information")}} Note</p>
{{ .Inner | .Page.RenderString }}
</aside>

View file

@ -0,0 +1,4 @@
<aside class="admonition warning">
<p class="title">{{partial "icon.html" (dict "icon" "error-warning-fill" "label" "warning")}} Warning</p>
{{ .Inner | .Page.RenderString }}
</aside>

View file

@ -0,0 +1,16 @@
{{ define "main" }}
<article>
<header>
<h1>{{ .Title }}</h1>
</header>
<ul>
{{ range .Data.Terms.Alphabetical }}
<li>
<a href="{{ .Page.RelPermalink }}">{{ .Page.LinkTitle }}</a>
({{ .Count }})
</li>
{{ end }}
</ul>
</article>
{{ end }}

View file

@ -0,0 +1,18 @@
{{ define "main" }}
<article class="h-feed">
<header>
<h1 class="p-name">{{ .Title }}</h1>
</header>
{{ .Content }}
{{ range .Pages.ByDate }}
{{ $dateMachine := .Date | time.Format "2006-01-02T15:04:05-07:00" }}
{{ $dateHuman := .Date | time.Format "2006-01-02" }}
<article class="h-entry">
<a class="u-url p-name" href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
(<time class="dt-published" datetime="{{ $dateMachine }}">{{ $dateHuman }}</time>)
</article>
{{ end }}
</article>
{{ end }}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" height="80"><svg viewBox="0 0 80 80" version="1.1" id="SvgjsSvg1017" sodipodi:docname="logo.svg" inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="SvgjsDefs1016"></defs>
<sodipodi:namedview id="SvgjsSodipodi:namedview1015" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" inkscape:zoom="1.9270133" inkscape:cx="65.645629" inkscape:cy="72.39182" inkscape:window-width="1916" inkscape:window-height="1026" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="0" inkscape:current-layer="layer1"></sodipodi:namedview>
<style id="SvgjsStyle1014">
:root { filter: none; }
path {
fill:none;
stroke:#000000;
stroke-width:3.5;
stroke-linecap:round;
stroke-linejoin:miter;
stroke-dasharray:none;
stroke-opacity:1
}
@media (prefers-color-scheme: dark) {
path { stroke: #f0f0f0 }
}
</style>
<g id="SvgjsG1013">
<path d="m 33.93812,57.35608 c -2.09127,-8.3238 -6.421596,-19.16538 -4.113261,-26.0517 1.134461,-3.38437 3.845371,-5.56463 7.977231,-5.73386 3.19716,-0.13095 8.72664,0.36824 9.59761,6.23246 0.76712,5.16491 -4.15506,6.39829 -7.22937,8.1022 6.50598,-1.93981 12.01867,1.47517 13.33694,7.105 1.31827,5.62984 -0.76053,10.88377 -3.67194,14.55887 -2.9114,3.67511 -7.14804,6.27136 -12.7811,6.50685 C 21.415486,68.72968 11.927485,54.67483 11.751441,39.19832 11.575406,23.72181 23.343582,11.494025 40.08723,11.290233 56.83088,11.086441 69.20544,25.91722 68.19167,42.02395 c -0.81502,12.94911 -5.57382,19.26544 -9.73003,22.70007 -1.66423,1.37528 -3.28127,3.98783 -3.28127,3.98783" id="SvgjsPath1012"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,21 @@
{
"name": "bcarlin.net",
"short_name": "bcarlin.net",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View file

@ -0,0 +1,88 @@
/* Generated using: hugo gen chromastyles --style solarized-dark */
/* Background */ .bg { color:#93a1a1;background-color:#002b36; }
/* PreWrapper */ .chroma { color:#93a1a1;background-color:#002b36; }
/* Other */ .chroma .x { color:#cb4b16 }
/* Error */ .chroma .err { }
/* CodeLine */ .chroma .cl { }
/* LineLink */ .chroma .lnlinks { outline:none;text-decoration:none;color:inherit }
/* LineTableTD */ .chroma .lntd { vertical-align:top;padding:0;margin:0;border:0; }
/* LineTable */ .chroma .lntable { border-spacing:0;padding:0;margin:0;border:0; }
/* LineHighlight */ .chroma .hl { background-color:#19404a }
/* LineNumbersTable */ .chroma .lnt { white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#495050 }
/* LineNumbers */ .chroma .ln { white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#495050 }
/* Line */ .chroma .line { display:flex; }
/* Keyword */ .chroma .k { color:#719e07 }
/* KeywordConstant */ .chroma .kc { color:#cb4b16 }
/* KeywordDeclaration */ .chroma .kd { color:#268bd2 }
/* KeywordNamespace */ .chroma .kn { color:#719e07 }
/* KeywordPseudo */ .chroma .kp { color:#719e07 }
/* KeywordReserved */ .chroma .kr { color:#268bd2 }
/* KeywordType */ .chroma .kt { color:#dc322f }
/* Name */ .chroma .n { }
/* NameAttribute */ .chroma .na { }
/* NameClass */ .chroma .nc { color:#268bd2 }
/* NameConstant */ .chroma .no { color:#cb4b16 }
/* NameDecorator */ .chroma .nd { color:#268bd2 }
/* NameEntity */ .chroma .ni { color:#cb4b16 }
/* NameException */ .chroma .ne { color:#cb4b16 }
/* NameLabel */ .chroma .nl { }
/* NameNamespace */ .chroma .nn { }
/* NameOther */ .chroma .nx { }
/* NameProperty */ .chroma .py { }
/* NameTag */ .chroma .nt { color:#268bd2 }
/* NameBuiltin */ .chroma .nb { color:#b58900 }
/* NameBuiltinPseudo */ .chroma .bp { color:#268bd2 }
/* NameVariable */ .chroma .nv { color:#268bd2 }
/* NameVariableClass */ .chroma .vc { color:#268bd2 }
/* NameVariableGlobal */ .chroma .vg { color:#268bd2 }
/* NameVariableInstance */ .chroma .vi { color:#268bd2 }
/* NameVariableMagic */ .chroma .vm { color:#268bd2 }
/* NameFunction */ .chroma .nf { color:#268bd2 }
/* NameFunctionMagic */ .chroma .fm { color:#268bd2 }
/* Literal */ .chroma .l { }
/* LiteralDate */ .chroma .ld { }
/* LiteralString */ .chroma .s { color:#2aa198 }
/* LiteralStringAffix */ .chroma .sa { color:#2aa198 }
/* LiteralStringBacktick */ .chroma .sb { color:#586e75 }
/* LiteralStringChar */ .chroma .sc { color:#2aa198 }
/* LiteralStringDelimiter */ .chroma .dl { color:#2aa198 }
/* LiteralStringDoc */ .chroma .sd { }
/* LiteralStringDouble */ .chroma .s2 { color:#2aa198 }
/* LiteralStringEscape */ .chroma .se { color:#cb4b16 }
/* LiteralStringHeredoc */ .chroma .sh { }
/* LiteralStringInterpol */ .chroma .si { color:#2aa198 }
/* LiteralStringOther */ .chroma .sx { color:#2aa198 }
/* LiteralStringRegex */ .chroma .sr { color:#dc322f }
/* LiteralStringSingle */ .chroma .s1 { color:#2aa198 }
/* LiteralStringSymbol */ .chroma .ss { color:#2aa198 }
/* LiteralNumber */ .chroma .m { color:#2aa198 }
/* LiteralNumberBin */ .chroma .mb { color:#2aa198 }
/* LiteralNumberFloat */ .chroma .mf { color:#2aa198 }
/* LiteralNumberHex */ .chroma .mh { color:#2aa198 }
/* LiteralNumberInteger */ .chroma .mi { color:#2aa198 }
/* LiteralNumberIntegerLong */ .chroma .il { color:#2aa198 }
/* LiteralNumberOct */ .chroma .mo { color:#2aa198 }
/* Operator */ .chroma .o { color:#719e07 }
/* OperatorWord */ .chroma .ow { color:#719e07 }
/* Punctuation */ .chroma .p { }
/* Comment */ .chroma .c { color:#586e75 }
/* CommentHashbang */ .chroma .ch { color:#586e75 }
/* CommentMultiline */ .chroma .cm { color:#586e75 }
/* CommentSingle */ .chroma .c1 { color:#586e75 }
/* CommentSpecial */ .chroma .cs { color:#719e07 }
/* CommentPreproc */ .chroma .cp { color:#719e07 }
/* CommentPreprocFile */ .chroma .cpf { color:#719e07 }
/* Generic */ .chroma .g { }
/* GenericDeleted */ .chroma .gd { color:#dc322f }
/* GenericEmph */ .chroma .ge { font-style:italic }
/* GenericError */ .chroma .gr { color:#dc322f;font-weight:bold }
/* GenericHeading */ .chroma .gh { color:#cb4b16 }
/* GenericInserted */ .chroma .gi { color:#719e07 }
/* GenericOutput */ .chroma .go { }
/* GenericPrompt */ .chroma .gp { }
/* GenericStrong */ .chroma .gs { font-weight:bold }
/* GenericSubheading */ .chroma .gu { color:#268bd2 }
/* GenericTraceback */ .chroma .gt { }
/* GenericUnderline */ .chroma .gl { }
/* TextWhitespace */ .chroma .w { }

View file

@ -0,0 +1,88 @@
/* Generated using: hugo gen chromastyles --style solarized-light */
/* Background */ .bg { color:#586e75;background-color:#eee8d5; }
/* PreWrapper */ .chroma { color:#586e75;background-color:#eee8d5; }
/* Other */ .chroma .x { }
/* Error */ .chroma .err { }
/* CodeLine */ .chroma .cl { }
/* LineLink */ .chroma .lnlinks { outline:none;text-decoration:none;color:inherit }
/* LineTableTD */ .chroma .lntd { vertical-align:top;padding:0;margin:0;border:0; }
/* LineTable */ .chroma .lntable { border-spacing:0;padding:0;margin:0;border:0; }
/* LineHighlight */ .chroma .hl { background-color:#d6d0bf }
/* LineNumbersTable */ .chroma .lnt { white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f }
/* LineNumbers */ .chroma .ln { white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f }
/* Line */ .chroma .line { display:flex; }
/* Keyword */ .chroma .k { color:#859900 }
/* KeywordConstant */ .chroma .kc { color:#859900;font-weight:bold }
/* KeywordDeclaration */ .chroma .kd { color:#859900 }
/* KeywordNamespace */ .chroma .kn { color:#dc322f;font-weight:bold }
/* KeywordPseudo */ .chroma .kp { color:#859900 }
/* KeywordReserved */ .chroma .kr { color:#859900 }
/* KeywordType */ .chroma .kt { color:#859900;font-weight:bold }
/* Name */ .chroma .n { color:#268bd2 }
/* NameAttribute */ .chroma .na { color:#268bd2 }
/* NameClass */ .chroma .nc { color:#cb4b16 }
/* NameConstant */ .chroma .no { color:#268bd2 }
/* NameDecorator */ .chroma .nd { color:#268bd2 }
/* NameEntity */ .chroma .ni { color:#268bd2 }
/* NameException */ .chroma .ne { color:#268bd2 }
/* NameLabel */ .chroma .nl { color:#268bd2 }
/* NameNamespace */ .chroma .nn { color:#268bd2 }
/* NameOther */ .chroma .nx { color:#268bd2 }
/* NameProperty */ .chroma .py { color:#268bd2 }
/* NameTag */ .chroma .nt { color:#268bd2;font-weight:bold }
/* NameBuiltin */ .chroma .nb { color:#cb4b16 }
/* NameBuiltinPseudo */ .chroma .bp { color:#cb4b16 }
/* NameVariable */ .chroma .nv { color:#268bd2 }
/* NameVariableClass */ .chroma .vc { color:#268bd2 }
/* NameVariableGlobal */ .chroma .vg { color:#268bd2 }
/* NameVariableInstance */ .chroma .vi { color:#268bd2 }
/* NameVariableMagic */ .chroma .vm { color:#268bd2 }
/* NameFunction */ .chroma .nf { color:#268bd2 }
/* NameFunctionMagic */ .chroma .fm { color:#268bd2 }
/* Literal */ .chroma .l { color:#2aa198 }
/* LiteralDate */ .chroma .ld { color:#2aa198 }
/* LiteralString */ .chroma .s { color:#2aa198 }
/* LiteralStringAffix */ .chroma .sa { color:#2aa198 }
/* LiteralStringBacktick */ .chroma .sb { color:#2aa198 }
/* LiteralStringChar */ .chroma .sc { color:#2aa198 }
/* LiteralStringDelimiter */ .chroma .dl { color:#2aa198 }
/* LiteralStringDoc */ .chroma .sd { color:#2aa198 }
/* LiteralStringDouble */ .chroma .s2 { color:#2aa198 }
/* LiteralStringEscape */ .chroma .se { color:#2aa198 }
/* LiteralStringHeredoc */ .chroma .sh { color:#2aa198 }
/* LiteralStringInterpol */ .chroma .si { color:#2aa198 }
/* LiteralStringOther */ .chroma .sx { color:#2aa198 }
/* LiteralStringRegex */ .chroma .sr { color:#2aa198 }
/* LiteralStringSingle */ .chroma .s1 { color:#2aa198 }
/* LiteralStringSymbol */ .chroma .ss { color:#2aa198 }
/* LiteralNumber */ .chroma .m { color:#2aa198;font-weight:bold }
/* LiteralNumberBin */ .chroma .mb { color:#2aa198;font-weight:bold }
/* LiteralNumberFloat */ .chroma .mf { color:#2aa198;font-weight:bold }
/* LiteralNumberHex */ .chroma .mh { color:#2aa198;font-weight:bold }
/* LiteralNumberInteger */ .chroma .mi { color:#2aa198;font-weight:bold }
/* LiteralNumberIntegerLong */ .chroma .il { color:#2aa198;font-weight:bold }
/* LiteralNumberOct */ .chroma .mo { color:#2aa198;font-weight:bold }
/* Operator */ .chroma .o { }
/* OperatorWord */ .chroma .ow { color:#859900 }
/* Punctuation */ .chroma .p { }
/* Comment */ .chroma .c { color:#93a1a1;font-style:italic }
/* CommentHashbang */ .chroma .ch { color:#93a1a1;font-style:italic }
/* CommentMultiline */ .chroma .cm { color:#93a1a1;font-style:italic }
/* CommentSingle */ .chroma .c1 { color:#93a1a1;font-style:italic }
/* CommentSpecial */ .chroma .cs { color:#93a1a1;font-style:italic }
/* CommentPreproc */ .chroma .cp { color:#93a1a1;font-style:italic }
/* CommentPreprocFile */ .chroma .cpf { color:#93a1a1;font-style:italic }
/* Generic */ .chroma .g { color:#d33682 }
/* GenericDeleted */ .chroma .gd { color:#d33682 }
/* GenericEmph */ .chroma .ge { color:#d33682 }
/* GenericError */ .chroma .gr { color:#d33682 }
/* GenericHeading */ .chroma .gh { color:#d33682 }
/* GenericInserted */ .chroma .gi { color:#d33682 }
/* GenericOutput */ .chroma .go { color:#d33682 }
/* GenericPrompt */ .chroma .gp { color:#d33682 }
/* GenericStrong */ .chroma .gs { color:#d33682 }
/* GenericSubheading */ .chroma .gu { color:#d33682 }
/* GenericTraceback */ .chroma .gt { color:#d33682 }
/* GenericUnderline */ .chroma .gl { color:#d33682 }
/* TextWhitespace */ .chroma .w { }

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
viewBox="0 0 80 80"
version="1.1"
id="svg1"
sodipodi:docname="logo.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.9270133"
inkscape:cx="65.645629"
inkscape:cy="72.39182"
inkscape:window-width="1916"
inkscape:window-height="1026"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<style
id="style1">
path {
fill:none;
stroke:#000000;
stroke-width:3.5;
stroke-linecap:round;
stroke-linejoin:miter;
stroke-dasharray:none;
stroke-opacity:1
}
@media (prefers-color-scheme: dark) {
path { stroke: #f0f0f0 }
}
</style>
<g
id="layer1">
<path
d="m 33.93812,57.35608 c -2.09127,-8.3238 -6.421596,-19.16538 -4.113261,-26.0517 1.134461,-3.38437 3.845371,-5.56463 7.977231,-5.73386 3.19716,-0.13095 8.72664,0.36824 9.59761,6.23246 0.76712,5.16491 -4.15506,6.39829 -7.22937,8.1022 6.50598,-1.93981 12.01867,1.47517 13.33694,7.105 1.31827,5.62984 -0.76053,10.88377 -3.67194,14.55887 -2.9114,3.67511 -7.14804,6.27136 -12.7811,6.50685 C 21.415486,68.72968 11.927485,54.67483 11.751441,39.19832 11.575406,23.72181 23.343582,11.494025 40.08723,11.290233 56.83088,11.086441 69.20544,25.91722 68.19167,42.02395 c -0.81502,12.94911 -5.57382,19.26544 -9.73003,22.70007 -1.66423,1.37528 -3.28127,3.98783 -3.28127,3.98783"
id="path1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

9
wrangler.toml Normal file
View file

@ -0,0 +1,9 @@
name = 'homepage'
compatibility_date = "2025-06-26"
#[build]
#command = "./build.sh"
[assets]
directory = "./public"
#not_found_handling = "404-page"