365 Days of Code - Day 043

Project Status

ProjectLanguageStatusDue DateLatest Update
Personal WebsiteHugoOngoingNoneThe site is live. There are some TODOs. Need to work on categorization, tagging, and layout improvements.
Laravel From ScratchLaravel (PHP)In-Progress2026-03-31Episode 8
PRMLaravel (PHP)In-Progress2026-03-31Working alongside other Laravel projects.
Client Website (J.L.)Laravel (PHP)In-Progress2026-03-31Working alongside other Laravel projects.
Project EulerCOngoingNoneWorking on P25. BigInt (AI gen) was a waste of time, need to rewrite
Practice JavaJavaPausedNoneInstalled, need to find a good project.
Practice PythonPythonPausedNoneInstalled, need to find a good project.
Learn GoGoPausedNoneInstalled, work on LDAP Injector from ippsec.
Learn RustRustHaven’t StartedNoneInstalled, will try network protocols after finishing in C and Zig.
Learn ElixirElixirHaven’t StartedNoneInstalled, need a good tutorial project.
Learn HaskellHaskellHaven’t StartedNoneInstalled, need a good tutorial project.
Learn ZigZigHaven’t StartedNoneInstalled, will try network protocols after finishing in C.
Linux+N/AIn-Progress2026-03-31Reading Chapter 4.
Cyber Quest 2026N/AIn-Progress2026-02-28Finished quiz 1 with 75%.
Operating SystemsN/AIn-Progress2026-03-31Reading Chapter 4: Abstraction
Grey-Hat HackingVariousIn-Progress2026-03-31Reading Chapter 8: Threat Hunting Lab
PHP Time TrackerPHPBeta FinishedNoneWorking on a basic level. Could use a couple more updates to make it fully functional.
HTTP Status Code ReaderCComplete2026-02-18Complete. Could potentially upgrade for more advanced functions or follow redirects.
ZSH Configurationbash/zshCompleteNoneSort of an ongoing process, but complete for now. Works good.
Network ProtocolsCIn-ProgressNoneV2 complete. Moving to V3, refactoring again.
Discinox WebsiteHTML, CSS, JSComplete2026-03-04The site is live.

Discinox

The Discinox is an annual disc golf event that occurs on the solstice every year. We started in 2008, so this year will be the 18th annual event. I’ve held onto the domain (opens in a new tab) for many years, and never really did anything with it. At one time I wanted to start a disc golf company, but that ship sailed after realizing I needed to sell hundreds of thousands of discs to make back my salary at the time. I still like to keep the memory alive, so I’m reinstating the Discinox website on the new webserver I built.

The website is simple. A javascript countdown timer that resets on June 21st each year. Let’s get it up and running. I already have a working template for the workflow: local > github > docker > webserver. I just need to replicate what I’ve already done.

Docker

The Dockerfile is just a copy of the nginx website template I did for testing the workflow at the beginning. A couple quick modifications to copy over the css and js, and this should be done.

dockerfile
FROM nginx:alpine
COPY src/ /usr/share/nginx/html/
COPY .nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
1
2
3
4
5
FROM nginx:alpine
COPY src/ /usr/share/nginx/html/
COPY .nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

I also need to setup a new repo on Docker Hub (opens in a new tab). Done.

Github

I’ll setup a new repo for the Discinox website (opens in a new tab), and new secrets (opens in a new tab) for the workflow. In order to setup the secrets, I’ll need to configure the server.

Webserver

The webserver setup is a bit more complicated for security purposes. I’ll setup a new user with new keys to deploy the new website, deployment scripts, and modify the Caddy configuration to support the new domain.

