From 5e545bc4374dbbc0b0f04b263f44ab49f0c985d3 Mon Sep 17 00:00:00 2001 From: The0Mikkel Date: Fri, 14 Nov 2025 00:18:57 +0100 Subject: [PATCH 01/29] Implement instance fallback generator with Docker setup, error handling pages, and CSS styles --- .gitignore | 1 + Dockerfile | 13 +++ docker-compose.yml | 13 +++ src/content/error_404.html | 14 +++ src/content/error_502.html | 8 ++ src/content/error_503.html | 8 ++ src/content/error_504.html | 10 ++ src/content/standard_index.html | 12 +++ src/css/main.css | 72 +++++++++++++++ src/generator.py | 92 +++++++++++++++++++ src/js/main.js | 34 +++++++ src/layouts/error.html | 156 ++++++++++++++++++++++++++++++++ src/layouts/standard.html | 156 ++++++++++++++++++++++++++++++++ 13 files changed, 589 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 src/content/error_404.html create mode 100644 src/content/error_502.html create mode 100644 src/content/error_503.html create mode 100644 src/content/error_504.html create mode 100644 src/content/standard_index.html create mode 100644 src/css/main.css create mode 100644 src/generator.py create mode 100644 src/js/main.js create mode 100644 src/layouts/error.html create mode 100644 src/layouts/standard.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d298be1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +public/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5d92e2c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.14-slim AS builder + +WORKDIR /app + +COPY src/ /app/src + +RUN python3 src/generator.py + +FROM joseluisq/static-web-server + +COPY --from=builder /app/public/ /public + +ENV SERVER_CACHE_CONTROL_HEADERS=false diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2e90262 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + instancing-fallback: + build: . + ports: + - "8080:80" + restart: always + development: + image: joseluisq/static-web-server + ports: + - "8081:80" + volumes: + - ./public:/public + restart: always diff --git a/src/content/error_404.html b/src/content/error_404.html new file mode 100644 index 0000000..34c3509 --- /dev/null +++ b/src/content/error_404.html @@ -0,0 +1,14 @@ +

Your personal challenge instance is offline

+

+ Please start your challenge or wait a few moments for the instance to become available, if you have + already + started it.
+ This may take a few minutes. You may see "No Available Server", "Bad Gateway" or "Service Unavailable", + this is + normal and you will just need to wait a bit longer. +

+ +

+ This page will update automatically.
+ If this page does not update within 5 minutes please request support. +

\ No newline at end of file diff --git a/src/content/error_502.html b/src/content/error_502.html new file mode 100644 index 0000000..eec238d --- /dev/null +++ b/src/content/error_502.html @@ -0,0 +1,8 @@ +

Bad gateway

+

+ The challenge did not answer correctly. Your challenge may still be starting up. +

+

+ This page will update automatically.
+ If this page does not update within 5 minutes please request support. +

\ No newline at end of file diff --git a/src/content/error_503.html b/src/content/error_503.html new file mode 100644 index 0000000..f34cc86 --- /dev/null +++ b/src/content/error_503.html @@ -0,0 +1,8 @@ +

Your personal challenge instance is starting

+

+ The challenge is not yet ready. Please wait a few moments for the instance to become available. +

+

+ This page will update automatically.
+ If this page does not update within 5 minutes please request support. +

\ No newline at end of file diff --git a/src/content/error_504.html b/src/content/error_504.html new file mode 100644 index 0000000..3d8eb21 --- /dev/null +++ b/src/content/error_504.html @@ -0,0 +1,10 @@ +

Gateway timeout

+

+ The challenge was too slow to respond.
+ This may either indicate that your challenge is still starting up, or that the action you took made the + server not respond in time. +

+

+ This page will update automatically.
+ If this page does not update within 5 minutes please request support. +

\ No newline at end of file diff --git a/src/content/standard_index.html b/src/content/standard_index.html new file mode 100644 index 0000000..f54db3e --- /dev/null +++ b/src/content/standard_index.html @@ -0,0 +1,12 @@ +

