Skip to content

Commit

Permalink
Merge pull request #87 from sinamics/planet_server
Browse files Browse the repository at this point in the history
mkworld for building private root
  • Loading branch information
sinamics authored Aug 16, 2023
2 parents d606db1 + 057d8cc commit bb2fa83
Show file tree
Hide file tree
Showing 34 changed files with 1,401 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
working_dir: /workspaces
volumes:
- .:/workspaces:cached
- zerotier:/var/lib/zerotier-one:ro
- zerotier:/var/lib/zerotier-one
container_name: ztnet
environment:
ZT_ADDR: http://zerotier:9993
Expand Down
9 changes: 9 additions & 0 deletions .devcontainer/init-cmd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ until PGPASSWORD=$POSTGRES_PASSWORD psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER"
sleep 1
done

# Check architecture and copy the corresponding file if it exists
ARCH=$(uname -m)
if [ "$ARCH" = "x86_64" ] && [ -f "/workspaces/ztnodeid/build/linux_amd64/ztmkworld" ]; then
cp /workspaces/ztnodeid/build/linux_amd64/ztmkworld /usr/local/bin/ztmkworld
elif [ "$ARCH" = "aarch64" ] && [ -f "/workspaces/ztnodeid/build/linux_arm64/ztmkworld" ]; then
cp /workspaces/ztnodeid/build/linux_arm64/ztmkworld /usr/local/bin/ztmkworld
fi
chmod +x /usr/local/bin/ztmkworld

# apply migrations to the database
echo "Applying migrations to the database..."
npx prisma migrate deploy
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
},
"[typescript]": {
"editor.defaultFormatter": "rome.rome"
}
},
"editor.tabSize": 2
}
16 changes: 16 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ COPY . .

RUN SKIP_ENV_VALIDATION=1 npm run build


# Copy the ztmkworld binary based on the target platform architecture
FROM base AS ztmkworld_builder
ARG TARGETPLATFORM
WORKDIR /app
COPY /workspaces/ztnodeid/build/linux_amd64/ztmkworld ztmkworld_amd64
COPY /workspaces/ztnodeid/build/linux_arm64/ztmkworld ztmkworld_arm64
RUN \
case "${TARGETPLATFORM}" in \
"linux/amd64") cp ztmkworld_amd64 /usr/local/bin/ztmkworld ;; \
"linux/arm64") cp ztmkworld_arm64 /usr/local/bin/ztmkworld ;; \
*) echo "Unsupported architecture" && exit 1 ;; \
esac && \
chmod +x /usr/local/bin/ztmkworld

# Production image, copy all the files and run next
FROM $NODEJS_IMAGE AS runner
WORKDIR /app
Expand Down Expand Up @@ -66,6 +81,7 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nextjs:nodejs /app/init-db.sh ./init-db.sh
COPY --from=ztmkworld_builder /usr/local/bin/ztmkworld /usr/local/bin/ztmkworld

# prepeare .env file for the init-db.sh script
RUN touch .env && chown nextjs:nodejs .env
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,8 @@ Now start the development server:
### ⚠️ Disclaimer:

Please note that while this application aims to make managing ZeroTier networks easier, it is provided "as is" without any warranties or guarantees of any kind. As the user, you assume all responsibility for its use. Always ensure you have adequate backups and understanding of any changes you make to your network configurations. This includes understanding that the first registered user will be granted administrative privileges.

### Attribution and Licensing Notice for Third-Party Components
This project utilizes the **mkworld** tool, written in Go, to generate the custom planet file. While the original mkworld tool was developed by ZeroTier, the version we are using was adapted and re-implemented in Go by Patrick Young (@kmahyyg). This Go adaptation is licensed under the GNU General Public License v3.0. We would like to express our appreciation to Patrick Young (@kmahyyg) for his efforts in creating this Go version, which has benefited our project.

