GeistHaus
log in · sign up

https://w00kie.com/feed

rss
10 posts
Polling state
Status active
Last polled May 19, 2026 01:37 UTC
Next poll May 20, 2026 03:50 UTC
Poll interval 86400s
ETag W/"f6880be655fc5a53032313a7bc6daea8"
Last-Modified Fri, 08 Nov 2024 08:28:20 GMT

Posts

Launching a SaaS application – EpicForge.app
Coding

Shipping a real paid SaaS app for the first time can be really emotional.

The post Launching a SaaS application – EpicForge.app first appeared on w00kie's ramblings.

Show full content

For a bit more than a month now I’ve been working hard on finishing my new app EpicForge.app and finally launching it. This is a big step for me: I’ve shipped my fair share of websites, apps and random tools on internet, but this is the first time I’m making a paid app, asking real money to use it.

Why is it that as soon as you complete that stripe checkout integration and slap a pricing section on your landing page, it becomes so much more emotional?

When it’s a free tool, you don’t really care whether people use it or not, you just had an idea and feel content to have made it a thing and put it out in the world but when it’s a paid app, if nobody buys it then suddenly it feels like this big failure.

Are you good enough? Somehow this becomes a judgement of your skills and business acumen. Because of this feeling I’ve refrained from turning on payments in some of my apps from fear of failure… But not this time.

What is it?

Epic Forge is based on a tool I built for myself and have used for the past 3 or 4 years at work. I manage these recurring projects in JIRA that consist of an epic and a bunch of stories/tasks which are mostly the same but have different assignees depending on the geo (APAC/AMER/EMEA).

At first I would use the out-of-the-box JIRA deep clone tool but then would spend an hour editing the titles, changing the assignees, etc. I also hate that it keeps cloned-from links to the original issue.

So I built my own tool that stores templates for your epics (or sets of stories) and lets you export to a CSV for easy import into your projects. It has nifty features:

  • Templates can have multiple variants where you can toggle part of the issues and/or change the default assignee.
  • Issues can have placeholder text (using the {{placeholder}} format) in the issue summary and description which you can auto-replace at export time.
Tech Stack

I always go for my trusty Django for the backend, but this time I tried my hand at a modern React frontend with NextJS.

While I’m really not a frontend guy, shadcn/ui has made building professional looking interface a real breeze and I’m actually enjoying this part of the build for the first time in years.

Finally, I’m hosting all of this with Coolify on a Hetzner VPS which is also really cool and easy to setup.

Conclusion

I don’t know if this app will be successful, but at least I went all the way and shipped something out in the world. That’s always a good muscle to flex.

The post Launching a SaaS application – EpicForge.app first appeared on w00kie's ramblings.

https://w00kie.com/?p=1466
Extensions
Running my local dev environment with SSL & domain names
Coding

An up-to-date setup guide to run a dev environment with test domain names and SSL on Apple Silicon and MacOS Sequoia.

The post Running my local dev environment with SSL & domain names first appeared on w00kie's ramblings.

Show full content

So I’m building this app with a NextJS frontend and a Django backend and this is bringing tons of headaches with cross-origin resource sharing (CORS for the uninitiated… and you should feel lucky if you don’t know about it) and all kind of cookie troubles.

In order to debug this, I need to have the same setup on my dev environment as in production, just using localhost and different ports is not good enough. So I set out to get some local domain names and SSL, which is actually fairly standard, but hit a few snags on the way in that many guides out there are outdated with Apple Silicon Macs and MacOS Sequoia.

So here’s what I found that works.

The plan

I will setup 3 things:

  1. A .test top level domain that will resolve anything, such as app.test and api.app.test to 127.0.0.1
  2. A way to have valid SSL certificates for any of these domains
  3. A web server that can route requests to these domains to your dev servers running with things like bun run dev or python manage.py runserver

All of this on a recent Mac with Apple Silicon and the latest MacOS Sequoia.

