Compare commits
15 Commits
508af46e66
...
dev
Author | SHA1 | Date | |
---|---|---|---|
8af115ee29 | |||
b9837cc947 | |||
23e60b1a75 | |||
5e11172576 | |||
c25a925a5e | |||
6a6c1089e8 | |||
8ba1d45ccc | |||
da0e95a44b | |||
4bcf0b7dd7 | |||
c94bf80568 | |||
479a1a3d27 | |||
796f5ac759 | |||
01b83ea721 | |||
720d81a12c | |||
b6b7146f50 |
3
Makefile
Normal file
3
Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
demo:
|
||||
docker build -t wip .
|
||||
docker run --rm -it -p 8080:8080 wip hugo serve --bind 0.0.0.0 --port 8080
|
31
README.md
31
README.md
@ -1,3 +1,34 @@
|
||||
# gtz blog
|
||||
An opinionated blog.
|
||||
I write posts about technology and other interests that I have.
|
||||
|
||||
To iterate locally:
|
||||
```sh
|
||||
docker build -t wip .
|
||||
docker run --rm -it -p 8080:8080 wip hugo serve --bind 0.0.0.0 --port 8080
|
||||
```
|
||||
|
||||
## Things I want to write
|
||||
|
||||
### Opinion Pieces
|
||||
- [ ] Clean Architecture is stupid - dependency injection is king
|
||||
- [ ] Neorg is bad, actually - ?? is king
|
||||
- [ ] Clean Architecture is stupid and overly complicated - dependency injection is king
|
||||
- [ ] For want of a neater (human) internet
|
||||
- [ ] A truly FOSS printer.
|
||||
even the hardware should be FOSS. - most parts should be 3d printable.
|
||||
should be a laser printer, as inkjet is stupid.
|
||||
- [ ] A truly FOSS eink reader.
|
||||
- [ ] VIM Bindings everywhere please
|
||||
- [ ] trying to use a MIDI controller as a piano on Linux is insanity
|
||||
|
||||
### Digital Soverignty
|
||||
- [x] how to host a blog
|
||||
- [ ] how to securely "self-host" using a VPS, portainer and traefik
|
||||
- [x] how to configure neomutt
|
||||
- [ ] how to securely host a mail server
|
||||
- [ ] how to private tracker with a NAS
|
||||
|
||||
### Old sillyblog
|
||||
- [x] Avr memory model
|
||||
- [x] similarity graph
|
||||
|
@ -3,5 +3,17 @@ title: About gtz blog
|
||||
author: Asger Gitz-Johansen
|
||||
---
|
||||
|
||||
This is just a simple blog.
|
||||
<!-- TODO: Add more shit about myself. -->
|
||||
I am a software engineer from Denmark working at [GomSpace](https://gomspace.com/home.aspx). This is just my simple
|
||||
blog that I use for letting out "blogging-steam". I write GNU/Linux based tutorials and sometimes I write opinion
|
||||
pieces. These posts are mostly for my own sake, but I hope you find my stuff useful.
|
||||
|
||||
If you want more from me, check my links:
|
||||
|
||||
- [GitHub](https://github.com/sillydan1)
|
||||
- [Model Based Development Paper 1](https://github.com/sillydan1/aaltitoad/blob/master/.github/resources/docs/SW9__AALTITOAD.pdf)
|
||||
- [Model Based Development Paper 2](https://github.com/sillydan1/aaltitoad/blob/master/.github/resources/docs/SW10__Tick_Tock_Automata.pdf)
|
||||
- [Model Based Development Paper 3](https://github.com/sillydan1/aaltitoad/blob/master/.github/resources/docs/aaltitoad-v1.0.0.pdf)
|
||||
- [Graphedit](https://github.com/sillydan1/graphedit) (in hibernation)
|
||||
- [AALTITOAD](https://github.com/sillydan1/aaltitoad) (in hibernation)
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
||||
|
108
content/posts/avr-memory-model.md
Normal file
108
content/posts/avr-memory-model.md
Normal file
@ -0,0 +1,108 @@
|
||||
+++
|
||||
date = '2019-03-24'
|
||||
draft = false
|
||||
title = 'AVR Memory Model: The Practical Explanation'
|
||||
tags = ['avr', 'programming', 'memory']
|
||||
categories = ['technical']
|
||||
+++
|
||||
|
||||
There is A LOT of people that have already explained this, but I personally don't feel like going through billions of
|
||||
forum posts, with most of them just dying out somewhere in 2008 with no better answer than "I figured it out, thanks".
|
||||
|
||||
Regardless. BIG shoutout to the amazing people at [AVR Freaks](https://www.avrfreaks.net/). They are really cool.
|
||||
Seriously. Make a user and ask them about anything, and they'll help you.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
I have only been debugging the memory usage of a specific ATMega chip. I don't know if other AVR chip-types use the
|
||||
same, but this explanation should be valid for all MCUs that the
|
||||
[avr-libc](http://www.nongnu.org/avr-libc/user-manual/index.html) package supports.
|
||||
|
||||
I also assume that GNU/Linux is being used on the development computer.
|
||||
|
||||
### Open-Source development tools
|
||||
|
||||
The [avr-gcc](http://www.nongnu.org/avr-libc/user-manual/pages.html) compiler chain is an open source effort to have
|
||||
C/C++ for AVR Atmel chips. They do provide some rudimentary C++ support, but there's no STL and the `new` and `delete`
|
||||
keywords are not implemented by default. Even purely virtual functions doesn't work out of the box. These things can be
|
||||
added manually though.
|
||||
|
||||
## The Memory Model
|
||||
|
||||
As the avr-libc developers [explain](http://www.nongnu.org/avr-libc/user-manual/malloc.html), there's typically not a
|
||||
lot of RAM available on most many devices and therefore it's very important to keep track of how much memory you are
|
||||
using.
|
||||
|
||||
{{< centered image="/malloc-std.png" >}}
|
||||
|
||||
All of these symbols `SP`, `RAMEND`, `__data_start`, `__malloc_heap_start`, etc. Can be modified in the compiler, but
|
||||
the picture above gives the default layout (for an ATMega128 MCU). It goes without saying, that if you don't have an
|
||||
external RAM chip, you won't be able to utilize the extra RAM space for that. Otherwise, the memory addresses are pretty
|
||||
straight forward: `0x0100 => 256` bytes is the start of the memory, `0x10FF => 4351` bytes is the end. If you're
|
||||
wondering where the RAM ends on your specific MCU, you can usually simply open the spec-sheet of the chip and see the
|
||||
amount of available memory is in it.
|
||||
For the [ATMega128](https://www.microchip.com/wwwproducts/en/ATMEGA128) that number is 4096 (`4351 - 256 = 4095` (the
|
||||
spec-sheet also counts the 0th byte)).
|
||||
|
||||
## The avr-libc Memory Allocators
|
||||
|
||||
Now for the juicy part. whenever you `malloc` something in your program, the allocator first writes a 2-byte *free-list
|
||||
entry* that tells the system how big your object is.
|
||||
|
||||
Example:
|
||||
|
||||
```cpp
|
||||
/* ... */
|
||||
// Allocate an array of 5 integers
|
||||
int* my_heap_object = static_cast<int*>(malloc(sizeof(int) * 5));
|
||||
/* ... */
|
||||
```
|
||||
|
||||
Assuming that the memory has been cleared on chip-startup, the above example ends up with the memory setup looking like
|
||||
this: (Don't mind the specifc memory addresses. If you're curious, you can try doing this, by attaching `avr-gdb` to a
|
||||
simulator or On Chip Debugger (OCD)).
|
||||
|
||||
```
|
||||
gdb: > x/16xb my_heap_object
|
||||
0x800100: 0a 00 00 00 00 00 00 00
|
||||
0x800108: 00 00 00 00 00 00 00 00
|
||||
```
|
||||
|
||||
The first bytes at address `0x800100` are `0a` and `00`. These bytes are the *free-list* entry and explains how "big"
|
||||
the object is. When reading this, we have to remember that the model is little endian-based (meaning that the bytes are
|
||||
switched),
|
||||
so we actually have the value of `0x000a`, meaning `10` in decimal. This makes a lot of sense, since we allocated 5
|
||||
`int`s, that is of size 2 (16bit integers).
|
||||
|
||||
The memory dump shows 16 bytes in total, so the last 4 bytes displayed in the gdb example are not part of the object.
|
||||
However, if you look at the Memory Model picture again, you can see that the `__brkval` value points to the biggest
|
||||
memory address that has not been allocated. In our example, if you check where the `__brkval` points to after our
|
||||
allocation, we get:
|
||||
|
||||
```
|
||||
gdb: > __brkval
|
||||
$ 268
|
||||
```
|
||||
|
||||
268 in hexadecimal is `0x10c`, and if interpreted as an address we get `0x80010c`, which fits very well with our
|
||||
example, since it is exactly 12 bytes away from where the free-list entry of `my_heap_object` is located at.
|
||||
|
||||
When `free`-ing the object again, the deallocator looks at the free-list entry at the given address, and wipes the
|
||||
free-list entry. **This is why you should not free a dangling pointer**. Freeing something that is not really free-list
|
||||
entry *will* result in undefined behaviour, and I think we all know how bad **that** is. (Even though the AVR
|
||||
environment is actually very good at handling it. In my experience, it usually just crashes and starts over.)
|
||||
However, as
|
||||
[explained](http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#gafb8699abb1f51d920a176e695ff3be8a) in
|
||||
the avrlibc documentation, freeing the `NULL` value, doesn't do anything. So remember to assign your free'd pointers to
|
||||
`NULL` afterwards.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
The memory allocators of AVR can be very confusing and if you don't keep your thoughts straight when programming, you
|
||||
can very easily get yourself into a lot of trouble. Since STL is not available to avr-gcc programmers, we dont have our
|
||||
glorious smart pointers, so we should implement them ourselves (or use arduino's implementations). That might become a
|
||||
future blogpost.
|
||||
|
||||
Regardless, I hope this helps the lost souls that are trying to actually use these tools.
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
103
content/posts/bubble-graph.md
Normal file
103
content/posts/bubble-graph.md
Normal file
@ -0,0 +1,103 @@
|
||||
+++
|
||||
date = '2019-03-13'
|
||||
draft = false
|
||||
title = 'Plotting Circular Bubble Graph in LaTeX'
|
||||
tags = ['latex', 'programming']
|
||||
categories = ['technical']
|
||||
+++
|
||||
|
||||
When doing web-intelligence, you might need to visualize what's called inter-sentence similarity in a particular format.
|
||||
I personally haven't found an official name for these kinds of graphs, so I just simply call them Circular Bubble
|
||||
Graphs.
|
||||
|
||||
During a university project we needed such a graph in our report, and I got the idea of automatically plotting it
|
||||
through `gnuplot` and integrating it directly into our report with `gnuplottex`. You can see an example of the outcome
|
||||
of the script.
|
||||
|
||||

|
||||
|
||||
The script operates on a comma-seperated file (`.csv`). The data should be provided as a matrix of sentences assumed to
|
||||
be symmetric, with the cells containing a real number from 0 to 1, indicating the similarity between the column and the
|
||||
row. Because of this symmetric property, half of the matrix is ignored by the script. (It also ignores the diagonal,
|
||||
since sentence `23` will always have a maximum similarity to sentence `23`. It would also be hard to plot that line)
|
||||
|
||||
The whole script can be seen below, but you can also download it as a file
|
||||
[here](/sentence_similarity_graph.gnuplot). Make sure to set the `my_dataset` variable to your desired
|
||||
dataset. Example matrix can be downloaded [here](/example_similarities.csv).
|
||||
|
||||
```bash
|
||||
# Sentence similarity graph plotter
|
||||
# uncomment this for manual operation of the dataset plotted
|
||||
# my_dataset = "./sentence_similarities.csv" # ARG1
|
||||
set parametric
|
||||
set size square
|
||||
|
||||
# Styling
|
||||
set pointsize 7.5
|
||||
set style fill solid 1.0 border rgb 'grey30'
|
||||
set style line 1 lc rgb 'black' pt 6 lw 0.5
|
||||
|
||||
# Basically a one-dimensional circular coordinate system
|
||||
fx(t) = cos(t)
|
||||
fy(t) = sin(t)
|
||||
rownum = floor(system("wc -l ".my_dataset."")) +1
|
||||
coord(k) = (k/real(rownum))*(2*pi)
|
||||
fxx(t) = cos(coord(t))
|
||||
fyy(t) = sin(coord(t))
|
||||
|
||||
set trange [0:2*pi-(coord(1.0))]
|
||||
set sample rownum
|
||||
set noborder
|
||||
unset tics
|
||||
set xrange [-1.2:1.2]
|
||||
set yrange [-1.2:1.2]
|
||||
set title "Sentence inter-similarity graph"
|
||||
set multiplot
|
||||
refloptimization = 0
|
||||
do for [i = 0:rownum-1] {
|
||||
do for [j = refloptimization:rownum-1] {
|
||||
if (i != j) {
|
||||
# Get how many columns there are in the dataset.
|
||||
arrwidth = real(system("awk 'FNR == ".(i+1)." {print $".(j+1)."}' ".my_dataset.""))
|
||||
if (arrwidth > 0.0) {
|
||||
bubblerad = 0.125
|
||||
x1 = fxx(i)
|
||||
y1 = fyy(i)
|
||||
x2 = fxx(j)
|
||||
y2 = fyy(j)
|
||||
|
||||
dvx = x2-x1
|
||||
dvy = y2-y1
|
||||
dvl = sqrt((dvx ** 2) + (dvy ** 2))
|
||||
x1 = x1 + (dvx/dvl)*bubblerad
|
||||
y1 = y1 + (dvy/dvl)*bubblerad
|
||||
x2 = x2 - (dvx/dvl)*bubblerad
|
||||
y2 = y2 - (dvy/dvl)*bubblerad
|
||||
# Overleaf's arrow-width rendering is pretty terrible,
|
||||
# so we use a color-gradient to determine connection-strength.
|
||||
if (arrwidth > 0.2) {
|
||||
col = "#000000"
|
||||
} else {
|
||||
if (arrwidth < 0.1) {
|
||||
col = "#B8B8B8"
|
||||
} else {
|
||||
col = "#E4E4E4"
|
||||
}
|
||||
}
|
||||
|
||||
set arrow "".i.j."" from x1,y1 to x2,y2 nohead lw 0.5 lc rgb col
|
||||
#set label "H" at (fxx(j)-fxx(i)),(fyy(j)-fyy(i))
|
||||
show arrow "".i.j.""
|
||||
}
|
||||
}
|
||||
}
|
||||
refloptimization = refloptimization + 1
|
||||
}
|
||||
# Plot the circles
|
||||
plot '+' u (fx(t)):(fy(t)) w p ls 1 notitle
|
||||
|
||||
# Plot the sentence labels
|
||||
plot '+' u (fx(t)):(fy(t)):(sprintf("s.%d",$0+1)) with labels notitle
|
||||
```
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
90
content/posts/computation-in-nature.md
Normal file
90
content/posts/computation-in-nature.md
Normal file
@ -0,0 +1,90 @@
|
||||
+++
|
||||
date = '2018-05-10'
|
||||
draft = false
|
||||
title = 'Computation in Nature'
|
||||
tags = ['philosophy', 'old']
|
||||
categories = ['opinion']
|
||||
+++
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This was written as a student project as part of our ethics course, where we had to write an opinion piece on anything
|
||||
we wanted in the field of computer science. Note that this was before *LLM*'s was a thing. I also apologize if the
|
||||
wording is off - this was written in an afternoon as a rush-job. I still think it's an interesting read though.
|
||||
|
||||
# Computation in Nature
|
||||
|
||||
> "If you ever think your code is bad, just remember that CPUs are just rocks that we tricked into thinking"
|
||||
|
||||
**Does nature compute, or is computation something that only humans are doing? If so, what is it about?**
|
||||
|
||||
To answer this question, we would need to define what the terms used in it even means. If you look up the definition
|
||||
of the word ’compute’ on Merriam Websters dictionary, you find that it primarily means _"to determine
|
||||
especially by mathematical means"_. This definition mentions a usage of mathematics, which suggests a close bond
|
||||
between these concepts. What would it mean to determine something? I would argue that it means to make a decision
|
||||
based on either prior experience, data or calculations. This implies that computation have a focus on deciding
|
||||
and _"finishing a thought"_. It has an end goal. You can even use conclusions made by previous computations to
|
||||
compute more advanced problems.
|
||||
|
||||
In other words, computation is about answering queries about data and the process of doing so. We see
|
||||
mathematics emerge in nature aswell. In
|
||||
[The Chemical Basis of Morphogenesis](https://www.dna.caltech.edu/courses/cs191/paperscs191/turing.pdf), Alan M. Turing
|
||||
mentions that flower petals rarely exceeds a quantity of five. We even see the fibonacci sequence in [sunflower
|
||||
seeds](http://popmath.org.uk/rpamaths/rpampages/sunflower.html).
|
||||
|
||||
An example of computation in nature is actually very easy to find. Take a rabbit, or any other smaller prey. A rabbit
|
||||
is constantly observing and reacting to the sorrounding environment, otherwise it gets eaten by predators or fall
|
||||
into a lake and drown or any other scenario involving death. You could argue that the rabbit is constantly asking
|
||||
questions about what to: _"Should I run away?"_, _"Should I keep eating?"_, _"Should I be eating this?"_ etc. The
|
||||
rabbit may not be consciously aware of these queries, but nevertheless, it is answering to them through actions.
|
||||
|
||||
In Empiricism, the concept of a mind being a blank slate (in this case we are talking about a human mind, not
|
||||
a rabbit’s), which remembers experiences throughout it's life is very much applicable here. This trait means that a
|
||||
human mind has a memory, i.e. It can remember old conclusions and recall them when desired. We can actually prove that
|
||||
this trait is not exclusive to humans, since we can observe other mammals (and even fish and reptiles) demonstrating
|
||||
the usage of memory. A prime example is the elephant, but pretty much any animal applies here.
|
||||
|
||||
In Rationalism, we see a similar pattern that mimics memory - using the method of deduction to find conclusions and
|
||||
deduct new conclusions. However, if you were to believe the Rationalists, getting the data needed for computation
|
||||
in nature, may be a tricky task. We previously had an assumption that we could trust our senses and that other
|
||||
creatures also had senses. In Rationalism the only thing we can trust is the fact that we are doubting. Since we
|
||||
can’t say anything about if the rabbit is doubting everything. This is where the rabbit example breaks down a
|
||||
little, but we are not completely stuck here. Humans are mammals and mammals are a type of animal. Animals are
|
||||
part of nature, therefore humans must be part of nature. You and I are humans (I assume) and this logic enables us
|
||||
to make deductive arguments based on subjective thoughts. The first part of the Cartesian circle, _"Dubito ergo
|
||||
cogito"_ (_"I doubt, therefore I think"_) made by René Descartes, is actually very applicable to computation in
|
||||
nature. Thinking is a form of computation and since _I_ am part of nature. Computation rationally happens in nature.
|
||||
|
||||
But what about non-natural computation? Artificial computation machines such as modern digital computers or even
|
||||
old mechanical adding-machines are definitely things that compute data. There is a diconnect from artificial
|
||||
computers to natural computers (brains) though. Currently humans have not been able to produce a machine with natural
|
||||
intuition as we observe it in nature. We have come close to something that looks like it with simulated organisms,
|
||||
but we are not even close to the complexeties of an intuitive brain such as the human one (or even other clever
|
||||
mammals, such as the dolphin or chimp). I personally believe that the reason for this disconnect is because of
|
||||
the intention of the computational device at the point of origin. The intent that biology had when brains were
|
||||
invented was certainly not to fix mathematical problems. It was primarily for the organism to survive and have a
|
||||
better chance for survival. But fixing mathematical problems is the exact intent humans had when we created our
|
||||
artificial computers. This **intent** changes the very nature of the system, and because of this, even though brains
|
||||
and computers might be similar in some ways they are and always will be fundamentally different things.
|
||||
|
||||
A. M. Turing presents (and disagrees with) various arguments, in his article
|
||||
[Computing Machinery and Intelligence](https://archive.org/details/MIND--COMPUTING-MACHINERY-AND-INTELLIGENCE),
|
||||
that a computer will not be able to be conscious. I tend to follow Turings idea that most of the arguments he
|
||||
presents are either made out of a tendency towards thinking that humans have to be superior to machines, either
|
||||
intellectually or that we can *feel* things. I do disagree with one point that he makes in the article: Turing
|
||||
argues that a computer/program will never be able to change it’s behavior based off of prior experiences, which
|
||||
sounds ludicrous if you’ve taken any modern Machine Intelligence class. This was probably a conclusion he came
|
||||
to because of the time era he was in and that AI as a field was not as big (if at all existent) at the time.
|
||||
|
||||
Turing finishes the article with the notion that there is still a lot of work to be done in the field of AI and it
|
||||
is a fun contrast to see what arguments and thoughts a mind like his had back in the fifties, compared to modern
|
||||
AI can offer. Will humans ever be able to create a truly conscious artificial mind? And will we ever be able to
|
||||
test it properly?
|
||||
|
||||
If you believe the Mathematical Realists, mathematics is a thing that exists independently of humans, and can be
|
||||
found/discovered by any being clever enough to deduct these laws. This point of view can be very reassuring, if
|
||||
you believe that real artificial intelligence can be made. Since mathematics is an inherit trait of the universe,
|
||||
and that computers are machines that can (essentially) execute math, and that humans are part of nature. It might
|
||||
be possible to make an AI that is (atleast) as clever as humans, maybe it is innevitable.
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
@ -5,4 +5,7 @@ title = 'Example'
|
||||
tags = ['tutorial']
|
||||
categories = ['technical']
|
||||
+++
|
||||
|
||||
content goes here.
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
||||
|
89
content/posts/how-to-2famutt.md
Normal file
89
content/posts/how-to-2famutt.md
Normal file
@ -0,0 +1,89 @@
|
||||
+++
|
||||
date = '2025-01-11'
|
||||
title = 'Neomutt and Outlook'
|
||||
tags = ['howto', 'tutorial', 'mutt', '2fa', 'oauth2']
|
||||
categories = ['technical']
|
||||
+++
|
||||
Neomutt is a great way to read, send and manage your email.
|
||||
In this tutorial we will configure neomutt to be able to synchronize e-mails with Outlook (or other popular e-mail provider) addresses!
|
||||
By the end of this tutorial, you will be able to manually synchronize your emails using the `mailsync` command and read/manage your emails in an interface that looks like so:
|
||||
|
||||
{{< centered image="/neomutt-screenshot.png" >}}
|
||||
|
||||
Apologies for the blur, but I dont want you to read **my** e-mails.
|
||||
|
||||
## First Things First
|
||||
First, sign in to your mail through the browser. This is needed for the OAuth2 authorization flow.
|
||||
|
||||
You should also obviously install neomutt.
|
||||
This can just be done through your package manager.
|
||||
As I am using Arch linux, I will do so using `pacman`, but on Ubuntu or Debian you should use `apt`:
|
||||
|
||||
```sh
|
||||
pacman -S neomutt
|
||||
```
|
||||
|
||||
## GPG
|
||||
The first thing you'll need is a `gpg` key for encryption purposes.
|
||||
You can check your keys using `gpg --list-keys`.
|
||||
If you don't already have a `gpg` key, you can generate one with the `--full-gen-key` flag.
|
||||
|
||||
```sh
|
||||
gpg --full-gen-key
|
||||
```
|
||||
|
||||
## OAuth2
|
||||
As part of installing neomutt, you should have the oauth2 python script located in `/usr/share/neomutt/oauth2/`.
|
||||
We need to register neomutt as an already trusted app.
|
||||
We will simply abuse the thunderbird client-id for this, which is: `9e5f94bc-e8a4-4e73-b8be-63364c29d753` - with this you don't need to specify a client secret:
|
||||
|
||||
```sh
|
||||
/usr/share/neomutt/oauth2/mutt_oauth2.py \
|
||||
-v \
|
||||
-t \
|
||||
--authorize \
|
||||
--client-id "9e5f94bc-e8a4-4e73-b8be-63364c29d753" \
|
||||
--client-secret "" \
|
||||
--email "your-email-here" \
|
||||
--provider microsoft \
|
||||
$HOME/email-token
|
||||
```
|
||||
|
||||
This will ask you a couple questions.
|
||||
Select `authcode` for the preferred OAuth2 flow.
|
||||
If prompted for a client secret, simply press enter.
|
||||
You should get a link - enter that link into your browser and allow the app.
|
||||
By the end of the flow you should end up at an empty website.
|
||||
Copy the last part of the URL and paste it into your terminal.
|
||||
After this you should have a token file located at `$HOME/email-token`.
|
||||
It's a good idea to take a backup of this file just in case you overwrite it.
|
||||
But if you do loose it, you can just run the flow again.
|
||||
|
||||
## Mutt-Wizard
|
||||
We are almost there!
|
||||
The wonderful Luke Smith has made a neat setup wizard called [mutt-wizard](https://muttwizard.com/).
|
||||
Install (see the mutt-wizard website), run it and enter your email information.
|
||||
After this, you should edit your `~/.mbsyncrc` file, as the default `PassCmd` is not quite configured yet.
|
||||
It should look something like this (make sure to change `your-email-here` and `username` to the appropriate values):
|
||||
|
||||
```
|
||||
...
|
||||
PassCmd "/usr/share/neomutt/oauth2/mutt_oauth2.py --encryption-pipe 'gpg -e -r your-email-here' /home/username/email-token"
|
||||
...
|
||||
```
|
||||
|
||||
You should now be able to run `mailsync` (installed with mutt-wizard):
|
||||
|
||||
```sh
|
||||
mailsync
|
||||
```
|
||||
|
||||
It might ask you to select which profile to sync.
|
||||
Just provide the name you set when setting up your gpg profile and everything should sync now!
|
||||
After a successful sync, you should be able to just open `neomutt` and start reading, replying and whatever you do with email!
|
||||
|
||||
```sh
|
||||
neomutt
|
||||
```
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
@ -209,3 +209,5 @@ Just focus on writing posts and publish them by merging to `main`. Nice and auto
|
||||
## P.S.
|
||||
You may notice that the CI script on the real blog repository is a bit more complicated than what we've went through in this post, but the extra complexity only comes from some stupid technicalities regarding my build server being based on ARM rather than x86 (raspberry pi).
|
||||
The script we made here is plenty good to get you started.
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
||||
|
210
content/posts/how-to-crowdsec.md
Normal file
210
content/posts/how-to-crowdsec.md
Normal file
@ -0,0 +1,210 @@
|
||||
+++
|
||||
date = '2025-04-15'
|
||||
draft = true
|
||||
title = "How to Set Up Crowdsec"
|
||||
tags = ["howto", "tutorial", "web", "securoty"]
|
||||
categories = ["technical"]
|
||||
+++
|
||||
## Crowdsec
|
||||
|
||||
> NOTE: This configuration blocks *many* varieties of clients and services. You might want to whitelist your own ISP and
|
||||
> / or your own IP ranges (perhaps even your entire country if you're trusting enough) in case your own services and
|
||||
> homebrew experiments gets banned.
|
||||
|
||||
Short for [crowdsecurity](https://www.crowdsec.net/), crowdsec is a community effort to bring auto-banning security to
|
||||
the masses, and it's surprisingly easy to set up. You just have to understand how the thing works.
|
||||
|
||||
|
||||
I noticed that I am getting a lot of suspicious traffic on my gitea instance. Usernames such as `log4j` and `thomad`
|
||||
from china and bulgaria. Yea. Let's enable some fucking security.
|
||||
Fuck I hate that I have to do this, but I guess people will be assholes.
|
||||
|
||||
After [this](https://www.youtube.com/watch?v=-GxUP6bNxF0), the banhammer came down with the might of zeus. Now no-one
|
||||
gets access. Not even me. I tried to do some country-code whitelisting, but that was a bit of a dud. I'm tired now.
|
||||
Will look at it tomorrow.
|
||||
|
||||
Okay! I seem to have it working now! That was an adventure. Will elaborate when I get back home.
|
||||
|
||||
Okay, There's multiple "things" to a crowdsec setup. Crowdsec (the non-paid cloud solution) consists of:
|
||||
- The core crowdsec security engine (`crowdsecurity/crowdsec` container image)
|
||||
- Does the "detection" and hardcore logic and makes decisions.
|
||||
- The bouncer (`fbonalair/traefik-crowdsec-bouncer:latest` container image in my case)
|
||||
- Enforces the decisions.
|
||||
- There are multiple different "types" of bouncers. I just use the forwardAuth type, as that is the most straight
|
||||
forward one. Especially when combined with traefik.
|
||||
|
||||
### Concepts
|
||||
|
||||
In short, there are only a couple of concepts you should know in order to *use* crowdsec. This is
|
||||
Feel free to skip these if you
|
||||
don't care for now, and just want something up and running.
|
||||
|
||||
- Acquisitions
|
||||
- In order for `crowdsec` to know *what* and *where* to look for potential intruders, threats etc. You must tell it
|
||||
in the form of *acquisition* configurations. The easiest thing to do is to just give `crowdsec` access to your docker
|
||||
logs and traefik logs - this is excactly what we're aiming to do.
|
||||
- Parsers
|
||||
- Bucket Overflow
|
||||
- Bouncers
|
||||
|
||||
Note that the core crowdsec security engine should be part of the core traefik/portainer deployment because it will
|
||||
need some elevated privileges. The traefik service should also register some middlewares, so it can't be part of the
|
||||
portainer managed containers / stacks.
|
||||
|
||||
When using Traefik, make sure to add the docker labels that enable traefik trafficing to the containers:
|
||||
|
||||
```yaml
|
||||
# For the new containers.
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=proxy
|
||||
- traefik.http.routers.traefik-bouncer.entrypoints=websecure
|
||||
```
|
||||
|
||||
## "easy"
|
||||
|
||||
|
||||
This shit was not easy to set up. But it is easy to maintain. Keep a "new"/"learning" mind, and all should be fine.
|
||||
|
||||
## Configuring the Bouncer
|
||||
|
||||
Also called "Remediation"
|
||||
I am using the traefik bouncer, that is using
|
||||
[forwardAuth](https://doc.traefik.io/traefik/middlewares/http/forwardauth/) to check if an IP is blocked or not.
|
||||
|
||||
Configure the container in docker compose and afterwards, you should introduce the traefik middleware in the dynamic
|
||||
and static configuration, like so:
|
||||
|
||||
```yaml
|
||||
# dynamic traefik config
|
||||
http:
|
||||
middlewares:
|
||||
traefikBouncer:
|
||||
forwardauth:
|
||||
address: http://traefik-bouncer:8080/api/v1/forwardAuth
|
||||
trustForwardHeader: true
|
||||
```
|
||||
|
||||
```yaml
|
||||
# static traefik config
|
||||
entryPoints:
|
||||
http:
|
||||
address: ":80"
|
||||
http:
|
||||
middlewares:
|
||||
- traefikBouncer@file
|
||||
https:
|
||||
address: ":443"
|
||||
http:
|
||||
middlewares:
|
||||
- traefikBouncer@file
|
||||
```
|
||||
|
||||
If you have (I do) some other names for the `address: ":443"` and `":80"` middlewares, don worry, just add the
|
||||
`traefikBouncer@file` to the list of middlewares and you should be good.
|
||||
|
||||
You will have to register your bouncer through the `cscli` as well:
|
||||
|
||||
```sh
|
||||
docker exec crowdsec cscli bouncers list
|
||||
docker exec crowdsec cscli bouncers add traefikBouncer
|
||||
```
|
||||
|
||||
This should give you an API key. Place it in the environment variable `CROWDSEC_BOUNCER_API_KEY: <your-key-here>`.
|
||||
Additionally, you should add the `CROWDSEC_AGENT_HOST: crowdsec:8080` environment variable (assuming the crowdsec
|
||||
docker _service_ is called `crowdsec`) - the port is standard and you don't need to portmap or expose anything btw.
|
||||
|
||||
### Crowdsec Core Security Engine Configuration
|
||||
|
||||
In order for the crowdsec security engine to be able to detect intruders, it needs access to the logs of the other
|
||||
containers on the server. To do this, you can just volume mount: `/var/run/docker.sock:/var/run/docker.sock:ro` and
|
||||
then
|
||||
|
||||
Check out [https://app.crowdsec.net/hub/configurations](https://app.crowdsec.net/hub/configurations) if there are logparsers available for the service you want
|
||||
to integrate.
|
||||
|
||||
#### Acquisitions
|
||||
|
||||
In the `acquis.d` directory (volume mapped into the `crowdsec` container to `./acquis.d:/etc/crowdsec/acquis.d`),
|
||||
you should add YAML files for each source you want the crowdsec engine to scan for criminals and other scum:
|
||||
|
||||
```txt
|
||||
acquis.d/
|
||||
├── gitea.yaml
|
||||
└── traefik.yaml
|
||||
```
|
||||
|
||||
File Contents:
|
||||
|
||||
```yaml
|
||||
# traefik.yaml
|
||||
filenames:
|
||||
- /var/log/traefik/*
|
||||
labels:
|
||||
type: traefik
|
||||
```
|
||||
|
||||
```yaml
|
||||
# gitea.yaml
|
||||
source: docker
|
||||
container_name:
|
||||
- gitea
|
||||
labels:
|
||||
type: gitea
|
||||
```
|
||||
|
||||
`traefik.yml` is a `filename` based acquisition file, meaning you need to configure the `traefik` container to
|
||||
output access and system logs into a directory that is volume-mapped so that it's available to the crowdsec
|
||||
container (`traefik-logs:/var/log/traefik/:ro` and associated `traefik-logs:/var/log/traefik/` on traefik).
|
||||
|
||||
The acquisition file for the `gitea` service is using the `docker` source. So it'll read the `docker logs`. The
|
||||
cool thing about this, is that you dont have to do any extra configuration on the gitea side.
|
||||
|
||||
To configure `traefik` to output logs into a file (default it just outputs to stdout/stderr for no-one to read),
|
||||
add the following to your static config (`traefik.yml`) - make sure to `docker compose up -d --force-recreate`
|
||||
every time you edit the config (and want to apply the changes):
|
||||
|
||||
```yaml
|
||||
# ... at the end of traefik.yml
|
||||
log:
|
||||
level: INFO
|
||||
filePath: /var/log/traefik/traefik.log
|
||||
accessLog:
|
||||
filePath: /var/log/traefik/access.log
|
||||
```
|
||||
|
||||
Also, in docker compose file, install some collections:
|
||||
|
||||
```yaml
|
||||
# in crowdsec container spec
|
||||
environment:
|
||||
GID: "$(GID-1000)"
|
||||
COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik crowdsecurity/whitelist-good-actors LePresidente/gitea"
|
||||
```
|
||||
|
||||
#### Geofenching
|
||||
|
||||
You might have lost the bouncer - check with `docker exec crowdsec cscli bounders list`.
|
||||
|
||||
I am hosting some services that may produce some false-flags by crowdsec, so I will be whitelisting my country. To
|
||||
do this, we need to register a country-code whitelist
|
||||
[postoverflow](https://docs.crowdsec.net/docs/whitelist/create_postoverflow/) in the `postoverflows` directory,
|
||||
which is volume mapped `./postoverflows:/etc/crowdsec/postoverflows/`:
|
||||
|
||||
```yaml
|
||||
# postoverflow/s01-whitelist/sc-countries-whitelist.yaml
|
||||
name: my/whitelist
|
||||
description: Whitelist trusted regions
|
||||
whitelist:
|
||||
reason: Whitelisted country
|
||||
expression:
|
||||
- "evt.Enriched.IsoCode == 'DK'" # NO! Not anymore!
|
||||
```
|
||||
|
||||
Note that the data is not "enriched" with the IsoCode yet. You need to install the `geoip-enrich` thing:
|
||||
|
||||
```sh
|
||||
docker exec crowdsec cscli parsers install crowdsecurity/geoip-enrich
|
||||
```
|
||||
|
||||
This solution is not very sophisticated, so I might change this to something less "sledgehammer"-y in the future.
|
316
content/posts/how-to-portainer.md
Normal file
316
content/posts/how-to-portainer.md
Normal file
@ -0,0 +1,316 @@
|
||||
+++
|
||||
date = '2025-04-14'
|
||||
draft = true
|
||||
title = "How to Host Docker Containers Easily in The Cloud"
|
||||
tags = ["howto", "tutorial", "web"]
|
||||
categories = ["technical"]
|
||||
+++
|
||||
|
||||
In this post, we will be going over how to set up a [portainer](https://www.portainer.io/) managed docker environment,
|
||||
and how to use it. This is ideal if you want to host a personal website, a [blog](/posts/how-to-blog), a personal
|
||||
[github](git.gtz.dk) or whatever your development heart desire.
|
||||
If you choose to follow along, by the end of it, you will have an environment where you can just add or remove docker
|
||||
based services at a whim using a nice web-based interface.
|
||||
|
||||
I assume that you already know about `docker` and `docker compose` yaml syntax. If you don't, may I recommend the
|
||||
wonderful official [docker tutorial](https://docs.docker.com/get-started/workshop/) - once you're done with that come
|
||||
back here. Or just read on and roll with the punches.
|
||||
|
||||
Oh yea, you should also have good knowledge and experience working on GNU/Linux systems, as you'll be doing a lot of
|
||||
management and interaction with the terminal both during the setup process and during maintenance.
|
||||
|
||||
## Server
|
||||
|
||||
The very first thing to get is a server. This can either be the machine you're currently using if you don't want
|
||||
to mess around on the public internet, or it could be an actual desktop you have set up with a public IP. Or
|
||||
it could be a VPS (Virtual Private Server) - which is just a fancy word for a "cloud computer" that someone
|
||||
else hosts and powers, and you just get an SSH connection to it. Any VPS provider will work, but [digital
|
||||
ocean](https://www.digitalocean.com/) or [linode](https://www.linode.com/) are very affordable and easy to use
|
||||
VPS providers. As long as you get a VPS and avoid a *webhotel*, you should be fine (side note: web hotels are a
|
||||
scam and you shouldn't ever use them - especially not if you're tech-savvy enough to read this blog).
|
||||
|
||||
Once you have your server, [install](https://docs.docker.com/engine/install/) docker on it. Preferably the latest
|
||||
version.
|
||||
|
||||
## Traefik and Portainer
|
||||
|
||||
Traefik is a load balancer / application proxy that makes it easy for you to route network traffic into your various
|
||||
services on your server. By using traefik, you can have multiple docker containers, each providing their own service on
|
||||
a single server, and traefik just routes user traffic based on the URL request, or ports used.
|
||||
|
||||
Portainer is a web-based docker container management GUI (Graphical User Interface) - if you've tried Docker Desktop,
|
||||
think if portainer as a web-based version of that.
|
||||
|
||||
Getting traefik and portainer up and runinng is done by creating a new `docker-compose.yml` file on your server
|
||||
and adding them as individual services. Just to keep things tidy, you should make a directory for all you are
|
||||
going to do here. Do the following on your server.
|
||||
|
||||
```sh
|
||||
# Make the config directory in your $HOME dir - this is where
|
||||
# we'll be working throughout the tutorial. If not specified
|
||||
# otherwise, you should only be editing files inside this directory.
|
||||
mkdir -p ~/config
|
||||
mkdir -p ~/config/traefik-data
|
||||
mkdir -p ~/config/portainer-data
|
||||
cd ~/config
|
||||
|
||||
# Create an empty yaml file
|
||||
touch docker-compose.yml
|
||||
```
|
||||
|
||||
It might be a good idea to initialize the `config` directory as a (local) `git` project. That way you will always have
|
||||
a history of what you have been done, and what you did when you (inevitably) break things. This I will leave up to you
|
||||
though (you should gitignore the `portainer-data` directory, since that's managed by portainer and may contain a bunch
|
||||
of stuff you don't want).
|
||||
|
||||
Inside the new `docker-compose.yml` file, you should put the following content. Simply open the file using your favorite
|
||||
terminal text editor and paste the following. Note! Don't start the stack yet - we still need to configure a bunch of
|
||||
things.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:latest
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- proxy
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./traefik-data/traefik.yml:/traefik.yml:ro
|
||||
- ./traefik-data/acme.json:/acme.json
|
||||
- ./traefik-data/configurations:/configurations
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=proxy
|
||||
- traefik.http.routers.traefik-secure.entrypoints=websecure
|
||||
- traefik.http.routers.traefik-secure.rule=Host(`traefik.example.com`)
|
||||
- traefik.http.routers.traefik-secure.service=traefik
|
||||
- traefik.http.routers.traefik-secure.middlewares=user-auth@file
|
||||
- traefik.http.routers.traefik-secure.service=api@internal
|
||||
environment:
|
||||
- "CF_DNS_API_TOKEN=" # ADD YOUR OWN DNS API TOKEN HERE
|
||||
- "CF_ZONE_API_TOKEN=" # ADD YOUR OWN DNS API TOKEN HERE
|
||||
|
||||
portainer:
|
||||
image: portainer/portainer-ce:alpine
|
||||
container_name: portainer
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
networks:
|
||||
- proxy
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./portainer-data:/data
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=proxy
|
||||
- traefik.http.routers.portainer-secure.entrypoints=websecure
|
||||
- traefik.http.routers.portainer-secure.rule=Host(`portainer.example.com`)
|
||||
- traefik.http.routers.portainer-secure.service=portainer
|
||||
- traefik.http.services.portainer.loadbalancer.server.port=9000
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
Whew! That's a lot. Let's break it down. We define two services `traefik` and `portainer`. Starting with the things that
|
||||
are common to both of them, we set the initial niceties, such as the `container_name`, restart policy, security options
|
||||
and set their shared network to be the externally defined `proxy` network. Both services need (read-only) access to the
|
||||
system time for various reasons, so we volume mount `/etc/localtime` to their respective internal `/etc/localtime`. They
|
||||
also both need access to the system docker socket, so we also volume mount that in (again, read-only). Then we map the
|
||||
various configuration directories to their respective services.
|
||||
|
||||
If you haven't used `traefik` before, you might be scratching your head on the `labels` that we set on each of the
|
||||
services. This is just how you configure services to integrate into traefik, enabling you to route your various
|
||||
containers to various subdomains, integrate middle-wares such as forcing HTTPS and setting load-balancer settings etc.
|
||||
|
||||
The `CF_DNS_API_TOKEN` and `CF_ZONE_API_TOKEN` tokens are our cloudflare API keys. If you're using a different DNS
|
||||
provider, you should check the [traefik documentation](https://doc.traefik.io/traefik/https/acme/#providers) to see if
|
||||
your provider is supported, and change the environment variable names accordingly.
|
||||
|
||||
Since the configuration directories are currently empty, the setup won't work yet. Let's add the traefik configuration
|
||||
files first:
|
||||
|
||||
```sh
|
||||
cd ~/config/traefik-data
|
||||
mkdir -p configurations
|
||||
touch traefik.yml
|
||||
touch configurations/dynamic.yml
|
||||
```
|
||||
|
||||
The `traefik.yml` file contains your general traefik configuration. This is where you register certificates, enforce
|
||||
HTTPS and set general settings. The content we're interested in having is the following:
|
||||
|
||||
```yaml
|
||||
api:
|
||||
dashboard: true
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
websecure:
|
||||
address: ":443"
|
||||
http:
|
||||
middlewares:
|
||||
- secureHeaders@file
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: your-email-here
|
||||
storage: acme.json
|
||||
keyType: EC384
|
||||
dnsChallenge:
|
||||
provider: cloudflare
|
||||
delayBeforeCheck: 0
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
exposedByDefault: false
|
||||
file:
|
||||
filename: /configurations/dynamic.yml
|
||||
```
|
||||
|
||||
The first `api` section is pretty self-explanatory enables the web-ui dashboard. You can choose not to do
|
||||
this if you don't want the traefik web dashboard. The `entryPoints` section is a bit more interesting. This
|
||||
is where we enforce that all HTTP web-requests on port `80` will be redirected to port `443` using transport
|
||||
layer security (TLS). You might notice that we specifically mention `letsencrypt` here, this leads us
|
||||
to the `certificatesResolvers` section. Since I am using [cloudflare](https://www.cloudflare.com/)
|
||||
as my DNS (Domain Name Service) provider, I can also use them as my TLS certificate provider as
|
||||
they provide this service. This is a complex topic and if you're interested, I recommend reading
|
||||
[this](https://blog.cloudflare.com/introducing-automatic-ssl-tls-securing-and-simplifying-origin-connectivity/)
|
||||
blog post by cloudflare themselves. Boiling all this jargan down, we are just using cloudflare as a middleman to
|
||||
help us get the little lock icon in the browser when someone visits our website(s). I've set the certificates to
|
||||
automatically update, so I don't have to worry about it ever again.
|
||||
|
||||
The `providers` settings refer to where traefik can route internet traffic to. We simply register `docker` as a service
|
||||
provider as well as the configurations we define in `configurations/dynamic.yml`. Let's take a look at the content of
|
||||
that file.
|
||||
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
secureHeaders:
|
||||
headers:
|
||||
sslRedirect: true
|
||||
forceSTSHeader: true
|
||||
stsIncludeSubdomains: true
|
||||
stsPreload: true
|
||||
stsSeconds: 31536000
|
||||
user-auth:
|
||||
basicAuth:
|
||||
users:
|
||||
- "administrator:<password>" # ADD YOUR ADMIN PASSWORD HERE ()
|
||||
|
||||
tls:
|
||||
options:
|
||||
default:
|
||||
cipherSuites:
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
|
||||
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
|
||||
minVersion: VersionTLS12
|
||||
```
|
||||
|
||||
Starting in the `http.middlewares` section, we first register a TLS middleware that we call `secureHeaders` (note that
|
||||
this is the middleware referred in `traefik.yml`) - skipping past the details, this middleware simply adds security
|
||||
headers to each request. Our second middleware, `user-auth` is the authentication method to gain access to the traefik
|
||||
dashboard. Here we set the username `username` and you should generate the password using the `htpasswd` command. This
|
||||
command should be available through the `apache2-utils` package on ubuntu systems, and `extra/apache` on Arch. Simply
|
||||
copy / paste the generated hashed password into your yaml file.
|
||||
|
||||
```sh
|
||||
# -n = output to stdout -B = use bcrypt
|
||||
# Make sure to replace 'administrator' if you want a different username
|
||||
htpasswd -nB administrator
|
||||
```
|
||||
|
||||
## Starting Everything
|
||||
|
||||
We should now have everything set up and ready for starting! Simply navigate to the `~/control` directory and start the
|
||||
docker compose stack.
|
||||
|
||||
```sh
|
||||
# Start the containers (detached)
|
||||
docker compose up -d
|
||||
|
||||
# Follow along with the logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
Hopefully there shouldn't be any errors, but if there are, make doubly sure that your TLS settings are set correctly,
|
||||
as that's likely to be the thing to mess up (ask me how I know). If you need additional assistance, the [official
|
||||
traefik docs](https://doc.traefik.io/traefik/) are a great resource. Portainer is fairly fool-proof, so I don't expect
|
||||
that to cause you any problems.
|
||||
|
||||
## TODO
|
||||
- [ ] DNS records, ACME challenges, TXT records, Wildcard A records, CAA records - jesus there's so much shit I've forgotten
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgresql:
|
||||
image: postgres:16
|
||||
environment:
|
||||
- POSTGRES_USER=keycloak
|
||||
_ POSTGRES_DB=keycloak
|
||||
- POSTGRES_PASSWORD=secret
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- keycloak
|
||||
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloa/keycloak:22
|
||||
restart: always
|
||||
command: start
|
||||
depends_on:
|
||||
- postgresql
|
||||
environment:
|
||||
# traefik handles ssl
|
||||
- KC_PROXY_ADDRESS_FORWARDING=true
|
||||
- KC_HOSTNAME_STRUCT=false
|
||||
- KC_HOSTNAME=keycloak.gtz.dk
|
||||
- KC_PROXY=edge
|
||||
- KC_HTTP_ENABLED=true
|
||||
# connect to the postgres thing
|
||||
- DB=keycloak
|
||||
- DB_URL='jdbc:postgresql://postgres:5432/postgresql?ssl=allow'
|
||||
- DB_USERNAME=keycloak
|
||||
- DB_PASSWORD=secret
|
||||
- KEYCLOAK_ADMIN=admin
|
||||
- KEYCLOAK_ADMIN_PASSWORD=admin
|
||||
networks:
|
||||
- proxy
|
||||
- keycloa
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- port=8080
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
keycloak:
|
||||
```
|
102
content/posts/neorg.md
Normal file
102
content/posts/neorg.md
Normal file
@ -0,0 +1,102 @@
|
||||
+++
|
||||
date = '2024-11-27'
|
||||
draft = true
|
||||
title = 'Moving Away From Neorg'
|
||||
tags = ['tutorial']
|
||||
categories = ['technical', 'life']
|
||||
+++
|
||||
|
||||
Neorg is a great neovim plugin, but it's in that awkward state that many free and open source projects are in where
|
||||
it's only actively maintained by one guy, and the whole thing is apparently undergoing a rewrite. For me it was a bit
|
||||
of a honeypot trap, as I saw the tagline **"Modernity meets insane extensibility"** and thought that there surely was
|
||||
many, easy to use plugins that could extend the default functionality. There's obviously the great `awesome` list for
|
||||
neorg [here](https://github.com/nvim-neorg/awesome-neorg) but most of these plugins have been abandonned or won't work
|
||||
well with the new rewrite.
|
||||
Don't get me wrong, the default neorg experience is actually amazing and the syntax is clearly (subjectively) superior
|
||||
to regular emacs org-mode, but I'm not sure if either of those can compete with the increasingly standardized markdown
|
||||
syntax. In any case, when you are trusting your most personal inner thoughts with a syntax, you should really strive
|
||||
to use something that has (Neorg actually fails in both of these points):
|
||||
|
||||
1. Survived the bathtub curve, and
|
||||
2. Won't lock you down to a specific tool.
|
||||
|
||||
The second point is probably the most important one. A general life-advise that I first heard from Luke Smith is that
|
||||
whenever you have a life decision to make, you should favor the choice that maximises your personal freedom. i.e. If
|
||||
one of the choices limit you to only being able to do a thing in one way, using only one tool, provided, managed and
|
||||
maintained by one entity (company or person) and the other enables you to do the thing however you want. Or at least
|
||||
you can do the thing in multiple ways. You should strongly prefer the second option - even if it's a bit less
|
||||
convenient and less sleek or sexy.
|
||||
|
||||
We got a bit off track there. Let's get back to how to migrate away from Neorg. I have a couple of criteria for such a
|
||||
tool that I need.
|
||||
|
||||
## Criteria
|
||||
|
||||
Any agenda-ing and kanban-ing will (and should) be done from a separate program. This <u>should</u> also be possible with
|
||||
neorg, but the problem here is that the syntax is not that popular (yet), so not many programs actually support it.
|
||||
Finding such programs is a separate tools-search though, and should be done based on the syntax decision.
|
||||
|
||||
- Conceal level (trivial if there's native treesitter support)
|
||||
- Folding (`set foldmethod=expr` see [https://www.jmaguire.tech/posts/treesitter_folding/](https://www.jmaguire.tech/posts/treesitter_folding/))
|
||||
- Quickly marking checklists as done (e.g. `<C-space>` or `<leader>td`)
|
||||
- Standardized syntax that is widely used
|
||||
- Pressing `<Return>` to follow links (create if not exists)
|
||||
- `image.nvim` support - preferably through `snacks.nvim`
|
||||
|
||||
## Markdown
|
||||
|
||||
Just raw markdown might be the way to go. I can set `conceallevel=2` for prettier text whilst editing. There's only
|
||||
a couple of things that I want that I might need to mold out using custom things.
|
||||
- Follow links: [https://github.com/jghauser/follow-md-links.nvim](https://github.com/jghauser/follow-md-links.nvim)
|
||||
|
||||
|
||||
### Converting from Neorg
|
||||
|
||||
Neorg have an integrated markdown exporter which works fairly okay. It gets links a bit messed up - especially if it
|
||||
is a link to [another neorg file](#indexmd).
|
||||
|
||||
## Vimwiki
|
||||
|
||||
[plugin page](https://github.com/vimwiki/vimwiki)
|
||||
|
||||
This is a great plugin, but it is <u>just</u> a tad too "vim-pilled" and a bit difficult to get to do _excactly_ what I
|
||||
want from a note-taking system. The primary issue is that `vimwiki_global_ext = 0` doesn't do the thing it says. The
|
||||
`SYNTAX` is registered in neovim with as `vimwiki`, making any markdown based plugins useless. e.g. folding is
|
||||
impossible to do (in a good way), because treesitter does not have a vimwiki parser, and if you set the syntax to
|
||||
markdown, the actual syntax in the file is still not vimwiki.
|
||||
This is a bit dissapointing, because vimwiki is actually <u>great</u>.
|
||||
|
||||
```lua
|
||||
vim.g.vimwiki_list = {{
|
||||
syntax = "markdown",
|
||||
ext = ".md"
|
||||
}}
|
||||
vim.g.vimwiki_global_ext = 0
|
||||
|
||||
-- In your list of lazy.nvim plugins:
|
||||
"vimwiki/vimwiki"
|
||||
```
|
||||
|
||||
### Converting from Neorg
|
||||
|
||||
If using markdown as the vimwiki syntax, it should be the same procedure as [Markdown](#markdown). Otherwise, `pandoc` can
|
||||
probably get you there if you use markdown as a middle-step.
|
||||
|
||||
## Org-mode
|
||||
|
||||
[plugin page](https://github.com/nvim-orgmode/orgmode).
|
||||
|
||||
This is using the traditional org-mode syntax. This used by almost all emacs users, so it'll make it easy to change
|
||||
editor to emacs if I ever decide I want that. Neovim is nice and cool, but it's also very new and not even version 1
|
||||
yet.
|
||||
|
||||
### Converting from Neorg
|
||||
|
||||
You can do a two-step conversion from `.norg` to `.markdown` (see [Markdown](#markdown)) to `.org` (see
|
||||
[https://emacs.stackexchange.com/questions/5465/how-to-migrate-markdown-files-to-emacs-org-mode-format](https://emacs.stackexchange.com/questions/5465/how-to-migrate-markdown-files-to-emacs-org-mode-format)).
|
||||
|
||||
|
||||
# Conclusion
|
||||
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
96
content/posts/xbox-modding.md
Normal file
96
content/posts/xbox-modding.md
Normal file
@ -0,0 +1,96 @@
|
||||
+++
|
||||
date = '2025-01-27'
|
||||
draft = true
|
||||
title = 'Softmod your Xbox Original Today'
|
||||
tags = ['technical', 'games', 'modding']
|
||||
categories = ['technical']
|
||||
+++
|
||||
|
||||
The original Xbox is a phenominal little machine.
|
||||
In this post I will go over my journey of modding my own personal xbox.
|
||||
Feel free to follow along, but this is mostly just a recollection of my journey for the sake of writing it down.
|
||||
If you want to skip the personal story, the tutorial part starts [here]().
|
||||
|
||||
## First Things First
|
||||
If you own an Xbox Original and you haven't removed the clock capacitor yet, DO IT NOW. YOU SHOULD'VE DONE IT SEVERAL
|
||||
YEARS AGO, IT *WILL* KILL YOUR XBOX.
|
||||
Even if you are not sure if it's removed or not, please check to make sure. This is incredibly important.
|
||||
|
||||
With that out of the way, let's begin.
|
||||
|
||||
## The Beginnings
|
||||
It all started with I was about 14 years old.
|
||||
I remember it clearly.
|
||||
It was early 2010 and I had saved up my allowance for a while and wanted to buy something for myself.
|
||||
So as any 14 year old buy with marginal financial freedom, I went to the local GameStop just to browse.
|
||||
I was already an avid Halo fan, so I was looking around at the Halo 3 and Gears of War copies that they had, as well as
|
||||
the other xbox 360 games showing off on the store shelves.
|
||||
But alas, I did not own an Xbox 360, or any (real) videogame console for that matter.
|
||||
So I opted to buy something else, I don't remember what excactly.
|
||||
What I do remember is that when I went up to the counter, I saw that they had a used Xbox original (back then we called
|
||||
it the xbox 1) for sale!
|
||||
And only for 1001kr! Which was... not excactly cheap at the time, but hey, I didn't know better.
|
||||
I had saved up just over 1000kr! And the Halo 2 Collectors edition was bundled with it! Holy crap!!
|
||||
This was a match made in heaven and I bought it on the spot in favor of whatever else I wanted.
|
||||
Proud, and with my heart pumping (this was the biggest purchase I had ever done at the time), I took it home and
|
||||
deliberately hid it from my mother, because she wouldn't approve of me spending my hard earned allowance on a videogame
|
||||
console.
|
||||
|
||||
A couple of years earlier, my sister and I received a small CRT TV with an in-built DVD player for our rooms so we
|
||||
could watch movies and (some) TV in our rooms. This CRT had an S-VIDEO input.
|
||||
I remember that it was such an adventure trying to figure out how to plug the Xbox to the TV. The figure-8 cable scared
|
||||
me when it sparked when I plugged it into the Xbox and I thought I broke it, but I just had to change the input on the
|
||||
TV. And when I finally got it working I was rewarded with the comforting green glow of the internal clock needing to be
|
||||
set. I promptly pressed 'A' without changing anything, inserted the Halo 2 disc and played for the first time on my very
|
||||
own video game console.
|
||||
|
||||
I sneak-play'ed so much Halo 2, that I missed a lot of homework, and sleep. I distinctly remember one night I played
|
||||
(with no sound mind you) for uncountable hours. Oh to be a kid again. I know that at one point my mom found out and she
|
||||
didn't actually care that I "wasted" the money. She only cared about my bedtime (ugh!) and my homework (double ugh!) -
|
||||
which is fair, but still.
|
||||
|
||||
A couple months after the purchase, I wanted to try out the Xbox Live features and play Halo 2 online (I did not know
|
||||
you'd have to pay for it) so I found a way to connect an ethernet cable to the box and tried connecting.
|
||||
But I was not able to get any connection. I kept trouble-shooting and then I realized that LITERALLY THE WEEK BEFORE
|
||||
Microsoft had closed the Xbox Original live service down. What a bummer dude. Welp. At least I had the Halo 2 campaign.
|
||||
|
||||
## Getting a Taste for Modding
|
||||
Much later. I am now in my ??'s.
|
||||
|
||||
TODO:
|
||||
- Building my own PC
|
||||
- Building skills
|
||||
- Fixing my laptop (which broke all the time)
|
||||
- Modding the Wii
|
||||
- Modding the Playstation 2
|
||||
|
||||
The first game console that I modded was a Wii that I bought on a flea-market for next to nothing.
|
||||
Side tangent: The Wii is the _easiest_ console to softmod. You only need an SDCard - that's it.
|
||||
This Wii modding lit a fire under me, and I started taking apart
|
||||
|
||||
## Softmodding the Xbox
|
||||
There are a couple of directions you can take when it comes to modding the OG Xbox.
|
||||
I will be exclusively *softmodding* mine, as if I were to solder anything that is required for hardmodding it, I would
|
||||
at best brick the console and at worst burn my apartment to the ground.
|
||||
This mod _does_ require purchasing some hardware, namely:
|
||||
- An xbox (male) to USB (female) adapter.
|
||||
These are increasingly difficult to find, so if you tend to drag your feet on projects like these (like I tend to)
|
||||
buy it now!
|
||||
- An older USB stick
|
||||
- A DVD burner
|
||||
- Some (writable / blank) DVDs
|
||||
|
||||
TODO:
|
||||
- Full list of required hardware
|
||||
- Link to MrMario (check for peertube link as a backup)
|
||||
- Xbox Controller USB thingy
|
||||
- Xbox softmodding tool disc
|
||||
- Extras (chimp)
|
||||
- Holy crap the IDE hot-swapping
|
||||
|
||||
## Upgrading the Xbox
|
||||
Now that we have softmodded it, we can choose to upgrade the aging IDE harddrive with a slightly newer and larger
|
||||
harddrive! This is totally optional, but I highly recommend it as it'll enable you to store many more games on the
|
||||
console itself, rather than mucking about with DVD discs and a dying DVD drive.
|
||||
|
||||
{{< centered image="/6616144.png" >}}
|
BIN
static/6616144.png
Normal file
BIN
static/6616144.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
21
static/example_similarities.csv
Executable file
21
static/example_similarities.csv
Executable file
@ -0,0 +1,21 @@
|
||||
1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0492 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.2353 0.0000 0.0000 0.2182
|
||||
0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 1.0000 0.0000 0.1694 0.0510 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.1186 0.0371 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.1694 0.0000 1.0000 0.2748 0.0286 0.2232 0.2697 0.1424 0.0000 0.0000 0.2443 0.2312 0.0000 0.1602 0.0000 0.0000 0.2273 0.1699
|
||||
0.0000 0.0000 0.0000 0.0510 0.0000 0.2748 1.0000 0.0480 0.0627 0.0761 0.0432 0.0000 0.0000 0.2907 0.1857 0.0000 0.0543 0.0000 0.0000 0.0737 0.0517
|
||||
0.0492 0.0000 0.0000 0.0000 0.0000 0.0286 0.0480 1.0000 0.0520 0.0000 0.0634 0.0000 0.0739 0.1664 0.1848 0.1117 0.0370 0.2022 0.0739 0.2066 0.1055
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.2232 0.0627 0.0520 1.0000 0.0000 0.0000 0.0000 0.0000 0.1581 0.0518 0.0000 0.0000 0.0597 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.2697 0.0761 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.2148 0.0595 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.1424 0.0432 0.0634 0.0000 0.0000 1.0000 0.0000 0.0000 0.1091 0.0304 0.0000 0.0000 0.0391 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0739 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000 0.1010 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.1186 0.0000 0.2443 0.2907 0.1664 0.1581 0.2148 0.1091 0.0000 0.0000 1.0000 0.2803 0.0000 0.1291 0.0000 0.0000 0.2894 0.1543
|
||||
0.0000 0.0000 0.0000 0.0371 0.0000 0.2312 0.1857 0.1848 0.0518 0.0595 0.0304 0.0000 0.0000 0.2803 1.0000 0.0000 0.0375 0.0560 0.0000 0.2090 0.0748
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.1117 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0830 0.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.1602 0.0543 0.0370 0.0000 0.0000 0.0000 0.0000 0.0000 0.1291 0.0375 0.0000 1.0000 0.0000 0.0000 0.0000 0.0000
|
||||
0.2353 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.2022 0.0597 0.0000 0.0391 0.0000 0.1010 0.0000 0.0560 0.0830 0.0000 1.0000 0.0000 0.0000 0.2176
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0739 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000 0.0000 0.0000
|
||||
0.0000 0.0000 0.0000 0.0000 0.0000 0.2273 0.0737 0.2066 0.0000 0.0000 0.0000 0.0000 0.0000 0.2894 0.2090 0.0000 0.0000 0.0000 0.0000 1.0000 0.0650
|
||||
0.2182 0.0000 0.0000 0.0000 0.0000 0.1699 0.0517 0.1055 0.0000 0.0000 0.0000 0.0000 0.0000 0.1543 0.0748 0.0000 0.0000 0.2176 0.0000 0.0650 1.0000
|
|
BIN
static/malloc-std.png
Executable file
BIN
static/malloc-std.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
static/neomutt-screenshot.png
Normal file
BIN
static/neomutt-screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 274 KiB |
77
static/sentence_similarity_graph.gnuplot
Executable file
77
static/sentence_similarity_graph.gnuplot
Executable file
@ -0,0 +1,77 @@
|
||||
# Copyright 2019 sillydan1
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# Sentence similarity graph plotter
|
||||
# uncomment this for manual operation of the dataset plotted
|
||||
# my_dataset = "./sentence_similarities.csv" # ARG1
|
||||
set parametric
|
||||
set size square
|
||||
|
||||
# Styling
|
||||
set pointsize 7.5
|
||||
set style fill solid 1.0 border rgb 'grey30'
|
||||
set style line 1 lc rgb 'black' pt 6 lw 0.5
|
||||
|
||||
# Basically a one-dimensional circular coordinate system
|
||||
fx(t) = cos(t)
|
||||
fy(t) = sin(t)
|
||||
rownum = floor(system("wc -l ".my_dataset."")) +1
|
||||
coord(k) = (k/real(rownum))*(2*pi)
|
||||
fxx(t) = cos(coord(t))
|
||||
fyy(t) = sin(coord(t))
|
||||
|
||||
set trange [0:2*pi-(coord(1.0))]
|
||||
set sample rownum
|
||||
set noborder
|
||||
unset tics
|
||||
set xrange [-1.2:1.2]
|
||||
set yrange [-1.2:1.2]
|
||||
set title "Sentence inter-similarity graph"
|
||||
set multiplot
|
||||
refloptimization = 0
|
||||
do for [i = 0:rownum-1] {
|
||||
do for [j = refloptimization:rownum-1] {
|
||||
if (i != j) {
|
||||
# Get how many columns there are in the dataset.
|
||||
arrwidth = real(system("awk 'FNR == ".(i+1)." {print $".(j+1)."}' ".my_dataset.""))
|
||||
if (arrwidth > 0.0) {
|
||||
bubblerad = 0.125
|
||||
x1 = fxx(i)
|
||||
y1 = fyy(i)
|
||||
x2 = fxx(j)
|
||||
y2 = fyy(j)
|
||||
|
||||
dvx = x2-x1
|
||||
dvy = y2-y1
|
||||
dvl = sqrt((dvx ** 2) + (dvy ** 2))
|
||||
x1 = x1 + (dvx/dvl)*bubblerad
|
||||
y1 = y1 + (dvy/dvl)*bubblerad
|
||||
x2 = x2 - (dvx/dvl)*bubblerad
|
||||
y2 = y2 - (dvy/dvl)*bubblerad
|
||||
# Overleaf's arrow-width rendering is pretty terrible,
|
||||
# so we use a color-gradient to determine connection-strength.
|
||||
if (arrwidth > 0.2) {
|
||||
col = "#000000"
|
||||
} else {
|
||||
if (arrwidth < 0.1) {
|
||||
col = "#B8B8B8"
|
||||
} else {
|
||||
col = "#E4E4E4"
|
||||
}
|
||||
}
|
||||
|
||||
set arrow "".i.j."" from x1,y1 to x2,y2 nohead lw 0.5 lc rgb col
|
||||
#set label "H" at (fxx(j)-fxx(i)),(fyy(j)-fyy(i))
|
||||
show arrow "".i.j.""
|
||||
}
|
||||
}
|
||||
}
|
||||
refloptimization = refloptimization + 1
|
||||
}
|
||||
# Plot the circles
|
||||
plot '+' u (fx(t)):(fy(t)) w p ls 1 notitle
|
||||
|
||||
# Plot the sentence labels
|
||||
plot '+' u (fx(t)):(fy(t)):(sprintf("s.%d",$0+1)) with labels notitle
|
1562
static/sentence_similarity_graph_example.svg
Executable file
1562
static/sentence_similarity_graph_example.svg
Executable file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 80 KiB |
Reference in New Issue
Block a user