Our project, in its entirety, is also licensed under the GNU General Public License v3.0. For a comprehensive understanding of our project's licensing terms, please consult our LICENSE file.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ services:
container_name: ztnet
working_dir: /app
volumes:
- zerotier:/var/lib/zerotier-one:ro
- zerotier:/var/lib/zerotier-one
restart: unless-stopped
ports:
- 3000:3000
Expand Down
2 changes: 2 additions & 0 deletions prisma/migrations/20230815175550_custom_root/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "GlobalOptions" ADD COLUMN "customPlanetUsed" BOOLEAN NOT NULL DEFAULT false;
3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ model GlobalOptions {
// Notifications
userRegistrationNotification Boolean @default(false)
// mkworld
customPlanetUsed Boolean @default(false)
}

enum Role {
Expand Down
22 changes: 15 additions & 7 deletions src/components/elements/inputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type SubmitHandlerType = (

interface FormProps {
label: string;
labelClassName?: string;
isLoading?: boolean;
placeholder?: string;
description?: string;
Expand All @@ -48,6 +49,7 @@ interface FormProps {

const InputField = ({
label,
labelClassName,
placeholder,
description,
fields,
Expand Down Expand Up @@ -210,7 +212,9 @@ const InputField = ({
return (
<div key={field.name} className="form-control">
{field.description ? (
<label className="text-sm text-gray-500 leading-none pt-2">
<label
className={`text-sm text-gray-500 pt-2 ${labelClassName}`}
>
{field.description}
</label>
) : null}
Expand All @@ -230,9 +234,11 @@ const InputField = ({
}
if (field.elementType === "select" && field.selectOptions) {
return (
<React.Fragment key={field.name}>
<div key={field.name} className="form-control">
{field.description ? (
<label className="text-sm text-gray-500 leading-none pt-2">
<label
className={`text-sm text-gray-500 pt-2 ${labelClassName}`}
>
{field.description}
</label>
) : null}
Expand All @@ -249,14 +255,16 @@ const InputField = ({
</option>
))}
</select>
</React.Fragment>
</div>
);
}

return (
<React.Fragment key={field.name}>
<div key={field.name} className="form-control">
{field.description ? (
<label className="text-sm text-gray-500 leading-none pt-2">
<label
className={`text-sm text-gray-500 pt-2 ${labelClassName}`}
>
{field.description}
</label>
) : null}
Expand All @@ -270,7 +278,7 @@ const InputField = ({
name={field.name}
className={`input-bordered input-${size} w-full`}
/>
</React.Fragment>
</div>
);
})}
</div>
Expand Down
196 changes: 196 additions & 0 deletions src/components/modules/controller/privateRoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { useTranslations } from "next-intl";
import React from "react";
import toast from "react-hot-toast";
import InputField from "~/components/elements/inputField";
import { ErrorData } from "~/types/errorHandling";
import { api } from "~/utils/api";
import { useModalStore } from "~/utils/store";

const PrivateRoot = () => {
const t = useTranslations("admin");

const { callModal } = useModalStore((state) => state);
const { data: getWorld } = api.admin.getWorld.useQuery();

const { data: globalOptions, refetch: refetchOptions } =
api.settings.getAllOptions.useQuery();

const { mutate: makeWorld } = api.admin.makeWorld.useMutation({
onSuccess: () => {
refetchOptions();
callModal({
title: <p>{t("controller.generatePlanet.noteTitle")}</p>,
rootStyle: "text-left border border-yellow-300/30",
showButtons: true,
closeModalOnSubmit: true,
content: (
<span>
{t("controller.generatePlanet.customPlanetGenerated")}
<br />
<p>
{t.rich(
"controller.generatePlanet.restartContainerInstructions",
{
span: (content) => (
<span className="text-yellow-300">{content} </span>
),
br: () => <br />,
},
)}
</p>
</span>
),
});
},
onError: (error) => {
if ((error.data as ErrorData)?.zodError) {
const fieldErrors = (error.data as ErrorData)?.zodError.fieldErrors;
for (const field in fieldErrors) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-call
toast.error(`${fieldErrors[field].join(", ")}`);
}
} else if (error.message) {
toast.error(error.message);
} else {
toast.error("An unknown error occurred");
}
},
});
const { mutate: resetWorld } = api.admin.resetWorld.useMutation({
onSuccess: () => {
refetchOptions();
toast.success("Planet file has been restored!");
},
});
async function downloadPlanet() {
try {
const response = await fetch("/api/planet");
if (!response.ok) {
throw new Error("Network response was not ok");
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
// the filename you want
a.download = "planet.custom";
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("There was an error downloading the file:", error);
}
}

return (
<div className="space-y-4">
<div>
<p className="text-sm text-gray-500 pb-4">
{t("controller.generatePlanet.updatePlanetWarning")}
</p>
{globalOptions?.customPlanetUsed ? (
<div className="space-y-4">
<div className="alert">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
className="stroke-info shrink-0 w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>{t("controller.generatePlanet.customPlanetInUse")}</span>
</div>
<div className="flex justify-between">
<div className="join">
{/* <div>
<div>
<input
className="input input-bordered join-item"
placeholder="[email protected]"
/>
</div>
</div> */}
<div className="">
{/* <button className="btn join-item">INVITE USER</button> */}

<button
onClick={() => downloadPlanet()}
className="btn join-item bg-primary"
>
{t("controller.generatePlanet.downloadPlanetButton")}
</button>
</div>
</div>
<button
onClick={() => resetWorld()}
className="btn btn-outline btn-error"
>
{t("controller.generatePlanet.restoreOriginalPlanetButton")}
</button>
</div>
</div>
) : (
<InputField
isLoading={false}
label={t("controller.generatePlanet.generatePrivateRootLabel")}
placeholder={t(
"controller.generatePlanet.generatePrivateRootPlaceholder",
)}
size="sm"
buttonText="ADD"
rootFormClassName="space-y-3 "
rootClassName=""
labelClassName="text-sm leading-tight py-1"
fields={[
{
name: "domain",
description: t("controller.generatePlanet.domainDescription"),
type: "text",
placeholder: "Domain name",
defaultValue: "",
},
{
name: "comment",
description: t("controller.generatePlanet.commentDescription"),
type: "text",
placeholder: "comment",
value: "",
},
{
name: "Identity",
description: t("controller.generatePlanet.identityDescription"),
type: "text",
placeholder: "identity",
value: getWorld?.identity,
},
{
name: "endpoints",
type: "text",
description: t(
"controller.generatePlanet.endpointsDescription",
),
placeholder: "IP Address",
value: `${getWorld?.ip}/9993`,
},
]}
submitHandler={(params) =>
new Promise((resolve) => {
makeWorld(params);
resolve(true);
})
}
/>
)}
</div>
</div>
);
};

export default PrivateRoot;
15 changes: 15 additions & 0 deletions src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,21 @@
"zerotier_secret": "Zerotier Secret",
"submit_empty_field_default": "Submit empty field to use the default."
},
"generatePlanet": {
"noteTitle": "NOTE!",
"customPlanetGenerated": "Custom planet has been generated.",
"restartContainerInstructions": "You will need to restart zerotier container to load the new configuration:<br></br><br></br><span>docker restart zerotier</span>",
"updatePlanetWarning": "Updating the planet file will modify the core structure of your ZeroTier network, impacting routes, flexibility, and potentially availability. Proceed with caution, understanding the implications.",
"customPlanetInUse": "Custom Planet is currently in use!",
"downloadPlanetButton": "Download Planet",
"restoreOriginalPlanetButton": "Restore Original Planet",
"generatePrivateRootLabel": "Generate private root",
"generatePrivateRootPlaceholder": "Add a private root to your controller",
"domainDescription": "Add the domain name (optionally)",
"commentDescription": "Add a comment for this planet (optionally)",
"identityDescription": "Identity (optionally). Default value is the content of current identity.public",
"endpointsDescription": "Enter the external IP address of your controller. Use a comma-separated format (IP/PORT) for multiple addresses. For example: '84.17.53.155/9993,2a02:6ea0:d405::9993'. Ensure these addresses are globally reachable if you want nodes outside your local network to connect."
},
"yes": "Yes",
"no": "No"
}
Expand Down
Loading

0 comments on commit bb2fa83

Please sign in to comment.