I will assume you have homebrew running already, if not you need to rethink your life.

1. Local DNS with dnsmasq

dnsmasq is a lightweight DNS forwarder and DHCP server that provides simple network services for small networks. It can cache DNS queries to speed up subsequent requests and allows you to create custom DNS records for local domains.

brew install dnsmasq

    First we’ll declare a new .test tld and tell MacOS to resolve it with our local DNS server which we will setup with dnsmasq instead of calling the live DNS servers on internet. We do this by setting up a single file /etc/resolver/test

    nameserver 127.0.0.1

    Just change the name of the file if you want something else than .test

    Now setup dnsmasq by adding a conf file at /opt/homebrew/etc/dnsmasq.d/test.conf

    address=/test/127.0.0.1

    This tells it to resolve any domain *.test to localhost. That’s where a lot of existing guides fail because they tell you to add the conf in /usr/local/etc/ and that’s for Intel Macs.

    Start the service now:

    brew services start dnsmasq

    Verify your stuff is working by pinging foo.test

    2. SSL setup with mkcert

    mkcert is a simple tool for creating locally trusted SSL certificates without the hassle of configuring a Certificate Authority. It’s perfect for developers who need HTTPS for local projects, making it easy to set up secure connections during development.

    brew install mkcert

    Then we will install the root certificate and generate a certificate for our project.

    mkcert -install
    mkcert "app.test" "*.app.test"

    Move the files to somewhere safe. I just put them in /Users/francois/certs/. That’s it.

    3. Reverse Proxy with Caddy

    Caddy is a modern, user-friendly web server that automatically manages HTTPS by acquiring and renewing SSL certificates. Known for its simplicity and ease of configuration, it’s ideal for developers who want a secure, performant, and versatile server without complex setups.

    brew install caddy

    The configuration file for Caddy is in /opt/homebrew/etc/Caddyfile

    app.test {
      tls /Users/francois/certs/app.test.pem /Users/francois/certs/app.test-key.pem
      reverse_proxy localhost:5100
    }
    
    api.app.test {
      tls /Users/francois/certs/app.test.pem /Users/francois/certs/app.test-key.pem
      reverse_proxy localhost:8000
    }

    Just map your domains to whatever local ports your dev servers are running on. Now run Caddy as a service:

    brew services start caddy
    Conclusion

    That’s it! You should be all set now.

    As a tip, I like to use honcho to run my dev servers. You just set a Procfile with your service and run honcho start and you have everything running with logging in your console with different colors for each service.

    backend: cd backend && python manage.py runserver
    frontend: cd frontend && bun dev
    stripe: stripe listen --forward-to localhost:8000/webhooks/stripe/

    The post Running my local dev environment with SSL & domain names first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1460
    Extensions
    Django and WhiteNoise on Coolify
    Coding

    How to fix the default run command for Django apps on Coolify/Nixpacks to run collectstatic and fix your 404 errors.

    The post Django and WhiteNoise on Coolify first appeared on w00kie's ramblings.

    Show full content

    Just a little note to remind myself and anyone playing around with Django on Coolify: the default Nixpacks for Django will run migrations but not collectstatic. So if you have problems with your images or CSS not appearing, the quick solution is:

    1. Install whitenoise
    2. Set STATIC_ROOT = BASE_DIR / "staticfiles" in your settings.py file
    3. Add a nixpacks.toml file in your Django base directory that overrides the default run command and adds a collectstatic step
    [start]
      cmd = "python manage.py migrate && python manage.py collectstatic --noinput && gunicorn app.wsgi"

    Then redeploy and you should have your static files display again.

    The post Django and WhiteNoise on Coolify first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1458
    Extensions
    Hosting a Django/NextJS monorepo webapp on Coolify
    Coding

    Coolify has settings to host backend and frontend resources from the same repo without having to mess with manual docker-compose setup.

    The post Hosting a Django/NextJS monorepo webapp on Coolify first appeared on w00kie's ramblings.

    Show full content

    Recently I’ve been building a new web app and trying Coolify for hosting on a cheap but powerful Hetzner VPS.

    Architecture

    This is my first real production app using a NextJS frontend with a Django Rest Framework API backend so I’m learning a ton.

    There are tons of ways to go about it, all with different pros and cons, but I chose to go the monorepo route with a /backend folder for Django and a /frontend folder for NextJS. Using a Procfile with Honcho I can launch the dev server for both frontend and backend and hack away on localhost.

    Deploying to Production

    Now when it comes to production, I chose to install Coolify self-hosted version on a Hetzner CAX11 ARM server with 4GB of memory for a cool 3.79€ per month (need 50c for an IPV4).

    One way to deploy a monorepo is to build your own docker-compose.yml but I wasn’t really confident with my skills there. I’d much rather let nixpacks do the hard work for me. Luckily, it’s possible to do it with minimal setup, albeit not always obvious in the documentation.

    The trick is to create two resources, one for the backend and one for the frontend, both from the same Git repo. Use Nixpacks as a build pack and use the Base Directory option to tell it to base itself only on the content of one of the folder from your repo:

    Then set the Watch Paths option with backend/** or frontend/** (no slash prefix, it’s important) so that each resource redeploys only when there are changes in its own source folder.

    Other tips I wish I’d knew

    I no particular order:

    • Use whitenoise to handle static files in Django if you’re going to use the admin dashboard
    • Setup django-cors-headers if you run your API on a sub-domain
    • OpenAPIConfig.BASE = process.env.NEXT_PUBLIC_API_BASE_URL ?? ""; in your QueryClientProvider file to set your API URL to the said sub-domain

    Also, I really wanted to keep SQLite as my database and keep my resources usage low, but Coolify has a one click solution to backup Postgres and I didn’t want to spend too much time finding out how to make my own backup system.

    If your site has a favicon when you launch, you launched too late

    — Marc Köhlbrugge (@marckohlbrugge) February 26, 2023

    I’m already way past that level of detail and I haven’t shipped my site yet…

    The post Hosting a Django/NextJS monorepo webapp on Coolify first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1445
    Extensions
    Debunking Japanese dual-nationality myths with code
    Codingcoding

    Every single post on Japan-related subreddits that mentions someone is binational will have a bunch of commenters jump in to say it’s illegal, OP needs to hide it or they will be made to renounce as soon as they turn 20. While this is true for people who naturalized to a foreign nationality, it’s patently […]

    The post Debunking Japanese dual-nationality myths with code first appeared on w00kie's ramblings.

    Show full content

    Every single post on Japan-related subreddits that mentions someone is binational will have a bunch of commenters jump in to say it’s illegal, OP needs to hide it or they will be made to renounce as soon as they turn 20. While this is true for people who naturalized to a foreign nationality, it’s patently false for dual-citizens by birth.

    Bi-nationals by birth must select one of the nationalities upon reaching 20, but if they select Japan, there is no requirement to renounce the other citizenship unless they assume public office.

    Article 16(1)A Japanese citizen who makes the selection declaration must endeavor to renounce their foreign nationality.

    (2)In cases where a Japanese citizen having made the selection declaration and not having lost foreign nationality assumes the post of a public employee (with the exception of a post that may be assumed by a person not having the nationality of that country) at their own discretion, the Minister of Justice may pronounce a judgment of loss of Japanese nationality if it is found that the assumption of the post is markedly contrary to the purport of the selection of Japanese nationality.

    Source

    “must endeavor to renounce” is very different from “must renounce” and there are no provisions for failing to renounce outside of the public office case.

    When you point this out to the posters, they will invariably bring up the case of Naomi Osaka who very publicly chose her Japanese nationality to participate under the 日の丸 flag at the Tokyo Olympics. Many sports publications articles claim she thus renounced her US citizenship but there has never been any actual announcement directly from her to that purpose.


    Okay, so where does the coding come up you might ask? Well it was pointed out to me that US citizenship renouncement is a heavy process. There are many forms to fill and hefty fees to pay. Most importantly, all renouncements are published in the Federal Register under the title Quarterly Publication of Individuals, Who Have Chosen To Expatriate (see an example here). So let’s see if we can find Naomi in these publications.

    The Federal Register has a very good API available without any registration required, so there’s no need to fiddle with web scraping. Our python script is very simple:

    import requests
    
    url = "https://www.federalregister.gov/api/v1/documents.json"
    
    querystring = {
        "fields[]": ["title", "raw_text_url", "document_number"],
        "conditions[type][]": "NOTICE",
        "per_page": "500",
        "conditions[term]": "Quarterly Publication of Individuals, Who Have Chosen To Expatriate",
    }
    
    response = requests.request("GET", url, params=querystring)
    
    for item in response.json()["results"]:
        publication = requests.get(item["raw_text_url"])
    
        try:
            publication.raise_for_status()
        except requests.exceptions.HTTPError as e:
            print(f"Error downloading {item['document_number']}: {e}")
            continue
    
        # Save the publication text to a file
        with open(f"data/{item['document_number']}.txt", "w") as f:
            f.write(publication.text)
            print(f"Downloaded {item['document_number']}")
    

    Running this will download a text file version of all matching publications from 1997 to today in the /data folder. Now all we need to do is to recursively search for OSAKA in the downloaded files with grep -ri OSAKA data/

    Unsurprisingly, this does not bring any results, nor with her father’s name LEONARD. Therefore, unless there is a way for celebrities to opt out of having their name published in the government gazette, she has not renounces her US citizenship, despite her very high profile, because she does not have to.

    Hang around for the next chapter of this exploration where I will use ChatGPT to see if we can extract some interesting info from this data.

    The post Debunking Japanese dual-nationality myths with code first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1433
    Extensions
    Zip R2 Objects in Memory with Cloudflare Workers
    internetRandomcoding

    How to serve a list of files stored in R2 as a Zip file through Cloudflare Workers all in memory.

    The post Zip R2 Objects in Memory with Cloudflare Workers first appeared on w00kie's ramblings.

    Show full content

    I have a list of files stored in R2 and I want to serve them bundled as a Zip file to my user, but I want to do this through Cloudflare Workers and all in memory so it never hits even temporary storage.

    It seems like a super basic thing you’d want to do with Cloudflare Workers when you store files in R2, but I’ve googled far and wide and couldn’t find an example of this anywhere.

    So here’s my take on this, after much trial and error:

    import { z } from 'zod';
    import { ZipWriter, BlobReader, configure } from '@zip.js/zip.js';
    
    // Without this, we get uncaught error due to Workers runtime bug
    // See: https://github.com/gildas-lormeau/zip.js/discussions/514
    configure({
    	useCompressionStream: false,
    });
    
    // Payload schema that lists the files to be bundled, their filenames and the archive filename
    const schema = z.object({
    	archive_filename: z.string().endsWith('.zip'),
    	files: z.array(
    		z.object({
    			r2key: z.string(),
    			filename: z.string(),
    		})
    	),
    });
    
    export default {
    	async fetch(request, env, ctx): Promise<Response> {
    		const body = await request.json();
    
    		const payload = schema.safeParse(body);
    		if (!payload.success) {
    			return new Response(JSON.stringify({ status: 'failed', error: payload.error }), {
    				status: 409,
    				headers: { 'Content-Type': 'application/json' },
    			});
    		}
    
    		let { readable, writable } = new IdentityTransformStream();
    
    		// Create a ZIP archive stream
    		const archive = new ZipWriter(writable);
    
    		// Store all the promises to catch any errors all together later
    		let promises: Promise<any>[] = [];
    
    		for (const somefile of payload.data.files) {
    			const { r2key, filename } = somefile;
    
    			// Fetch the file from the R2 bucket
    			const fileContent = await env.STORAGE_BUCKET.get(r2key);
    			if (!fileContent) {
    				return new Response(`Object not found: ${r2key}`, { status: 404 });
    			}
    
    			// Add the file to the ZIP archive
    			promises.push(archive.add(filename, new BlobReader(await fileContent.blob())));
    		}
    
    		promises.push(archive.close());
    
    		Promise.all(promises).catch((err) => {
    			console.log(err);
    		});
    
    		return new Response(readable, {
    			headers: {
    				'Content-Type': 'application/zip',
    				'Content-Disposition': `attachment; filename="${payload.data.archive_filename}"`,
    			},
    		});
    	},
    } satisfies ExportedHandler<Env>;

    You can just POST to it a JSON object with the output filename for the zip file and a list of R2 object keys with a filename to set for each.

    Don’t forget to set your bindings in the wrangler.toml.

    The post Zip R2 Objects in Memory with Cloudflare Workers first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1372
    Extensions
    Simple Japanese Inheritance Calculator
    Randomcodingjapan

    I made a thing again! If you’re like me, a foreigner in your 40s living in Japan, the thought of dealing with inheritance taxes for your parents back home can be daunting. Understanding how these taxes might be applied is essential. To help with this, I created an app: Japanese Inheritance Tax Calculator. Here are […]

    The post Simple Japanese Inheritance Calculator first appeared on w00kie's ramblings.

    Show full content

    I made a thing again!

    If you’re like me, a foreigner in your 40s living in Japan, the thought of dealing with inheritance taxes for your parents back home can be daunting. Understanding how these taxes might be applied is essential.

    To help with this, I created an app: Japanese Inheritance Tax Calculator.

    Here are a few insights:

    • It’s not as straightforward as you might think.
    • The calculation method actually offers some advantages for foreigners.
    • The base deduction is quite high, meaning many people won’t owe taxes on even fairly sizable inheritances.

    Bear in mind that this is a super simplified calculator: it only works in the case of a standard family unit where a parent of the user passes away and the user is the only heir living in Japan. But that’s fits me and a lot of my friends in Japan, so #scratchyourownitch it is.

    It’s a tiny single page app built on Vite + React with shadcn/ui components. You can check out the code in the GitHub repo.

    The post Simple Japanese Inheritance Calculator first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1367
    Extensions
    Cost Breakdown of a Birth in Tokyo
    japanbabyhospitalmoney

    In June 2021, my wife and I were extremely happy to welcome our first son into the world. I know many people express curiousness towards the costs involved with delivery in Japan as the system of it not being covered by health insurance but also somewhat covered by various subventions from the national and local […]

    The post Cost Breakdown of a Birth in Tokyo first appeared on w00kie's ramblings.

    Show full content

    In June 2021, my wife and I were extremely happy to welcome our first son into the world. I know many people express curiousness towards the costs involved with delivery in Japan as the system of it not being covered by health insurance but also somewhat covered by various subventions from the national and local governments makes it all pretty confusing. So here’s a full breakdown of what it ended up costing us.

    tldr; I thought I would be claiming a massive 医療費控除 but after tabulating all of my receipts, it seems I severely underestimated how much cash-back I got from various institutions. I ended up in the black even though my wife gave birth with an epidural at the top hospital in Tokyo.

    Delivery costs

    Wife spent a month in Minato-ku’s Aiiku hospital (she was at risk of going in early labor) from week 31 to 34. She was in a private room per doctor’s order so no extra charge there. Then she gave birth with an epidural and stayed for the standard 5 days after delivery in a private room at 30,000¥ per night.

    Total cost: 1,317,059¥

    Then we got all of the following cash back:

    • Basic childbirth coverage: 420,000¥
    • Minato-ku resident childbirth coverage: 280,000¥
    • Company’s insurance childbirth coverage: 30,000¥
    • Company’s congratulations bonus: 10,000¥ (fucking stingy bastards)
    • Company’s insurance extra payments (not sure where this is coming from, I did claim 高額療養費制度): 282,100¥
    • Wife’s life insurance coverage (Nissay): 240,019¥
    • Wife’s women’s health insurance coverage (Coop Kyosai – subscribed after we confirmed pregnancy just in case): 228,000¥

    Total cash-back: 1,490,119¥

    Baby stayed in NICU for 2 weeks but that was almost free with extra coverage from Minato-ku. I think we only ended up paying the cost of diapers.

    Grand total: 173,060¥ profit

    But this profit was offset by other costs accrued during pregnancy as detailed in the next paragraph.

    Other costs

    The many many pregnancy checkups at Aiiku Clinic, even with the city office coupons, ended up costing quite a bit: I counted 65,000¥ total and it’s missing 2 or 3 visits that happened in 2020 calendar year for which I don’t have the numbers unless I fish out last year’s tax return forms.

    We also forked out for the NIPT test which is not covered by insurance nor by 医療費控除 and cost about 180,000¥.

    The post Cost Breakdown of a Birth in Tokyo first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1347
    Extensions
    Starting a business – part 1
    Me, myself and Ibusinessshopify

    Since the start of the year Fumina and I have been playing with the idea of starting an outbound online shop—selling Japanese products to customers abroad. Last month we finally launched Chuchulu and it’s proven to be quite an interesting adventure. Finding a product The first step was to find a product category: It had […]

    The post Starting a business – part 1 first appeared on w00kie's ramblings.

    Show full content

    Since the start of the year Fumina and I have been playing with the idea of starting an outbound online shop—selling Japanese products to customers abroad. Last month we finally launched Chuchulu and it’s proven to be quite an interesting adventure.

    Chuchulu
    Fashion accessories made in Japan

    Finding a product

    The first step was to find a product category:

    • It had to be something we like. We’re not natural born salesmen so there’s no way we could push something we don’t genuinely like ourselves.
    • It had be interesting to people abroad (obviously) and not largely available outside of Japan. With shipping costs and potential tariffs, it would be difficult for us to beat local shops competition.
    • It had to be easily shippable. Heavy bulky items would push up shipping cost and make us less attractive to foreign customers.
    • Unit costs had to be on the lower side, at least at the beginning. We did not want to invest massively into a starting stock in case things just don’t work out.

    Ultimately, we came to choose fashion accessories (earrings, bracelets and the like) with typical Japanese designs as our first products.

    Fumina knew a good craftsman’s workshop specialized in traditional prayer beads who could work with her on order made bracelets based on her designs. That was our first product.

    Agates in various colors and Black Onyx, made in Kyoto.

    We also started visiting industry events to meet with suppliers. That’s how we found our second product line: Minoyaki tile earrings from Tajimi in Gifu prefecture. We also went to visit the workshop where these are made.

    Various colors of tile earrings made in Tajimi, Gifu prefecture.

    The next steps of this adventure will be coming in future posts…

    The post Starting a business – part 1 first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1247
    Extensions
    REAL TALK 🇯🇵
    japanRandomWork stuffwork

    I appreciate the transparency effort of giving average monthly overtime at your company in a Job Description for recruitment. But how can you write down “40 to 70 hours” and not think “we have a problem” ?

    The post REAL TALK 🇯🇵 first appeared on w00kie's ramblings.

    Show full content

    I appreciate the transparency effort of giving average monthly overtime at your company in a Job Description for recruitment.

    But how can you write down “40 to 70 hours” and not think “we have a problem” ?

    The post REAL TALK 🇯🇵 first appeared on w00kie's ramblings.

    https://w00kie.com/?p=1258
    Extensions