bash
# New user
sudo adduser --disabled-password --gecos "" newuser
# SSH setup
sudo mkdir /home/newuser/.ssh
sudo chown -R newuser:newuser /home/newuser/.ssh
sudo chmod 700 /home/newuser/.ssh
sudo touch /home/newuser/.ssh/authorized_keys
sudo chmod 600 /home/newuser/.ssh/authorized_keys
sudo echo "new key file" | sudo tee -a /home/newuser/.ssh/authorized_keys
# Lock down the SSH login in authorized keys
command="/path/to/script.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty public-key
# Create new script, and set permissions
touch /path/to/script.sh
chown newuser:newuser /path/to/script.sh
chmod 770 /path/to/script.sh
# Add to docker group
sudo usermod -aG docker newuser
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# New user
sudo adduser --disabled-password --gecos "" newuser
# SSH setup
sudo mkdir /home/newuser/.ssh
sudo chown -R newuser:newuser /home/newuser/.ssh
sudo chmod 700 /home/newuser/.ssh
sudo touch /home/newuser/.ssh/authorized_keys
sudo chmod 600 /home/newuser/.ssh/authorized_keys
sudo echo "new key file" | sudo tee -a /home/newuser/.ssh/authorized_keys
# Lock down the SSH login in authorized keys
command="/path/to/script.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty public-key
# Create new script, and set permissions
touch /path/to/script.sh
chown newuser:newuser /path/to/script.sh
chmod 770 /path/to/script.sh
# Add to docker group
sudo usermod -aG docker newuser
caddyfile
discinox.com {
  redir https://www.discinox.com{uri} permanent
  log {
    output file /var/log/caddy/access-discinox.com.log
  }
}

www.discinox.com {
  reverse_proxy discinox-dot-com:80
  log {
    output file /var/log/caddy/access-www.discinox.com.log
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
discinox.com {
  redir https://www.discinox.com{uri} permanent
  log {
    output file /var/log/caddy/access-discinox.com.log
  }
}

www.discinox.com {
  reverse_proxy discinox-dot-com:80
  log {
    output file /var/log/caddy/access-www.discinox.com.log
  }
}

Javascript Countdown

As mentioned, the website is currently extremely simple. Nothing more than a countdown.

javascript
document.addEventListener("DOMContentLoaded", () => {
  const el = document.getElementById("discinox");
  if (!el) return;

  function updateCountdown() {
    const now = new Date(); // local timezone
    const year = now.getFullYear();

    // June is month 5 (0-based index)
    const thisYearStart = new Date(year, 5, 21, 0, 0, 0, 0);
    const thisYearEnd = new Date(year, 5, 22, 0, 0, 0, 0);

    let target;
    if (now >= thisYearStart && now < thisYearEnd) {
      el.textContent = "Discinox is today!";
      return;
    }

    if (now < thisYearStart) {
      target = thisYearStart;
    } else {
      // past this year's Discinox, look to next year
      target = new Date(year + 1, 5, 21, 0, 0, 0, 0);
    }

    // Calculate full days until target (round up so partial days count as a day)
    const msPerDay = 1000 * 60 * 60 * 24;
    const days = Math.ceil((target - now) / msPerDay);
    el.textContent = `${days} day${days === 1 ? "" : "s"} until the next Discinox!`;
  }

  // initial update and then update every minute
  updateCountdown();
  setInterval(updateCountdown, 60 * 1000);
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
document.addEventListener("DOMContentLoaded", () => {
  const el = document.getElementById("discinox");
  if (!el) return;

  function updateCountdown() {
    const now = new Date(); // local timezone
    const year = now.getFullYear();

    // June is month 5 (0-based index)
    const thisYearStart = new Date(year, 5, 21, 0, 0, 0, 0);
    const thisYearEnd = new Date(year, 5, 22, 0, 0, 0, 0);

    let target;
    if (now >= thisYearStart && now < thisYearEnd) {
      el.textContent = "Discinox is today!";
      return;
    }

    if (now < thisYearStart) {
      target = thisYearStart;
    } else {
      // past this year's Discinox, look to next year
      target = new Date(year + 1, 5, 21, 0, 0, 0, 0);
    }

    // Calculate full days until target (round up so partial days count as a day)
    const msPerDay = 1000 * 60 * 60 * 24;
    const days = Math.ceil((target - now) / msPerDay);
    el.textContent = `${days} day${days === 1 ? "" : "s"} until the next Discinox!`;
  }

  // initial update and then update every minute
  updateCountdown();
  setInterval(updateCountdown, 60 * 1000);
});

Related content