[{"content":"I don\u0026rsquo;t know how to answer the \u0026ldquo;why?\u0026rdquo; or \u0026ldquo;whyyyyy?\u0026rdquo; or even \u0026ldquo;whe the f***?\u0026rdquo; I have a skin routine. Last year, after I came to Germany, I asked a female friend about how to do skin care. She touched my face and said \u0026ldquo;Knock on wood, you have good skin!\u0026rdquo;. So\u0026hellip; idk why I decided to take extra care of my skin, but I did!\nGeneraly speaking, things like this make me feel good about myself. Like I\u0026rsquo;m doing something positive while not being tortured! It\u0026rsquo;s always fun to rub a creme on your face, or gently message it. Even cleaning the face skin feels refreshing. Everything also smells nice!\nOh\u0026hellip; and yeah, idk why I\u0026rsquo;m not good at excercising, but I really like to do things like this! Weird. I should definitly start going to gym and working out. It is needed for me.\nSo\u0026hellip; I decided to watch a few Youtube videos, and a guide about skin care for men. My routine is super simple! I have a face cleanser that I use first and wash my face with it. It always feels refreshing and nice to use it! I initially bought the Cerave cleaner, but switched to \u0026ldquo;Jack Black Pure Clean Daily Facial Cleanser\u0026rdquo; after that one ended. The cleanser deeply cleans your skin, and almost all of the bad things that might be on your skin. You don\u0026rsquo;t need to use the cleanser in the morning, but you should definitely use it at night. It\u0026rsquo;s ok to wash your face with water in the morning.\nNext step for me is applying the toner. I use \u0026ldquo;NIVEA Derma Skin Clear Toner\u0026rdquo;. It smells really nice, and is quite refreshing to apply to skin! The toner adjusts the PH of your skin, and deep cleans things that the cleanser could not. I gently rub it and let it dry, no rinsing is required here.\nAfter this I apply an exfoliant to exfoliate my skin. I\u0026rsquo;ve been using \u0026ldquo;Paula\u0026rsquo;s Choice Skin Perfecting 2% BHA Liquid Exfoliant\u0026rdquo; so far, but this time I got \u0026ldquo;BULLDOG Original Exfoliating Face Scrub for Purer Skin\u0026rdquo;. Haven\u0026rsquo;t used it yet, but the Paula\u0026rsquo;s choice one is definitly good. The thing with it is that you don\u0026rsquo;t have to message it, it\u0026rsquo;s not physical, it\u0026rsquo;s an acid. I prefer the scrubby oness, but I think these are better for your skin. I\u0026rsquo;ll see how I like the new one, and if I prefer it or not. No rinsing is required here either.\nI then apply my eye cream which is also from CeraVe. Haven\u0026rsquo;t really seen much of a difference under my eyes, but it is supposed to help. I don\u0026rsquo;t know! It feels good to apply the eye cream regardless.\nThe one to the last step for me is the best one! Moisturiser. Yayy!!! It actually feels weird to use a moisturiser since I\u0026rsquo;ve watched Mortuary Assisant\u0026rsquo;s gameplay, and the last step there for embalming the body is applying moisturiser. Feels weird. The one I use is water based, hence perfect for men. I use \u0026ldquo;Neutrogena Hydro Boost Aqua Gel Moisturiser\u0026rdquo;. As a man if you use oil-based, you\u0026rsquo;ll get acne. So don\u0026rsquo;t.\nI learned to use pads for applying some of these stuff, it feels cooler when using, specifically when you gently tap your face with a cotton pad! ^^\nAnd\u0026hellip; last but not least is applying sun screen. Nothing special here. I just make sure to use SPF 50+ sun screen for better protection.\nEven if it does nothing, it still makes me feel good about myself.\nThe whole routine does not take more than 10 min, but gives me an energy boost, and a lot of good vibes both at night and in the morning!\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/skin_routine/","summary":"\u003cp\u003eI don\u0026rsquo;t know how to answer the \u0026ldquo;why?\u0026rdquo; or \u0026ldquo;whyyyyy?\u0026rdquo; or even \u0026ldquo;whe the f***?\u0026rdquo; I have a skin routine.\nLast year, after I came to Germany, I asked a female friend about how to do skin care.\nShe touched my face and said \u0026ldquo;Knock on wood, you have good skin!\u0026rdquo;.\nSo\u0026hellip; idk why I decided to take extra care of my skin, but I did!\u003c/p\u003e\n\u003cp\u003eGeneraly speaking, things like this make me feel good about myself.\nLike I\u0026rsquo;m doing something positive while not being tortured!\nIt\u0026rsquo;s always fun to rub a creme on your face, or gently message it.\nEven cleaning the face skin feels refreshing.\nEverything also smells nice!\u003c/p\u003e","title":"Skin routine"},{"content":" I didn’t add a warning sign to the room. I taught the logo to breathe. ;-D\nThe Rotunde is a good room for bold ideas and yummy coffee. The door sighs shut and the outside world gives up on us. The only thing it\u0026rsquo;s not good at is ventilating itself. Forty minutes into a group meeting, or a sressful defence, and we all feel like sleeping and running back to our offices!\nSo I crafted the INET logo and gave it a 9-5 job: be a polite barometer. If the air is fresh, it glows cool and calm. If it’s getting stale, it warms up. No blinking warnings, no sound effects — just mood.\nThis post is the story of how I designed the channel and diffuser, printed the parts, cut, layed out, and soldered the snippets of LED strips, wrote the software, and wrapped it all in a systemd service so it wakes up with the Pi. Feel free to grab a cup of coffee and enjoy the post! I\u0026rsquo;ll try to include as much detail as I can.\n1) Sketch → CAD → Print I started the way all sensible hardware projects start: with overconfidence and a sketch. The INET logotype looks simple from the front but it’s a small maze inside. I modeled pockets for each letter in Fusion: a tall I, the curving N, three stacked bars for E, and the long arm of T. Each pocket got:\nMounting bosses for the front diffusers (M2 screws), Cable holes between chambers (rounded to avoid cutting silicone wire), and A small wiring bay for the controller and power. I printed the channel parts in matte black PLA for heat resilience and the diffusers in white PLA at 1-2 mm thick to hide hotspots without wasting brightness. That thickness ended up perfect; you can still see motion, but the individual LED package disappears. The print process took around 24h, including the misprints, and the in-between prints for adjusting the sizes and calibrating everything. Each letter was either printed in multiple parts, or individually so that the logo becomes as large as possible. I used around 1Kg of fillament to print the whole thing.\nThe design process took around 18 iterations, with initial iteration being simple letters in italic form, and the later ones being more similar to the original INET logo of our group. I had some experience with CAD, and had designed some simple things to address my everyday problems, but this was a more serious one. Overall it took me a few days to design the logo and print it part by part. The design is not the most artistic, but as authentic as I could get it.\n2) The Look I Wanted I wasn\u0026rsquo;t building a stage light. The goal was calm: slow waves, breathing brightness, gentle comets. Everything reads through those letter chambers like a tiny light sculpture. The cyan frame in the collage is the CO₂ monitor in a “fresh air” state (more on the color language below). There are many many other animations that I stole from different git repositories, but the most used one is the Co2 monitor with breathing animation. I did want to include more animations other than breathing, but that will be a future me problem with creativity left to spare; right now my creativity well is as dry as the paint on my wall! For now, I\u0026rsquo;m happy with it and want to let it be there doing it\u0026rsquo;s job.\n3) The Tidy Mess Behind the Panel Inside the box there’s a Raspberry Pi zero 2 W, a short run of WS2812B LEDs (78 pixels total), a 5 V 3–4 A supply, jumpers that mind their manners, and two little hardware choices that pay rent every day:\nA 330–470 Ω series resistor on the data line right at the first pixel. It damps ringing and prevents weird flickers. A 1000 µF electrolytic across 5V/GND at the strip start. It handles inrush so the first LED doesn’t faint at boot. I route the strip DIN → DOUT through the chambers and use short silicone jumpers at the bends. Every joint gets heat‑shrink and a tiny dab of hot glue as strain relief. I continuity‑check before power and bring brightness up slowly on a bench supply.\nThe soldering was a big mess. I had never done hardware debugging before. My soldering skills were definitly challenged for this project, as the connections kept breaking when I moved the logo. I did learn how to perform better soldering, but it was already too late. I had to tin the wires after adding flux, and let the soldering wire go up the wire to get stronger connections and leave no naked coppers. A note to remember for next soldering journeys. It took me around two whole days to solder everything and debug it. At some point there was a faulty LED in the middle that caused shorting, and I found and cut it out. This was the most annoying part of the debugging as I had to go through the LEDs, measuring voltage difference to see what is causing the problem. I honestly gave up somewhere in here and thought I won\u0026rsquo;t be able to push through the marathon, but here I am, done with the logo, happy as a four young old licking their ice cream! xD\n4) A Web Panel that Stays Out of the Way The web UI is quiet on purpose: a single navbar, a /control page with big same‑size buttons, a /schedule table, a drag‑and‑drop /calendar, a /status page that updates once a second, and /history charts for CO₂/temperature/humidity with white backgrounds so they’re legible on a projector. Temprature values are not about room, but the box, as I could not mount the sensor outside of the box easily. I just let it sit inside the box. The panel was created with the help of trusty vibe-coding (!). I used chat GPT to create a template, and expanded it for the purpose. Flask made things very easy.\nThere’s also a Safe Mode switch that hides flashier patterns. Meeting rooms are for thinking, not strobe tests, and you never know who might have photosensitive epilepsy, and it\u0026rsquo;s not funny for anyone to fidn this out when looking at your creation. So\u0026hellip; yea, I took precautions not to see people getting seizures looking at my creation. :-)\n5) The Color Language for Air The co2_monitor animation maps CO₂ ppm → color and adds slow motion so it doesn’t feel like a stoplight:\n\u0026lt; 500 ppm: Cyan — outdoor‑like fresh air. 500–799: Green — good. 800–1199: Yellow — getting stale. 1200–1499: Orange — poor; you’ll feel it. 1500–1999: Red — ventilate now. ≥ 2000: Purple — the logo is politely yelling. I blend the readings with an exponential moving average and use hysteresis around thresholds so it doesn’t ping‑pong colors when the room hovers at 800 ppm. Then I ease the current hue toward the target color and apply a very slow breathing brightness. The result feels alive but never flashy.\nBy default, the lights turn off from 20 to 6, then from 6 to 9, and 5 to 8 the standby red pulse is shown. From 9-5 on workdays the logo does it\u0026rsquo;s daily job of Co2 monitoring. Well, technically it does that all the time, but during those hours it shows the Co2 level by it\u0026rsquo;s color, the remaining time it just keeps a record of the Co2, temprature and humidity of room in the database.\n6) The Code — a guided tour (no giant blob, promise) This whole project runs from a single Python file. Think of it like a tiny orchestra:\none thread conducts the LEDs, one listens politely to the CO₂ sensor, one keeps time as the scheduler, and a small web server hands you the baton when you want to improvise.\nBelow is what each section does, with just enough code to be useful (and not enough to make your eyes cross).\n6.1 Configuration (the knobs) Let\u0026rsquo;s define where the LEDs live, how bright they can get, and where to save tiny bits of state (schedule and sensor DB). All of it can be overridden with environment variables so you don’t edit code to change pin numbers.\nLED_COUNT=78, LED_PIN=18, LED_BRIGHT=255 SCHEDULE_PATH=\u0026#34;schedule.json\u0026#34; DB_PATH=\u0026#34;sensor.db\u0026#34; SAFE_MODE=True # hide flashy animations by default Why it’s like this: simple, explicit defaults → less “why is it dark” debugging.\n6.2 Strip setup + frame-diff (no flicker magic) Now initialize rpi_ws281x.PixelStrip and put a shadow frame buffer in front of it.\nframe = [(0,0,0)] * LED_COUNT _dirty = False def _set_pixel(i, r, g, b): # only touch hardware if the value actually changed if frame[i] != (r,g,b): strip.setPixelColorRGB(i, r, g, b) frame[i] = (r,g,b) global _dirty; _dirty = True def flush(): if _dirty: strip.show(); _dirty=False Why it’s like this: LEDs are zen monks—disturb them only when you must. The frame-diff prevents those mysterious “one frame flash” moments that happen when you call show() with identical data.\n6.3 A tiny color wheel helper Classic rainbow helper used by a few animations:\ndef wheel(pos): # 0..255 → (r,g,b) ... Why it’s like this: I\u0026rsquo;ll use it again and again; it keeps color math out of the animations.\n6.4 State \u0026amp; orchestration Let\u0026rsquo;s hold a registry of animations, a stop flag, and the currently playing thread.\nanimations = {} stop_event = threading.Event() current_thread = None def register(name, safe=True): def wrap(fn): animations[name] = {\u0026#34;fn\u0026#34;: fn, \u0026#34;safe\u0026#34;: safe} return fn return wrap def run_animation(fn): # cooperative hand-off: stop current, start next if current_thread and current_thread.is_alive(): stop_event.set(); current_thread.join() stop_event.clear() t = threading.Thread(target=fn, name=fn.__name__, daemon=True) t.start(); globals()[\u0026#34;current_thread\u0026#34;] = t Why it’s like this: adding a new animation is a one-liner @register(\u0026quot;name\u0026quot;). Clean exits prevent torn frames and half-drawn comets.\n6.5 Animations (the mood) Each animation is a loop that checks stop_event.is_set() and draws frames with _set_pixel(...) + flush().\nredpulse — calm breathing in red; great default. colorwave — slow rainbow that reads clearly behind diffusers. comet — a single head with a fading tail orbiting the logo. They all end with finally: clear_strip() so the panel doesn’t freeze on the last pose if you stop mid-frame.\nWhy it’s like this: cooperative loops + frame-diff = smooth, interruption-safe effects.\n6.6 CO₂ color language (with smoothing + hysteresis) The star of the show. Now map ppm to color bands and keep transitions human-pleasant.\n\u0026lt; 500: Cyan (fresh) 500–799: Green 800–1199: Yellow 1200–1499: Orange 1500–1999: Red ≥ 2000: Purple BANDS=[(0,500,(0,214,255)), (500,800,(17,204,85)), ... (2000,∞,(123,44,191))] EMA_ALPHA=0.15; HYST=35 # Every tick: ema = (1-EMA_ALPHA)*ema + EMA_ALPHA*co2 band = hysteresis_aware_band(ema, last_band, HYST) target = BANDS[band].color current = lerp(current, target, 0.10) # gentle hue easing level = breathe(0.25 Hz, low=10, high=220) draw all pixels = current * level Why it’s like this: EMA + hysteresis prevents “800↔801 disco.” The slow breathing keeps the panel feeling alive, not like a traffic light.\n6.7 Optional SCD41 sensor thread (the polite listener) If the SCD41 is present, a dedicated thread owns I²C and updates a global co2_data dict every few seconds. It also logs to a tiny SQLite database.\ndef _start_sensor(): try: scd4x = adafruit_scd4x.SCD4X(i2c); scd4x.start_periodic_measurement() except: return # sensor optional def loop(): while True: time.sleep(5) if scd4x.data_ready: co2_data.update({...}) db.insert(timestamp, co2, temperature, humidity) threading.Thread(target=loop, daemon=True).start() Why it’s like this: one thread owns the bus → no read contention. The controller keeps working even without a sensor.\n6.8 Scheduler + 90-minute override (humans win, then reset) A background thread asks, every few seconds, which mode should be running:\nIf the user chose something recently (override), respect it for TTL = 90 min (or the duration set in the UI). Otherwise, apply the default schedule (Wednesday 14–17 → slow, else redpulse). Save/load the schedule atomically to schedule.json. def scheduler_pick(): if now \u0026lt; override_until: return override_mode for row in load_schedule(): if matches(now, row): return row[\u0026#34;mode\u0026#34;] return \u0026#34;redpulse\u0026#34; Why it’s like this: you can play DJ for a meeting, and the room quietly returns to its routine afterward.\n6.9 The web panel (tiny Flask, big buttons) Three pages, no fuss:\n/status — live JSON (/status_data) every second → current mode + CO₂/Temp/Humidity. /control — grid of same-size buttons (respects Safe Mode), optional duration (0–180 min) with validation. /schedule — simple table editor; Add Row and Save; writes atomically. @app.post(\u0026#34;/set\u0026#34;) def set_mode(): mode = request.form[\u0026#34;mode\u0026#34;] minutes = clamp( int(form[\u0026#34;duration\u0026#34;]), 0, 180 ) set_override(mode, minutes) run_animation(animations[mode][\u0026#34;fn\u0026#34;]) return redirect(\u0026#34;/control\u0026#34;) Why it’s like this: minimal routes are easier to maintain and don’t compete with the art piece.\n6.10 Boot choreography At startup I:\nTry the sensor thread (if hardware is there). Start the scheduler thread. Immediately play redpulse (so there’s a friendly glow right away). Start Flask; on shutdown I clear the strip. if __name__ == \u0026#34;__main__\u0026#34;: _start_sensor() threading.Thread(target=scheduler_loop, daemon=True).start() run_animation(redpulse) app.run(host=\u0026#34;0.0.0.0\u0026#34;, port=5000) Why it’s like this: the room sees something alive instantly; the scheduler will swap in the “real” choice within a few seconds.\nTL;DR (but keep the vibes) Frame-diff = no flashes. EMA + hysteresis = smooth, truthful color. Breathing = presence, not signage. Threads per thing (LEDs / sensor / scheduler) = no fights. Atomic files = no corrupted schedules. Safe Mode by default = people \u0026gt; pixels. That’s it — the code behaves like a considerate colleague: dependable, quiet, and occasionally very pretty.\nWhy this shape of code? One thread per hardware thing. The sensor thread owns I²C. The animation thread owns LEDs. The scheduler just decides what to run and when. No bus fights. Frame diff. I only call strip.show() when a pixel actually changes. That’s why it doesn’t randomly flash during web requests. Atomic schedule saves. The code writes to a temporary file and replaces the old one so a mid‑save power cut can’t corrupt the schedule. No, you don’t need the sensor. If it’s not connected, the app still runs; co2_monitor just sits at the default value. Well, technically it shows NaN as the numbers! But it is fun to have a sensor, don\u0026rsquo;t you agree?\n7) Sensor Database — what it stores, where it lives, and how far it goes This project logs room conditions to a tiny SQLite database so you can graph trends, spot stuffy meetings, and keep a record without running a separate server.\nWhat’s stored A single table called readings:\nCREATE TABLE IF NOT EXISTS readings ( timestamp TEXT PRIMARY KEY, -- \u0026#34;YYYY-MM-DD HH:MM:SS\u0026#34; co2 INTEGER, -- ppm temperature REAL, -- °C humidity REAL -- % ); One row per sample (typically every 5–60 seconds; slow it down to reduce writes). timestamp is the primary key → easy “latest” queries and natural time ordering. Where the file lives Path is configurable via env var DB_PATH (see your systemd unit). Default: sensor.db in the app’s working directory (e.g. /home/pi/inet-led/sensor.db). Check size:\nls -lh /home/pi/inet-led/sensor.db How writes happen (and why it’s safe) The sensor thread owns I²C and inserts a row only when the sensor reports data_ready. Inserts are short and journaling protects against power loss. For friendlier read/write concurrency, enable WAL once at startup: PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; Typical size \u0026amp; retention Back-of-envelope:\n~100–200 bytes per row (including overhead). 1 sample/minute → ~1,440 rows/day → ~150–300 KB/day → 55–110 MB/year. If you log every 5 seconds, multiply by ~12 (consider slower logging or downsampling). Prune old data (keep last 90 days):\nDELETE FROM readings WHERE timestamp \u0026lt; date(\u0026#39;now\u0026#39;,\u0026#39;-90 day\u0026#39;); (Optionally run VACUUM; after large deletes to reclaim file space.)\nUseful queries Latest reading:\nSELECT * FROM readings ORDER BY timestamp DESC LIMIT 1; Range for charts (last 7 days):\nSELECT * FROM readings WHERE timestamp \u0026gt;= datetime(\u0026#39;now\u0026#39;,\u0026#39;-7 day\u0026#39;) ORDER BY timestamp; Downsample to hourly averages (30 days):\nSELECT strftime(\u0026#39;%Y-%m-%d %H:00:00\u0026#39;, timestamp) AS hour, AVG(co2) AS co2, AVG(temperature) AS t, AVG(humidity) AS h FROM readings WHERE timestamp \u0026gt;= datetime(\u0026#39;now\u0026#39;,\u0026#39;-30 day\u0026#39;) GROUP BY hour ORDER BY hour; Export to CSV (example via sqlite3 CLI):\nsqlite3 /home/pi/inet-led/sensor.db \u0026lt;\u0026lt;\u0026#39;SQL\u0026#39; .headers on .mode csv .output /home/pi/inet-led/export_readings.csv SELECT * FROM readings ORDER BY timestamp; .output stdout SQL SD card friendliness Choose a sensible interval (e.g., 30–60 s). Optionally buffer in memory and write every N samples. Prefer WAL mode; periodically prune \u0026amp; vacuum. If you care about card wear, place the DB on USB/SSD. Backups \u0026amp; restore Nightly backup (simple):\nsqlite3 /home/pi/inet-led/sensor.db \u0026#34;.backup \u0026#39;/home/pi/backup/sensor-$(date +%F).db\u0026#39;\u0026#34; Restore:\nsudo systemctl stop inet-led Copy backup file back to /home/pi/inet-led/sensor.db sudo systemctl start inet-led Security \u0026amp; permissions chown pi:pi /home/pi/inet-led/sensor.db chmod 600 /home/pi/inet-led/sensor.db Keep the app directory non-world-readable.\nIs SQLite the right choice? Yes, for a single Pi logging every few seconds and rendering local charts:\n✅ Zero admin, a single file, reliable journaling, great performance at this scale. ⚠️ One writer at a time (I only have the sensor thread writing, so it’s fine). ⬆️ If you later need multi-device ingestion, alerts, or \u0026gt;10s of millions of rows, consider a time-series DB (TimescaleDB/InfluxDB/VictoriaMetrics) and migrate using CSV exports. TL;DR SQLite is perfect here: simple, robust, easy to back up. Start with it, and only upgrade when your ambitions outgrow a single Raspberry Pi.\n8) Run at Boot (systemd) I wrote this simple systemd to /etc/systemd/system/inet-led.service so that it runs the script when RPi boots up:\n[Unit] Description=INET LED Panel After=network.target [Service] ExecStart=/usr/bin/python3 /home/pi/inet-led/inet_led_panel.py WorkingDirectory=/home/pi/inet-led Restart=always User=pi Environment=LED_COUNT=78 Environment=SCHEDULE_FILE=/home/pi/inet-led/schedule.json Environment=DB_PATH=/home/pi/inet-led/sensor.db [Install] WantedBy=multi-user.target Then you can run:\nsudo systemctl daemon-reload sudo systemctl enable --now inet-led journalctl -u inet-led -f to control the systemd service.\n9) Soldering Notes (a love letter to hot glue) Tin first, solder second. Tiny pads like tiny puddles. Fast in/out, no lifted pads. Stagger joints so nothing stacks under the diffuser. Heat‑shrink + a dot of hot glue = happier future you. Continuity before power. Multimeter first, electrons last. If a pad does lift: magnet wire to a trace, UV mask to seal. Ugly heroics, but it works. Besides the soldering part, I also used m2 screws to fix the diffusers. Initially magnets were suggested, but I felt like figuring that out might take me long, and screws just feel more maintainable\u0026hellip; So\u0026hellip; yea.\n10) Why these design choices? Calm motion, not flashy. Meeting rooms breed fatigue; the light should be a helper, not a distraction. Cyan→Purple language. Easy to learn, visible at a glance, meaningful without numbers. White charts, dark UI. Data should be legible on a projector; buttons shouldn’t shout. Safe Mode default. People first. Demos are opt‑in. PLA body, PLA diffuser. Fast and easy rapid prototyping, easy and fast printing. 11) What I learned A logo is a better messenger than a dashboard. Everyone knows what orange means without a legend. If you show the room a mirror, it corrects itself. Someone will open the door long before you have to ask, and if someone notices the orange/red color of the logo, they would hint the end of the gathering! Sometimes debugging is not fun at all, and you want to bang your head to the wall, but try not to! Life is short. ^^ 12) Final notes I did this project as a fun distraction and \u0026ldquo;not work\u0026rdquo; as my advisor tells me to do all the time. My advisor provided the LED strip, RPi zero 2 W, and the voltage divider. I got the sensor, designed and coded everything else, and had a lot of fun doing so. I\u0026rsquo;m so so thankful of my advisor for this cool idea, and all the support. It\u0026rsquo;s not the first time I\u0026rsquo;ve been gifted such toys, and as I have recieved other things after that, I know it\u0026rsquo;s not the last.\nThere is a bug I never was able to figure out, sometimes when I stop the script, I get segmentation fault. I know it is not directly my code with extensive testing, and that the problem is with the library, but I never understood why it happens. Let\u0026rsquo;s hope that doesn\u0026rsquo;t turn into a backdoor into my logo. * Shakingly crosses fingers and sighs in distress!*\nThe whole project took around 8 days, 4 of which were part of a long weekend. I did do some \u0026ldquo;not work\u0026rdquo; as Tobias always tells me to do, and honestly it was fun and refreshing! I really enjoyed it. I don\u0026rsquo;t know how the group feels/thinks about the logo, but I love it! I hope it won\u0026rsquo;t die after I leave, but even if so, so be it. I had my fun with it. :)\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/inet_logo/","summary":"\u003cblockquote\u003e\n\u003cp\u003eI didn’t add a warning sign to the room. I taught the \u003cstrong\u003elogo\u003c/strong\u003e to breathe. ;-D\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eThe Rotunde is a good room for bold ideas and yummy coffee. The door sighs shut and the outside world gives up on us. The only thing it\u0026rsquo;s not good at is \u003cstrong\u003eventilating\u003c/strong\u003e itself. Forty minutes into a group meeting, or a sressful defence, and we all feel like sleeping and running back to our offices!\u003c/p\u003e","title":"INET Logo That Breathes — From CAD to LEDs to a Calm Little Server"},{"content":"I\u0026rsquo;m not a big movie watcher. In fact, I don\u0026rsquo;t remember the last time I watched a whole movie in a single day. It\u0026rsquo;s not that I don\u0026rsquo;t enjoy it, it\u0026rsquo;s that I want to do this with at least another person. It feels much better to not be alone when watching a movie for me for some reason. Buuuut, I watched Scent of a Woman on Sunday, and after my therapist encouraged me to do so.\nFew points before I begin.\nThis was aside from the therapy session. My therapist does not tell me what to do, or if I\u0026rsquo;m wrong or right or anything like that. I watched this movie in about two parts in a single day! I knew Al Pacino\u0026rsquo;s face from Godfather series, but didn\u0026rsquo;t know his name. I\u0026rsquo;m not a big movie person, so whatever I say should be taken with a grain of salt. Just don\u0026rsquo;t be offended, take it face up and as is. The movie is about a good young student named Chris O\u0026rsquo;Donnell, played by Charlie Simms ,who is dependent on a scholarship to continue his studies after school, worthy of going to Harvard.\nThe second main character of the movie is a blind man called Lt. Col. Frank Slade, played by Al Pacino, who is an arrogant, rude, assertive, proud, self-centered person that gives bad vibes throughout the movie.\nChris needs to work over the thanksgiving weekend, just to be able to make it home during Christmas. He lives with his mother, and a not so nice step-father who isn\u0026rsquo;t really there for him.\nWhen taking the job to watch over Frank, he quickly realizes what a hard task this will be, but the lady who hires him (Karen Rossie) being his niece assures him that he is a pile of suger under these bad vibes, and that she needs him to do this for her for these fea days. At last Chris agrees.\nUpon Karen\u0026rsquo;s leaving, Frank gets ready for a trip to New York. To do his last vows before taking his own life without Chris knowing this.\nHe plans staying at a nice hotel, having an awesome Meal at a gorgeous restaurant, seeing a woman, his brother, and then taking his own life.\nFrank is in love with Jack Daniel\u0026rsquo;s and cannot get enough of it. Throughout the movie this drunk, addiction-like behaviour is portrayed.\nFrank also learns about a problem Chris is facing. Chris is being forced to snitch on a group of the people he knows, or he will be expelled and loses his chance to got o Harvard with another scholarship. He doesn\u0026rsquo;t want to snitch, but frank learns about this, he encourages him to take the deal.\nFrank then tries to teach Chris how to flirt, how to get woman, and how to be a gentleman. There is an amazing scene in the movie where Frank tries to setup Chris with a girl who is waiting for her boyfriend! He dances Tango with her and this scene is probably the most majestic scene in any movie I have ever seen. The feelings I got were so strong and positive that I just cannot express!\nIn the end, when Frank wants to take his own life, Chris stops him and talks him out of it. He helps him to ride a Ferrari, the second thing he liked in his life (the first thing was woman lmao!). A police officer stopped them because they were going too fast, and Frank spoke his way out of the ticket! The officer didn\u0026rsquo;t even recognize Frank was blind!!!!\nAfter these emotional roller coaster, Chris became ever-so attached to Frank, and so did Frank become attached to Chris, showing up to an open student hearing in place of Chris\u0026rsquo;s parent.\nChris ended up not snitching, and Frank spoke up for him, saying how big of a man is he! How valuable he is, and how disgusting the acts of the other kid who also could snitch but hid behind his father\u0026rsquo;s back was.\nIn essense, the movie was amazing, with great acting all over it. The characters portreayed their roles as amazingly as possible. I loved the movie and how we got to know the characters more and more as the movie went on, and how the story progressed.\nThe story was also amazing. Characters really added life to the scenes by their amazing play. Donna did the tango dance professionaly. Al Pacino played to role of a blind man very well, you would easily be fooled by his amazing acting.\nThat leaves me thinking about why did my therapist suggested me to watch this movie and asked me to tell him how I felt and how I thought about the movie?\nWas it about Frank? That I was trying to be Chris, protecting and caring for Frank, although his behaviour is horrible? But Frank gave so much back to Chris. I guess it is the same for me too, I\u0026rsquo;m also getting a lot back while making some sacrifices.\nOr maybe was it about that amazing Tango dancing scene? The sensations that Frank describes about relationships, and how he interacts with woman?\nI honestly do not know. I just know that watching the movie gave me good feelings. That I\u0026rsquo;m a happier Iman than the Iman I was before watching it. That my feelings and experiences are not abnormal, they are valid. That I\u0026rsquo;m on the right track, but still need time to figure out the way. That I shouldn\u0026rsquo;t worry too much. That I should look at everything as a way to have \u0026ldquo;fun\u0026rdquo;. That I shouldn\u0026rsquo;t overthink things. That if I mess up, oh well, I should take the responsibility and move on.\nI don\u0026rsquo;t know what the intention of my therapist was, but I\u0026rsquo;m so curious! I want to figure it out. :) Fell free to cross your fingers for me!\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/scent_of_a_woman/","summary":"\u003cp\u003eI\u0026rsquo;m not a big movie watcher.\nIn fact, I don\u0026rsquo;t remember the last time I watched a whole movie in a single day.\nIt\u0026rsquo;s not that I don\u0026rsquo;t enjoy it, it\u0026rsquo;s that I want to do this with at least another person.\nIt feels much better to not be alone when watching a movie for me for some reason. Buuuut, I watched \u003ca href=\"https://www.imdb.com/title/tt0105323\"\u003eScent of a Woman\u003c/a\u003e on Sunday, and after my therapist encouraged me to do so.\u003c/p\u003e","title":"Movie review: Scent of a Woman"},{"content":"When I started my PhD, I was told \u0026ldquo;get yourself a hobby!\u0026rdquo; many many times by both my advisor, and the director of the group I worked in. In fact, when being interviewed for the position, I was asked about my hobbies.\nFor some reason I think I have like the weirdest hobbies of all time. Like, you know, people binge series, go to clubs, bars, hang out, and so on. But I always had interests in things that when I talk about people get weirded out, or at least some of them do so. To be honest though, when I talk more about some of my hobbies, they do show enthusiasm, but it feels like there is a clear gap between us afterwards.\nLet\u0026rsquo;s go back in time. What hobbies have I had over the course of my life? Actually as a kid I used to play games on Mobile for some reason. I was also quite competative, reaching the highest possible ranks in pretty much every game I played by solo-queueing. I also enjoyed playing with my brother, but for some reason he always liked games that were not necesserially my most favorite. Like I liked Vain Glory, and my brother liked Fifa. We did play both in the end.\nAfter my brother went to the capital to attend univesity, we bacame more distant. I had to also focus on my own studies, and was actually struggling in junior-high. Now that is a story in itself for another time. I used to play games back then as hobby. But my real interest was chess. I was really good at calculating. I was extremely sharp for my age, but since I was obbese my parents wanted me to take \u0026ldquo;active\u0026rdquo; sport classes. So\u0026hellip; getting into a chess class was conditional.\nI have\u0026rsquo;t really done any chess studying after those early years, and I just know a few openings with little depth. I do have a relatively good understanding of piece synergy and also ok-ish calculation skills to beat a good portion of people I see on a daily basis, but any professional player would easily beat me out of the opening.\nIn high-school I didn\u0026rsquo;t really have any hobbies. I did become interested in Basketball, but nothing serious. It\u0026rsquo;s funny how I was able to become good at any sport I tried. I tried Football, Basketball, Volleyball, Table Tennis, Tennis, Chess, Footsal, Swimming, and Badminton. I think swimming was, and still is my best sport. I never pursued it though, which is a shame.\nBefore starting my Bachelors, I became really interested in Cicada3301, and cryptography. Well, not the fancy state-of-the-art cryptography, the simple classic methods. The notion of delivering a message in plain sight that only the two parties envolved can understand it was so cool to me! That reminds me, we had these special things in junior-high called \u0026ldquo;Karsoogh\u0026rdquo; for math, physics, chemistry, and biology. I think I quilified for pretty much every one of them, and had a blast every time! In the chemistry one we cooked honey(!), in the biology one they dissected a bunny wabbit (with w to pay respect!), in the physics one we did cool experiments and tried to solve fancy problems. But the math one was different, it happend between a few close-by NODET cities, and annualy. The idea was simple. They gave us questions that did were solveable by developing ideas, with almost always little to no pre-requisite. Only in one of the years this event was internal, and they taught us 13-15 year young folks basics of cryptography, and ciphers. We then competed against each other. The competition was simple, develope a cipher with your team in a n*n (I think n was 6 or 8), then you split up into two groups, one team encrypts a message and you recieve the delivered message, the other team has to decrypt it. The fastest team gets the most points. The the message is common between teams. After this phase the teams got together and had to figure out other teams encryption algorithm, if successful, they got points. First teams to get an algorithm gets more points. Our team won the best algorithm in the end, and my ideas were most influential in this achievement! My teammates became really good friends with me after this competition which felt really cool! I think this was the reason I became interested in cryptography. I don\u0026rsquo;t remember the first year, but the first and last year in the final phase and after qualifying they tought us basics of game theory, and we had a set of two player fair and unfair games for which we had to compete against other players. If your team solved a game, you got more points. It was really fun, and since the games happened in parallel, all teammtes had to be active.\nI said all of this just to say thinking on problems became a hobby for me out of all of these cool expereinces. I like to casually get lost in questions and think about weird equations, or natural problems. Something like Feynman\u0026rsquo;s obsession with the spaghetti problem! Initially when starting my PhD, I did this, but then I diverged to other things I will talk about later on. But I do want to get back to this fun hobby. I just need some questions, and an empty mind. Or I guess some time slots to chill and not stress about my other research.\nDuring my bachelors, I became fascinated by hardware. Like sensors, actuators, micro-controllers, and anything that I could program to do something I do but do not want to do manually. Later on I realized this is related to IoT and Embedded systems. I did take the embedded systems course, and for the project that the cap was 120 points, we got a wopping 125 out of 100! Not only we implemented the whole project parts that we had to implement, but also we went beyond and just surprised the professor and his TAs! Unfortunately I don\u0026rsquo;t have any images from the project, but the code can be found here.\nI bought myself a raspberrypi, many sensors and actuators, and did small fun projects! I deployed a VPN on my home network, made my Rpi accessible with dynamic IP over the Internet via DDNS, deployed my very own nextcloud, website, and so on. I smartified my room AC s.t. it would keep the temprature at a certain range in a way to avoid hysteresis via a simple temprature and humidity sensor, and an IR-transceiver by recording the controllers signal. I could not figure out how to fix the state when commands don\u0026rsquo;t go through correctly though, a challenge that I never solved or came up with a solution for. I would say my love with computers, making things smart, and networking became my main hobby!\nAfter some protests and Internet blockages, I became interested in setting up VPNs that could penetrate through censorship attempts. It was, and still is a rat race. Well, it\u0026rsquo;s the story of my PhD pretty much now.\nReading books became a hobby for a while, but Youtube kinda distroyed that. I really love to get back to reading more books, I have a really exciting list of books to be read in my library. Listening to Music is another amazing hobby I have, specifically when I walk around or do chores. I find doing chores so relaxing! Since we talked about music I should mention I also tried to learn Piano, but didn\u0026rsquo;t pursue it. I actaully wanted to learn Violine, but the consultant we talked to said \u0026ldquo;if you\u0026rsquo;re not a hard worker it won\u0026rsquo;t workout for you\u0026rdquo;, and since my brother was going to learn Piano, I followed suit. And oh well, our teacher although he though I was doing really good for my age, did not give me the same set of practices that my brother got, and naturally since I was learning from kids books, I felt sad since his music always sounded better, and I eventually gave up feeling sad. This is a pattern that has happened in my life a lot, something I need to stop from happening. Comparing myself with other, and competing with them. It\u0026rsquo;s just distroying me mentally. I am me, and the best me ever to exist. And that\u0026rsquo;s how it should be.\nSomewhere during my bachelors I also became fascinated by coffee! I watched many James Hoffman videos and learned how to use different berewing methods for coffee, and did lots of experiments with it. Then came matcha, though with matcha things are much more limited.\nAnother thing that comes to mind is boardgames. I love boardgames that you need to think and be smart! An example is Cluedo. People usually don\u0026rsquo;t like to play it with me because I pay attention to \u0026ldquo;everything\u0026rdquo;. But I also anjoy playing other simpler games like UNO, Risk, Catan, card games, Coup, and many many other fun party games. I have a whole collection of boardgames that I don\u0026rsquo;t get to play! :-P One of my all time favorite games is \u0026ldquo;Zaar\u0026rdquo; (a persian game that was discontinued), and a game kinda similar to it called \u0026ldquo;The Night Cage\u0026rdquo;. I like them because there is a bit of strategy, luck, and a lot of co-op in them. In the later you either all win together, or get doomed. In the first one there is a comperition aspect to the game which makes it cool.\nCooking is another hobby of mine, although I only enjoy it when done with someone, or in a group. Aaaaaand guess what, I\u0026rsquo;m a loner! (Drat. :P) I love to \u0026ldquo;not follow recipes\u0026rdquo; and try new things. Foods I make usually turn out to be quite yummy actually, though definitly not authentic. I also think I do a good job with the presentation part when I try. And I\u0026rsquo;m open to cooking anything and everything!\nI was also interested in Hiking, but never really got to know someone who is both interested and willing to go with me, and when I\u0026rsquo;m alone, I rather do my other hobbies.\nAnd that leaves me with my latest two hobbies. CTFs, and 3D printing! Oh ,and I guess maybe blogging and sharing photos online? Idk! xD\n3D printing is an interesting one. I was fascinated about it from before, but never got my hands on a 3D printer until like 7 months ago. When I went to 38c3 last year, I saw so many printers, and how cool they were. And somehting in my heart was touched, that I need one! The thing is, I tend to like thinks that limit me to my creativity, like the IoT stuff, or how I always loved to play Minecraft as a kid. And oh well, 3D printing is just the hobby! I also tried to do some 3D design, but I\u0026rsquo;m quite a noob at it still. I will probably share some of the things I\u0026rsquo;ve made somewhere somehow, but not for now at least. Well, one of my cool projects inspired and mostly funded by my advisor was the INET logo (post comming soon!). It\u0026rsquo;s so cute and fascinating, and I had an absolute blast working on it for the one week I did. So much designing, fixing measurements, printing, coding, soldering, wiring, debugging, etc.! I also 3D printed and painted many gifts and organizers and other figues for either myself, or my friends. It\u0026rsquo;s just a fun thing to have, and to play around with. The fixing part of it, and maintaining it is not as fun, but it\u0026rsquo;s part of the journey. I will probably write about me and my 3D printer a lot more in the future. Another cool thing I can do with it, and have been doing so, is to do prints for the PhD hat of the people who will be graduating, a German cute and cool tradition!\nAnd now let\u0026rsquo;s talk about the CTF stuff. This is somewhat related to my interest in computers, problem solving, and cryptography (kinda). I\u0026rsquo;ve been wanting to do CTFs for a long time and throughout my Bachelors, but never did so. After starting my PhD, I was introduced to Saarsec, and now I\u0026rsquo;m a proud member, trying to contribute as much as my time allows me to. I\u0026rsquo;m not good at CTFs, but the joy of getting stuck on a problem, and maybe finally solving it is just too good to pass. I love it! I want to do more of it. The only sad part is that it takes a long time, and well, I need to spend time on the social aspects of my life too. Too shay. I would love to share some of my write-ups here too, and also write about it in the future, but there is only so much time I can spend on the blog.\nAnd last but not least, blogging. Well, I kinda started it for no reason to be honest. I just want to share my stories, and to show my vulnerable side with no guilt. It feels freeing to do this, and I hope I continue! I hope people won\u0026rsquo;t get mad if they are a part of these stories I share. I try to not name any names if not required, but I do think putting my PoV helps me with reducing some anxiety and social pressure. I really enjoy it!\nWith all this being said, I think that\u0026rsquo;s it. If I remember other hobbies that I missed, I will add them to the end of the article or maybe write a new post about it, Idk. The only thing I want to emphasize is that I\u0026rsquo;m into things that make me limited to my creativity. Oh, and also books, if only I read them instead of watching Youtube!!!!\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/hobbies/","summary":"\u003cp\u003eWhen I started my PhD, I was told \u0026ldquo;get yourself a hobby!\u0026rdquo; many many times by both my advisor, and the director of the group I worked in.\nIn fact, when being interviewed for the position, I was asked about my hobbies.\u003c/p\u003e\n\u003cp\u003eFor some reason I think I have like the weirdest hobbies of all time.\nLike, you know, people binge series, go to clubs, bars, hang out, and so on.\nBut I always had interests in things that when I talk about people get weirded out, or at least some of them do so.\nTo be honest though, when I talk more about some of my hobbies, they do show enthusiasm, but it feels like there is a clear gap between us afterwards.\u003c/p\u003e","title":"Hobbies"},{"content":" Notice: You\u0026rsquo;ll hear me ranting in this post, so buckle up, or just go to another fun post. I am very vulnerable right now and want to put out my story without naming anyone.\nSo\u0026hellip; do you also think you\u0026rsquo;re a lover boy, kind, nice person? So do I! I think I\u0026rsquo;m actually really good at making friends with people. Like you know, you start introducing yourself, asking them some fun personal questions about hobbies or other stuff, and then you eventually start talking about other things.\nMy problem is when things are to become more serious! Like, you know\u0026hellip; connecting at a more emotional level. A \u0026ldquo;relationship\u0026rdquo;. I\u0026rsquo;m really bad at those. In fact, I haven\u0026rsquo;t ever been in one! Now let\u0026rsquo;s be honest, I\u0026rsquo;m not your typical jacked, handsome boy, but I think I\u0026rsquo;m somewhere around the average if not higher a bit? Well, at least I hope so! :D\nI actually haven\u0026rsquo;t even dated once so far. My closest dating experience was something that turned out not to be a date, although most people I spoke to, were like\u0026hellip; yea dude\u0026hellip; it was a date buddy. Why do I say it wasn\u0026rsquo;t a date? Well, because I was told so yesterday by her. That if it\u0026rsquo;s a date, you\u0026rsquo;re supposed to say it beforehand. I find that very fair actually. I really do. I wasn\u0026rsquo;t even looking for anything more than a friendship from whatever these two weeks of interactions were, but things seemed abnormal. Like we planned a future together. We planned planting flowers, hosting Kareoki. We planned cooking/baking together for fun. We talked about me taaching her cycling since she said she was interested in it. And many many other things!\nWe even found our \u0026ldquo;favorite shops\u0026rdquo; in the city together! It was weird! I\u0026rsquo;m so confused. I remember the second time we went out together, we sat next to the river and close to eachother. She leaned into me at some point. She had also touched my arm gently when making a joke earlier when we went shopping together. It felt weird. I did feel she was flirting with me, making plans with me. She sought my opinion on many things, the house she was considering moving into, color and design of the cushions for her house, and many other things. We also had many deep conversations in this short amount of time.\nBut then it happened. Suddenly she messaged me saying how she is going to have a bf soon \u0026ldquo;hopefully\u0026rdquo;. That others know him, but not me. That where we live there are not that many people. That supply is low, and demand is high. That this person asked her fast so that \u0026ldquo;the slots won\u0026rsquo;t be filled\u0026rdquo;. It was weird, as if she was telling me I\u0026rsquo;m going to be taken if you don\u0026rsquo;t do anything\u0026hellip;? After this weird encounter I talked to two of my friends, and my married brother and his wife. When she heard about our interactions and all of the messages we passed, she did feel like I\u0026rsquo;ve been friendzoning her. That I\u0026rsquo;m not resiprocating her feelings. That she perhaps has a crush on me. Even my brother felt the same! My friend was a bit more unsure though, but he also felt like it could really be that she is playing games. With all honestly, in her messages when she was teaching me the \u0026ldquo;ways\u0026rdquo;, she said \u0026ldquo;I don\u0026rsquo;t play games and I also advise you not to get together with people that do so\u0026rdquo;. Last time we went out together, one-on-one, many of the people we knew saw us together and some of them gave us looks that gave me more confidence that what I\u0026rsquo;m doing is normal and things are going well. But I guess I was wrong?\nI do want to talk about some things that we exchanged on this weird conversation that made me confused. She initially asked why I didn\u0026rsquo;t go out with them last night. I had an exam on 7th, but we had planned two one-on-one going outs for 4th and 5th, hence the reason I skipped going out with a group of people rather to have less on my plate for those days we were going to hang out together. With all honestly I didn\u0026rsquo;t even know she was joining them or not. It\u0026rsquo;s kinda weird, but the plans were made in a group she was not inside of. Anyway.\nOctober 4th was a rainy day. She said she might be catching a cold, and that going out in this weather is maybe not a good choice. She said let\u0026rsquo;s go next weekend as the weather is better, and also that we can go out after work hours in weekdays. You see? She proposed a different time and date, she didn\u0026rsquo;t just cancel last second. And then she started talking about last night, the things that happened. A guy apparently knew him, called her and then hugged her, and she insisted that she had only seen him once in Mensa last year and that she doesn\u0026rsquo;t know him. Then she started talking about how there is so much gossip behind her back, and people who know those gossips are wrong do not speak up for her. And then she talked about how hard it is to be a girl, and how boys do not understand difference between flirting and being polite. Then she talked about how when two people see each other, a click can happen, and that\u0026rsquo;s how you can know if you like someone or not. If it\u0026rsquo;s two way ofc. I asked her what is this \u0026ldquo;click\u0026rdquo;? She said \u0026ldquo;you know when you know, and if you think you don\u0026rsquo;t know then there isn\u0026rsquo;t one\u0026rdquo;. Interesting. My poor brain started looking back at our own interactions. I remembered how she smiled at me, leaned toward me, touched my arm, planned a future with me in it\u0026hellip; And again I felt she is hinting something. Poor me was stuck between a rock and a hard place. She told me that if I like someone I should ask them on a date \u0026ldquo;early\u0026rdquo;, as time is of essence. Again, I was confused. I was lost. We\u0026rsquo;ve been hanging out for two weeks, we have our favorite similar shops, hobbies. We have plans together. We understood eachother. Or maybe that\u0026rsquo;s just how I felt in my mind? Maybe things in my brain are just different? I don\u0026rsquo;t know.\nMy sister in law told me that she probably had a crush on me and that I didn\u0026rsquo;t reciprocate her feelings perhaps? That I\u0026rsquo;m friendzoning her by not touching her back or asking her out on a \u0026ldquo;date\u0026rdquo;. Her reasoning is that she initiated all of this. She asked me to go to see the house she was considering renting, and then asked me to go out with her after that. She told me that I should invite her to a date. That I have to do something romantic now, maybe get flowers, and a gift. And I did just that. I messaged her talking about all of the good traits I had seen from her, her behaviour, and habits. And I asked her on a date.\nShe said she enjoyed the company too, and thanked me. She then said she is starting a new relationship, but even if that was not the case, \u0026ldquo;we were so different from eachother at a much deeper level\u0026rdquo;. That her \u0026ldquo;life experiecnes\u0026rdquo; are just different, \u0026ldquo;and so on\u0026rdquo;. She did actually say \u0026ldquo;and so on\u0026rdquo;.\nYou know what it reminds me of? Of when your paper gets rejected by saying \u0026ldquo;lacks novelty\u0026rdquo;. xD\nAnother extremely funny thing is that she said we\u0026rsquo;re so different at a much deeper level, but she doesn\u0026rsquo;t even know me. What was meant at a deeper level? I\u0026rsquo;m confused again. She definitly does not know my hobbies, most of my interests, my stories, my family, friends, nothing. She knew nothing about me. How are we different so deeply if you don\u0026rsquo;t even know me? I\u0026rsquo;m so so incredibly confused. I guess it could be the looks, and the \u0026ldquo;vibes\u0026rdquo;? But again, I do think she did not have a more polite better reason but did want to provide some sort of an explanation so that I won\u0026rsquo;t pursue her I guess? Idk.\nI want to also come back to this point she made that she is \u0026ldquo;starting a new relationship\u0026rdquo;. She told me in the same conversation that someone asked her out (which she technically didn\u0026rsquo;t even say this, but it could be induced), and that going on a date \u0026ldquo;does not mean you\u0026rsquo;re in a relationship, that you want to know eachother\u0026rdquo;. Her saying that \u0026ldquo;I said I\u0026rsquo;m starting a new relationship\u0026rdquo; hurt me, not because she is doing that, because she didn\u0026rsquo;t technically tell me that. She then said \u0026ldquo;I always tell people this to avoid confusion\u0026rdquo;. I definitly didn\u0026rsquo;t miss it if she did. She didn\u0026rsquo;t, but whatever. It\u0026rsquo;s fine. I did apologize. The reason I say this is that some other guys we knew came up to her and she was encouraging them to go see other girls, but not me\u0026hellip; Did I miss it? She didn\u0026rsquo;t. Just trust me on this one. ;)\nI\u0026rsquo;m just confused. The last three times I had a crush on someone I confessed too. First case was in a relationship which became awkward as she told her bf and he bullied me (drat!). Second case ended up in a tragedy that I do not want to talk about, lol! All I can say is that she definitly panicked and I don\u0026rsquo;t blame her. I do think in her case she had avoidant attachment and my anxious attachment style made her uncomfortable. The third one said that she is in an \u0026ldquo;undefined state\u0026rdquo; of a relationship, and that she wants to keep it \u0026ldquo;friendly\u0026rdquo; in university, which is completely fair. But this last one. This was the weird one. The first and third one were really sweet to me when I confessed/asked them out. The last one didn\u0026rsquo;t even show interest about being friends, which is again totally fine. But I\u0026rsquo;m just so baffeled by all of this.\nSo\u0026hellip; yea. This last \u0026ldquo;thing\u0026rdquo;, whatever it was, was my closest encounter with a relationship, and maybe it will be for a while. I do think I need to take a break from trying to get into a relationship. I\u0026rsquo;ve been hurt a lot by the second one, and this was just confusing. In her defence she was an amazing person. Smart, cute, kind, positive, jolly, witty, social, and her taste was just amazing. She also had deep knowledge in many things I was also interested in. Oh well. such is life! I can\u0026rsquo;t just say the good stuff though. She was definitly a bit self-centered. It\u0026rsquo;s funny how she told me that \u0026ldquo;people say I\u0026rsquo;m so proud in a negative way, but anyone who talks with me knows I\u0026rsquo;m not like that\u0026rdquo;. Which is true, she wasn\u0026rsquo;t proud, the correct term is self-centered, or \u0026ldquo;narcissistic\u0026rdquo; if you will, which I don\u0026rsquo;t neceserrialy think is bad, but it is definitly an orange/yellow flag.\nOne thing I know is that I do wish her and whoever she dates the best! \u0026lt;3 And that I would be ok to stay friends with her, but since I\u0026rsquo;m hurt, I will be much colder. Sorry. :) And another thing I know is that I will never think about her, or people like her romantically, or at least I try not to. Fast progression ends like this? But then maybe we never progressed? I don\u0026rsquo;t know.\nWell. I don\u0026rsquo;t know what\u0026rsquo;s gonna happen next, but I\u0026rsquo;m going to just live my life and have fun. Aaaaaaaand maybe not worry about being single? Being single is fun too! ^^ You have so much freedom. I love to also work on myself a bit, lose some weight, do the sports I love, and make new friends. Maybe someday this hopeless romantic little lover boy won\u0026rsquo;t be alone? Who knows? haha.\nP.S.: My therapist thought this relationship was abusive in essense, and the thing that happened in the end was purely seductive. I was chasing bread crumbs to be taken advantage of. I\u0026rsquo;m worth more than that. :) In my therapy session I remembered that in each of these so called \u0026ldquo;not dates\u0026rdquo; we had a conversation about \u0026ldquo;her\u0026rdquo;, and she was dumping her emotional baggage on me, just like what happened this last time that confused me.\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/relationships/","summary":"\u003cblockquote\u003e\n\u003cp\u003eNotice: You\u0026rsquo;ll hear me ranting in this post, so buckle up, or just go to another fun post. I am very vulnerable right now and want to put out my story without naming anyone.\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eSo\u0026hellip; do you also think you\u0026rsquo;re a lover boy, kind, nice person?\nSo do I!\nI think I\u0026rsquo;m actually really good at making friends with people.\nLike you know, you start introducing yourself, asking them some fun personal questions about hobbies or other stuff, and then you eventually start talking about other things.\u003c/p\u003e","title":"Relationships"},{"content":"I started the month by finalizing my draft for Conext Student workshop. Let\u0026rsquo;s cross our fingers and hope things work out and that it gets accepted. Notification should arrive on 25th, and I\u0026rsquo;d have until 30th to do the camera ready stuff which should be plenty of time.\nI did a vacation from 6th until 15th for a total of 10 days by taking 6 days off. I visited my parents after 13 months(!), and also my brother after more than two years. We had so much fun! I did a bunch of sight-seeing in Istanbul, tried out yummy foods and sweets, many different fishes, and snorkled in Antalya. What a delightful trip it was. I do have to get used to this as this is the sole way I will most probably see my parents from now on, unless they want to travel to somewhere else or visit me here. Now that my brother also has his greencard, we can travel together and see eachother more often too!\nSurprise surprize, the notification did not come and it\u0026rsquo;s the last day of the month. Ever since I came back from vacation I tried to catch up with everything, did my ML re-exam, and setup all different cool things I talked about in my blog. I\u0026rsquo;ve been waiting for the notification to get started with the camera-ready process to make sure I have enough time to study for my next and last exam on 7th of October\u0026hellip; Drat!\nI will probably do more literature review this last day of the month, and start working on the code base from next month. I should do a lot more literature review to be caught up with whatever that\u0026rsquo;s been done so far.\nMy social life has been much more exciting too. I\u0026rsquo;ve been socializing a lot more and have made many new friends. Some other exciting things have also been hapening that I don\u0026rsquo;t have the courage to write about now. ;) Buuuuuut\u0026hellip; I will probably write about them at some point if things go the way I hope theuy would. Just remember Wed 24th of September 2025 and Sunday 28th of same month and year. :)\nAnd with that, that\u0026rsquo;s my September in a nutshell. I will probably start writing through the month and then turn the draft into a post from now on. That way it would look like a story!\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/phd_journey/september_2025/","summary":"\u003cp\u003eI started the month by finalizing my draft for Conext Student workshop.\nLet\u0026rsquo;s cross our fingers and hope things work out and that it gets accepted.\nNotification should arrive on 25th, and I\u0026rsquo;d have until 30th to do the camera ready stuff which should be plenty of time.\u003c/p\u003e\n\u003cp\u003eI did a vacation from 6th until 15th for a total of 10 days by taking 6 days off.\nI visited my parents after 13 months(!), and also my brother after more than two years.\nWe had so much fun!\nI did a bunch of sight-seeing in Istanbul, tried out yummy foods and sweets, many different fishes, and snorkled in Antalya.\nWhat a delightful trip it was.\nI do have to get used to this as this is the sole way I will most probably see my parents from now on, unless they want to travel to somewhere else or visit me here.\nNow that my brother also has his greencard, we can travel together and see eachother more often too!\u003c/p\u003e","title":"September '25"},{"content":"Why I’ve been running a Java Minecraft world (with Bedrock players via Geyser) in tmux. I moved it to Docker for easier updates, backups, and restarts. Along the way I hit:\nfailed to register layer: ... no space left on device Client saying: “Outdated client, please use 1.21.9 Pre-Release 2” Wanting config in a neat .env and no whitelist Here’s exactly what I did.\nFolder layout ~/minecraft/ ├─ docker-compose.yml ├─ .env └─ plugins/ .env (secrets \u0026amp; knobs) Use the latest stable (not snapshots/pre-releases) by setting VERSION=LATEST.\n# Minecraft server configuration EULA=TRUE TYPE=PAPER VERSION=LATEST MEMORY=4G USE_AIKAR_FLAGS=true # RCON (remote console) ENABLE_RCON=true RCON_PASSWORD=superSecretPassword123 I don’t use a whitelist, so no WHITELIST/ENFORCE_WHITELIST variables.\ndocker-compose.yml services: mc: image: itzg/minecraft-server:latest container_name: mc restart: unless-stopped ports: - \u0026#34;25565:25565\u0026#34; # Java - \u0026#34;19132:19132/udp\u0026#34; # Bedrock via Geyser plugin env_file: - .env volumes: - mc-data:/data - ./plugins:/plugins:ro volumes: mc-data: {} The version: key in Compose is obsolete now, so I dropped it.\nAdd Geyser (and optional Floodgate) as plugins mkdir -p ~/minecraft/plugins cd ~/minecraft/plugins # Geyser (Spigot/Paper) wget https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest/downloads/spigot -O Geyser-Spigot.jar # Floodgate (optional: Bedrock accounts without Java linking) wget https://download.geysermc.org/v2/projects/floodgate/versions/latest/builds/latest/downloads/spigot -O Floodgate-Spigot.jar Start it up:\ndocker compose up -d On first run, Geyser writes its config to /data/plugins/Geyser-Spigot/. Open UDP 19132 on your firewall.\nHit a wall: /var ran out of space Docker stores layers at /var/lib/docker by default. My /var LV was tiny:\n/ d e v / m a p p e r / u b u n t u - - v g - v a r 3 . 9 G 3 . 4 G 3 3 3 M 9 2 % v a r Quick cleanup docker system df docker system prune -f docker builder prune -af sudo apt-get clean sudo journalctl --vacuum-size=100M If that’s not enough, you have two good options:\nOption A — Move Docker’s data off /var sudo systemctl stop docker sudo mkdir -p /home/docker sudo rsync -aHAX --info=progress2 /var/lib/docker/ /home/docker/ echo \u0026#39;{ \u0026#34;data-root\u0026#34;: \u0026#34;/home/docker\u0026#34; }\u0026#39; | sudo tee /etc/docker/daemon.json sudo systemctl start docker docker info | grep \u0026#34;Docker Root Dir\u0026#34; If all looks good, free the old space:\nsudo rm -rf /var/lib/docker/* Option B — Grow /var (LVM) Check free space in the VG:\nsudo vgdisplay # look for \u0026#34;Free PE / Size\u0026#34; If available, extend /var by +5G:\nsudo lvextend -L +5G /dev/mapper/ubuntu--vg-var sudo resize2fs /dev/mapper/ubuntu--vg-var The version mismatch: pre-release vs stable My logs showed:\nS t a r t i n g m i n e c r a f t s e r v e r v e r s i o n 1 . 2 1 . 9 P r e - R e l e a s e 2 That happens if the server pulls a pre-release build (or if VERSION=LATEST). Fix:\nEnsure .env has: VERSION=LATEST_RELEASE Recreate: docker compose down docker compose pull docker compose up -d Verify: docker logs mc | grep \u0026#34;Starting minecraft server version\u0026#34; # -\u0026gt; Starting minecraft server version 1.21.1 (example) Tip: If you want to pin and avoid auto-updates entirely, set VERSION=1.21.1 (or whatever stable you’ve validated).\nNo whitelist Because I don’t set WHITELIST/ENFORCE_WHITELIST, anyone can join (subject to online-mode, bans, and Geyser/Floodgate auth settings). Manage ops/permissions via:\ndocker exec -it mc rcon-cli \u0026#34;op YourJavaIGN\u0026#34; (or edit /data/ops.json).\nBackup \u0026amp; updates World/data live under the mc-data volume → back up /data regularly. Update cleanly: docker compose pull \u0026amp;\u0026amp; docker compose up -d Watch space: watch -n5 \u0026#39;df -h /var; docker system df\u0026#39; TL;DR Put Geyser/Floodgate jars in ./plugins and expose 19132/udp. Keep configs in .env. Fix /var space by pruning, moving Docker’s data-root, or extending /var via LVM. Verify version in logs after each change. Happy block-breaking! 🧱🚀\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/minecraft_server/","summary":"How I containerized a Java Minecraft server with Geyser, hit a /var space wall, and locked the server to the latest stable release.","title":"Dockerizing my Minecraft Server + Geyser: from 'no space left' to stable releases"},{"content":" TL;DR — The site is built once (Hugo project), published twice:\nOnion: http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/ via Tor, no TLS/HSTS, bound to 127.0.0.1:3301. Clearnet: https://blog.alipourimjourneys.ir behind Cloudflare, Let’s Encrypt cert, Onion-Location header pointing to the onion mirror. 1) Tor hidden service (onion) basics I used Tor’s v3 onion services and mapped onion port 80 → my local web server on 127.0.0.1:3301.\nInstall \u0026amp; configure Tor (Debian/Ubuntu):\nsudo apt update \u0026amp;\u0026amp; sudo apt install -y tor sudoedit /etc/tor/torrc Add:\nH H i i d d d d e e n n S S e e r r v v i i c c e e D P i o r r t v 8 a 0 r / 1 l 2 i 7 b . 0 t . o 0 r . 1 h : i 3 d 3 d 0 e 1 n _ s i t e / Make sure the directory is owned by Tor’s user and private:\nsudo mkdir -p /var/lib/tor/hidden_site sudo chown -R debian-tor:debian-tor /var/lib/tor/hidden_site sudo chmod 700 /var/lib/tor/hidden_site Important: use the right systemd unit:\n# validate as the Tor user sudo -u debian-tor tor -f /etc/tor/torrc --verify-config # (re)start the real service sudo systemctl enable --now tor@default sudo systemctl restart tor@default Get the onion address:\nsudo cat /var/lib/tor/hidden_site/hostname Quick check (do remote DNS via SOCKS):\ncurl -I --socks5-hostname 127.0.0.1:9050 \u0026#34;http://$(sudo cat /var/lib/tor/hidden_site/hostname)\u0026#34; 2) Nginx for the onion (localhost-only) I keep the onion site strictly on localhost: no HTTPS, no redirects, no HSTS. Tor already provides e2e encryption and authenticity.\n/etc/nginx/sites-available/onion-blog:\nserver { listen 127.0.0.1:3301 default_server; server_name \u0026lt;your-56-char\u0026gt;.onion 127.0.0.1 localhost; # keep onion simple; no HSTS/redirects here add_header Referrer-Policy no-referrer always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; # (optional) strict CSP so nothing leaks to clearnet # add_header Content-Security-Policy \u0026#34;default-src \u0026#39;self\u0026#39;; img-src \u0026#39;self\u0026#39; data:; style-src \u0026#39;self\u0026#39; \u0026#39;unsafe-inline\u0026#39;; script-src \u0026#39;self\u0026#39;\u0026#34; always; root /srv/hugo/mysite/public-onion; index index.html; location / { try_files $uri $uri/ /index.html; } location ~* \\.(css|js|ico|png|jpg|jpeg|gif|svg|webp|txt|xml)$ { access_log off; add_header Cache-Control \u0026#34;public, max-age=31536000, immutable\u0026#34;; try_files $uri =404; } } Enable/reload:\nsudo ln -sf /etc/nginx/sites-available/onion-blog /etc/nginx/sites-enabled/onion-blog sudo nginx -t \u0026amp;\u0026amp; sudo systemctl reload nginx Gotcha I hit: I had two server blocks on 127.0.0.1:3301, which caused 404s via onion. Make sure only the intended vhost listens there (or mark it default_server). Also, if you ever use a regex in server_name, the syntax is server_name ~* \\.onion$ (note the space after ~*).\n3) Hugo + PaperMod setup (and version bumps) PaperMod now requires Hugo Extended ≥ 0.146.0. I installed the extended binary from the official tarball to avoid Snap’s sandbox limitations (Snap can’t read /srv paths by default).\n# install Hugo extended (example) VER=0.146.0 cd /tmp wget https://github.com/gohugoio/hugo/releases/download/v${VER}/hugo_extended_${VER}_Linux-amd64.tar.gz tar -xzf hugo_extended_${VER}_Linux-amd64.tar.gz sudo mv hugo /usr/local/bin/hugo hugo version # should say \u0026#34;extended\u0026#34; and \u0026gt;= 0.146.0 Create the site and theme:\nsudo mkdir -p /srv/hugo \u0026amp;\u0026amp; sudo chown -R \u0026#34;$USER\u0026#34;:\u0026#34;$USER\u0026#34; /srv/hugo cd /srv/hugo hugo new site mysite cd mysite git init git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod Config updates: in newer Hugo, paginate is deprecated → use:\n# config/_default/hugo.toml title = \u0026#34;My Blog\u0026#34; theme = \u0026#34;PaperMod\u0026#34; enableRobotsTXT = true [pagination] pagerSize = 10 [params] defaultTheme = \u0026#34;auto\u0026#34; showReadingTime = true showPostNavLinks = true showBreadCrumbs = true showCodeCopyButtons = true I added per-environment overrides so I can build two outputs with different baseURLs:\n# config/clearnet/hugo.toml baseURL = \u0026#34;https://blog.alipourimjourneys.ir/\u0026#34; # config/onion/hugo.toml baseURL = \u0026#34;http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/\u0026#34; 4) Dual builds (clearnet + onion) and one-command deploy I publish the same content twice—once for each base URL and docroot:\ncd /srv/hugo/mysite # clearnet build (served over HTTPS) hugo --minify --environment clearnet -d public-clearnet # onion build (served via Tor) hugo --minify --environment onion -d public-onion sudo systemctl reload nginx Helper script I use:\nsudo tee /usr/local/bin/build-both \u0026gt;/dev/null \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; #!/usr/bin/env bash set -euo pipefail cd /srv/hugo/mysite hugo --minify --environment clearnet -d public-clearnet hugo --minify --environment onion -d public-onion sudo systemctl reload nginx echo \u0026#34;Deployed both at $(date)\u0026#34; EOF sudo chmod +x /usr/local/bin/build-both While editing, I sometimes auto-rebuild on file save:\nsudo apt install -y entr cd /srv/hugo/mysite find content layouts assets static config -type f | entr -r build-both 5) Clearnet behind Cloudflare + Let’s Encrypt (manual DNS-01) Cloudflare is set to Full (strict). I issued a public cert for blog.alipourimjourneys.ir using manual DNS-01 (no API token):\nsudo snap install --classic certbot sudo certbot certonly --manual --preferred-challenges dns -d blog.alipourimjourneys.ir --agree-tos -m you@example.com --no-eff-email Certbot tells you to add a TXT record _acme-challenge.blog.alipourimjourneys.ir. Add it in Cloudflare DNS, verify with dig, then continue. Cert ends up at:\n/ / e e t t c c / / l l e e t t s s e e n n c c r r y y p p t t / / l l i i v v e e / / b b l l o o g g . . a a l l i i p p o o u u r r i i m m j j o o u u r r n n e e y y s s . . i i r r / / f p u r l i l v c k h e a y i . n p . e p m e m Clearnet Nginx vhosts:\n# HTTP → HTTPS redirect (optional; CF usually talks HTTPS to origin anyway) server { listen 80; listen [::]:80; server_name blog.alipourimjourneys.ir; return 301 https://blog.alipourimjourneys.ir$request_uri; } # HTTPS origin (behind Cloudflare) server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name blog.alipourimjourneys.ir; ssl_certificate /etc/letsencrypt/live/blog.alipourimjourneys.ir/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/blog.alipourimjourneys.ir/privkey.pem; # HSTS on clearnet only add_header Strict-Transport-Security \u0026#34;max-age=31536000; includeSubDomains; preload\u0026#34; always; # Help Tor Browser discover the onion mirror (safe on clearnet) add_header Onion-Location \u0026#34;http://\u0026lt;your-56-char\u0026gt;.onion$request_uri\u0026#34; always; root /srv/hugo/mysite/public-clearnet; index index.html; location / { try_files $uri $uri/ /index.html; } } Note: don’t add HSTS or HTTPS redirects to the onion vhost. Keep onion pure HTTP on localhost.\n6) Troubleshooting I ran into (and fixes) Tor unit confusion: tor.service is a tiny master; the real daemon is tor@default. Use that unit and verify config as debian-tor. Permissions: HiddenServiceDir must be owned by debian-tor and mode 700. Mapping mismatch: If HiddenServicePort 80 127.0.0.1:3301 is set, visit http://\u0026lt;onion\u0026gt;/ (no :3301). If you set HiddenServicePort 3301 127.0.0.1:3301, you must use http://\u0026lt;onion\u0026gt;:3301/. Curl \u0026amp; .onion: modern curl refuses .onion unless you use remote DNS via SOCKS: curl -I --socks5-hostname 127.0.0.1:9050 \u0026#34;http://\u0026lt;onion\u0026gt;/\u0026#34; Two Nginx vhosts on the same port: I had a duplicate server on 127.0.0.1:3301 pointing somewhere else, which caused onion 404s. Keep only one (or mark one default_server). Regex in server_name: if you use it, write server_name ~* \\.onion$ (space after ~*). I fixed an invalid variable name error caused by a missing space. PaperMod with old Hugo: upgraded to extended ≥ 0.146.0. Also updated paginate → [pagination].pagerSize. Snap confinement: Snap’s hugo couldn’t read /srv (.../void: permission denied). Switched to the tarball build in /usr/local/bin. 7) “Not secure” in Tor Browser? That message can appear because onion uses HTTP. It’s OK: Tor provides e2e encryption + onion auth. If you enable HTTPS-Only Mode, add an exception for the site. Also ensure the onion build doesn’t reference clearnet resources (scan the built HTML for http(s):// links that aren’t your onion).\n8) Day-to-day workflow Create content: hugo new posts/my-first-post.md # then set draft: false Publish both: build-both (Optional) Auto on save: find content layouts assets static config -type f | entr -r build-both (Optional) Git push-to-deploy with a bare repo + post-receive hook that runs build-both. Final notes Clearnet gets HTTPS + HSTS and an Onion-Location header. Onion gets no HSTS/redirects, and all assets are self-hosted to avoid mixed content. Serving both worlds from one Hugo repo is easy: two builds, two vhosts, one workflow. ","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/tor_clearnet_blog/","summary":"\u003cblockquote\u003e\n\u003cp\u003eTL;DR — The site is built once (Hugo project), \u003cstrong\u003epublished twice\u003c/strong\u003e:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eOnion\u003c/strong\u003e: \u003ccode\u003ehttp://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/\u003c/code\u003e via Tor, no TLS/HSTS, bound to \u003ccode\u003e127.0.0.1:3301\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eClearnet\u003c/strong\u003e: \u003ccode\u003ehttps://blog.alipourimjourneys.ir\u003c/code\u003e behind Cloudflare, Let’s Encrypt cert, \u003ccode\u003eOnion-Location\u003c/code\u003e header pointing to the onion mirror.\u003c/li\u003e\n\u003c/ul\u003e\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-tor-hidden-service-onion-basics\"\u003e1) Tor hidden service (onion) basics\u003c/h2\u003e\n\u003cp\u003eI used Tor’s v3 onion services and mapped onion port 80 → my local web server on \u003ccode\u003e127.0.0.1:3301\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eInstall \u0026amp; configure Tor (Debian/Ubuntu):\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo apt install -y tor\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudoedit /etc/tor/torrc\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAdd:\u003c/p\u003e","title":"Running my blog on Tor (.onion) and the Clearnet with Hugo + PaperMod"},{"content":"Introduction So you want a VPN that doesn\u0026rsquo;t scream \u0026ldquo;I am a VPN\u0026rdquo; to every censor and firewall out there?\nWelcome to the world of Trojan over WebSocket + TLS behind Cloudflare.\nThis guide not only shows you how to set it up but also sprinkles in some debugging magic so you can figure out why things break (and they will break, trust me).\nWe’ll anonymise domains and secrets, so substitute with your own:\nVPN domain: web.example.com Panel domain: panel.example.com Secret WS path: /stealth-path_abcd1234 Password: \u0026lt;PASSWORD\u0026gt; Architecture at a Glance Think of it as a disguise party:\nTrojan = the shy guest (your VPN protocol) Nginx = the bouncer checking IDs (reverse proxy) Cloudflare = the doorman who makes sure nobody sees who\u0026rsquo;s inside (CDN \u0026amp; proxy) Your fake website = the mask (camouflage page) Traffic flow:\nC l i e n t → C l o u d f l a r e ( 4 4 3 ) → N g i n x ( 4 4 3 ) → T r o j a n ( l o c a l h o s t : 5 4 3 2 1 ) Step 1: Panel Setup (panel.example.com) Bind the 3x-ui panel to localhost (e.g., 127.0.0.1:46309). Choose a funky web base path like /panel-bananas_42/. Proxy it through Nginx with HTTPS + Basic Auth. Test it at https://panel.example.com/panel-bananas_42/. Pro tip: If you see a blank page → your base path is mismatched or Nginx is eating it. Check logs!\nStep 2: Trojan Inbound In 3x-ui, create a Trojan inbound:\nLocal port: 54321 Transport: WebSocket Path: /stealth-path_abcd1234 Security: none Password: \u0026lt;PASSWORD\u0026gt; Step 3: Nginx for VPN Domain (web.example.com) Your Nginx is the gatekeeper. Sample config:\nserver { listen 443 ssl http2; server_name web.example.com; ssl_certificate /etc/letsencrypt/live/web.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/web.example.com/privkey.pem; # Fake website at root location / { root /var/www/html; index index.html; } # Real VPN under secret WS path location /stealth-path_abcd1234 { proxy_pass http://127.0.0.1:54321; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; proxy_set_header Host $host; } } Step 4: Firewall Rules Lock things down! Only Cloudflare should reach you:\nufw allow 22/tcp ufw allow 80/tcp ufw allow 443/tcp ufw deny 54321/tcp Step 5: Client Config Trojan URI:\nt r o j a n : / / \u0026lt; P A S S W O R D \u0026gt; @ w e b . e x a m p l e . c o m : 4 4 3 ? t y p e = w s \u0026amp; s e c u r i t y = t l s \u0026amp; h o s t = w e b . e x a m p l e . c o m \u0026amp; p a t h = % 2 F s t e a l t h - p a t h _ a b c d 1 2 3 4 \u0026amp; s n i = w e b . e x a m p l e . c o m # M y S t e a l t h V P N iOS clients: Shadowrocket, Stash, FoXray\nAndroid clients: v2rayNG, Clash Meta, NekoBox\nDebugging Time (a.k.a. “Why the heck doesn’t it work?!”) Symptom: 520 Unknown Error (Cloudflare) Likely cause: Nginx couldn’t talk to Trojan. Check Nginx error log: tail -n 50 /var/log/nginx/error.log If you see upstream sent no valid HTTP/1.0 header → you’re mixing HTTPS/HTTP between Nginx and Trojan. Symptom: 526 Invalid SSL certificate Cloudflare → Nginx cert mismatch. Run: openssl s_client -connect web.example.com:443 -servername web.example.com -showcerts Make sure CN = web.example.com. If not, fix your cert. Symptom: Blank page on panel Check if the base path matches exactly (case-sensitive, slash-sensitive). Watch for sneaky trailing spaces! Test locally: curl -s -D- http://127.0.0.1:46309/panel-bananas_42/ Symptom: Client won’t connect Run a WebSocket test: curl -i -k -H \u0026#34;Connection: Upgrade\u0026#34; -H \u0026#34;Upgrade: websocket\u0026#34; https://web.example.com/stealth-path_abcd1234 Expect 101 Switching Protocols. If not, check Nginx config. Symptom: Censor still blocks you Did you expose another service (like SSH, Matrix, or Minecraft) on the same IP?\n→ They’ll find your origin IP. Use a second VPS or lock those ports down. Did you use an obvious path like /ws?\n→ Use a random-looking one like /cdn-assets-329df/. Pro Tips \u0026amp; Fun Tricks Serve a fake blog or portfolio at root so your domain looks legit. Rotate WebSocket paths occasionally. Use Cloudflare Page Rules to disable Rocket Loader \u0026amp; Minify for your VPN domain. Make friends with your Nginx error.log — it will roast you but it tells the truth. Conclusion By putting Trojan behind Cloudflare, you’ve given your VPN a shiny new disguise:\nLooks like normal HTTPS. Hides your origin IP. Forces censors into a tough choice: block Cloudflare (and half the web) or let you pass. Congrats — you’ve built a VPN with both style and stealth. 🥷\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/vpn/","summary":"\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003eSo you want a VPN that \u003cstrong\u003edoesn\u0026rsquo;t scream \u0026ldquo;I am a VPN\u0026rdquo;\u003c/strong\u003e to every censor and firewall out there?\u003cbr\u003e\nWelcome to the world of \u003cstrong\u003eTrojan over WebSocket + TLS behind Cloudflare\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eThis guide not only shows you how to set it up but also sprinkles in some \u003cstrong\u003edebugging magic\u003c/strong\u003e so you can figure out why things break (and they \u003cem\u003ewill\u003c/em\u003e break, trust me).\u003c/p\u003e\n\u003cp\u003eWe’ll anonymise domains and secrets, so substitute with your own:\u003c/p\u003e","title":"Stealth Trojan VPN Behind Cloudflare Guide"},{"content":"This month started with me setting up a deadline for Conext student workshop. I wanted to submit something not only to get the chance to attend a nice conference, but to create a network, meet researchers, and to get a chance to present my work and get feedback on it. I also worked on my code-base, finally making everything work by replacing Jool with clatd for performing CxLAT. This darn thing wasted so much of my time! I did learn a bit along the way, but oh well. Such is life!\nOverall not the most productive month, but one reason for it is that I have\u0026rsquo;t really had a real vacation in a long time. I will be taking a 10 day vacation next month just to reset, and gain back my power. I cross my fingers for the month ahead!\nMy social life has been becoming better too. I\u0026rsquo;ve been trying to attend more ZiS events to meet people, make new connections, and to have fun! My depression is a serious issue. I also have anxiety disorder that I\u0026rsquo;m talking with my therapist about. I will most likely start taking SSRIs once again. Idiot me was cutting it when the catasrophe in February happened and did not think twice to keep taking them. I\u0026rsquo;m really glad I was able to recover, though it was not easy at all, it did work out.\nI do think there is bright nice future ahead of me; I just need to keep on trucking and not worry too much. Things will workout!\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/phd_journey/augest_2025/","summary":"\u003cp\u003eThis month started with me setting up a deadline for Conext student workshop.\nI wanted to submit something not only to get the chance to attend a nice conference, but to create a network, meet researchers, and to get a chance to present my work and get feedback on it.\nI also worked on my code-base, finally making everything work by replacing Jool with clatd for performing CxLAT.\nThis darn thing wasted so much of my time!\nI did learn a bit along the way, but oh well.\nSuch is life!\u003c/p\u003e","title":"Augest '25"},{"content":"HedgeDoc is an open-source collaborative Markdown editor. Think Google Docs for Markdown: multiple people can edit the same note in real-time, with support for diagrams, math, polls, and slide decks. In this post we’ll walk through setting up your own instance on a server, secured with HTTPS.\nPrerequisites A Linux server with Docker and Docker Compose A domain name pointing to your server (e.g. notes.alipourimjourneys.ir) Nginx installed for reverse proxying Certbot for Let’s Encrypt certificates 1. Create the project directory mkdir ~/hedgedoc \u0026amp;\u0026amp; cd ~/hedgedoc 2. Create .env POSTGRES_PASSWORD=ChangeThisStrongPassword HD_DOMAIN=notes.alipourimjourneys.ir Generate a strong password with:\nopenssl rand -base64 32 3. Create docker-compose.yml version: \u0026#34;3.9\u0026#34; services: db: image: postgres:16 environment: POSTGRES_USER: hedgedoc POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: hedgedoc volumes: - db:/var/lib/postgresql/data restart: unless-stopped hedgedoc: image: quay.io/hedgedoc/hedgedoc:1.10.2 depends_on: - db environment: CMD_DB_URL: postgres://hedgedoc:${POSTGRES_PASSWORD}@db:5432/hedgedoc CMD_DOMAIN: ${HD_DOMAIN} CMD_PROTOCOL_USESSL: \u0026#34;true\u0026#34; CMD_URL_ADDPORT: \u0026#34;false\u0026#34; CMD_PORT: \u0026#34;3000\u0026#34; CMD_EMAIL: \u0026#34;true\u0026#34; CMD_ALLOW_EMAIL_REGISTER: \u0026#34;false\u0026#34; volumes: - uploads:/hedgedoc/public/uploads ports: - \u0026#34;127.0.0.1:3000:3000\u0026#34; restart: unless-stopped volumes: db: uploads: Bring it up:\ndocker compose up -d 4. Get a Let’s Encrypt certificate Request a cert with a DNS challenge:\nsudo certbot certonly --manual --preferred-challenges dns -d notes.alipourimjourneys.ir -m you@example.com --agree-tos --no-eff-email Add the TXT record certbot asks for, wait for DNS to propagate, then continue.\nCertificates will be in:\n/etc/letsencrypt/live/notes.alipourimjourneys.ir/ 5. Configure Nginx Create /etc/nginx/sites-available/notes.alipourimjourneys.ir:\nserver { server_name notes.alipourimjourneys.ir; listen 80; listen [::]:80; return 301 https://$host$request_uri; } server { server_name notes.alipourimjourneys.ir; listen 443 ssl http2; listen [::]:443 ssl http2; ssl_certificate /etc/letsencrypt/live/notes.alipourimjourneys.ir/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/notes.alipourimjourneys.ir/privkey.pem; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; } } Enable the config and reload Nginx:\nsudo ln -s /etc/nginx/sites-available/notes.alipourimjourneys.ir /etc/nginx/sites-enabled/ sudo nginx -t \u0026amp;\u0026amp; sudo systemctl reload nginx 6. Create users Because we disabled self-registration, create accounts manually:\ndocker compose exec hedgedoc ./bin/manage_users --add alice@example.com You’ll be prompted for a password.\nTo reset a password later:\ndocker compose exec hedgedoc ./bin/manage_users --reset alice@example.com 7. Done! Visit https://notes.alipourimjourneys.ir and log in with the user you created. You now have your own collaborative Markdown editor 🎉\nExtras:\nBackups: dump the PostgreSQL database and save the uploads volume. Upgrades: docker pull quay.io/hedgedoc/hedgedoc:latest \u0026amp;\u0026amp; docker compose up -d. Integrations: HedgeDoc supports S3/MinIO image storage, GitHub/GitLab/Google login, and more. ","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/hedge_doc/","summary":"\u003cp\u003eHedgeDoc is an open-source collaborative Markdown editor. Think \u003cem\u003eGoogle Docs for Markdown\u003c/em\u003e: multiple people can edit the same note in real-time, with support for diagrams, math, polls, and slide decks. In this post we’ll walk through setting up your own instance on a server, secured with HTTPS.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"prerequisites\"\u003ePrerequisites\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eA Linux server with \u003cstrong\u003eDocker\u003c/strong\u003e and \u003cstrong\u003eDocker Compose\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eA domain name pointing to your server (e.g. \u003ccode\u003enotes.alipourimjourneys.ir\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNginx\u003c/strong\u003e installed for reverse proxying\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCertbot\u003c/strong\u003e for Let’s Encrypt certificates\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-create-the-project-directory\"\u003e1. Create the project directory\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003emkdir ~/hedgedoc \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e cd ~/hedgedoc\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003chr\u003e\n\u003ch2 id=\"2-create-env\"\u003e2. Create \u003ccode\u003e.env\u003c/code\u003e\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-env\" data-lang=\"env\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ePOSTGRES_PASSWORD\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eChangeThisStrongPassword\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eHD_DOMAIN\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003enotes.alipourimjourneys.ir\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp\u003eGenerate a strong password with:\u003c/p\u003e","title":"Self-Hosting HedgeDoc with Docker + Nginx + Let's Encrypt"},{"content":" TL;DR: The call kept saying “waiting for media” because the browser never opened a WebSocket to LiveKit. The root cause was duplicate Access-Control-Allow-Origin headers on /sfu/get (CORS), which stopped the JWT response. Fixing CORS and ensuring the WS proxy worked (HTTP 101 in logs) solved it.\nWhat I\u0026rsquo;m building A Matrix homeserver (Synapse) at matrix.example.com (replace with your domain). TURN/STUN (coTURN) for NAT traversal. Element Call backed by LiveKit, fronted by rtc.example.com. Nginx (host) as the single reverse proxy for everything. Cloudflare DNS (with rtc.* set to DNS-only, no orange cloud). UFW firewall opened for Matrix federation, TURN, and LiveKit media ports. I used Docker for Synapse, PostgreSQL, LiveKit and the JWT helper. I used host Nginx (not Nginx in Docker) to avoid port binding conflicts on 80/443/8448.\nPrereqs DNS A/AAAA: matrix.example.com → your server (v4/v6) rtc.example.com → your server (v4/v6) Certificates: matrix.example.com and rtc.example.com via Let’s Encrypt on the host Cloudflare: DNS-only (grey cloud) for rtc.example.com so WebSockets \u0026amp; UDP work without interference. UFW / firewall open: 80/tcp, 443/tcp 8448/tcp (Matrix federation) 3478/tcp, 3478/udp and 5349/tcp (TURN/TLS) LiveKit: 7881/tcp and 50100–50200/udp (or your chosen range) Docker + docker compose installed. Synapse + PostgreSQL 1) The PostgreSQL collation gotcha Synapse prefers the database collation C. If your Postgres cluster was initialized with en_US.utf8, Synapse will error like:\nD a t a b a s e h a s i n c o r r e c t c o l l a t i o n o f ' e n _ U S . u t f 8 ' . S h o u l d b e ' C ' Two ways to resolve:\nPreferred (clean): Re-initialize the Postgres cluster with C and UTF-8: # docker-compose.yml (excerpt for Postgres) services: db: image: postgres:16 environment: POSTGRES_DB: synapse POSTGRES_USER: synapse POSTGRES_PASSWORD: \u0026lt;strong-password\u0026gt; POSTGRES_INITDB_ARGS: \u0026#34;--locale=C --encoding=UTF8 --lc-collate=C --lc-ctype=C\u0026#34; volumes: - ./pgdata:/var/lib/postgresql/data Requires wiping the volume and recreating the DB.\nPragmatic (works quickly): In Synapse’s DB config, set allow_unsafe_locale: true. This bypasses the check. It’s fine for hobby use; for production, prefer the clean C cluster. 2) Start Synapse and generate config docker compose up -d db synapse # Logs docker compose logs --tail=200 synapse Ensure Synapse prints your server_name and public base URL and stays up.\n3) Create an admin user # Exec into the running Synapse container: docker compose exec synapse register_new_matrix_user \\ -c /data/homeserver.yaml -u \u0026lt;username\u0026gt; -p \u0026lt;password\u0026gt; \\ -a -k If you see “Unknown execution mode”, you probably ran the binary with the wrong entrypoint. Use docker compose exec synapse … against the running container.\nTURN (coTURN) 1) Avoid bad inline comments If you see errors like:\nE R R O R : U n k n o w n b o o l e a n v a l u e : # l o g t o j o u r n a l d / s y s l o g . Y o u c a n u s e o n / o f f , y e s / n o , 1 / 0 , t r u e / f a l s e . …it means a # comment is on the same line as a boolean directive. Move comments to their own lines.\n2) Minimal turnserver.conf listening-port=3478 tls-listening-port=5349 fingerprint use-auth-secret static-auth-secret=\u0026lt;shared-secret\u0026gt; # also set in Synapse realm=example.com # used in creds generation total-quota=0 bps-capacity=0 cli-password=\u0026lt;admin-pass\u0026gt; no-cli cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem # If behind NAT: # external-ip=\u0026lt;public-ip\u0026gt;/\u0026lt;internal-ip\u0026gt; 3) Wire TURN into Synapse In homeserver.yaml:\nturn_uris: - \u0026#34;turn:turn.example.com?transport=udp\u0026#34; - \u0026#34;turn:turn.example.com?transport=tcp\u0026#34; - \u0026#34;turns:turn.example.com:5349?transport=tcp\u0026#34; turn_shared_secret: \u0026#34;\u0026lt;shared-secret\u0026gt;\u0026#34; turn_user_lifetime: \u0026#34;1d\u0026#34; Verify the homeserver issues TURN creds:\nTOKEN=\u0026#39;\u0026lt;your matrix access token\u0026gt;\u0026#39; curl -s -H \u0026#34;Authorization: Bearer $TOKEN\u0026#34; \\ https://matrix.example.com/_matrix/client/v3/voip/turnServer | jq You should see uris, a time-limited username and password.\nNginx (host) for Synapse and federation Create /etc/nginx/conf.d/matrix.conf:\n# Client traffic on 443 server { listen 443 ssl; listen [::]:443 ssl; server_name matrix.example.com; ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem; # Proxy client \u0026amp; admin APIs to Synapse (container) on 8008 location ~ ^(/_matrix|/_synapse/client) { proxy_pass http://127.0.0.1:8008; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; client_max_body_size 50M; } # Advertise homeserver base + RTC focus (MSC4143) via .well-known location = /.well-known/matrix/client { default_type application/json; add_header Access-Control-Allow-Origin \u0026#34;*\u0026#34; always; return 200 \u0026#39;{\u0026#34;m.homeserver\u0026#34;:{\u0026#34;base_url\u0026#34;:\u0026#34;https://matrix.example.com\u0026#34;},\u0026#34;org.matrix.msc4143.rtc_foci\u0026#34;:[{\u0026#34;type\u0026#34;:\u0026#34;livekit\u0026#34;,\u0026#34;livekit_service_url\u0026#34;:\u0026#34;https://rtc.example.com\u0026#34;}]}\u0026#39;; } } # Federation on 8448 server { listen 8448 ssl http2; listen [::]:8448 ssl http2; server_name matrix.example.com; ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8008; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; client_max_body_size 50M; } } Reload and sanity check:\nsudo nginx -t \u0026amp;\u0026amp; sudo systemctl reload nginx curl -s https://matrix.example.com/.well-known/matrix/client | jq Also check Synapse supports RTC signaling (MSC4140) so clients actually use it:\ncurl -s https://matrix.example.com/_matrix/client/versions | jq \u0026#39;.unstable_features.\u0026#34;org.matrix.msc4140\u0026#34;\u0026#39; # expect: true Element Call + LiveKit I’ll run LiveKit and the small JWT helper (Element’s elementcall_jwt) in Docker. LiveKit handles media; the JWT helper mints access tokens for WebSocket connects.\n1) LiveKit config (/etc/livekit.yaml inside container) port: 7880 bind_addresses: [\u0026#34;0.0.0.0\u0026#34;] rtc: tcp_port: 7881 port_range_start: 50100 port_range_end: 50200 use_external_ip: true logging: level: info turn: enabled: false keys: lk_prod_1: \u0026#34;REPLACE_WITH_A_64_CHAR_RANDOM_SECRET___________________________________\u0026#34; Important: the secret must be \u0026gt;= 32 chars. The default devkey will trigger secret is too short warnings and won’t work with the JWT helper.\n2) elementcall_jwt env Run it with:\nLIVEKIT_URL=wss://rtc.example.com (root WS URL, no /livekit/sfu path) LIVEKIT_KEY=lk_prod_1 LIVEKIT_SECRET=\u0026lt;the long secret above\u0026gt; LIVEKIT_JWT_PORT=8080 (internal HTTP port the proxy will hit) Optionally: LIVEKIT_FULL_ACCESS_HOMESERVERS=* during setup Check logs on start; it prints the LIVEKIT_URL it will advertise.\n3) Nginx (host) for rtc.example.com Create /etc/nginx/conf.d/rtc.conf:\nserver { listen 443 ssl; listen [::]:443 ssl; server_name rtc.example.com; ssl_certificate /etc/letsencrypt/live/rtc.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/rtc.example.com/privkey.pem; access_log /var/log/nginx/rtc.access.log combined; # 3a) JWT endpoint with clean CORS (avoid duplicate ACAO) location = /sfu/get { proxy_hide_header Access-Control-Allow-Origin; add_header Access-Control-Allow-Origin $http_origin always; add_header Vary \u0026#34;Origin\u0026#34; always; add_header Access-Control-Allow-Methods \u0026#34;POST, OPTIONS\u0026#34; always; add_header Access-Control-Allow-Headers \u0026#34;Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization\u0026#34; always; if ($request_method = OPTIONS) { return 204; } proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:8070/sfu/get; # elementcall_jwt } # 3b) LiveKit WS \u0026amp; HTTP (catch-all) location / { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; proxy_set_header Sec-WebSocket-Protocol $http_sec_websocket_protocol; # \u0026#34;livekit\u0026#34; proxy_set_header Origin $http_origin; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; proxy_read_timeout 3600s; proxy_pass http://127.0.0.1:7880; # livekit } } Reload and basic checks:\nsudo nginx -t \u0026amp;\u0026amp; sudo systemctl reload nginx # JWT preflight (should return a single ACAO header) curl -si -X OPTIONS https://rtc.example.com/sfu/get \\ -H \u0026#39;Origin: https://app.element.io\u0026#39; \\ -H \u0026#39;Access-Control-Request-Method: POST\u0026#39; \\ -H \u0026#39;Access-Control-Request-Headers: authorization, content-type\u0026#39; | sed -n \u0026#39;1,30p\u0026#39; # Expected: one Access-Control-Allow-Origin and 200/204 Why I did this: I initially had two Access-Control-Allow-Origin headers (one added by the upstream, one by Nginx). Browsers reject that with “Access-Control-Allow-Origin cannot contain more than one origin”, so the JWT response never reached the client. Fixing CORS fixed everything.\nThe “waiting for media” debugging story (how I found it) Symptom: Element Call created rooms, but calls stayed on “waiting for media.”\nWhat worked:\nelementcall_jwt could CreateRoom in LiveKit (seen in logs). TURN creds endpoint returned time-limited credentials. What didn’t appear:\nNo HTTP 101 lines in rtc.access.log → the browser never established a WebSocket to LiveKit. Step 1: prove the WS vhost works (even without auth) curl -v --http1.1 \\ -H \u0026#39;Connection: Upgrade\u0026#39; -H \u0026#39;Upgrade: websocket\u0026#39; \\ -H \u0026#39;Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\u0026#39; \\ -H \u0026#39;Sec-WebSocket-Version: 13\u0026#39; \\ https://rtc.example.com/rtc -o /dev/null Result: I got a 401 (expected), but importantly I saw a log line in rtc.access.log. So Nginx WS proxying was fine.\nStep 2: check the browser console The smoking gun in DevTools:\nA F c e c t e c s h s - A C P o I n t c r a o n l n - o A t l l l o o w a - d O r h i t g t i p n s : c / a / n r n t o c t . e c x o a n m t p a l i e n . c m o o m r / e s f t u h / a g n e t o n d e u e o r t i o g i a n c . c e s s c o n t r o l c h e c k s . The browser refused the JWT call due to duplicated ACAO headers, so no token → no WebSocket connect.\nFix: in Nginx I added:\nproxy_hide_header Access-Control-Allow-Origin; add_header Access-Control-Allow-Origin $http_origin always; add_header Vary \u0026#34;Origin\u0026#34; always; …and ensured no other add_header created duplicates. After that, /sfu/get succeeded and the WebSocket to wss://rtc.example.com immediately followed (I saw HTTP 101 in the logs).\nStep 3: confirm LiveKit side Once the WS was up, LiveKit logs showed participants joining (not just RoomService.CreateRoom), and calls were established.\nUseful verification commands # Synapse features (expect MSC4140 true) curl -s https://matrix.example.com/_matrix/client/versions | jq \u0026#39;.unstable_features.\u0026#34;org.matrix.msc4140\u0026#34;\u0026#39; # Well-known with RTC focus curl -s https://matrix.example.com/.well-known/matrix/client | jq # TURN creds (with your access token) curl -s -H \u0026#34;Authorization: Bearer $TOKEN\u0026#34; \\ https://matrix.example.com/_matrix/client/v3/voip/turnServer | jq # JWT health curl -si -X POST https://rtc.example.com/sfu/get # LiveKit simple HTTP probe curl -si https://rtc.example.com | head # Nginx logs (look for 101 Switching Protocols when a call starts) sudo tail -f /var/log/nginx/rtc.access.log | grep \u0026#39; 101 \u0026#39; Common pitfalls (I hit these so you don’t have to) Host Nginx vs Docker Nginx: If you already run Nginx on the host, don’t also bind 80/443/8448 in a Docker Nginx — you’ll get bind() ... already in use and restart loops. Use host Nginx to reverse proxy to containers. Nginx http2 directive: Old Nginx may not support the http2 directive on listen. Use listen 443 ssl; (and add http2 if your version supports it). Certificate name mismatch: Make sure rtc.example.com’s vhost uses a certificate for that exact hostname (initially I had the matrix.* cert on rtc.* and curl complained). Postgres collation: Either initialize the cluster with C or use allow_unsafe_locale: true in Synapse DB config to get running quickly. CORS duplication on /sfu/get: Only ONE Access-Control-Allow-Origin header. If the upstream adds it too, use proxy_hide_header Access-Control-Allow-Origin; on the Nginx location. Cloudflare: Use DNS-only for rtc.*. Proxies can interfere with WS and UDP paths. Firewall: Open the LiveKit UDP range and TURN ports on both v4 and v6. Final checklist (print me) https://matrix.example.com/.well-known/matrix/client returns both m.homeserver.base_url and org.matrix.msc4143.rtc_foci pointing to https://rtc.example.com. /_matrix/client/versions shows \u0026quot;org.matrix.msc4140\u0026quot;: true. /sfu/get preflight returns one Access-Control-Allow-Origin and 200/204. Starting a call creates HTTP 101 entries to wss://rtc.example.com in rtc.access.log. LiveKit logs show participants joining (not just CreateRoom). /voip/turnServer returns time-limited TURN credentials. Cloudflare set to DNS-only for rtc.*. UFW allows 7881/tcp and your LiveKit UDP range. Credits \u0026amp; tooling Matrix Synapse, coTURN, LiveKit, Element Call. curl, jq, docker compose logs, Nginx access logs. These are your best friends. The debugging breakthrough was catching CORS errors in the browser console and looking for HTTP 101 in Nginx logs. Happy calling! 🎉\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/matrix_setup/","summary":"\u003cblockquote\u003e\n\u003cp\u003eTL;DR: The call kept saying \u003cstrong\u003e“waiting for media”\u003c/strong\u003e because the browser never opened a WebSocket to LiveKit. The root cause was \u003cstrong\u003eduplicate \u003ccode\u003eAccess-Control-Allow-Origin\u003c/code\u003e headers\u003c/strong\u003e on \u003ccode\u003e/sfu/get\u003c/code\u003e (CORS), which stopped the JWT response. Fixing CORS and ensuring the WS proxy worked (HTTP \u003cstrong\u003e101\u003c/strong\u003e in logs) solved it.\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"what-im-building\"\u003eWhat I\u0026rsquo;m building\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eA \u003cstrong\u003eMatrix homeserver\u003c/strong\u003e (Synapse) at \u003ccode\u003ematrix.example.com\u003c/code\u003e (replace with your domain).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTURN/STUN\u003c/strong\u003e (coTURN) for NAT traversal.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eElement Call\u003c/strong\u003e backed by \u003cstrong\u003eLiveKit\u003c/strong\u003e, fronted by \u003ccode\u003ertc.example.com\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNginx (host)\u003c/strong\u003e as the single reverse proxy for everything.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCloudflare\u003c/strong\u003e DNS (with \u003ccode\u003ertc.*\u003c/code\u003e set to \u003cstrong\u003eDNS-only\u003c/strong\u003e, no orange cloud).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eUFW\u003c/strong\u003e firewall opened for Matrix federation, TURN, and LiveKit media ports.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI used Docker for Synapse, PostgreSQL, LiveKit and the JWT helper. I used \u003cstrong\u003ehost\u003c/strong\u003e Nginx (not Nginx in Docker) to avoid port binding conflicts on 80/443/8448.\u003c/p\u003e","title":"Self-hosting Matrix + Element Call with LiveKit: from zero to working (and the [not so!!] fun debugging along the way)"},{"content":"This is a test hello world post just to make sure everything works!\n","permalink":"http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/hello-world/","summary":"\u003cp\u003eThis is a test hello world post just to make sure everything works!\u003c/p\u003e","title":"Hello World"}]