The challenge instance is offline

+

+ Please start the challenge or wait a few moments for the instance to become available, if you have already + started it.
+ This may take a few minutes. You may see "No Available Server", "Bad Gateway" or "Service Unavailable", this + is normal and you will just need to wait a bit longer. +

+ +

+ This page will update automatically.
+ If this page does not update within 5 minutes please contact support. +

\ No newline at end of file diff --git a/src/css/main.css b/src/css/main.css new file mode 100644 index 0000000..00d155b --- /dev/null +++ b/src/css/main.css @@ -0,0 +1,72 @@ +:root { + --header-font: "Hack", monospace, sans-serif; + --body-font: "Hack", monospace, sans-serif; + + --light-theme-color: #eebc1d; + --dark-theme-color: #eebc1d; + --text-light: #232323; + --text-dark: #f9f9f9; + --bg-light: #f9f9f9; + --bg-dark: #232323; + + --theme-color: var(--light-theme-color); + --bg-color: var(--bg-light); + --text-color: var(--text-light); +} + +@media (prefers-color-scheme: dark) { + :root { + --theme-color: var(--dark-theme-color); + --bg-color: var(--bg-dark); + --text-color: var(--text-dark); + } +} + +html, +body { + background-color: var(--bg-color); + color: var(--text-color) !important; + font-family: var(--body-font) !important; + margin: 0; + padding: 0; +} + +main { + padding: 2rem; +} + +h1, +h2, +h3, +h4, +h5 { + font-family: var(--header-font) !important; +} + +a { + color: var(--theme-color); + filter: brightness(100%); + transition: filter 0.2s; +} + +a:hover { + filter: brightness(95%); + transition: filter 0.2s; +} + +.container { + display: flex; + justify-content: center; + text-align: center; + height: 100vh; + overflow-y: hidden; + align-items: center; +} + +.challenge-loading { + display: none; +} + +.wrong-domain { + display: none; +} diff --git a/src/generator.py b/src/generator.py new file mode 100644 index 0000000..fa6d3f4 --- /dev/null +++ b/src/generator.py @@ -0,0 +1,92 @@ + +import os +from pathlib import Path + +LAYOUTS = [] +CONTENTS = [] +JS_SCRIPTS = "" +CSS_STYLES = "" + +def load_layouts(): + layouts_path = Path(__file__).parent.absolute() / "layouts" + print("Loading layouts from:", layouts_path) + for layout_file in os.listdir(layouts_path): + if layout_file.endswith(".html"): + filename = layout_file.split(".")[0] + with open(layouts_path / layout_file, "r") as f: + layout_content = f.read() + LAYOUTS.append((filename, layout_content)) + +def load_contents(): + contents_path = Path(__file__).parent.absolute() / "content" + print("Loading contents from:", contents_path) + for content_file in os.listdir(contents_path): + if content_file.endswith(".html"): + filename = content_file.split(".")[0] + with open(contents_path / content_file, "r") as f: + content_data = f.read() + CONTENTS.append((filename, content_data)) + +def load_js_scripts(): + filename = "./js/main.js" + js_path = Path(__file__).parent.absolute() / filename + print("Loading JS scripts from:", js_path) + global JS_SCRIPTS + with open(js_path, "r") as f: + JS_SCRIPTS = f.read() + +def load_css_styles(): + filename = "./css/main.css" + css_path = Path(__file__).parent.absolute() / filename + print("Loading CSS styles from:", css_path) + global CSS_STYLES + with open(css_path, "r") as f: + CSS_STYLES = f.read() + +def replace_indented_placeholder(template, placeholder, content): + lines = template.splitlines() + new_lines = [] + for line in lines: + if placeholder in line: + indent = line[:line.index(placeholder)] + content_lines = content.splitlines() + for c_line in content_lines: + new_lines.append(indent + c_line) + else: + new_lines.append(line) + return "\n".join(new_lines) + +def generate(): + for content_name, content_data in CONTENTS: + content_filename = content_name.split(".")[0] + + layout = content_filename.split("_")[0] + filename = content_filename.split("_")[1] + + layout_template = next((l for l_name, l in LAYOUTS if l_name == layout), None) + if layout_template is None: + print(f"Layout '{layout}' not found for content '{content_name}'") + continue + + final_html = replace_indented_placeholder(layout_template, "/**CHALLENGE_HTML**/", content_data) + final_html = replace_indented_placeholder(final_html, "/**CHALLENGE_JS**/", JS_SCRIPTS) + final_html = replace_indented_placeholder(final_html, "/**CHALLENGE_CSS**/", CSS_STYLES) + + if (layout == "error"): + final_html = final_html.replace("/**ERROR_CODE**/", filename) + + output_path = (Path(__file__).parent.absolute() / ".." / "public" / f"{filename}.html").resolve() + + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_path, "w") as f: + f.write(final_html) + print(f"Generated: {output_path}") + + + +load_layouts() +load_contents() +load_js_scripts() +load_css_styles() +generate() + diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..848fb5b --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,34 @@ +// Check subdomain matches +const subdomain = window.location.host.split(".")[0]; +const re = new RegExp("^[a-z0-9-]+-[a-f0-9]{16}$"); +const parser = new DOMParser(); + +function checkChallengeReady() { + const host = window.location.origin; + fetch(host) + .then(res => { + if (res.status === 200) { + // Parse response text + res.text() + .then(text => { + // Check if 200 res has same title as this loading page + const doc = parser.parseFromString(text, "text/html") + if (doc.title !== document.title) { + location.reload(true) + } + }) + } else { console.log("instance not ready") } + }) + .catch(e => console.error(e)) +} + +if (re.test(subdomain)) { + document.getElementsByClassName("challenge-loading")[0].style.display = + "block"; + const POLL_INTERVAL = 5000; + setInterval(() => checkChallengeReady(), POLL_INTERVAL); +} else { + document.getElementsByClassName("wrong-domain")[0].style.display = + "block"; +} + diff --git a/src/layouts/error.html b/src/layouts/error.html new file mode 100644 index 0000000..9fb0d55 --- /dev/null +++ b/src/layouts/error.html @@ -0,0 +1,156 @@ + + + + + + + + CTF challenge is loading - /**ERROR_CODE**/ + + + +
+
+
+ /**CHALLENGE_HTML**/ + +
+
+
+
+
+
+
+
+
+
+
+
+

Invalid URL!

+

+ This URL currently does not resolve to a challenge, please check the + URL and try again. +

+
+
+
+ + + + + \ No newline at end of file diff --git a/src/layouts/standard.html b/src/layouts/standard.html new file mode 100644 index 0000000..db8e64a --- /dev/null +++ b/src/layouts/standard.html @@ -0,0 +1,156 @@ + + + + + + + + CTF challenge is loading + + + +
+
+
+ /**CHALLENGE_HTML**/ + +
+
+
+
+
+
+
+
+
+
+
+
+

Invalid URL!

+

+ This URL currently does not resolve to a challenge, please check the + URL and try again. +

+
+
+
+ + + + + \ No newline at end of file From aaa14f1dc4c6d28f2e7410f35e914e3c94528ce9 Mon Sep 17 00:00:00 2001 From: The0Mikkel Date: Fri, 14 Nov 2025 00:22:05 +0100 Subject: [PATCH 02/29] Add local development guide --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index d88eff4..058b5bc 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,16 @@ The service can also be run locally, using the provided Docker compose file: docker compose up -d ``` +### Development + +In order to generate the pages, run the [`generator.py`](./src/generator.py) script in `src`: + +```sh +python3 src/generator.py +``` + +*This is done automatically in the Docker container build process.* + ## Contributing We welcome contributions of all kinds, from **code** and **documentation** to **bug reports** and **feedback**! From b8a985e35d35b2ec383e575d6c0e48fcf488bc9b Mon Sep 17 00:00:00 2001 From: The0Mikkel Date: Fri, 14 Nov 2025 21:49:55 +0100 Subject: [PATCH 03/29] Update wording of auto-page reload and request of support --- src/content/error_404.html | 5 ----- src/content/error_502.html | 4 ---- src/content/error_503.html | 4 ---- src/content/error_504.html | 4 ---- src/content/standard_index.html | 5 ----- src/layouts/error.html | 5 +++++ src/layouts/standard.html | 5 +++++ 7 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/content/error_404.html b/src/content/error_404.html index 34c3509..7f4afa9 100644 --- a/src/content/error_404.html +++ b/src/content/error_404.html @@ -7,8 +7,3 @@

Your personal challenge instance is offline

this is normal and you will just need to wait a bit longer.

- -

- This page will update automatically.
- If this page does not update within 5 minutes please request support. -

\ No newline at end of file diff --git a/src/content/error_502.html b/src/content/error_502.html index eec238d..4feef70 100644 --- a/src/content/error_502.html +++ b/src/content/error_502.html @@ -2,7 +2,3 @@

Bad gateway

The challenge did not answer correctly. Your challenge may still be starting up.

-

- This page will update automatically.
- If this page does not update within 5 minutes please request support. -

\ No newline at end of file diff --git a/src/content/error_503.html b/src/content/error_503.html index f34cc86..47161db 100644 --- a/src/content/error_503.html +++ b/src/content/error_503.html @@ -2,7 +2,3 @@

Your personal challenge instance is starting

The challenge is not yet ready. Please wait a few moments for the instance to become available.

-

- This page will update automatically.
- If this page does not update within 5 minutes please request support. -

\ No newline at end of file diff --git a/src/content/error_504.html b/src/content/error_504.html index 3d8eb21..d7e7937 100644 --- a/src/content/error_504.html +++ b/src/content/error_504.html @@ -3,8 +3,4 @@

Gateway timeout

The challenge was too slow to respond.
This may either indicate that your challenge is still starting up, or that the action you took made the server not respond in time. -

-

- This page will update automatically.
- If this page does not update within 5 minutes please request support.

\ No newline at end of file diff --git a/src/content/standard_index.html b/src/content/standard_index.html index f54db3e..7b8b3f8 100644 --- a/src/content/standard_index.html +++ b/src/content/standard_index.html @@ -5,8 +5,3 @@

The challenge instance is offline

This may take a few minutes. You may see "No Available Server", "Bad Gateway" or "Service Unavailable", this is normal and you will just need to wait a bit longer.

- -

- This page will update automatically.
- If this page does not update within 5 minutes please contact support. -

\ No newline at end of file diff --git a/src/layouts/error.html b/src/layouts/error.html index 9fb0d55..cb103bd 100644 --- a/src/layouts/error.html +++ b/src/layouts/error.html @@ -16,6 +16,11 @@
/**CHALLENGE_HTML**/ +

+ This page will update automatically.
+ If this page does not update within 5 minutes please contact support. +

+
diff --git a/src/layouts/standard.html b/src/layouts/standard.html index db8e64a..098eb22 100644 --- a/src/layouts/standard.html +++ b/src/layouts/standard.html @@ -16,6 +16,11 @@
/**CHALLENGE_HTML**/ +

+ This page will update automatically.
+ If this page does not update within 5 minutes please contact support. +

+
From 8ee8d28723f82ddf4343ec18575769980af17f65 Mon Sep 17 00:00:00 2001 From: The0Mikkel Date: Fri, 14 Nov 2025 21:51:31 +0100 Subject: [PATCH 04/29] Remove redundant code --- src/generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generator.py b/src/generator.py index fa6d3f4..1201777 100644 --- a/src/generator.py +++ b/src/generator.py @@ -58,7 +58,7 @@ def replace_indented_placeholder(template, placeholder, content): def generate(): for content_name, content_data in CONTENTS: - content_filename = content_name.split(".")[0] + content_filename = content_name layout = content_filename.split("_")[0] filename = content_filename.split("_")[1] From 5f15a4f617c4f97a92dca57c24fb1277a64e8bdd Mon Sep 17 00:00:00 2001 From: The0Mikkel Date: Fri, 14 Nov 2025 21:54:08 +0100 Subject: [PATCH 05/29] Update html formatting to fit standard --- src/layouts/error.html | 79 +++++++++++++++++++------------------- src/layouts/standard.html | 81 +++++++++++++++++++-------------------- 2 files changed, 79 insertions(+), 81 deletions(-) diff --git a/src/layouts/error.html b/src/layouts/error.html index cb103bd..f740ac8 100644 --- a/src/layouts/error.html +++ b/src/layouts/error.html @@ -1,49 +1,12 @@ - - - CTF challenge is loading - /**ERROR_CODE**/ - - - -
-
-
- /**CHALLENGE_HTML**/ - -

- This page will update automatically.
- If this page does not update within 5 minutes please contact support. -

+ -
-
-
-
-
-
-
-
-
-
-
-
-

Invalid URL!

-

- This URL currently does not resolve to a challenge, please check the - URL and try again. -

-
-
-
- + + + +
+
+
+ /**CHALLENGE_HTML**/ + +

+ This page will update automatically.
+ If this page does not update within 5 minutes please contact support. +

+ +
+
+
+
+
+
+
+
+
+
+
+
+

Invalid URL!

+

+ This URL currently does not resolve to a challenge, please check the + URL and try again. +

+
+
+
+ \ No newline at end of file diff --git a/src/layouts/standard.html b/src/layouts/standard.html index 098eb22..e46e8ca 100644 --- a/src/layouts/standard.html +++ b/src/layouts/standard.html @@ -1,49 +1,11 @@ - - - CTF challenge is loading - - - -
-
-
- /**CHALLENGE_HTML**/ - -

- This page will update automatically.
- If this page does not update within 5 minutes please contact support. -

- -
-
-
-
-
-
-
-
-
-
-
-
-

Invalid URL!

-

- This URL currently does not resolve to a challenge, please check the - URL and try again. -

-
-
-
- + + + + +
+
+
+ /**CHALLENGE_HTML**/ + +

+ This page will update automatically.
+ If this page does not update within 5 minutes please contact support. +

+ +
+
+
+
+
+
+
+
+
+
+
+
+

Invalid URL!

+

+ This URL currently does not resolve to a challenge, please check the + URL and try again. +

+
+
+
+ + \ No newline at end of file From 9c5ebe54f35aa0ccd4d0367d7d775001e1be17b4 Mon Sep 17 00:00:00 2001 From: The0Mikkel Date: Fri, 14 Nov 2025 21:54:21 +0100 Subject: [PATCH 06/29] Update formatting to fit linting standards --- src/js/main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 848fb5b..d941b16 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -14,12 +14,12 @@ function checkChallengeReady() { // Check if 200 res has same title as this loading page const doc = parser.parseFromString(text, "text/html") if (doc.title !== document.title) { - location.reload(true) + location.reload() } }) - } else { console.log("instance not ready") } + } else { console.log("instance not ready"); } }) - .catch(e => console.error(e)) + .catch(e => console.error(e)); } if (re.test(subdomain)) { From 40cd5c9d3c5e1544c7b8c00f3b2af95b0fdbd0a2 Mon Sep 17 00:00:00 2001 From: The0Mikkel Date: Fri, 14 Nov 2025 22:00:36 +0100 Subject: [PATCH 07/29] Add language and charset attributes to HTML files for better accessibility --- src/layouts/error.html | 4 ++-- src/layouts/standard.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/layouts/error.html b/src/layouts/error.html index f740ac8..f7ac475 100644 --- a/src/layouts/error.html +++ b/src/layouts/error.html @@ -1,7 +1,7 @@ - - + + CTF challenge is loading - /**ERROR_CODE**/