Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/jekyll-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Build Jekyll Site to Tarball

on:
push:
branches:
- master
pull_request:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main

- name: Setup Nix cache
uses: DeterminateSystems/magic-nix-cache-action@main

- name: Build site and create tarball
run: nix run .#tarball

- name: Upload tarball as artifact
uses: actions/upload-artifact@v4
with:
name: bitcoinj-site-${{ github.sha }}
path: bitcoinj-site.tar.gz
retention-days: 30

- name: Create release on tag
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: bitcoinj-site.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@ This is the source-code repository for the **bitcoinj** website, published at [b

This site is written in [GitHub-flavored Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/about-writing-and-formatting-on-github), built using the [Jekyll static site generator](https://jekyllrb.com) and published via [GitHub Pages](https://pages.github.com).

## Building locally in a Nix Shell

The Nix development shell will install Jekyll for building and serving the site and Java 25 (if you want a simple Java-based tool to serve/test the static site.)

1. Run `nix develop`
2. Run `jekyll build`

This will build the site into the `_site` directory.

## Serving the site locally

To serve the site with Jekyll and dynamic reloading:

* `jekyll serve --livereload --incremental`

To run a simple, local Java-based webserver to view the site use:

* `./scripts/JekyllServer.java`

## Building locally without Nix

If you install Jekyll 3.10.0 and Ruby 3.3, you should be able to use the same commands as shown above, but this is untested/unsupported. You should also be able to serve the generated static site with the Java 25 script.

## Building as a Nix Package

* `nix build .#`

This should build the package in `result`.

## Contributing

To report a documentation issue or make a suggestion for improvement use [GitHub Issues](). You can also submit a [pull-request](https://github.com/bitcoinj/bitcoinj.github.io/pulls) for the website.
Expand Down
61 changes: 61 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 94 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
description = "Development environment for bitcoinj.github.io Jekyll site";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/release-25.11";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};

# Ruby environment with Jekyll and bundler
rubyEnv = pkgs.ruby_3_3.withPackages (ps: with ps; [
jekyll
kramdown-parser-gfm
]);

in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
rubyEnv
jdk25_headless
];

shellHook = ''
echo "=== bitcoinj.github.io development environment ==="
echo ""
echo "Available commands:"
echo " jekyll build - Build the site to _site/"
echo " jekyll serve - Serve the site locally at http://localhost:4000"
echo " jekyll serve --livereload - Serve with auto-reload on changes"
echo " ./scripts/JekyllServer.java - Serve the static site with a Java script"
echo ""
'';
};

# Package that builds the site
packages.default = pkgs.stdenv.mkDerivation {
pname = "bitcoinj-site";
version = "0.0.1";

src = ./.;

buildInputs = [ rubyEnv ];

buildPhase = ''
export HOME=$TMPDIR
jekyll build
'';

installPhase = ''
mkdir -p $out
cp -r _site/* $out/
'';
};

# App for easy site building
apps.build = {
type = "app";
program = "${pkgs.writeShellScript "build-site" ''
set -e
export HOME=$TMPDIR
${rubyEnv}/bin/jekyll build
echo "Site built to _site/"
''}";
};

apps.serve = {
type = "app";
program = "${pkgs.writeShellScript "serve-site" ''
set -e
export HOME=$TMPDIR
${rubyEnv}/bin/jekyll serve --livereload
''}";
};

apps.tarball = {
type = "app";
program = "${pkgs.writeShellScript "create-tarball" ''
set -e
export HOME=$TMPDIR
echo "Building site..."
${rubyEnv}/bin/jekyll build
echo "Creating tarball..."
${pkgs.gnutar}/bin/tar -czf bitcoinj-site.tar.gz -C _site .
echo "Tarball created: bitcoinj-site.tar.gz"
''}";
};
}
);
}
77 changes: 77 additions & 0 deletions scripts/JekyllServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
///usr/bin/env java "$0" "$@" ; exit $?
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpHandlers;
import com.sun.net.httpserver.SimpleFileServer;
import java.net.InetSocketAddress;
import java.nio.file.Path;

static final int port = 4000;

public static void main(String[] args) {
Path root = Path.of("./_site").toAbsolutePath();

// Create the default file handler
var fileHandler = SimpleFileServer.createFileHandler(root);

// This handler checks if we need to append .html internally
HttpHandler rewritingHandler = exchange -> {
String path = exchange.getRequestURI().getPath();

// If path is not root, doesn't have a dot, and doesn't end in a slash
if (!path.equals("/") && !path.contains(".") && !path.endsWith("/")) {
String newPath = path + ".html";

// We wrap the exchange to override the Request URI
// The file handler will now look for "name.html" on disk
fileHandler.handle(new WrappedExchange(exchange, newPath));
} else {
fileHandler.handle(exchange);
}
};

// Start the server
var server = SimpleFileServer.createFileServer(
new InetSocketAddress(port),
root,
SimpleFileServer.OutputLevel.INFO
);

// Replace the default context with our custom handler
server.removeContext("/");
server.createContext("/", rewritingHandler);

System.out.printf("Serving Jekyll site at http://localhost:%s\n", port);
server.start();
IO.println("Use Control-C to cancel");
}

// Helper class to "trick" the file handler into looking for a different path
static class WrappedExchange extends HttpExchange {
private final HttpExchange delegate;
private final URI interceptedUri;

WrappedExchange(HttpExchange delegate, String newPath) {
this.delegate = delegate;
this.interceptedUri = delegate.getRequestURI().resolve(newPath);
}

@Override public URI getRequestURI() { return interceptedUri; }
// Delegate all other methods
@Override public com.sun.net.httpserver.Headers getRequestHeaders() { return delegate.getRequestHeaders(); }
@Override public com.sun.net.httpserver.Headers getResponseHeaders() { return delegate.getResponseHeaders(); }
@Override public String getRequestMethod() { return delegate.getRequestMethod(); }
@Override public void sendResponseHeaders(int rCode, long responseLength) throws java.io.IOException { delegate.sendResponseHeaders(rCode, responseLength); }
@Override public java.io.InputStream getRequestBody() { return delegate.getRequestBody(); }
@Override public java.io.OutputStream getResponseBody() { return delegate.getResponseBody(); }
@Override public void close() { delegate.close(); }
@Override public InetSocketAddress getRemoteAddress() { return delegate.getRemoteAddress(); }
@Override public int getResponseCode() { return delegate.getResponseCode(); }
@Override public InetSocketAddress getLocalAddress() { return delegate.getLocalAddress(); }
@Override public String getProtocol() { return delegate.getProtocol(); }
@Override public Object getAttribute(String name) { return delegate.getAttribute(name); }
@Override public void setAttribute(String name, Object value) { delegate.setAttribute(name, value); }
@Override public void setStreams(java.io.InputStream i, java.io.OutputStream o) { delegate.setStreams(i, o); }
@Override public com.sun.net.httpserver.HttpContext getHttpContext() { return delegate.getHttpContext(); }
@Override public com.sun.net.httpserver.HttpPrincipal getPrincipal() { return delegate.getPrincipal(); }
}
Loading