Compare commits
9 Commits
3feca8b295
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
065cfbb230 | ||
|
|
b11b3ee754 | ||
|
|
007b9f86fc | ||
|
|
18bc5d61d6 | ||
|
|
fc183f32ee | ||
|
|
d350d403c5 | ||
|
|
6800ccf24a | ||
|
|
e66c4f35b9 | ||
|
|
61485856cb |
@@ -44,10 +44,11 @@ So you specify some machine specific things in my tool:
|
|||||||
- networking related stuff
|
- networking related stuff
|
||||||
- ? TODO
|
- ? TODO
|
||||||
|
|
||||||
and then the corresponding configuration for the installer is automatically created.
|
and then the final configuration for the installer is automatically created.
|
||||||
|
|
||||||
# Why not MAAS?
|
# Why not MAAS?
|
||||||
Well...
|
The simple answer is simplicity and that I wanted to understand how these things work.
|
||||||
|
Basically even what I am outlining here is already overkill, as I am maybe deploying a new server once every 1 or 2 years.
|
||||||
|
|
||||||
# Future Work
|
# Future Work
|
||||||
## Deploy to "production"
|
## Deploy to "production"
|
||||||
|
|||||||
@@ -1,34 +1,52 @@
|
|||||||
+++
|
+++
|
||||||
title = "Homelab - IPv4 Expose"
|
title = "Homelab - IPv4 Expose"
|
||||||
date = 2026-04-04
|
date = 2026-03-20
|
||||||
description = "How I expose my IPv6 only Homelab for IPv4 clients"
|
description = "How I expose my IPv6 only Homelab for IPv4 clients"
|
||||||
draft = true
|
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
categories = ["Homelab"]
|
categories = ["Homelab"]
|
||||||
tags = ["Homelab", "IPv4", "IPv6"]
|
tags = ["Homelab", "IPv4", "IPv6"]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
toc = true
|
||||||
+++
|
+++
|
||||||
|
|
||||||
## Motivation
|
How I plan to expose the services running in my homelab to IPv4 users despite not having a publicly routable IPv4 address/subnet.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
|
||||||
|
# Motivation
|
||||||
The problem I have is one that a lot of people in the self-hosting community will be familiar with.
|
The problem I have is one that a lot of people in the self-hosting community will be familiar with.
|
||||||
My ISP does not give me a public IPv4 address, which I could use to expose my self-hosted services to the broader internet.
|
My ISP does not give me a public IPv4 address, to expose my self-hosted services to the broader internet.
|
||||||
|
|
||||||
Luckily my ISP provides me with a publicly routable `/48` IPv6 address space.
|
Luckily my ISP provides me with a publicly routable `/48` IPv6 subnet.
|
||||||
So I natively expose my services to the internet using IPv6 and use the approach outlined below to expose my services for IPv4 users as well.
|
So I natively expose my services to the internet using IPv6 and then only have the problem of allowing native IPv4 only clients to use my IPv6 services as well.
|
||||||
|
|
||||||
## Previous Setup
|
# Old Setup
|
||||||
Currently I rent a VPS from DigitalOcean, which then naively forwards any TCP connections to my IPv6 services.
|
Currently I rent a VPS from DigitalOcean, which then naively forwards any TCP connections to my IPv6 services.
|
||||||
For this it periodically loads all services I have registered in Consul and looks for specific tags, which I use to mark services that should be exposed.
|
|
||||||
|
For this I have created [ipv-proxy](https://github.com/Lol3rrr/ipv-proxy), which loads registered services from Consul and looks for specific tags in their configuration.
|
||||||
|
These allow me to easily configure a service to be exposed and how that should be done, like what external port etc.
|
||||||
For each such service, it then starts a TCP-listener on it's public IPv4 address and for all incoming requests connects to the IPv6 service being exposed, forwarding all data in both directions.
|
For each such service, it then starts a TCP-listener on it's public IPv4 address and for all incoming requests connects to the IPv6 service being exposed, forwarding all data in both directions.
|
||||||
|
|
||||||
## Idea 1 - The Plan
|
However because this is centered around having a connection, connectionless things like UDP do not work with this setup.
|
||||||
Services that need to be exposed get their own virtual IPv6 address using keepalived. The active/master node will be forced to the node on which the service is currently running using priorities.
|
And obviously it is not ideal to essentially split the one "logical" tcp connection from the client to my service, into two underlying tcp connections from the client to the vps and then the vps to the service.
|
||||||
|
|
||||||
On my external server setup Jool with SIIT-DC and iptable rules.
|
# Idea 1 - SIIT-EAM
|
||||||
1. Everything coming in at the given port for the service (for example HTTP, Teamspeak, etc.) is redirected to a different internal IPv4 address using iptables
|
## The Idea
|
||||||
2. Jool listens on the internal IPv4 address and performs SIIT-DC or SIIT-EAM forwarding to the correct virtual IPv6 address
|
SIIT-EAM (Stateless IP/ICMP Translation) allows one to have a "translator" in the network, which receives the incoming IPv4 packets and translates them into their corresponding IPv6 destinations.
|
||||||
|
The translator needs to have one more public IPv4 and an IPv6 subnet with at least "free" 32 bits.
|
||||||
|
One can then configure how the public IPv4 addresses should map to IPv6 addresses and the translator will act as a middle-man between IPv4 and IPv6.
|
||||||
|
|
||||||
## Idea 1 - New Setup - Part 1 SIIT-EAM
|
For more details I would recommend going through the jool[^jool] documentation.
|
||||||
|
|
||||||
|
## The Plan
|
||||||
|
I can rent a relatively cheap VPS on some cloud provider, with at least 1 public IPv4 and then it should ideally have a routable `/64` IPv6 subnet as well.
|
||||||
|
Then I configure SIIT-EAM in jool to forward the IPv4 traffic to one of my servers IPv6 addresses.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
{% details(summary="Detailed instructions on how to setup the VPS") %}
|
||||||
1. Get a server that supports Dual-Stack networking and in the best case a /64 ipv6 subnet (I choose Scaleway as a European cloud provider, with cheap servers)
|
1. Get a server that supports Dual-Stack networking and in the best case a /64 ipv6 subnet (I choose Scaleway as a European cloud provider, with cheap servers)
|
||||||
[Scaleway IPv6 Docs](https://www.scaleway.com/en/docs/instances/how-to/use-flexips/#flexible-ipv6)
|
[Scaleway IPv6 Docs](https://www.scaleway.com/en/docs/instances/how-to/use-flexips/#flexible-ipv6)
|
||||||
[Scaleway Check neighbor discovery](https://www.scaleway.com/en/docs/dedibox-ipv6/how-to/debug-ipv6/#check-the-neighbor-discovery-protocol-ndp)
|
[Scaleway Check neighbor discovery](https://www.scaleway.com/en/docs/dedibox-ipv6/how-to/debug-ipv6/#check-the-neighbor-discovery-protocol-ndp)
|
||||||
@@ -56,43 +74,76 @@ On my external server setup Jool with SIIT-DC and iptable rules.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
{% end %}
|
||||||
|
|
||||||
### Idea 1 - Troubles
|
## Troubles encountered
|
||||||
Jool missing pool6, because I first wanted to try with only the EAM entry
|
This is a small list of issues that I have encountered, while setting all of this up and you might also run into if you are doing this yourself.
|
||||||
|
|
||||||
Lots of debugging with tcpdump on external and local server
|
Jool always requires a pool6 to be configured for SIIT, even if you are only configuring/using EAM entries.
|
||||||
|
|
||||||
Neighbor Solicitation not working
|
There will be a lot of debugging of the network traffic going on.
|
||||||
`13:31:35.919212 IP6 _gateway > ff02::1:ff52:2f24: ICMP6, neighbor solicitation, who has 2001:bc8:1640:6554::4a52:2f24, length 32`
|
I recommend getting comfortable with tools like tcpdump or wireshark on both the "target" machine, the client and the VPS, to hopefully get a full picture of how packets are flowing.
|
||||||
fixed using ndppd.
|
|
||||||
|
|
||||||
Fixed forwarding for everything received on the v4 ip, no way to forward based on service
|
Depending on your provider and specific configuration, Neighbor Solicitation might not work out of the box for you.
|
||||||
|
I first saw logs like `13:31:35.919212 IP6 _gateway > ff02::1:ff52:2f24: ICMP6, neighbor solicitation, who has 2001:bc8:1640:6554::4a52:2f24, length 32` in my tcpdumps on the VPS, which the server should have replied to, as it has the specific subnet assigned.
|
||||||
|
To fix this I added the last step in the setup, to configure ndppd, as that fixed my issue.
|
||||||
|
|
||||||
## Idea 2 - NAT64 with static BIB
|
The biggest drawback of this approach is that everything ariving on the IPv4 address gets forwarded to the specific IPv6 address, with no fine grained control.
|
||||||
The idea with this is to basically perform some static NAT64, to map ports on the ipv4 side to specific addresses and ports on the ipv6 side.
|
|
||||||
This would allow me to have one entry for every port that I want to expose, regardless of the IPv6 or port of the service.
|
|
||||||
|
|
||||||
|
# Idea 2 - NAT64 with static BIB
|
||||||
|
## The Idea
|
||||||
|
The idea with this is to basically perform some static NAT64, to map ports on the IPv4 side to specific addresses and ports on the IPv6 side.
|
||||||
|
This would allow me to have one entry for every port that I want to expose, regardless of the IPv6 address or port of the service.
|
||||||
|
|
||||||
1. Same
|
## Compared to Idea 1
|
||||||
2. Same
|
The main difference between this and Idea 1 (SIIT-EAM), is that NAT64 allows me to map single ports and basically use a single IPv4 address to host a variety of services,
|
||||||
3. Same
|
that might be running on different hosts.
|
||||||
4. Same
|
|
||||||
|
## Setup
|
||||||
|
{% details(summary="Detailed instructions on how to setup the VPS") %}
|
||||||
|
1. Get a server that supports Dual-Stack networking and in the best case a /64 ipv6 subnet (I choose Scaleway as a European cloud provider, with cheap servers)
|
||||||
|
[Scaleway IPv6 Docs](https://www.scaleway.com/en/docs/instances/how-to/use-flexips/#flexible-ipv6)
|
||||||
|
[Scaleway Check neighbor discovery](https://www.scaleway.com/en/docs/dedibox-ipv6/how-to/debug-ipv6/#check-the-neighbor-discovery-protocol-ndp)
|
||||||
|
2. apt-get update and apt-get upgrade
|
||||||
|
3. Install Jool
|
||||||
|
1. Based on the [jool documentation](https://www.jool.mx/en/ubuntu.html)
|
||||||
|
2. `sudo apt install jool-dkms jool-tools`
|
||||||
|
3. Enable IP forwarding
|
||||||
|
- `/sbin/sysctl -w net.ipv4.conf.all.forwarding=1`
|
||||||
|
- `/sbin/sysctl -w net.ipv6.conf.all.forwarding=1`
|
||||||
|
4. Install NDP Proxy Daemon [ndppd](https://manpages.ubuntu.com/manpages/focal/man1/ndppd.1.html)
|
||||||
|
1. `sudo apt-get install ndppd`
|
||||||
|
2. `/sbin/sysctl -w net.ipv6.conf.all.proxy_ndp=1`
|
||||||
5. Configure [Jool](https://www.jool.mx/en/index.html)
|
5. Configure [Jool](https://www.jool.mx/en/index.html)
|
||||||
1. `/sbin/modprobe jool`
|
1. `/sbin/modprobe jool`
|
||||||
2. `jool instance add "example" --netfilter --pool6 2001:0bc8:1640:6554:0:0:0:0/96`[^nat64_setup]
|
2. Setup NAT64 `jool instance add "example" --netfilter --pool6 2001:0bc8:1640:6554:0:0:0:0/96`
|
||||||
3. `jool -i "example" pool4 add --udp 51.158.177.228 1-65535`[^pool4_setup]
|
3. Setup pool4 for UDP `jool -i "example" pool4 add --udp 51.158.177.228 1-65535`
|
||||||
4. `jool -i "example" pool4 add --tcp 51.158.177.228 1-65535`[^pool4_setup]
|
4. Setup pool4 for TCP `jool -i "example" pool4 add --tcp 51.158.177.228 1-65535`
|
||||||
6. Same
|
6. ndppd for neighbor discovery
|
||||||
|
1. In `/etc/ndppd.conf`
|
||||||
|
```
|
||||||
|
proxy ens2 {
|
||||||
|
rule 2001:0bc8:1640:6554:0:0:0:0/96 {
|
||||||
|
static
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
7. Example Setup of bib[^bib_explained] entries (for teamspeak3 in this case)
|
7. Example Setup of bib[^bib_explained] entries (for teamspeak3 in this case)
|
||||||
1. `jool -i "example" bib add 2001:4dd5:b276:1:f652:14ff:fe94:dc00#9987 51.158.177.228#9987 --udp`[^bib_add_command]
|
1. `jool -i "example" bib add 2001:4dd5:b276:1:f652:14ff:fe94:dc00#9987 51.158.177.228#9987 --udp`[^bib_add_command]
|
||||||
2. `jool -i "example" bib add 2001:4dd5:b276:1:f652:14ff:fe94:dc00#30033 51.158.177.228#30033 --tcp`[^bib_add_command]
|
2. `jool -i "example" bib add 2001:4dd5:b276:1:f652:14ff:fe94:dc00#30033 51.158.177.228#30033 --tcp`[^bib_add_command]
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
|
||||||
## Future Work
|
# Future Work
|
||||||
- Automate the jool setup (ansible playbook or maybe even using cloud-init)
|
I need to automate the setup of jool and the VPS.
|
||||||
- Automate the configuration of the corresponding entries (likely a custom integration with consul)
|
My first thought would be to use ansible, as with most of my other services, but I might actually think that using something like terraform + cloud-init could be a better fit.
|
||||||
|
Using terraform together with cloud-init, I could avoid having to log into the VPS at all and the configuration will be done automatically upon creation/startup.
|
||||||
|
|
||||||
## References
|
I also want to integrate the configuration of jool, like the NAT64 entries, directly into ipv-proxy.
|
||||||
|
This way I can easily migrate from the old setup the new one (related [MR](https://github.com/Lol3rrr/ipv-proxy/pull/4)) and do not have to change anything else.
|
||||||
|
|
||||||
|
# References
|
||||||
|
[^jool]: [Jool Homepage](https://www.jool.mx/en/index.html)
|
||||||
[^nat64_setup]: [basic NAT64 tutorial](https://www.jool.mx/en/run-nat64.html)
|
[^nat64_setup]: [basic NAT64 tutorial](https://www.jool.mx/en/run-nat64.html)
|
||||||
[^pool4_setup]: We need to configure the [pool4](https://www.jool.mx/en/pool4.html) used by jool using the given [pool4 commands](https://nicmx.github.io/Jool/en/usr-flags-pool4.html)
|
[^pool4_setup]: We need to configure the [pool4](https://www.jool.mx/en/pool4.html) used by jool using the given [pool4 commands](https://nicmx.github.io/Jool/en/usr-flags-pool4.html)
|
||||||
[^bib_explained]: [BIB](https://www.jool.mx/en/bib.html)
|
[^bib_explained]: [BIB](https://www.jool.mx/en/bib.html)
|
||||||
|
|||||||
12
content/quickfacts-1/example.svg
Normal file
12
content/quickfacts-1/example.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<svg version="1.1"
|
||||||
|
baseProfile="full"
|
||||||
|
width="300" height="200"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<rect width="100%" height="100%" fill="red" />
|
||||||
|
|
||||||
|
<circle cx="150" cy="100" r="80" fill="green" id="test" onclick="hide()" />
|
||||||
|
|
||||||
|
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 336 B |
@@ -1,12 +1,333 @@
|
|||||||
+++
|
+++
|
||||||
title = "Quickfacts - Part 1"
|
title = "Quickfacts - Part 1: The Mental Model"
|
||||||
date = 2026-05-01
|
date = 2026-04-02
|
||||||
description = "The first part of a series about my Quickfacts project"
|
description = "Building a Mental Model"
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
categories = ["Programming"]
|
categories = ["Programming"]
|
||||||
tags = ["Factorio", "Rust"]
|
tags = ["Factorio", "Rust"]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
toc = true
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
The first real entry in my series about my quickfacts project.
|
||||||
|
For a small introduction, see the [intro blogpost](@/quickfacts-intro/index.md)
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
# Idea
|
||||||
|
- Everything is a graph
|
||||||
|
- Each Entity is a node in the graph
|
||||||
|
- What extra information to include
|
||||||
|
- Each Edge represents that certain items can move from one node to the other
|
||||||
|
- Specify what items can move over the edge (for example because of filters)
|
||||||
|
- Outgoing and incoming priority
|
||||||
|
- Capacity ?
|
||||||
|
- What extra information to include
|
||||||
|
|
||||||
|
# Interactions
|
||||||
|
The Construction basically comes down to a large table mapping the different interactions between all the types of entities.
|
||||||
|
|
||||||
|
## Belts
|
||||||
|
For the purpose of my analysis, you can split a belt into the two sides, as they act independent of one another.
|
||||||
|
|
||||||
|
### Belt-Belt
|
||||||
|
Direct 1-to-1 connection, is the simplest and most common way two belts are connected.
|
||||||
|
In this configuration you have 1 belt basically forwarding everything onto another belt, where each side of the source belt feeds into the corresponding side of the target belt.
|
||||||
|
For example if you have a simple line of belts forming a long line, they are all direct 1-to-1 connected.
|
||||||
|
|
||||||
|
Sideloading, is a more special case. This happens when the target belt already has another belt connected and now the new source belt is connected from the side (as the name suggests).
|
||||||
|
In this configuration both sides of the source belt are going into one side of the target belt.
|
||||||
|
|
||||||
|
### Belt-Splitter
|
||||||
|
The splitter can only accept direct 1-to-1 style connections going into it, from belts or the output of underground belts.
|
||||||
|
|
||||||
|
The ouput of a splitter basically acts the same way as a normal belt would.
|
||||||
|
|
||||||
|
### Belt-UndergroundBelt
|
||||||
|
The input side of the underground supports direct 1-to-1 connections, if the other source belt is coming from the same direction (so no turn is being made).
|
||||||
|
The input can also be sideloaded, however opposed to normal belt sideloading, only the upstream side of the source belt will feed into the target belt and the other side of the source belt will not be connected.
|
||||||
|
|
||||||
|
The output again acts like a normal belt.
|
||||||
|
|
||||||
|
### Belt-Direct output machines (miner, recycler, etc.)
|
||||||
|
Sideloading
|
||||||
|
|
||||||
|
TODO What to do if at the "head" of a beltline
|
||||||
|
|
||||||
|
## Inserter
|
||||||
|
### Inserter-Belt
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
|
# Modelling
|
||||||
|
Each node essentially has a set of input and output "slots", which have different priorities.
|
||||||
|
|
||||||
|
Each slot can have filters attached, from both the upstream and downstream side.
|
||||||
|
Filters from the upstream side are mostly dependent on configuration, like filters on splitters.
|
||||||
|
Filters from the downstream dependent on their slot filters and also their type, like machines will apply filters to their upstream slots that constrain it to items that it needs/can process.
|
||||||
|
For transport nodes (belts, inserter, etc.) they should forward their downward filters and combine them with their own filters
|
||||||
|
|
||||||
|
To collect input items, a node goes through its input slots in decreasing priority and collects everything that fits its own criteria.
|
||||||
|
|
||||||
|
To distribute the items, a node goes through its output slots in decreasing priority and puts as many items as possible in that slot.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
The idea with constraints is basically to have some logic formula that constraints the items and their rates along an edge.
|
||||||
|
So for example if an inserter has a filter configured, its constraints will only allow the specific items as configured by the filter.
|
||||||
|
Another example is an assembly machine, which will impose constraints on anything going into it, to only be the items that it needs for the recipe.
|
||||||
|
|
||||||
|
Constraints can then be propogated and combined upstream, so that an inserter going into an assembly machine also only selects the items that can actually go into that machine.
|
||||||
|
For propogation, the constraints will be combined by either an intersection or a union.
|
||||||
|
For example an inserter will perform an intersection, because it can only accept things that fit its specific configuration and any downstream configuration.
|
||||||
|
But a belt will perform a union, because might have constraints coming from inserters taking from them and downstream belts so the items can take either path.
|
||||||
|
Basically the constraints for every edge will be combined using an intersection with the constraints of the node itself, and then all downstream edge constraints will be combined using a union to form the final upstream constraint.
|
||||||
|
|
||||||
|
JOIN Operator: used to combine multiple "or" constraints, for example for combining downstream constraints.
|
||||||
|
This is done by adding all the limits for items together.
|
||||||
|
|
||||||
|
CHAIN Operator: used to combine multiple "and" constraints.
|
||||||
|
|
||||||
|
### Representation
|
||||||
|
A tree with 3 levels, each doing something different
|
||||||
|
|
||||||
|
The lowest level, selects by item.
|
||||||
|
The next level limits the rate per item.
|
||||||
|
The top level limits the total throughput.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
{% carousel() %}
|
||||||
|
{% carousel_item() %}
|
||||||
|
Before propogation
|
||||||
|
{% mermaid() %}
|
||||||
|
---
|
||||||
|
title: Inserter Feeding into Assembler
|
||||||
|
---
|
||||||
|
flowchart BT
|
||||||
|
subgraph assembler
|
||||||
|
direction BT
|
||||||
|
root_1["unlimited"]
|
||||||
|
1_first_limit["<= 5/s"]
|
||||||
|
1_first_items["iron"]
|
||||||
|
1_second_limit["<= 10/s"]
|
||||||
|
1_second_items["copper wire"]
|
||||||
|
|
||||||
|
1_first_limit --> root_1
|
||||||
|
1_second_limit --> root_1
|
||||||
|
1_first_items --> 1_first_limit
|
||||||
|
1_second_items --> 1_second_limit
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph inserter with iron filter
|
||||||
|
direction BT
|
||||||
|
root_2["total <= 20/s"]
|
||||||
|
2_first_limit["<= 20/s"]
|
||||||
|
2_first_items["iron"]
|
||||||
|
|
||||||
|
2_first_limit --> root_2
|
||||||
|
2_first_items --> 2_first_limit
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% carousel_item() %}
|
||||||
|
After first propogation step
|
||||||
|
{% mermaid() %}
|
||||||
|
---
|
||||||
|
title: Inserter Feeding into Assembler
|
||||||
|
---
|
||||||
|
flowchart BT
|
||||||
|
subgraph assembler
|
||||||
|
direction BT
|
||||||
|
root_1["unlimited"]
|
||||||
|
1_first_limit["<= 5/s"]
|
||||||
|
1_first_items["iron"]
|
||||||
|
1_second_limit["<= 10/s"]
|
||||||
|
1_second_items["copper wire"]
|
||||||
|
|
||||||
|
1_first_limit --> root_1
|
||||||
|
1_second_limit --> root_1
|
||||||
|
1_first_items --> 1_first_limit
|
||||||
|
1_second_items --> 1_second_limit
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph inserter with iron filter
|
||||||
|
direction BT
|
||||||
|
root_2["total <= 20/s"]
|
||||||
|
2_first_limit["<= 20/s && <= 5/s"]
|
||||||
|
2_first_items["iron"]
|
||||||
|
2_second_limit["<= 0/s && <= 10/s"]
|
||||||
|
2_second_items["copper wire"]
|
||||||
|
|
||||||
|
2_first_limit --> root_2
|
||||||
|
2_second_limit --> root_2
|
||||||
|
2_first_items --> 2_first_limit
|
||||||
|
2_second_items --> 2_second_limit
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% carousel_item() %}
|
||||||
|
After second propogation step
|
||||||
|
{% mermaid() %}
|
||||||
|
---
|
||||||
|
title: Inserter Feeding into Assembler
|
||||||
|
---
|
||||||
|
flowchart BT
|
||||||
|
subgraph assembler
|
||||||
|
direction BT
|
||||||
|
root_1["unlimited"]
|
||||||
|
1_first_limit["<= 5/s"]
|
||||||
|
1_first_items["iron"]
|
||||||
|
1_second_limit["<= 10/s"]
|
||||||
|
1_second_items["copper wire"]
|
||||||
|
|
||||||
|
1_first_limit --> root_1
|
||||||
|
1_second_limit --> root_1
|
||||||
|
1_first_items --> 1_first_limit
|
||||||
|
1_second_items --> 1_second_limit
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph inserter with iron filter
|
||||||
|
direction BT
|
||||||
|
root_2["total <= 20/s"]
|
||||||
|
2_first_limit["<= 5/s"]
|
||||||
|
2_first_items["iron"]
|
||||||
|
2_second_limit["<= 0/s"]
|
||||||
|
2_second_items["copper wire"]
|
||||||
|
|
||||||
|
2_first_limit --> root_2
|
||||||
|
2_second_limit --> root_2
|
||||||
|
2_first_items --> 2_first_limit
|
||||||
|
2_second_items --> 2_second_limit
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% carousel_item() %}
|
||||||
|
After final propogation step
|
||||||
|
{% mermaid() %}
|
||||||
|
---
|
||||||
|
title: Inserter Feeding into Assembler
|
||||||
|
---
|
||||||
|
flowchart BT
|
||||||
|
subgraph assembler
|
||||||
|
direction BT
|
||||||
|
root_1["unlimited"]
|
||||||
|
1_first_limit["<= 5/s"]
|
||||||
|
1_first_items["iron"]
|
||||||
|
1_second_limit["<= 10/s"]
|
||||||
|
1_second_items["copper wire"]
|
||||||
|
|
||||||
|
1_first_limit --> root_1
|
||||||
|
1_second_limit --> root_1
|
||||||
|
1_first_items --> 1_first_limit
|
||||||
|
1_second_items --> 1_second_limit
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph inserter with iron filter
|
||||||
|
direction BT
|
||||||
|
root_2["total <= 20/s"]
|
||||||
|
2_first_limit["<= 5/s"]
|
||||||
|
2_first_items["iron"]
|
||||||
|
|
||||||
|
2_first_limit --> root_2
|
||||||
|
2_first_items --> 2_first_limit
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Belt Line with one slower belt
|
||||||
|
{% carousel() %}
|
||||||
|
|
||||||
|
{% carousel_item() %}
|
||||||
|
Initial configuration
|
||||||
|
{% mermaid() %}
|
||||||
|
flowchart LR
|
||||||
|
Src
|
||||||
|
T1
|
||||||
|
T2
|
||||||
|
T3
|
||||||
|
Output
|
||||||
|
|
||||||
|
Src -->|x <= 20| T1
|
||||||
|
T1 -->|x <= 20| T2
|
||||||
|
T2 -->|x <= 10| T3
|
||||||
|
T3 -->|x <= 20| Output
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% carousel_item() %}
|
||||||
|
Raw Constraint Propogation
|
||||||
|
{% mermaid() %}
|
||||||
|
flowchart LR
|
||||||
|
Src
|
||||||
|
T1
|
||||||
|
T2
|
||||||
|
T3
|
||||||
|
Output
|
||||||
|
|
||||||
|
Src -->|x <= 20 && x <= 20 && x <= 10 && x <= 20| T1
|
||||||
|
T1 -->|x <= 20 && x <= 10 && x <= 20| T2
|
||||||
|
T2 -->|x <= 10 && x <= 20| T3
|
||||||
|
T3 -->|x <= 20| Output
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% carousel_item() %}
|
||||||
|
Simplified Constraint Propogation
|
||||||
|
{% mermaid() %}
|
||||||
|
flowchart LR
|
||||||
|
Src
|
||||||
|
T1
|
||||||
|
T2
|
||||||
|
T3
|
||||||
|
Output
|
||||||
|
|
||||||
|
Src -->|x <= 10| T1
|
||||||
|
T1 -->|x <= 10| T2
|
||||||
|
T2 -->|x <= 10| T3
|
||||||
|
T3 -->|x <= 20| Output
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
## TMP
|
||||||
|
{% carousel() %}
|
||||||
|
{% carousel_item() %}
|
||||||
|
Initial Configuration
|
||||||
|
{% mermaid() %}
|
||||||
|
flowchart TD
|
||||||
|
Src
|
||||||
|
T1
|
||||||
|
Splitter
|
||||||
|
T2
|
||||||
|
T3
|
||||||
|
Output1
|
||||||
|
Output2
|
||||||
|
|
||||||
|
Src --> T1
|
||||||
|
T1 --> Splitter
|
||||||
|
Splitter --> T2
|
||||||
|
Splitter --> T3
|
||||||
|
T2 --> Output1
|
||||||
|
T3 --> Output2
|
||||||
|
{% end %}
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% carousel_item() %}
|
||||||
|
Raw Propogation
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
|||||||
@@ -1,23 +1,39 @@
|
|||||||
+++
|
+++
|
||||||
title = "Quickfacts - Intro"
|
title = "Quickfacts - Intro"
|
||||||
date = 2026-05-01
|
date = 2026-04-01
|
||||||
description = "An introduction to my Quickfacts project"
|
description = "An introduction to my Quickfacts project"
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
categories = ["Programming"]
|
categories = ["Programming"]
|
||||||
tags = ["Factorio", "Rust", "Quickfacts"]
|
tags = ["Factorio", "Rust", "Quickfacts"]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
toc = true
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Quickfacts is a long ongoing hobby project of mine, with the aim of being able to analyze most factorio blueprints.
|
A small introduction to my ongoing quickfacts project.
|
||||||
The goal would be to have a website or application, which you can give most blueprints and it then can tell you things about how the blueprint will perform.
|
A set of tools/progams to automatically analyze factorio blueprints, basically the LLVM of factorio.
|
||||||
|
I have to no idea if this will ever work to a good degree or how far I will get, but I want to share this journey and document my ideas and approaches.
|
||||||
|
|
||||||
## Analysis Goals
|
<!-- more -->
|
||||||
|
|
||||||
|
# Motivation
|
||||||
|
I love playing factorio and I especially love optimizing my builds and going down the rabbit hole on analyzing blueprints.
|
||||||
|
However given my interest in analysis of computer programs and formal methods, I thought why not combine them, because if you squint with your eyes these problems look very similar.
|
||||||
|
|
||||||
|
At the end of this road, I would love to have a website, where I can paste a blueprint I am interested in, select/configure an analysis and then get the results.
|
||||||
|
|
||||||
|
# Analysis Goals
|
||||||
- Determine the output rate given a fixed input rates (and how much of the input is actually being consumed)
|
- Determine the output rate given a fixed input rates (and how much of the input is actually being consumed)
|
||||||
- Check how well a belt-balancer works
|
- Check how well a belt-balancer works
|
||||||
- Highlight bottlenecks in a setup
|
- Highlight bottlenecks in a setup
|
||||||
|
|
||||||
## Known Limitations
|
# Known Limitations
|
||||||
- Any form of circuit networks/logic
|
- Any form of circuit networks/logic
|
||||||
- Trains
|
- Trains
|
||||||
- Spoilage (for now)
|
- Spoilage (for now)
|
||||||
|
|
||||||
|
# Approach
|
||||||
|
I am going to develop a rough mathematical/formal model for factorio and its' semantics.
|
||||||
|
Then I hope to do something similar to data-flow analysis and essentially express all the logic and analysis as a set of mathematical steps/functions to apply.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Streaming setup"
|
title = "Streaming setup"
|
||||||
date = 2026-03-15
|
date = 2026-03-31
|
||||||
description = "Custom streaming setup to complement teamspeak"
|
description = "Custom streaming setup to complement teamspeak"
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
+++
|
+++
|
||||||
title = "Homelab - UDMPro"
|
title = "Homelab - UDMPro"
|
||||||
date = 2026-03-04
|
date = 2026-03-31
|
||||||
description = "Documenting my configuration for the UDMPro"
|
description = "Documenting my configuration for the UDMPro"
|
||||||
draft = true
|
draft = true
|
||||||
|
|
||||||
|
|||||||
126
templates/shortcodes/carousel.html
Normal file
126
templates/shortcodes/carousel.html
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<ul class="carousel_{{ nth }}">
|
||||||
|
{{ body | safe }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.carousel_{{ nth }} {
|
||||||
|
width: 100%;
|
||||||
|
/* height: 300px; */
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 4vw;
|
||||||
|
|
||||||
|
overflow-x: scroll;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
|
||||||
|
/* this will hide the scrollbar in mozilla based browsers */
|
||||||
|
overflow: -moz-scrollbars-none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
/* this will hide the scrollbar in internet explorers */
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
}
|
||||||
|
/* this will hide the scrollbar in webkit based browsers - safari, chrome, etc */
|
||||||
|
.carousel_{{ nth }} ul::-webkit-scrollbar {
|
||||||
|
width: 0 !important;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}>li {
|
||||||
|
list-style-type: none;
|
||||||
|
/* background-color: #eeeeee; */
|
||||||
|
/* border: 1px solid #dddddd; */
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
flex: 0 0 100%;
|
||||||
|
|
||||||
|
scroll-snap-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}>li:nth-child(even) {
|
||||||
|
/* background-color: cyan; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}>li::after {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(*) {
|
||||||
|
border: 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
background: none;
|
||||||
|
color: white;
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(*):hover,
|
||||||
|
.carousel_{{ nth }}::scroll-button(*):focus {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(*):active {
|
||||||
|
translate: 1px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(*):disabled {
|
||||||
|
opacity: 0.2;
|
||||||
|
cursor: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(left) {
|
||||||
|
content: "◄" / "Previous";
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(right) {
|
||||||
|
content: "►" / "Next";
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }} {
|
||||||
|
anchor-name: --my-carousel_{{ nth }};
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(*) {
|
||||||
|
position: absolute;
|
||||||
|
position-anchor: --my-carousel_{{ nth }};
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(left) {
|
||||||
|
right: calc(anchor(left) - 70px);
|
||||||
|
bottom: calc(anchor(bottom) + 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-button(right) {
|
||||||
|
left: calc(anchor(right) - 70px);
|
||||||
|
bottom: calc(anchor(bottom) + 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }} {
|
||||||
|
scroll-marker-group: after;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}::scroll-marker-group {
|
||||||
|
position: absolute;
|
||||||
|
position-anchor: --my-carousel_{{ nth }};
|
||||||
|
bottom: calc(anchor(bottom) + 10px);
|
||||||
|
justify-self: anchor-center;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}>li::scroll-marker {
|
||||||
|
content: attr(data-accName);
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 2px solid white;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-indent: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel_{{ nth }}>li::scroll-marker:target-current {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
3
templates/shortcodes/carousel_item.html
Normal file
3
templates/shortcodes/carousel_item.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<li>
|
||||||
|
{{ body | safe }}
|
||||||
|
</li>
|
||||||
2
templates/shortcodes/svg.html
Normal file
2
templates/shortcodes/svg.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
{% set data = load_data(path=path) -%}
|
||||||
|
{{ data | safe }}
|
||||||
Reference in New Issue
Block a user