Compare commits
2 Commits
9cdd088ad7
...
5f3f3d80fd
Author | SHA1 | Date | |
---|---|---|---|
5f3f3d80fd | |||
b6bf8e2367 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,4 +1,4 @@
|
|||||||
.vscode
|
.vscode
|
||||||
services.json
|
services.json
|
||||||
.vscode/**/*
|
.vscode/**/*
|
||||||
|
.env
|
225
deploy.ts
225
deploy.ts
@ -1,12 +1,19 @@
|
|||||||
#!/usr/bin/env -S /home/jaeung/.deno/bin/deno run --allow-env --allow-read --unstable --allow-sys --allow-run --allow-write
|
#!/usr/bin/env -S /root/.deno/bin/deno run --allow-env --allow-read --allow-sys --allow-run --allow-write
|
||||||
|
|
||||||
import { printf } from "https://deno.land/std@0.158.0/fmt/printf.ts";
|
import { printf } from "https://deno.land/std@0.170.0/fmt/printf.ts";
|
||||||
import {brightGreen, brightMagenta} from "https://deno.land/std@0.160.0/fmt/colors.ts";
|
import {brightGreen, brightMagenta} from "https://deno.land/std@0.170.0/fmt/colors.ts";
|
||||||
import { Command } from "https://deno.land/x/cliffy@v0.25.2/command/mod.ts";
|
import { Command } from "https://deno.land/x/cliffy@v0.25.2/command/mod.ts";
|
||||||
|
import { getLogger } from "https://deno.land/std@0.170.0/log/mod.ts";
|
||||||
|
|
||||||
|
|
||||||
|
class CommandError extends Error{}
|
||||||
|
class ArgError extends CommandError {}
|
||||||
|
class NginxError extends CommandError {}
|
||||||
|
|
||||||
interface DeployedService{
|
interface DeployedService{
|
||||||
name: string,
|
name: string,
|
||||||
port: number
|
port?: number,
|
||||||
|
unixSocketPath?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveService(services: DeployedService[]){
|
async function saveService(services: DeployedService[]){
|
||||||
@ -28,8 +35,18 @@ async function loadService(): Promise<DeployedService[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface NginxConfigOption {
|
interface NginxConfigOption {
|
||||||
port: number,
|
|
||||||
name: string,
|
name: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* port number
|
||||||
|
*/
|
||||||
|
port?: number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unix socket path
|
||||||
|
*/
|
||||||
|
unixSocket?: string,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* megabyte unit
|
* megabyte unit
|
||||||
*/
|
*/
|
||||||
@ -46,10 +63,25 @@ interface NginxConfigOption {
|
|||||||
socketIO?: boolean,
|
socketIO?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNginxConfigContent({ port, name, clientMaxBodySize, proxyPass, socketIO }: NginxConfigOption) {
|
function createNginxConfigContent({ port, unixSocket, name, clientMaxBodySize, proxyPass, socketIO }: NginxConfigOption) {
|
||||||
clientMaxBodySize ??= 20;
|
clientMaxBodySize ??= 20;
|
||||||
proxyPass ??= "127.0.0.1";
|
proxyPass ??= "127.0.0.1";
|
||||||
socketIO ??= true;
|
socketIO ??= true;
|
||||||
|
if (!port && !unixSocket) {
|
||||||
|
throw new ArgError("Both port and unixSocket cannot be false.");
|
||||||
|
}
|
||||||
|
if ((!!port) && (!!unixSocket)){
|
||||||
|
throw new ArgError("Both port and unixSocket cannot be true.");
|
||||||
|
}
|
||||||
|
if (!!port && (isNaN(port) || !isFinite(port) )){
|
||||||
|
throw new ArgError("Port must be number.");
|
||||||
|
}
|
||||||
|
if (!!port && (port > 65535 || port < 0)){
|
||||||
|
throw new ArgError("Port cannnot be less than 0 or greater than 65535")
|
||||||
|
}
|
||||||
|
if (!!unixSocket && !unixSocket.startsWith("/")){
|
||||||
|
throw new ArgError("unix socket path must be absolute path.")
|
||||||
|
}
|
||||||
const content = `# it created by deploy script.
|
const content = `# it created by deploy script.
|
||||||
server {
|
server {
|
||||||
server_name ${name}.prelude.duckdns.org;
|
server_name ${name}.prelude.duckdns.org;
|
||||||
@ -61,7 +93,9 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header X-NginX-Proxy true;
|
proxy_set_header X-NginX-Proxy true;
|
||||||
proxy_pass http://${proxyPass}:${port};
|
proxy_pass ${
|
||||||
|
port ? `http://${proxyPass}:${port}` : `http://unix:${unixSocket}/`
|
||||||
|
};
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
|
|
||||||
# client body size
|
# client body size
|
||||||
@ -85,99 +119,122 @@ function isRunAsRoot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function NginxCheck() {
|
async function NginxCheck() {
|
||||||
const p = Deno.run({
|
const c = new Deno.Command("nginx",{
|
||||||
cmd: ["nginx", "-t"]
|
args: ["-t"],
|
||||||
});
|
});
|
||||||
const status = (await p.status())
|
const status = await c.output();
|
||||||
return status.success && status.code == 0;
|
return status.success && status.code == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function NginxReload() {
|
async function NginxReload() {
|
||||||
const p = Deno.run({
|
const c = new Deno.Command("nginx",{
|
||||||
cmd: ["nginx", "-s","reload"]
|
args: ["-s", "reload"],
|
||||||
});
|
});
|
||||||
const status = (await p.status())
|
const status = await c.output();
|
||||||
return status.success && status.code == 0;
|
return status.success && status.code == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeployCommandOption{
|
||||||
|
port?: number;
|
||||||
|
proxy?: string;
|
||||||
|
disableSocket?: boolean;
|
||||||
|
clientMaxBodySize?: number;
|
||||||
|
unixSocket?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDomainName(name: string){
|
||||||
|
return /^[a-z0-9][a-z0-9_-]{0,61}[a-z0-9]?$/i.test(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deployCommand(deployName: string,
|
||||||
|
options?: DeployCommandOption): Promise<void>{
|
||||||
|
options ??= {}
|
||||||
|
|
||||||
|
if(!checkDomainName(deployName)){
|
||||||
|
throw new ArgError("name invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isRunAsRoot()){
|
||||||
|
getLogger().warning("It's not executed as root")
|
||||||
|
}
|
||||||
|
|
||||||
|
const services = await loadService();
|
||||||
|
const dir = [...Deno.readDirSync("/etc/nginx/sites-available")]
|
||||||
|
|
||||||
|
if (dir.map(x => x.name).includes(deployName)) {
|
||||||
|
throw new ArgError("duplicated name: please use other name.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyPass = options.proxy;
|
||||||
|
const socketIO = !options.disableSocket;
|
||||||
|
const content = createNginxConfigContent({
|
||||||
|
port: options.port,
|
||||||
|
name: deployName,
|
||||||
|
unixSocket: options.unixSocket,
|
||||||
|
proxyPass: proxyPass,
|
||||||
|
socketIO: socketIO,
|
||||||
|
clientMaxBodySize: options.clientMaxBodySize
|
||||||
|
});
|
||||||
|
await Deno.writeTextFile("/etc/nginx/sites-available/"+deployName,content);
|
||||||
|
await Deno.symlink("/etc/nginx/sites-available/"+deployName,"/etc/nginx/sites-enabled/"+deployName);
|
||||||
|
|
||||||
|
if(services.map(x=>x.name).includes(deployName)){
|
||||||
|
const target = services.find(x=>x.name == deployName);
|
||||||
|
if(target){
|
||||||
|
target.port = options.port;
|
||||||
|
target.unixSocketPath = options.unixSocket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
services.push({
|
||||||
|
name: deployName,
|
||||||
|
port: options.port,
|
||||||
|
unixSocketPath: options.unixSocket,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await saveService(services);
|
||||||
|
|
||||||
|
if(!await NginxCheck()){
|
||||||
|
throw new NginxError("nginx config grammar failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!await NginxReload()){
|
||||||
|
throw new NginxError("nginx reload failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (import.meta.main) {
|
if (import.meta.main) {
|
||||||
const cmd = new Command()
|
const cmd = new Command()
|
||||||
.name("deployNginx")
|
.name("deployNginx")
|
||||||
.version("1.0.1")
|
.version("1.0.2")
|
||||||
.description("CLI")
|
.description("CLI")
|
||||||
.action(()=>{
|
.action(()=>{
|
||||||
console.log("sub command required");
|
console.log("sub command required");
|
||||||
})
|
})
|
||||||
.command("deploy", "deploy app")
|
.command("deploy", "deploy app")
|
||||||
.option("-p, --port <port:number>","port for app",{
|
.option("-p, --port <port:number>","port for app")
|
||||||
required: true
|
.option("--unixSocket <unixSocket:string>","unix socket path")
|
||||||
})
|
|
||||||
.option("--disableSocket","disable socket io")
|
.option("--disableSocket","disable socket io")
|
||||||
.option("--clientMaxBodySize <clientMaxBodySize:number>","client max body size: MB Unit",{
|
.option("--clientMaxBodySize <clientMaxBodySize:number>","client max body size: MB Unit",{
|
||||||
default: 20
|
default: 20
|
||||||
})
|
})
|
||||||
.option("--proxy <proxy:string>","proxy pass for app")
|
.option("--proxy <proxy:string>","proxy pass for app. you can bind unix socket like \"unix:<your unix socket path>\"")
|
||||||
.arguments("<value:string>")
|
.arguments("<value:string>")
|
||||||
.action(async (options, name)=>{
|
.action(async (options, name)=>{
|
||||||
const deployPort = options.port;
|
try {
|
||||||
const deployName = name;
|
await deployCommand(name,{
|
||||||
|
port: options.port,
|
||||||
if(deployName.includes("/")){
|
unixSocket: options.unixSocket,
|
||||||
console.log("name invalid");
|
clientMaxBodySize: options.clientMaxBodySize,
|
||||||
return;
|
disableSocket: options.disableSocket,
|
||||||
}
|
proxy: options.proxy
|
||||||
|
|
||||||
if(!isRunAsRoot()){
|
|
||||||
console.log("Warning! It's not executed as root");
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
console.log("run as root");
|
|
||||||
}
|
|
||||||
|
|
||||||
const services = await loadService();
|
|
||||||
|
|
||||||
const dir = [...Deno.readDirSync("/etc/nginx/sites-available")]
|
|
||||||
|
|
||||||
if (dir.map(x => x.name).includes(deployName)) {
|
|
||||||
console.log("duplicate!")
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxyPass = options.proxy;
|
|
||||||
const socketIO = !options.disableSocket;
|
|
||||||
const content = createNginxConfigContent({
|
|
||||||
port: deployPort,
|
|
||||||
name: deployName,
|
|
||||||
proxyPass: proxyPass,
|
|
||||||
socketIO: socketIO,
|
|
||||||
clientMaxBodySize: options.clientMaxBodySize
|
|
||||||
});
|
|
||||||
await Deno.writeTextFile("/etc/nginx/sites-available/"+deployName,content);
|
|
||||||
await Deno.symlink("/etc/nginx/sites-available/"+deployName,"/etc/nginx/sites-enabled/"+deployName);
|
|
||||||
|
|
||||||
if(services.map(x=>x.name).includes(deployName)){
|
|
||||||
const target = services.find(x=>x.name == deployName);
|
|
||||||
if(target){
|
|
||||||
target.port = deployPort;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
services.push({
|
|
||||||
name: deployName,
|
|
||||||
port: deployPort,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await saveService(services);
|
catch(e){
|
||||||
|
if (e instanceof Error){
|
||||||
if(!await NginxCheck()){
|
console.log(e.message);
|
||||||
console.log("nginx config grammar failed");
|
}
|
||||||
Deno.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!await NginxReload()){
|
|
||||||
console.log("nginx reload failed");
|
|
||||||
Deno.exit(1);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.command("undeploy","undeploy app")
|
.command("undeploy","undeploy app")
|
||||||
@ -204,26 +261,28 @@ if (import.meta.main) {
|
|||||||
.command("list","list deployed service")
|
.command("list","list deployed service")
|
||||||
.action(async ()=>{
|
.action(async ()=>{
|
||||||
const services = await loadService();
|
const services = await loadService();
|
||||||
if(!Deno.isatty(Deno.stdout.rid)){
|
if(!Deno.stdout.isTerminal()){
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
printf("%s %s\n",service.name, service.port.toString());
|
printf("%s %s\n",service.name, (service.port ?? service.unixSocketPath ?? "ERROR").toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const maxServiceNameLength = services.length == 0 ? 0 : services.map(x=>x.name.length).reduce((x,y)=>Math.max(x,y));
|
const maxServiceNameLength = services.map(x=>x.name.length).reduce((x,y)=>Math.max(x,y), 0);
|
||||||
const maxPadLength = Math.max(6,maxServiceNameLength);
|
const maxPadLength = Math.max(6,maxServiceNameLength);
|
||||||
|
const maxServiceOrNumberLength = services.map(x=> Math.max((x.port ?? 0).toString().length, x.unixSocketPath?.length ?? 0)).reduce((x,y)=>Math.max(x,y), 0);
|
||||||
|
const maxPad2Length = Math.max("PORT OR SOCKET PATH".length, maxServiceOrNumberLength);
|
||||||
const prettyPrint = (name: string, port: string) => {
|
const prettyPrint = (name: string, port: string) => {
|
||||||
printf("%s %s\n",brightGreen(name) + " ".repeat(maxPadLength - name.length),
|
printf("%s %s\n",brightGreen(name.padEnd(maxPadLength, " ")),
|
||||||
brightMagenta(port.padStart(5 - port.length)));
|
brightMagenta(port.padEnd(maxPad2Length)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
prettyPrint("NAME","PORT");
|
prettyPrint("NAME","PORT OR SOCKET PATH");
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
prettyPrint(service.name,service.port.toString());
|
prettyPrint(service.name,(service.port ?? service.unixSocketPath ?? "ERROR").toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await cmd.parse(Deno.args);
|
await cmd.parse(Deno.args);
|
||||||
}
|
}
|
||||||
|
64
renew_dns.ts
Executable file
64
renew_dns.ts
Executable file
@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env -S /root/.deno/bin/deno run --allow-env --allow-read --unstable --allow-sys --allow-run --allow-write
|
||||||
|
import {load} from "https://deno.land/std@0.214.0/dotenv/mod.ts";
|
||||||
|
import { Command } from "https://deno.land/x/cliffy@v0.25.7/mod.ts";
|
||||||
|
|
||||||
|
async function changeTextRecord(domain: string, token:string, text: string){
|
||||||
|
const url = `https://www.duckdns.org/update?domains=${domain}&token=${token}&txt=${text}`
|
||||||
|
const res = await fetch(url);
|
||||||
|
const c = await res.text()
|
||||||
|
return c === "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const env = await load();
|
||||||
|
const domain = env.DUCKDNS_DOMAIN;
|
||||||
|
const token = env.DUCKDNS_TOKEN;
|
||||||
|
/*
|
||||||
|
sudo certbot certonly --manual --preferred-challenges dns --renew-by-default -d "*.pages.prelude.duckdns.org"
|
||||||
|
*/
|
||||||
|
if(import.meta.main){
|
||||||
|
if (!domain || !token){
|
||||||
|
console.log("Env not found!");
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//const cmd = new Deno.Command("certbot",{
|
||||||
|
// args:[
|
||||||
|
// "certonly",
|
||||||
|
// "--manual",
|
||||||
|
// "--preferred-challenges",
|
||||||
|
// "dns",
|
||||||
|
// "--renew-by-default",
|
||||||
|
// "-d", "*.pages.prelude.duckdns.org"
|
||||||
|
// ],
|
||||||
|
// stdin: "piped",
|
||||||
|
// stdout: "piped",
|
||||||
|
//});
|
||||||
|
//
|
||||||
|
//const proc = cmd.spawn();
|
||||||
|
//await proc.stdout.pipeTo(Deno.stdout.writable);
|
||||||
|
|
||||||
|
await new Command()
|
||||||
|
.name("renew_dns")
|
||||||
|
.description("Duckdns TXT Record renewer")
|
||||||
|
.version("v1.0.0")
|
||||||
|
.option("-q, --quiet", "disable output.")
|
||||||
|
.arguments("<text>")
|
||||||
|
.action(async ({quiet},text)=>{
|
||||||
|
const s = await changeTextRecord(domain, token, text);
|
||||||
|
if (s) {
|
||||||
|
if (!quiet){
|
||||||
|
console.log("success!");
|
||||||
|
}
|
||||||
|
Deno.exit(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(!quiet){
|
||||||
|
console.log("failed!");
|
||||||
|
}
|
||||||
|
Deno.exit(1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.parse(Deno.args);
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user