This commit is contained in:
2026-03-12 03:56:24 +00:00
parent 6d2413a25f
commit f95770dec6
120 changed files with 573 additions and 50 deletions
@@ -1,20 +1,14 @@
import { AppContext } from "@/src/pages/_app";
import { TurboCISignupFormObject } from "@/src/types";
import useStatus from "@/twui/components/hooks/useStatus";
import { useContext, useState } from "react";
export type SettingsFormData = {
first_name?: string;
last_name?: string;
email?: string;
image?: string;
};
export default function useSettingsForm() {
const { pageProps } = useContext(AppContext);
const { user } = pageProps;
const { loading, setLoading } = useStatus();
const [formData, setFormData] = useState<SettingsFormData>({
const [formData, setFormData] = useState<TurboCISignupFormObject>({
first_name: user?.first_name || "",
last_name: user?.last_name || "",
email: user?.email || "",
@@ -10,6 +10,7 @@ import Stack from "@/twui/components/layout/Stack";
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
import { useContext } from "react";
import useSettingsForm from "../(hooks)/use-settings-form";
import { APIReqObject } from "@/src/types";
export default function SettingsForm() {
const { setToast } = useContext(AppContext);
@@ -22,9 +23,14 @@ export default function SettingsForm() {
setLoading(true);
try {
const res = await fetchApi<typeof formData, APIResponseObject>(
const res = await fetchApi<APIReqObject, APIResponseObject>(
`/api/admin/settings`,
{ method: "POST", body: formData },
{
method: "POST",
body: {
new_user: formData,
},
},
);
if (res.success) {
@@ -55,7 +61,9 @@ export default function SettingsForm() {
Username
</Span>
<Span>
<strong>{user?.username || "—"}</strong>
<strong>
{user.super_admin ? "root" : user?.username || "—"}
</strong>
</Span>
</Stack>
@@ -166,7 +166,7 @@ export default function SignupForm({ new_deployment_user }: Props) {
}}
loading={loading}
>
Signup
{pageProps.user.super_admin ? "Add User" : "Signup"}
</Button>
</Stack>
</form>
+13 -1
View File
@@ -1,5 +1,17 @@
import { AppData } from "@/src/data/app-data";
import cronCheckServices from "./functions/check-services";
import { watchFile } from "fs";
import path from "path";
import { execSync } from "child_process";
if (process.env.NODE_ENV !== "production") {
watchFile(path.resolve(__dirname, "../db/db-schema.js"), (curr, prev) => {
execSync(`bunx nsqlite schema -t`, {
cwd: path.resolve(__dirname, "../.."),
stdio: "inherit",
});
});
}
while (true) {
console.log(`Running Cron Services ...`);
@@ -11,4 +23,4 @@ while (true) {
await Bun.sleep(AppData["CronInterval"]);
}
export {};
// export {};
+13
View File
@@ -58,6 +58,19 @@ const schema = {
},
],
},
{
tableName: "users_allowed_services",
fields: [
{
fieldName: "user_id",
dataType: "INTEGER",
},
{
fieldName: "service_name",
dataType: "TEXT",
},
],
},
],
};
+19 -1
View File
@@ -1,6 +1,7 @@
export const NSQLiteTables = [
"users",
"users_ports",
"users_allowed_services",
] as const
export type NSQLITE_TURBOCI_ADMIN_USERS = {
@@ -43,4 +44,21 @@ export type NSQLITE_TURBOCI_ADMIN_USERS_PORTS = {
port?: number;
}
export type NSQLITE_TURBOCI_ADMIN_ALL_TYPEDEFS = NSQLITE_TURBOCI_ADMIN_USERS & NSQLITE_TURBOCI_ADMIN_USERS_PORTS
export type NSQLITE_TURBOCI_ADMIN_USERS_ALLOWED_SERVICES = {
/**
* The unique identifier of the record.
*/
id?: number;
/**
* The time when the record was created. (Unix Timestamp)
*/
created_at?: number;
/**
* The time when the record was updated. (Unix Timestamp)
*/
updated_at?: number;
user_id?: number;
service_name?: string;
}
export type NSQLITE_TURBOCI_ADMIN_ALL_TYPEDEFS = NSQLITE_TURBOCI_ADMIN_USERS & NSQLITE_TURBOCI_ADMIN_USERS_PORTS & NSQLITE_TURBOCI_ADMIN_USERS_ALLOWED_SERVICES
+1 -1
View File
@@ -103,7 +103,7 @@ export default async function loginUser({
logged_in_status: true,
image: fetched_user.image,
image_thumbnail: fetched_user.image,
super_admin: fetched_user.is_super_admin ? 1 : 0,
super_admin: fetched_user.is_super_admin ? true : false,
};
const payload_string = EJSON.stringify(logged_in_user_payload);
@@ -1,6 +1,8 @@
import { NSQLITE_TURBOCI_ADMIN_USERS, NSQLiteTables } from "@/src/db/types";
import { _n } from "@/src/exports/client-exports";
import grabDeploymentUserDirNames from "@/src/utils/grab-deployment-user-dir-names";
import NSQLite from "@moduletrace/nsqlite";
import { execSync } from "child_process";
import { existsSync } from "fs";
type Params = {
@@ -24,15 +26,17 @@ export default async function setupDeploymentUser({ user_id }: Params) {
const { username } = target_user;
const user_dir = `/home/${username}`;
const ssh_dir = `${user_dir}/.ssh`;
const ssh_key_file = `${ssh_dir}/${username}`;
const sshd_config_file = `/etc/ssh/sshd_config.d/${username}`;
const force_command_file = `/usr/local/bin/turboci-deployment-user-${username}`;
const {
force_command_file,
ssh_dir,
ssh_key_file,
sshd_config_file,
user_dir,
} = grabDeploymentUserDirNames({ user: target_user });
let cmd = `/bin/bash << 'TURBOCIHEREDOC'\n`;
if (!existsSync(user_dir)) {
let cmd = ``;
cmd += `useradd --create-home --shell /bin/bash --comment "TurboCI Deployment user ${username}" ${username}\n`;
cmd += `passwd --lock "${username}"\n`;
cmd += `mkdir -p "${ssh_dir}"\n`;
@@ -44,26 +48,35 @@ export default async function setupDeploymentUser({ user_id }: Params) {
cmd += `chmod 600 "${ssh_key_file}"\n`;
cmd += `chmod 644 "${ssh_key_file}.pub"\n`;
cmd += `chmod 600 "${ssh_dir}/authorized_keys"\n`;
cmd += `cat << 'EOF' > ${force_command_file}\n`;
cmd += `#!/bin/bash\n`;
cmd += `EOF\n`;
cmd += `cat << 'EOF' > ${sshd_config_file}\n`;
cmd += `Match User ${username}\n`;
cmd += ` # Authentication\n`;
cmd += ` PasswordAuthentication no\n`;
cmd += ` PubkeyAuthentication yes\n`;
cmd += ` AuthenticationMethods publickey\n\n`;
cmd += ` # Restrict shell / tunneling\n`;
cmd += ` AllowTcpForwarding yes\n`;
cmd += ` X11Forwarding no\n\n`;
cmd += ` # Restrict Commands\n`;
cmd += ` ForceCommand ${force_command_file}\n`;
cmd += `EOF\n`;
}
cmd += `cat << 'EOF' > ${force_command_file}\n`;
cmd += `#!/bin/bash\n`;
cmd += `case "$SSH_ORIGINAL_COMMAND" in\n`;
cmd += ` "shell")\n`;
cmd += ` exec /bin/bash\n`;
cmd += ` ;;\n`;
cmd += ` *)\n`;
cmd += ` echo "Access denied."\n`;
cmd += ` exit 1\n`;
cmd += ` ;;\n`;
cmd += `esac\n`;
cmd += `EOF\n\n`;
cmd += `chmod +x ${force_command_file}\n\n`;
cmd += `cat << 'EOF' > ${sshd_config_file}\n`;
cmd += `Match User ${username}\n`;
cmd += ` PasswordAuthentication no\n`;
cmd += ` PubkeyAuthentication yes\n`;
cmd += ` AuthenticationMethods publickey\n\n`;
cmd += ` AllowTcpForwarding yes\n`;
cmd += ` X11Forwarding no\n\n`;
cmd += ` ForceCommand ${force_command_file}\n`;
cmd += `EOF\n`;
cmd += `TURBOCIHEREDOC\n`;
execSync(cmd);
return;
}
+8 -8
View File
@@ -4,13 +4,7 @@ import userAuth from "@/src/utils/user-auth";
import NSQLite from "@moduletrace/nsqlite";
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
import type { NextApiRequest, NextApiResponse } from "next";
type ReqBody = {
first_name?: string;
last_name?: string;
email?: string;
image?: string;
};
import { APIReqObject } from "@/src/types";
export default async function handler(
req: NextApiRequest,
@@ -33,7 +27,13 @@ export default async function handler(
});
}
const { first_name, last_name, email, image } = req.body as ReqBody;
const { new_user } = req.body as APIReqObject;
if (!new_user) {
throw new Error(`No User Form Sent.`);
}
const { first_name, last_name, email, image } = new_user;
if (!first_name?.match(/./)) {
return res.json({ success: false, msg: "First name is required" });
+3
View File
@@ -7,6 +7,7 @@ import NSQLite from "@moduletrace/nsqlite";
import type { NextApiRequest, NextApiResponse } from "next";
import userAuth from "@/src/utils/user-auth";
import { slugify } from "@/src/exports/client-exports";
import setupDeploymentUser from "@/src/functions/deployment-users/setup-deployment-user";
export default async function handler(
req: NextApiRequest,
@@ -103,6 +104,8 @@ export default async function handler(
}
if (user?.id) {
await setupDeploymentUser({ user_id: newly_inserted_user.id });
return res.json({
success: true,
singleRes: newly_inserted_user,
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
export NODE_ENV=production
bun next build
+2
View File
@@ -1,5 +1,7 @@
#!/bin/bash
export NODE_ENV=production
pm2 kill
pm2 start --name turboci-web 'bunx next start -p 3772'
+2 -1
View File
@@ -6,7 +6,7 @@ import { ChildProcess } from "child_process";
import { NSQLITE_TURBOCI_ADMIN_USERS } from "../db/types";
export type User = DATASQUIREL_LoggedInUser & {
super_admin?: 0 | 1;
super_admin?: boolean;
};
export const CloudProviders = [
@@ -231,6 +231,7 @@ export type TurboCISignupFormObject = {
password?: string;
confirmed_password?: string;
username?: string;
image?: string;
};
export type TurboCIAdminAppContextType = ReturnType<typeof useAppInit>;
@@ -0,0 +1,24 @@
import { NSQLITE_TURBOCI_ADMIN_USERS } from "../db/types";
import { User } from "../types";
type Params = {
user: NSQLITE_TURBOCI_ADMIN_USERS;
};
export default function grabDeploymentUserDirNames({ user }: Params) {
const { username } = user;
const user_dir = `/home/${username}`;
const ssh_dir = `${user_dir}/.ssh`;
const ssh_key_file = `${ssh_dir}/${username}`;
const sshd_config_file = `/etc/ssh/sshd_config.d/${username}`;
const force_command_file = `/usr/local/bin/turboci-deployment-user-${username}`;
return {
user_dir,
ssh_dir,
ssh_key_file,
sshd_config_file,
force_command_file,
};
}