✨ Deploy command
This commit is contained in:
parent
ae12eb2a15
commit
dd36e2ab1a
@ -1,19 +1,15 @@
|
|||||||
# Building Backend
|
# Building Backend
|
||||||
FROM golang:alpine as roadsign-server
|
FROM golang:alpine as roadsign-server
|
||||||
|
|
||||||
RUN apk add nodejs npm
|
|
||||||
|
|
||||||
WORKDIR /source
|
WORKDIR /source
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR /source/pkg/sideload/view
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
WORKDIR /source
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/cmd/server/main.go
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/cmd/server/main.go
|
||||||
|
|
||||||
# Runtime
|
# Runtime
|
||||||
FROM golang:alpine
|
FROM golang:alpine
|
||||||
|
|
||||||
|
RUN apk add zip
|
||||||
|
|
||||||
COPY --from=roadsign-server /dist /roadsign/server
|
COPY --from=roadsign-server /dist /roadsign/server
|
||||||
|
|
||||||
EXPOSE 81
|
EXPOSE 81
|
||||||
|
2
cli/.gitignore
vendored
2
cli/.gitignore
vendored
@ -173,3 +173,5 @@ dist
|
|||||||
|
|
||||||
# Finder (MacOS) folder config
|
# Finder (MacOS) folder config
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
/test/static-files
|
BIN
cli/bun.lockb
BIN
cli/bun.lockb
Binary file not shown.
@ -8,6 +8,7 @@ import { ListServerCommand } from "./src/cmd/list.ts"
|
|||||||
import { StatusCommand } from "./src/cmd/status.ts"
|
import { StatusCommand } from "./src/cmd/status.ts"
|
||||||
import { InfoCommand } from "./src/cmd/info.ts"
|
import { InfoCommand } from "./src/cmd/info.ts"
|
||||||
import { ProcessCommand } from "./src/cmd/process-info.ts"
|
import { ProcessCommand } from "./src/cmd/process-info.ts"
|
||||||
|
import { DeployCommand } from "./src/cmd/deploy.ts"
|
||||||
|
|
||||||
const [node, app, ...args] = process.argv
|
const [node, app, ...args] = process.argv
|
||||||
|
|
||||||
@ -31,4 +32,5 @@ cli.register(ListServerCommand)
|
|||||||
cli.register(StatusCommand)
|
cli.register(StatusCommand)
|
||||||
cli.register(InfoCommand)
|
cli.register(InfoCommand)
|
||||||
cli.register(ProcessCommand)
|
cli.register(ProcessCommand)
|
||||||
|
cli.register(DeployCommand)
|
||||||
cli.runExit(args)
|
cli.runExit(args)
|
86
cli/src/cmd/deploy.ts
Normal file
86
cli/src/cmd/deploy.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { RsConfig } from "../utils/config.ts"
|
||||||
|
import { Command, Option, type Usage } from "clipanion"
|
||||||
|
import chalk from "chalk"
|
||||||
|
import ora from "ora"
|
||||||
|
import * as fs from "node:fs"
|
||||||
|
import * as child_process from "node:child_process"
|
||||||
|
import * as path from "node:path"
|
||||||
|
import { createAuthHeader } from "../utils/auth.ts"
|
||||||
|
|
||||||
|
export class DeployCommand extends Command {
|
||||||
|
static paths = [[`deploy`]]
|
||||||
|
static usage: Usage = {
|
||||||
|
category: `Building`,
|
||||||
|
description: `Deploying App / Static Site onto RoadSign`,
|
||||||
|
details: `Deploying an application or hosting a static site via RoadSign, you need preconfigured the RoadSign, or sync the configurations via sync command.`,
|
||||||
|
examples: [["Deploying to RoadSign", `deploy <server> <site> <slug> <file / directory>`]]
|
||||||
|
}
|
||||||
|
|
||||||
|
server = Option.String({ required: true })
|
||||||
|
site = Option.String({ required: true })
|
||||||
|
upstream = Option.String({ required: true })
|
||||||
|
input = Option.String({ required: true })
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const config = await RsConfig.getInstance()
|
||||||
|
|
||||||
|
const server = config.config.servers.find(item => item.label === this.server)
|
||||||
|
if (server == null) {
|
||||||
|
this.context.stdout.write(chalk.red(`Server with label ${chalk.bold(this.server)} was not found.\n`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(this.input)) {
|
||||||
|
this.context.stdout.write(chalk.red(`Input file ${chalk.bold(this.input)} was not found.\n`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let isDirectory = false
|
||||||
|
if (fs.statSync(this.input).isDirectory()) {
|
||||||
|
if (this.input.endsWith("/")) {
|
||||||
|
this.input = this.input.slice(0, -1)
|
||||||
|
}
|
||||||
|
this.input += "/*"
|
||||||
|
|
||||||
|
const compressPrefStart = performance.now()
|
||||||
|
const compressSpinner = ora(`Compressing ${chalk.bold(this.input)}...`).start()
|
||||||
|
const destName = `${Date.now()}-roadsign-archive.zip`
|
||||||
|
child_process.execSync(`zip -rj ${destName} ${this.input}`)
|
||||||
|
const compressPrefTook = performance.now() - compressPrefStart
|
||||||
|
compressSpinner.succeed(`Compressing completed in ${(compressPrefTook / 1000).toFixed(2)}s 🎉`)
|
||||||
|
this.input = destName
|
||||||
|
isDirectory = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const destBreadcrumb = [this.site, this.upstream].join(" ➜ ")
|
||||||
|
const spinner = ora(`Deploying ${chalk.bold(destBreadcrumb)} to ${chalk.bold(this.server)}...`).start()
|
||||||
|
|
||||||
|
const prefStart = performance.now()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = new FormData()
|
||||||
|
payload.set("attachments", await fs.openAsBlob(this.input), isDirectory ? "dist.zip" : path.basename(this.input))
|
||||||
|
const res = await fetch(`${server.url}/webhooks/publish/${this.site}/${this.upstream}?mimetype=application/zip`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: payload,
|
||||||
|
headers: {
|
||||||
|
Authorization: createAuthHeader(server.credential)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(await res.text())
|
||||||
|
}
|
||||||
|
const prefTook = performance.now() - prefStart
|
||||||
|
spinner.succeed(`Deploying completed in ${(prefTook / 1000).toFixed(2)}s 🎉`)
|
||||||
|
} catch (e) {
|
||||||
|
this.context.stdout.write(`Failed to deploy to remote: ${e}\n`)
|
||||||
|
spinner.fail(`Server with label ${chalk.bold(this.server)} is not running! 😢`)
|
||||||
|
} finally {
|
||||||
|
if (isDirectory && this.input.endsWith(".zip")) {
|
||||||
|
fs.unlinkSync(this.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
}
|
12
cli/test/static-files/index.html
Normal file
12
cli/test/static-files/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Hello, World!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello, there!</p>
|
||||||
|
<p>Here's the newer version of static files hosted by roadsign!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -46,7 +46,7 @@ func doPublish(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
} else if destination != nil && destination.GetType() != navi.DestinationStaticFile {
|
} else if destination != nil && destination.GetType() != navi.DestinationStaticFile {
|
||||||
return fiber.ErrUnprocessableEntity
|
return fiber.ErrUnprocessableEntity
|
||||||
} else {
|
} else if destination == nil {
|
||||||
return fiber.ErrNotFound
|
return fiber.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ func doPublish(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if instance != nil {
|
if instance != nil {
|
||||||
instance.Wake()
|
_ = instance.Wake()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
@ -43,24 +43,38 @@ func doSync(c *fiber.Ctx) error {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
var rebootQueue []*warden.AppInstance
|
var stopQueue, startQueue []*warden.AppInstance
|
||||||
|
// Getting things need to stop
|
||||||
if region, ok := lo.Find(navi.R.Regions, func(item *navi.Region) bool {
|
if region, ok := lo.Find(navi.R.Regions, func(item *navi.Region) bool {
|
||||||
return item.ID == id
|
return item.ID == id
|
||||||
}); ok {
|
}); ok {
|
||||||
for _, application := range region.Applications {
|
for _, application := range region.Applications {
|
||||||
if instance := warden.GetFromPool(application.ID); instance != nil {
|
if instance := warden.GetFromPool(application.ID); instance != nil {
|
||||||
instance.Stop()
|
stopQueue = append(stopQueue, instance)
|
||||||
rebootQueue = append(rebootQueue, instance)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload
|
// Reload
|
||||||
navi.ReadInConfig(viper.GetString("paths.configs"))
|
_ = navi.ReadInConfig(viper.GetString("paths.configs"))
|
||||||
|
|
||||||
|
// Getting things need to start
|
||||||
|
if region, ok := lo.Find(navi.R.Regions, func(item *navi.Region) bool {
|
||||||
|
return item.ID == id
|
||||||
|
}); ok {
|
||||||
|
for _, application := range region.Applications {
|
||||||
|
if instance := warden.GetFromPool(application.ID); instance != nil {
|
||||||
|
startQueue = append(startQueue, instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reboot
|
// Reboot
|
||||||
for _, instance := range rebootQueue {
|
for _, instance := range stopQueue {
|
||||||
instance.Wake()
|
_ = instance.Stop()
|
||||||
|
}
|
||||||
|
for _, instance := range startQueue {
|
||||||
|
_ = instance.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
1
test/data/.gitignore
vendored
1
test/data/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/capital
|
/capital
|
||||||
|
/static-files
|
9
test/roadsign/config/example.toml
Normal file
9
test/roadsign/config/example.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
id = "static-files"
|
||||||
|
|
||||||
|
[[locations]]
|
||||||
|
id = "static-files-loc"
|
||||||
|
hosts = ["localhost:8000"]
|
||||||
|
paths = ["/"]
|
||||||
|
[[locations.destinations]]
|
||||||
|
id = "static-files-des"
|
||||||
|
uri = "files://../data/static-files"
|
@ -1,8 +0,0 @@
|
|||||||
name: Example Site
|
|
||||||
rules:
|
|
||||||
- host: ["localhost:8000"]
|
|
||||||
path: ["/"]
|
|
||||||
upstreams:
|
|
||||||
- id: example
|
|
||||||
name: Benchmarking Data
|
|
||||||
uri: files://../data
|
|
@ -1,18 +1,21 @@
|
|||||||
|
id = "central-dc"
|
||||||
|
|
||||||
[debug]
|
[debug]
|
||||||
print_routes = true
|
print_routes = false
|
||||||
|
|
||||||
|
[sideload]
|
||||||
|
ports = [":81"]
|
||||||
|
secured_ports = []
|
||||||
|
trusted_proxies = ["localhost"]
|
||||||
|
|
||||||
[hypertext]
|
[hypertext]
|
||||||
sideload_ports = [":81"]
|
|
||||||
sideload_secured_ports = []
|
|
||||||
ports = [":8000"]
|
ports = [":8000"]
|
||||||
secured_ports = []
|
secured_ports = []
|
||||||
|
force_https = false
|
||||||
|
|
||||||
[hypertext.certificate]
|
# [[hypertext.certificate]]
|
||||||
redirect = false
|
# key = "./certs/privkey.pem"
|
||||||
sideload_key = "./cert.key"
|
# pem = "./certs/fullchain.pem"
|
||||||
sideload_pem = "./cert.pem"
|
|
||||||
key = "./cert.key"
|
|
||||||
pem = "./cert.pem"
|
|
||||||
|
|
||||||
[hypertext.limitation]
|
[hypertext.limitation]
|
||||||
max_body_size = 549_755_813_888 # 512 GiB
|
max_body_size = 549_755_813_888 # 512 GiB
|
||||||
@ -21,11 +24,13 @@ max_qps = -1
|
|||||||
[paths]
|
[paths]
|
||||||
configs = "./config"
|
configs = "./config"
|
||||||
|
|
||||||
[performance]
|
[telemetry]
|
||||||
request_logging = true
|
request_logging = true
|
||||||
network_timeout = 3_000
|
capture_traces = true
|
||||||
|
|
||||||
|
[performance]
|
||||||
|
traces_limit = 256
|
||||||
prefork = false
|
prefork = false
|
||||||
|
|
||||||
[security]
|
[security]
|
||||||
sideload_trusted_proxies = ["localhost"]
|
|
||||||
credential = "e81f43f32d934271af6322e5376f5f59"
|
credential = "e81f43f32d934271af6322e5376f5f59"
|
||||||
|
Loading…
Reference in New Issue
Block a user