aboutsummaryrefslogtreecommitdiff
path: root/pages/wg2nd
diff options
context:
space:
mode:
authorflu0r1ne <flu0r1ne@flu0r1ne.net>2023-08-28 21:33:44 -0500
committerflu0r1ne <flu0r1ne@flu0r1ne.net>2023-08-28 21:33:44 -0500
commitf0c03a9b8e15387c4defd0a0e3e0298324406fae (patch)
tree564011d0265666953b17258954ff68614ff6566a /pages/wg2nd
parent2f0439621cff059e414d67f6ce43a7a6c4de13bc (diff)
downloadhomepage-f0c03a9b8e15387c4defd0a0e3e0298324406fae.tar.xz
homepage-f0c03a9b8e15387c4defd0a0e3e0298324406fae.zip
Add wg2nd
Diffstat (limited to 'pages/wg2nd')
-rw-r--r--pages/wg2nd/desc.md13
-rw-r--r--pages/wg2nd/index.tsx199
2 files changed, 212 insertions, 0 deletions
diff --git a/pages/wg2nd/desc.md b/pages/wg2nd/desc.md
new file mode 100644
index 0000000..1d917ac
--- /dev/null
+++ b/pages/wg2nd/desc.md
@@ -0,0 +1,13 @@
+wg2nd
+=====
+
+`wg2nd` converts WireGuard configurations from `wg-quick` format into `systemd-networkd` compatible configurations.
+This is a web port of `wg2nd` which converts WireGuard configurations into Bash commands to set up the `networkd`
+device and network configurations. Note that the output is not compatible with Zsh. **The web port operates entirely on the client-side,
+ensuring that no sensitive data is transmitted or stored externally.** For more comprehensive control and features,
+including batch conversions and the option to install a firewall, the Linux tool `wg2nd` is available [here](https://www.git.flu0r1ne.net/wg2nd)
+and can be installed from source.
+
+Both versions are open-source and dual-licensed under GPL-2.0 and MIT. Limitations and further details for each version
+are elaborated in this [comprehensive post](/logs/announcing-wg2nd). For installation instructions
+for the Linux tool and additional information, please refer to the [README](https://www.git.flu0r1ne.net/wg2nd/tree/README.md?h=main).
diff --git a/pages/wg2nd/index.tsx b/pages/wg2nd/index.tsx
new file mode 100644
index 0000000..02537b3
--- /dev/null
+++ b/pages/wg2nd/index.tsx
@@ -0,0 +1,199 @@
+// pages/index.tsx
+import Head from 'next/head';
+import { useEffect, useRef, useState } from 'react';
+
+import Box from '../../components/Box';
+import TextArea from '../../components/TextArea';
+import Input from '../../components/Input';
+import Button from '../../components/Button';
+import Markdown from '../../components/Markdown';
+import Typography from '../../components/Typography';
+
+import Script from 'next/script';
+
+// @ts-ignore
+import desc from './desc.md';
+
+
+/*
+ * HOOK THE MODULE FROM THE DOM
+ */
+
+declare global {
+ interface Window {
+ Module: any;
+ }
+}
+
+const useModule = <T extends any>(onInitCallback? : (module: T) => void): T | null => {
+ const [module, setModule] = useState<T | null>(null);
+
+ useEffect(() => {
+
+ var Module = {
+ onRuntimeInitialized: function () {
+ if(onInitCallback !== undefined) {
+ onInitCallback(window.Module);
+ }
+
+ setModule(window.Module);
+ },
+ };
+
+ if(module === null && typeof window.Module === "object") {
+ if(onInitCallback !== undefined) {
+ onInitCallback(window.Module);
+ }
+
+ setModule(window.Module);
+ }
+
+ });
+
+
+ return module;
+};
+
+
+const DEMO_CONFIG = `[Interface]
+PrivateKey = 0OCS+dV5wsDje6qUAEDQzPmTNWOLE9HE8kfGU1wJUE0=
+Address = 10.55.127.342/32, ab00:aaaa:aaa:aa02::5:abcd/128
+DNS = 10.64.0.1
+
+[Peer]
+PublicKey = WBSnuq6Vswxz5G5zz9pUt60ZSA+JfZ1iTXdg0RJGjks=
+AllowedIPs = 0.0.0.0/0,::0/0
+Endpoint = 128.45.210.64:51821
+`;
+
+let rows = 0;
+for(const char of DEMO_CONFIG)
+ if(char == '\n') rows++;
+
+interface PanelProps {
+ children?: React.ReactNode;
+ customStyle?: React.CSSProperties;
+};
+
+const Panel: React.FC<PanelProps> = ({ children, customStyle }) => (
+ <section style={{
+ backgroundColor: '#f8f9fa',
+ marginTop: '1.5rem',
+ padding: '1.5rem',
+ ...customStyle
+ }}>
+ {children}
+ </section>
+);
+
+interface Wg2ndModule {
+ wg2nd_cmdseq: (intfName : string, intfconfig : string) => string;
+};
+
+import { Breadcrumbs, LinkCrumb } from "../../components/Breadcrumbs";
+
+const Wg2nd = () => {
+ const intfNameRef = useRef<HTMLInputElement>(null);
+ const intfConfigRef = useRef<HTMLTextAreaElement>(null);
+ const ndConfigRef = useRef<HTMLTextAreaElement>(null);
+ const parentDebounceDiv = useRef<HTMLDivElement>(null);
+
+ const convertConfig = (module : Wg2ndModule | null) => {
+ if(intfNameRef.current === null || intfConfigRef.current === null || module === null)
+ return;
+
+ const intfName = intfNameRef.current!.value;
+ const intfConfig = intfConfigRef.current!.value;
+
+ console.log(module);
+ const result = module.wg2nd_cmdseq(intfName, intfConfig);
+
+ // This is a hack: adjust a wrapper around the output text area
+ // Thus, we can detect the scrollHeight of the TextArea without
+ // the screen moving.
+ parentDebounceDiv.current!.style.height = ndConfigRef.current!.style.height;
+ ndConfigRef.current!.style.height = `0px`;
+
+ ndConfigRef.current!.value = result;
+
+ const scrollHeight = ndConfigRef.current!.scrollHeight;
+ ndConfigRef.current!.style.height = `${scrollHeight}px`;
+ parentDebounceDiv.current!.style.height = 'auto';
+ };
+
+ const module = useModule<Wg2ndModule>(convertConfig);
+
+ return (
+ <>
+ <script src="/wg2nd/wg2nd_bindings.js" />
+ <main
+ style={{
+ maxWidth: '1200px',
+ margin: 'auto',
+ }}
+ >
+ {/* Description */}
+ <Panel>
+ <Breadcrumbs>
+ <LinkCrumb href="/">flu0r1ne.net</LinkCrumb>
+ <LinkCrumb href="/wg2nd">wg2nd</LinkCrumb>
+ </Breadcrumbs>
+
+ <Markdown md={desc} />
+ </Panel>
+
+ {/* Input Panel */}
+ <Panel>
+ <Typography variant="h3">WireGuard Configuration</Typography>
+ <Box my={1}>
+ <label htmlFor="wg_config_intf_name">
+ <Typography variant="h4" gutter>Interface Name</Typography>
+ </label>
+ <Input
+ ref={intfNameRef}
+ id="wg_config_intf_name"
+ defaultValue="wg0"
+ spellCheck={false}
+ />
+ </Box>
+ <Box my={1}>
+ <label htmlFor="wg_config_conf">
+ <Typography variant="h4" gutter>Interface Configuration</Typography>
+ </label>
+ <TextArea
+ ref={intfConfigRef}
+ id="wg_config_conf"
+ defaultValue={DEMO_CONFIG}
+ rows={rows}
+ spellCheck={false}
+ />
+ </Box>
+ <Button
+ sx={{width: '100%'}}
+ onClick={(e) => { convertConfig(module); }}
+ >Generate</Button>
+ </Panel>
+
+ {/* Output Panel */}
+ <Panel>
+ <label htmlFor="nd_config_cmds">
+ <Typography variant="h3">Networkd Configuration</Typography>
+ </label>
+ <Box my={1}>
+ <div ref={parentDebounceDiv} >
+ <TextArea
+ sx={{ overflowY: 'hidden' }}
+ id="nd_config_cmds"
+ ref={ndConfigRef}
+ spellCheck={false}
+ readOnly
+ />
+ </div>
+ </Box>
+ </Panel>
+ </main>
+ </>
+ );
+};
+
+export default Wg2nd;